• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jasonish / suricata / 23105300094

15 Mar 2026 06:48AM UTC coverage: 75.784% (-0.7%) from 76.495%
23105300094

push

github

jasonish
github-ci: ubuntu minimal build fixups

- Don't run on the GitHub provided VM, it contains a newer Rust than
  stock Ubuntu does.

252836 of 333628 relevant lines covered (75.78%)

1978514.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.97
/src/util-affinity.c
1
/* Copyright (C) 2010-2016 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17

18
/** \file
19
 *
20
 *  \author Eric Leblond <eric@regit.org>
21
 *
22
 *  CPU affinity related code and helper.
23
 */
24

25
#include "suricata-common.h"
26
#include "suricata.h"
27
#define _THREAD_AFFINITY
28
#include "util-affinity.h"
29
#include "conf.h"
30
#include "conf-yaml-loader.h"
31
#include "runmodes.h"
32
#include "util-cpu.h"
33
#include "util-byte.h"
34
#include "util-debug.h"
35
#include "util-dpdk.h"
36
#include "util-unittest.h"
37

38
ThreadsAffinityType thread_affinity[MAX_CPU_SET] = {
39
    {
40
            .name = "receive-cpu-set",
41
            .mode_flag = EXCLUSIVE_AFFINITY,
42
            .prio = PRIO_MEDIUM,
43
            .lcpu = { 0 },
44
    },
45
    {
46
            .name = "worker-cpu-set",
47
            .mode_flag = EXCLUSIVE_AFFINITY,
48
            .prio = PRIO_MEDIUM,
49
            .lcpu = { 0 },
50
    },
51
    {
52
            .name = "verdict-cpu-set",
53
            .mode_flag = BALANCED_AFFINITY,
54
            .prio = PRIO_MEDIUM,
55
            .lcpu = { 0 },
56
    },
57
    {
58
            .name = "management-cpu-set",
59
            .mode_flag = BALANCED_AFFINITY,
60
            .prio = PRIO_MEDIUM,
61
            .lcpu = { 0 },
62
    },
63

64
};
65

66
int thread_affinity_init_done = 0;
67

68
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
69
#ifdef HAVE_HWLOC
70
static hwloc_topology_t topology = NULL;
71
#endif /* HAVE_HWLOC */
72
#endif /* OS_WIN32 and __OpenBSD__ */
73

74
static ThreadsAffinityType *AllocAndInitAffinityType(
75
        const char *name, const char *interface_name, ThreadsAffinityType *parent)
76
{
10✔
77
    ThreadsAffinityType *new_affinity = SCCalloc(1, sizeof(ThreadsAffinityType));
10✔
78
    if (new_affinity == NULL) {
10✔
79
        FatalError("Unable to allocate memory for new CPU affinity type");
×
80
    }
×
81

82
    new_affinity->name = SCStrdup(interface_name);
10✔
83
    if (new_affinity->name == NULL) {
10✔
84
        FatalError("Unable to allocate memory for new CPU affinity type name");
×
85
    }
×
86
    new_affinity->parent = parent;
10✔
87
    new_affinity->mode_flag = EXCLUSIVE_AFFINITY;
10✔
88
    new_affinity->prio = PRIO_MEDIUM;
10✔
89
    for (int i = 0; i < MAX_NUMA_NODES; i++) {
170✔
90
        new_affinity->lcpu[i] = 0;
160✔
91
    }
160✔
92

93
    if (parent != NULL) {
10✔
94
        if (parent->nb_children == parent->nb_children_capacity) {
10✔
95
            if (parent->nb_children_capacity == 0) {
7✔
96
                parent->nb_children_capacity = 2;
6✔
97
            } else {
6✔
98
                parent->nb_children_capacity *= 2;
1✔
99
            }
1✔
100
            void *p = SCRealloc(
7✔
101
                    parent->children, parent->nb_children_capacity * sizeof(ThreadsAffinityType *));
7✔
102
            if (p == NULL) {
7✔
103
                FatalError("Unable to reallocate memory for children CPU affinity types");
×
104
            }
×
105
            parent->children = p;
7✔
106
        }
7✔
107
        parent->children[parent->nb_children++] = new_affinity;
10✔
108
    }
10✔
109

110
    return new_affinity;
10✔
111
}
10✔
112

113
ThreadsAffinityType *FindAffinityByInterface(
114
        ThreadsAffinityType *parent, const char *interface_name)
115
{
13✔
116
    if (parent == NULL || interface_name == NULL || parent->nb_children == 0 ||
13✔
117
            parent->children == NULL) {
13✔
118
        return NULL;
7✔
119
    }
7✔
120

121
    for (uint32_t i = 0; i < parent->nb_children; i++) {
15✔
122
        if (parent->children[i] && parent->children[i]->name &&
11✔
123
                strcmp(parent->children[i]->name, interface_name) == 0) {
11✔
124
            return parent->children[i];
2✔
125
        }
2✔
126
    }
11✔
127
    return NULL;
4✔
128
}
6✔
129

130
/**
131
 * \brief Find affinity by name (*-cpu-set name) and an interface name.
132
 * \param name the name of the affinity (e.g. worker-cpu-set, receive-cpu-set).
133
 * The name is required and cannot be NULL.
134
 * \param interface_name the name of the interface.
135
 * If NULL, the affinity is looked up by name only.
136
 *  \retval a pointer to the affinity or NULL if not found
137
 */
138
ThreadsAffinityType *GetAffinityTypeForNameAndIface(const char *name, const char *interface_name)
139
{
4✔
140
    if (name == NULL || *name == '\0') {
4✔
141
        return NULL;
2✔
142
    }
2✔
143

144
    ThreadsAffinityType *parent_affinity = NULL;
2✔
145
    for (int i = 0; i < MAX_CPU_SET; i++) {
4✔
146
        if (thread_affinity[i].name != NULL && strcmp(thread_affinity[i].name, name) == 0) {
4✔
147
            parent_affinity = &thread_affinity[i];
2✔
148
            break;
2✔
149
        }
2✔
150
    }
4✔
151

152
    if (parent_affinity == NULL) {
2✔
153
        SCLogError("CPU affinity with name \"%s\" not found", name);
×
154
        return NULL;
×
155
    }
×
156

157
    if (interface_name != NULL) {
2✔
158
        ThreadsAffinityType *child_affinity =
1✔
159
                FindAffinityByInterface(parent_affinity, interface_name);
1✔
160
        // found or not found, it is returned
161
        return child_affinity;
1✔
162
    }
1✔
163

164
    return parent_affinity;
1✔
165
}
2✔
166

167
/**
168
 * \brief Finds affinity by its name and interface name.
169
 * Interfaces are children of cpu-set names. If the queried interface is not
170
 * found, then it is allocated, initialized and assigned to the queried cpu-set.
171
 * \param name the name of the affinity (e.g. worker-cpu-set, receive-cpu-set).
172
 * The name is required and cannot be NULL.
173
 * \param interface_name the name of the interface.
174
 * If NULL, the affinity is looked up by name only.
175
 * \retval a pointer to the affinity or NULL if not found
176
 */
177
ThreadsAffinityType *GetOrAllocAffinityTypeForIfaceOfName(
178
        const char *name, const char *interface_name)
179
{
40✔
180
    int i;
40✔
181
    ThreadsAffinityType *parent_affinity = NULL;
40✔
182

183
    for (i = 0; i < MAX_CPU_SET; i++) {
97✔
184
        if (strcmp(thread_affinity[i].name, name) == 0) {
97✔
185
            parent_affinity = &thread_affinity[i];
40✔
186
            break;
40✔
187
        }
40✔
188
    }
97✔
189

190
    if (parent_affinity == NULL) {
40✔
191
        SCLogError("CPU affinity with name \"%s\" not found", name);
×
192
        return NULL;
×
193
    }
×
194

195
    if (interface_name != NULL) {
40✔
196
        ThreadsAffinityType *child_affinity =
11✔
197
                FindAffinityByInterface(parent_affinity, interface_name);
11✔
198
        if (child_affinity != NULL) {
11✔
199
            return child_affinity;
1✔
200
        }
1✔
201

202
        // If not found, allocate and initialize a new child affinity
203
        return AllocAndInitAffinityType(name, interface_name, parent_affinity);
10✔
204
    }
11✔
205

206
    return parent_affinity;
29✔
207
}
40✔
208

209
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
210
static void AffinitySetupInit(void)
211
{
25✔
212
    int i, j;
25✔
213
    int ncpu = UtilCpuGetNumProcessorsOnline();
25✔
214

215
    SCLogDebug("Initialize CPU affinity setup");
25✔
216
    /* be conservative relatively to OS: use all cpus by default */
217
    for (i = 0; i < MAX_CPU_SET; i++) {
125✔
218
        cpu_set_t *cs = &thread_affinity[i].cpu_set;
100✔
219
        CPU_ZERO(cs);
100✔
220
        for (j = 0; j < ncpu; j++) {
500✔
221
            CPU_SET(j, cs);
400✔
222
        }
400✔
223
        SCMutexInit(&thread_affinity[i].taf_mutex, NULL);
100✔
224
    }
100✔
225
}
25✔
226

227
int BuildCpusetWithCallback(
228
        const char *name, SCConfNode *node, void (*Callback)(int i, void *data), void *data)
229
{
33✔
230
    SCConfNode *lnode;
33✔
231
    TAILQ_FOREACH(lnode, &node->head, next) {
53✔
232
        uint32_t i;
53✔
233
        uint32_t a, b;
53✔
234
        uint32_t stop = 0;
53✔
235
        uint32_t max = UtilCpuGetNumProcessorsOnline();
53✔
236
        if (max > 0) {
53✔
237
            max--;
53✔
238
        }
53✔
239
        if (!strcmp(lnode->val, "all")) {
53✔
240
            a = 0;
3✔
241
            b = max;
3✔
242
            stop = 1;
3✔
243
        } else if (strchr(lnode->val, '-') != NULL) {
50✔
244
            char *sep = strchr(lnode->val, '-');
7✔
245
            if (StringParseUint32(&a, 10, sep - lnode->val, lnode->val) <= 0) {
7✔
246
                SCLogError("%s: invalid cpu range (start invalid): \"%s\"", name, lnode->val);
2✔
247
                return -1;
2✔
248
            }
2✔
249
            if (StringParseUint32(&b, 10, strlen(sep) - 1, sep + 1) <= 0) {
5✔
250
                SCLogError("%s: invalid cpu range (end invalid): \"%s\"", name, lnode->val);
×
251
                return -1;
×
252
            }
×
253
            if (a > b) {
5✔
254
                SCLogError("%s: invalid cpu range (bad order): \"%s\"", name, lnode->val);
1✔
255
                return -1;
1✔
256
            }
1✔
257
            if (b > max) {
4✔
258
                SCLogError("%s: upper bound (%d) of cpu set is too high, only %d cpu(s)", name, b,
2✔
259
                        max + 1);
2✔
260
                return -1;
2✔
261
            }
2✔
262
        } else {
43✔
263
            if (StringParseUint32(&a, 10, strlen(lnode->val), lnode->val) <= 0) {
43✔
264
                SCLogError("%s: invalid cpu range (not an integer): \"%s\"", name, lnode->val);
×
265
                return -1;
×
266
            }
×
267
            b = a;
43✔
268
        }
43✔
269
        for (i = a; i<= b; i++) {
109✔
270
            Callback(i, data);
61✔
271
        }
61✔
272
        if (stop) {
48✔
273
            break;
3✔
274
        }
3✔
275
    }
48✔
276
    return 0;
28✔
277
}
33✔
278

279
static void AffinityCallback(int i, void *data)
280
{
61✔
281
    CPU_SET(i, (cpu_set_t *)data);
61✔
282
}
61✔
283

284
static int BuildCpuset(const char *name, SCConfNode *node, cpu_set_t *cpu)
285
{
33✔
286
    return BuildCpusetWithCallback(name, node, AffinityCallback, (void *)cpu);
33✔
287
}
33✔
288

289
/**
290
 * \brief Get the appropriate set name for a given affinity value.
291
 */
292
static const char *GetAffinitySetName(const char *val)
293
{
28✔
294
    if (strcmp(val, "decode-cpu-set") == 0 || strcmp(val, "stream-cpu-set") == 0 ||
28✔
295
            strcmp(val, "reject-cpu-set") == 0 || strcmp(val, "output-cpu-set") == 0) {
28✔
296
        return NULL;
×
297
    }
×
298

299
    return (strcmp(val, "detect-cpu-set") == 0) ? "worker-cpu-set" : val;
28✔
300
}
28✔
301

302
/**
303
 * \brief Set up CPU sets for the given affinity type.
304
 */
305
static void SetupCpuSets(ThreadsAffinityType *taf, SCConfNode *affinity, const char *setname)
306
{
34✔
307
    CPU_ZERO(&taf->cpu_set);
34✔
308
    SCConfNode *cpu_node = SCConfNodeLookupChild(affinity, "cpu");
34✔
309
    if (cpu_node != NULL) {
34✔
310
        if (BuildCpuset(setname, cpu_node, &taf->cpu_set) < 0) {
28✔
311
            SCLogWarning("Failed to parse CPU set for %s", setname);
5✔
312
        }
5✔
313
    } else {
28✔
314
        SCLogWarning("Unable to find 'cpu' node for set %s", setname);
6✔
315
    }
6✔
316
}
34✔
317

318
/**
319
 * \brief Build a priority CPU set for the given priority level.
320
 */
321
static void BuildPriorityCpuset(ThreadsAffinityType *taf, SCConfNode *prio_node,
322
        const char *priority, cpu_set_t *cpuset, const char *setname)
323
{
12✔
324
    SCConfNode *node = SCConfNodeLookupChild(prio_node, priority);
12✔
325
    if (node != NULL) {
12✔
326
        if (BuildCpuset(setname, node, cpuset) < 0) {
5✔
327
            SCLogWarning("Failed to parse %s priority CPU set for %s", priority, setname);
×
328
        }
×
329
    } else {
7✔
330
        SCLogDebug("Unable to find '%s' priority for set %s", priority, setname);
7✔
331
    }
7✔
332
}
12✔
333

334
/**
335
 * \brief Set up the default priority for the given affinity type.
336
 * \retval 0 on success, -1 on error
337
 */
338
static int SetupDefaultPriority(
339
        ThreadsAffinityType *taf, SCConfNode *prio_node, const char *setname)
340
{
4✔
341
    SCConfNode *default_node = SCConfNodeLookupChild(prio_node, "default");
4✔
342
    if (default_node == NULL) {
4✔
343
        return 0;
×
344
    }
×
345

346
    if (strcmp(default_node->val, "low") == 0) {
4✔
347
        taf->prio = PRIO_LOW;
×
348
    } else if (strcmp(default_node->val, "medium") == 0) {
4✔
349
        taf->prio = PRIO_MEDIUM;
1✔
350
    } else if (strcmp(default_node->val, "high") == 0) {
3✔
351
        taf->prio = PRIO_HIGH;
2✔
352
    } else {
2✔
353
        SCLogError("Unknown default CPU affinity priority: %s", default_node->val);
1✔
354
        return -1;
1✔
355
    }
1✔
356

357
    SCLogConfig("Using default priority '%s' for set %s", default_node->val, setname);
3✔
358
    return 0;
3✔
359
}
4✔
360

361
/**
362
 * \brief Set up priority CPU sets for the given affinity type.
363
 * \retval 0 on success, -1 on error
364
 */
365
static int SetupAffinityPriority(
366
        ThreadsAffinityType *taf, SCConfNode *affinity, const char *setname)
367
{
34✔
368
    CPU_ZERO(&taf->lowprio_cpu);
34✔
369
    CPU_ZERO(&taf->medprio_cpu);
34✔
370
    CPU_ZERO(&taf->hiprio_cpu);
34✔
371
    SCConfNode *prio_node = SCConfNodeLookupChild(affinity, "prio");
34✔
372
    if (prio_node == NULL) {
34✔
373
        return 0;
30✔
374
    }
30✔
375

376
    BuildPriorityCpuset(taf, prio_node, "low", &taf->lowprio_cpu, setname);
4✔
377
    BuildPriorityCpuset(taf, prio_node, "medium", &taf->medprio_cpu, setname);
4✔
378
    BuildPriorityCpuset(taf, prio_node, "high", &taf->hiprio_cpu, setname);
4✔
379
    return SetupDefaultPriority(taf, prio_node, setname);
4✔
380
}
34✔
381

382
/**
383
 * \brief Set up CPU affinity mode for the given affinity type.
384
 * \retval 0 on success, -1 on error
385
 */
386
static int SetupAffinityMode(ThreadsAffinityType *taf, SCConfNode *affinity)
387
{
33✔
388
    SCConfNode *mode_node = SCConfNodeLookupChild(affinity, "mode");
33✔
389
    if (mode_node == NULL) {
33✔
390
        return 0;
30✔
391
    }
30✔
392

393
    if (strcmp(mode_node->val, "exclusive") == 0) {
3✔
394
        taf->mode_flag = EXCLUSIVE_AFFINITY;
2✔
395
    } else if (strcmp(mode_node->val, "balanced") == 0) {
2✔
396
        taf->mode_flag = BALANCED_AFFINITY;
×
397
    } else {
1✔
398
        SCLogError("Unknown CPU affinity mode: %s", mode_node->val);
1✔
399
        return -1;
1✔
400
    }
1✔
401
    return 0;
2✔
402
}
3✔
403

404
/**
405
 * \brief Set up the number of threads for the given affinity type.
406
 * \retval 0 on success, -1 on error
407
 */
408
static int SetupAffinityThreads(ThreadsAffinityType *taf, SCConfNode *affinity)
409
{
32✔
410
    SCConfNode *threads_node = SCConfNodeLookupChild(affinity, "threads");
32✔
411
    if (threads_node == NULL) {
32✔
412
        return 0;
29✔
413
    }
29✔
414

415
    if (StringParseUint32(&taf->nb_threads, 10, 0, threads_node->val) < 0 || taf->nb_threads == 0) {
3✔
416
        SCLogError("Invalid thread count: %s", threads_node->val);
2✔
417
        return -1;
2✔
418
    }
2✔
419
    return 0;
1✔
420
}
3✔
421

422
/**
423
 * \brief Get the YAML path for the given affinity type.
424
 * The path is built using the parent name (if available) and the affinity name.
425
 * Do not free the returned string.
426
 * \param taf the affinity type - if NULL, the path is built for the root node
427
 * \return a string containing the YAML path, or NULL if the path is too long
428
 */
429
char *AffinityGetYamlPath(ThreadsAffinityType *taf)
430
{
52✔
431
    static char rootpath[] = "threading.cpu-affinity";
52✔
432
    static char path[1024] = { 0 };
52✔
433
    char subpath[256] = { 0 };
52✔
434

435
    if (taf == NULL) {
52✔
436
        return rootpath;
51✔
437
    }
51✔
438

439
    if (taf->parent != NULL) {
1✔
440
        long r = snprintf(
×
441
                subpath, sizeof(subpath), "%s.interface-specific-cpu-set.", taf->parent->name);
×
442
        if (r < 0 || r >= (long)sizeof(subpath)) {
×
443
            SCLogError("Unable to build YAML path for CPU affinity %s.%s", taf->parent->name,
×
444
                    taf->name);
×
445
            return NULL;
×
446
        }
×
447
    } else {
1✔
448
        subpath[0] = '\0';
1✔
449
    }
1✔
450

451
    long r = snprintf(path, sizeof(path), "%s.%s%s", rootpath, subpath, taf->name);
1✔
452
    if (r < 0 || r >= (long)sizeof(path)) {
1✔
453
        SCLogError("Unable to build YAML path for CPU affinity %s", taf->name);
1✔
454
        return NULL;
1✔
455
    }
1✔
456

457
    return path;
×
458
}
1✔
459

460
static void ResetCPUs(ThreadsAffinityType *taf)
461
{
×
462
    for (int i = 0; i < MAX_NUMA_NODES; i++) {
×
463
        taf->lcpu[i] = 0;
×
464
    }
×
465
}
×
466

467
/**
468
 * \brief Check if the set name corresponds to a worker CPU set.
469
 */
470
static bool IsWorkerCpuSet(const char *setname)
471
{
19✔
472
    return (strcmp(setname, "worker-cpu-set") == 0);
19✔
473
}
19✔
474

475
/**
476
 * \brief Check if the set name corresponds to a receive CPU set.
477
 */
478
static bool IsReceiveCpuSet(const char *setname)
479
{
6✔
480
    return (strcmp(setname, "receive-cpu-set") == 0);
6✔
481
}
6✔
482

483
/**
484
 * \brief Set up affinity configuration for a single interface.
485
 */
486
/**
487
 * \brief Set up affinity configuration for a single interface.
488
 * \retval 0 on success, -1 on error
489
 */
490
static int SetupSingleIfaceAffinity(ThreadsAffinityType *taf, SCConfNode *iface_node)
491
{
6✔
492
    // offload to Setup function
493
    SCConfNode *child_node;
6✔
494
    const char *interface_name = NULL;
6✔
495
    TAILQ_FOREACH (child_node, &iface_node->head, next) {
6✔
496
        if (strcmp(child_node->name, "interface") == 0) {
6✔
497
            interface_name = child_node->val;
6✔
498
            break;
6✔
499
        }
6✔
500
    }
6✔
501
    if (interface_name == NULL) {
6✔
502
        return 0;
×
503
    }
×
504

505
    ThreadsAffinityType *iface_taf =
6✔
506
            GetOrAllocAffinityTypeForIfaceOfName(taf->name, interface_name);
6✔
507
    if (iface_taf == NULL) {
6✔
508
        SCLogError("Failed to allocate CPU affinity type for interface: %s", interface_name);
×
509
        return -1;
×
510
    }
×
511

512
    SetupCpuSets(iface_taf, iface_node, interface_name);
6✔
513
    if (SetupAffinityPriority(iface_taf, iface_node, interface_name) < 0) {
6✔
514
        return -1;
×
515
    }
×
516
    if (SetupAffinityMode(iface_taf, iface_node) < 0) {
6✔
517
        return -1;
×
518
    }
×
519
    if (SetupAffinityThreads(iface_taf, iface_node) < 0) {
6✔
520
        return -1;
×
521
    }
×
522
    return 0;
6✔
523
}
6✔
524

525
/**
526
 * \brief Set up per-interface affinity configurations.
527
 * \retval 0 on success, -1 on error
528
 */
529
static int SetupPerIfaceAffinity(ThreadsAffinityType *taf, SCConfNode *affinity)
530
{
15✔
531
    char if_af[] = "interface-specific-cpu-set";
15✔
532
    SCConfNode *per_iface_node = SCConfNodeLookupChild(affinity, if_af);
15✔
533
    if (per_iface_node == NULL) {
15✔
534
        return 0;
10✔
535
    }
10✔
536

537
    SCConfNode *iface_node;
5✔
538
    TAILQ_FOREACH (iface_node, &per_iface_node->head, next) {
6✔
539
        if (strcmp(iface_node->val, "interface") == 0) {
6✔
540
            if (SetupSingleIfaceAffinity(taf, iface_node) < 0) {
6✔
541
                return -1;
×
542
            }
×
543
        } else {
6✔
544
            SCLogWarning("Unknown node in %s: %s", if_af, iface_node->name);
×
545
        }
×
546
    }
6✔
547
    return 0;
5✔
548
}
5✔
549

550
/**
551
 * \brief Check if CPU affinity configuration node follows format used in Suricata 7 and below
552
 * \retval true if CPU affinity uses Suricata <=7.0, false if it uses the new format (Suricata
553
 * >=8.0)
554
 */
555
static bool AffinityConfigIsLegacy(void)
556
{
107✔
557
    static bool is_using_legacy_affinity_format = false;
107✔
558
    if (thread_affinity_init_done == 0) {
107✔
559
        // reset the flag
560
        is_using_legacy_affinity_format = false;
25✔
561
    } else {
82✔
562
        return is_using_legacy_affinity_format;
82✔
563
    }
82✔
564

565
    SCConfNode *root = SCConfGetNode(AffinityGetYamlPath(NULL));
25✔
566
    if (root == NULL) {
25✔
567
        return is_using_legacy_affinity_format;
×
568
    }
×
569

570
    SCConfNode *affinity;
25✔
571
    TAILQ_FOREACH (affinity, &root->head, next) {
28✔
572
        // If a child does not contain "-cpu-set", then the conf is legacy
573
        // Names in the legacy format (list of *-cpu-sets) contain
574
        // list item IDs - "0" : "management-cpu-set", "1" : "worker-cpu-set"
575
        if (strstr(affinity->name, "-cpu-set") == NULL) {
28✔
576
            is_using_legacy_affinity_format = true;
9✔
577
            return is_using_legacy_affinity_format;
9✔
578
        }
9✔
579
    }
28✔
580

581
    return is_using_legacy_affinity_format;
16✔
582
}
25✔
583
#endif /* OS_WIN32 and __OpenBSD__ */
584

585
/**
586
 * \brief Extract CPU affinity configuration from current config file
587
 */
588
void AffinitySetupLoadFromConfig(void)
589
{
25✔
590
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
25✔
591
    if (thread_affinity_init_done == 0) {
25✔
592
        AffinitySetupInit();
25✔
593
        AffinityConfigIsLegacy();
25✔
594
        thread_affinity_init_done = 1;
25✔
595
    }
25✔
596

597
    SCLogDebug("Loading %s from config", AffinityGetYamlPath(NULL));
25✔
598
    SCConfNode *root = SCConfGetNode(AffinityGetYamlPath(NULL));
25✔
599
    if (root == NULL) {
25✔
600
        SCLogInfo("Cannot find %s node in config", AffinityGetYamlPath(NULL));
×
601
        return;
×
602
    }
×
603

604
    SCConfNode *affinity;
25✔
605
    TAILQ_FOREACH(affinity, &root->head, next) {
28✔
606
        char *v = AffinityConfigIsLegacy() ? affinity->val : affinity->name;
28✔
607
        const char *setname = GetAffinitySetName(v);
28✔
608
        if (setname == NULL) {
28✔
609
            continue;
×
610
        }
×
611

612
        ThreadsAffinityType *taf = GetOrAllocAffinityTypeForIfaceOfName(setname, NULL);
28✔
613
        if (taf == NULL) {
28✔
614
            SCLogError("Failed to allocate CPU affinity type: %s", setname);
×
615
            continue;
×
616
        }
×
617

618
        SCLogConfig("Found CPU affinity definition for \"%s\"", setname);
28✔
619

620
        SCConfNode *aff_query_node = AffinityConfigIsLegacy() ? affinity->head.tqh_first : affinity;
28✔
621
        SetupCpuSets(taf, aff_query_node, setname);
28✔
622
        if (SetupAffinityPriority(taf, aff_query_node, setname) < 0) {
28✔
623
            SCLogError("Failed to setup priority for CPU affinity type: %s", setname);
1✔
624
            continue;
1✔
625
        }
1✔
626
        if (SetupAffinityMode(taf, aff_query_node) < 0) {
27✔
627
            SCLogError("Failed to setup mode for CPU affinity type: %s", setname);
1✔
628
            continue;
1✔
629
        }
1✔
630
        if (SetupAffinityThreads(taf, aff_query_node) < 0) {
26✔
631
            SCLogError("Failed to setup threads for CPU affinity type: %s", setname);
2✔
632
            continue;
2✔
633
        }
2✔
634

635
        if (!AffinityConfigIsLegacy() && (IsWorkerCpuSet(setname) || IsReceiveCpuSet(setname))) {
24✔
636
            if (SetupPerIfaceAffinity(taf, affinity) < 0) {
15✔
637
                SCLogError("Failed to setup per-interface affinity for CPU affinity type: %s",
×
638
                        setname);
×
639
                continue;
×
640
            }
×
641
        }
15✔
642
    }
24✔
643
#endif /* OS_WIN32 and __OpenBSD__ */
25✔
644
}
25✔
645

646
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
647
#ifdef HAVE_HWLOC
648
static int HwLocDeviceNumaGet(hwloc_topology_t topo, hwloc_obj_t obj)
649
{
650
#if HWLOC_VERSION_MAJOR >= 2 && HWLOC_VERSION_MINOR >= 5
651
    hwloc_obj_t nodes[MAX_NUMA_NODES];
652
    unsigned num_nodes = MAX_NUMA_NODES;
653
    struct hwloc_location location;
654

655
    location.type = HWLOC_LOCATION_TYPE_OBJECT;
656
    location.location.object = obj;
657

658
    int result = hwloc_get_local_numanode_objs(topo, &location, &num_nodes, nodes, 0);
659
    if (result == 0 && num_nodes > 0 && num_nodes <= MAX_NUMA_NODES) {
660
        return nodes[0]->logical_index;
661
    }
662
    return -1;
663
#else
664
    hwloc_obj_t non_io_ancestor = hwloc_get_non_io_ancestor_obj(topo, obj);
665
    if (non_io_ancestor == NULL) {
666
        return -1;
667
    }
668

669
    // Iterate over NUMA nodes and check their nodeset
670
    hwloc_obj_t numa_node = NULL;
671
    while ((numa_node = hwloc_get_next_obj_by_type(topo, HWLOC_OBJ_NUMANODE, numa_node)) != NULL) {
672
        if (hwloc_bitmap_isset(non_io_ancestor->nodeset, numa_node->os_index)) {
673
            return numa_node->logical_index;
674
        }
675
    }
676

677
    return -1;
678
#endif /* ! HWLOC_VERSION_MAJOR >= 2 && HWLOC_VERSION_MINOR >= 5 */
679
}
680

681
static hwloc_obj_t HwLocDeviceGetByKernelName(hwloc_topology_t topo, const char *interface_name)
682
{
683
    hwloc_obj_t obj = NULL;
684

685
    while ((obj = hwloc_get_next_osdev(topo, obj)) != NULL) {
686
        if (obj->attr->osdev.type == HWLOC_OBJ_OSDEV_NETWORK &&
687
                strcmp(obj->name, interface_name) == 0) {
688
            hwloc_obj_t parent = obj->parent;
689
            while (parent) {
690
                if (parent->type == HWLOC_OBJ_PCI_DEVICE) {
691
                    return parent;
692
                }
693
                parent = parent->parent;
694
            }
695
        }
696
    }
697
    return NULL;
698
}
699

700
// Static function to deparse PCIe interface string name to individual components
701
/**
702
 * \brief Parse PCIe address string to individual components
703
 * \param[in] pcie_address PCIe address string
704
 * \param[out] domain Domain component
705
 * \param[out] bus Bus component
706
 * \param[out] device Device component
707
 * \param[out] function Function component
708
 */
709
static int PcieAddressToComponents(const char *pcie_address, unsigned int *domain,
710
        unsigned int *bus, unsigned int *device, unsigned int *function)
711
{
712
    // Handle both full and short PCIe address formats
713
    if (sscanf(pcie_address, "%x:%x:%x.%x", domain, bus, device, function) != 4) {
714
        if (sscanf(pcie_address, "%x:%x.%x", bus, device, function) != 3) {
715
            return -1;
716
        }
717
        *domain = 0; // Default domain to 0 if not provided
718
    }
719
    return 0;
720
}
721

722
// Function to convert PCIe address to hwloc object
723
static hwloc_obj_t HwLocDeviceGetByPcie(hwloc_topology_t topo, const char *pcie_address)
724
{
725
    hwloc_obj_t obj = NULL;
726
    unsigned int domain, bus, device, function;
727
    int r = PcieAddressToComponents(pcie_address, &domain, &bus, &device, &function);
728
    if (r == 0) {
729
        while ((obj = hwloc_get_next_pcidev(topo, obj)) != NULL) {
730
            if (obj->attr->pcidev.domain == domain && obj->attr->pcidev.bus == bus &&
731
                    obj->attr->pcidev.dev == device && obj->attr->pcidev.func == function) {
732
                return obj;
733
            }
734
        }
735
    }
736
    return NULL;
737
}
738

739
static void HwlocObjectDump(hwloc_obj_t obj, const char *iface_name)
740
{
741
    if (!obj) {
742
        SCLogDebug("No object found for the given PCIe address.\n");
743
        return;
744
    }
745

746
    static char pcie_address[32];
747
    snprintf(pcie_address, sizeof(pcie_address), "%04x:%02x:%02x.%x", obj->attr->pcidev.domain,
748
            obj->attr->pcidev.bus, obj->attr->pcidev.dev, obj->attr->pcidev.func);
749
    SCLogDebug("Interface (%s / %s) has NUMA ID %d", iface_name, pcie_address,
750
            HwLocDeviceNumaGet(topology, obj));
751

752
    SCLogDebug("Object type: %s\n", hwloc_obj_type_string(obj->type));
753
    SCLogDebug("Logical index: %u\n", obj->logical_index);
754
    SCLogDebug("Depth: %u\n", obj->depth);
755
    SCLogDebug("Attributes:\n");
756
    if (obj->type == HWLOC_OBJ_PCI_DEVICE) {
757
        SCLogDebug("  Domain: %04x\n", obj->attr->pcidev.domain);
758
        SCLogDebug("  Bus: %02x\n", obj->attr->pcidev.bus);
759
        SCLogDebug("  Device: %02x\n", obj->attr->pcidev.dev);
760
        SCLogDebug("  Function: %01x\n", obj->attr->pcidev.func);
761
        SCLogDebug("  Class ID: %04x\n", obj->attr->pcidev.class_id);
762
        SCLogDebug("  Vendor ID: %04x\n", obj->attr->pcidev.vendor_id);
763
        SCLogDebug("  Device ID: %04x\n", obj->attr->pcidev.device_id);
764
        SCLogDebug("  Subvendor ID: %04x\n", obj->attr->pcidev.subvendor_id);
765
        SCLogDebug("  Subdevice ID: %04x\n", obj->attr->pcidev.subdevice_id);
766
        SCLogDebug("  Revision: %02x\n", obj->attr->pcidev.revision);
767
        SCLogDebug("  Link speed: %f GB/s\n", obj->attr->pcidev.linkspeed);
768
    } else {
769
        SCLogDebug("  No PCI device attributes available.\n");
770
    }
771
}
772

773
static bool TopologyShouldAutopin(ThreadVars *tv, ThreadsAffinityType *taf)
774
{
775
    bool cond;
776
    SCMutexLock(&taf->taf_mutex);
777
    cond = tv->type == TVT_PPT && tv->iface_name &&
778
           (strcmp(tv->iface_name, taf->name) == 0 ||
779
                   (strcmp("worker-cpu-set", taf->name) == 0 && RunmodeIsWorkers()) ||
780
                   (strcmp("receive-cpu-set", taf->name) == 0 && RunmodeIsAutofp()));
781
    SCMutexUnlock(&taf->taf_mutex);
782
    return cond;
783
}
784

785
/**
786
 * \brief Initialize the hardware topology.
787
 * \retval 0 on success, -1 on error
788
 */
789
static int TopologyInitialize(void)
790
{
791
    if (topology == NULL) {
792
        if (hwloc_topology_init(&topology) == -1) {
793
            SCLogError("Failed to initialize topology");
794
            return -1;
795
        }
796

797
        if (hwloc_topology_set_flags(topology, HWLOC_TOPOLOGY_FLAG_WHOLE_SYSTEM) == -1 ||
798
                hwloc_topology_set_io_types_filter(topology, HWLOC_TYPE_FILTER_KEEP_ALL) == -1 ||
799
                hwloc_topology_load(topology) == -1) {
800
            SCLogError("Failed to set/load topology");
801
            hwloc_topology_destroy(topology);
802
            topology = NULL;
803
            return -1;
804
        }
805
    }
806
    return 0;
807
}
808

809
void TopologyDestroy(void)
810
{
811
    if (topology != NULL) {
812
        hwloc_topology_destroy(topology);
813
        topology = NULL;
814
    }
815
}
816

817
static int InterfaceGetNumaNode(ThreadVars *tv)
818
{
819
    hwloc_obj_t if_obj = HwLocDeviceGetByKernelName(topology, tv->iface_name);
820
    if (if_obj == NULL) {
821
        if_obj = HwLocDeviceGetByPcie(topology, tv->iface_name);
822
    }
823

824
    if (if_obj != NULL && SCLogGetLogLevel() == SC_LOG_DEBUG) {
825
        HwlocObjectDump(if_obj, tv->iface_name);
826
    }
827

828
    int32_t numa_id = HwLocDeviceNumaGet(topology, if_obj);
829
    if (numa_id < 0 && SCRunmodeGet() == RUNMODE_DPDK) {
830
        // DPDK fallback for e.g. net_bonding (vdev) PMDs
831
        int32_t r = DPDKDeviceNameSetSocketID(tv->iface_name, &numa_id);
832
        if (r < 0) {
833
            numa_id = -1;
834
        }
835
    }
836

837
    if (numa_id < 0) {
838
        SCLogDebug("Unable to find NUMA node for interface %s", tv->iface_name);
839
    }
840

841
    return numa_id;
842
}
843
#endif /* HAVE_HWLOC */
844

845
static bool CPUIsFromNuma(uint16_t ncpu, uint16_t numa)
846
{
×
847
#ifdef HAVE_HWLOC
848
    int core_id = ncpu;
849
    int depth = hwloc_get_type_depth(topology, HWLOC_OBJ_NUMANODE);
850
    hwloc_obj_t numa_node = NULL;
851
    bool found = false;
852
    uint16_t found_numa = 0;
853

854
    // Invalid depth or no NUMA nodes available
855
    if (depth == HWLOC_TYPE_DEPTH_UNKNOWN) {
856
        return false;
857
    }
858

859
    while ((numa_node = hwloc_get_next_obj_by_depth(topology, depth, numa_node)) != NULL) {
860
        hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();
861
        if (cpuset == NULL) {
862
            SCLogDebug("Failed to allocate cpuset");
863
            continue;
864
        }
865
        hwloc_bitmap_copy(cpuset, numa_node->cpuset);
866

867
        if (hwloc_bitmap_isset(cpuset, core_id)) {
868
            SCLogDebug("Core %d - NUMA %d", core_id, numa_node->logical_index);
869
            found = true;
870
            found_numa = numa_node->logical_index;
871
            hwloc_bitmap_free(cpuset);
872
            break;
873
        }
874
        hwloc_bitmap_free(cpuset);
875
    }
876

877
    // After loop, check if we found the CPU and match the requested NUMA node
878
    if (found && numa == found_numa) {
879
        return true;
880
    }
881

882
    // CPU was not found in any NUMA node or did not match requested NUMA
883
#endif /* HAVE_HWLOC */
884

885
    return false;
×
886
}
×
887

888
static int16_t FindCPUInNumaNode(int numa_node, ThreadsAffinityType *taf)
889
{
×
890
    if (numa_node < 0) {
×
891
        return -1;
×
892
    }
×
893

894
    if (taf->lcpu[numa_node] >= UtilCpuGetNumProcessorsOnline()) {
×
895
        return -1;
×
896
    }
×
897

898
    uint16_t cpu = taf->lcpu[numa_node];
×
899
    while (cpu < UtilCpuGetNumProcessorsOnline() &&
×
900
            (!CPU_ISSET(cpu, &taf->cpu_set) || !CPUIsFromNuma(cpu, (uint16_t)numa_node))) {
×
901
        cpu++;
×
902
    }
×
903

904
    taf->lcpu[numa_node] =
×
905
            (CPU_ISSET(cpu, &taf->cpu_set) && CPUIsFromNuma(cpu, (uint16_t)numa_node))
×
906
                    ? cpu + 1
×
907
                    : UtilCpuGetNumProcessorsOnline();
×
908
    return (CPU_ISSET(cpu, &taf->cpu_set) && CPUIsFromNuma(cpu, (uint16_t)numa_node)) ? (int16_t)cpu
×
909
                                                                                      : -1;
×
910
}
×
911

912
static int16_t CPUSelectFromNuma(int iface_numa, ThreadsAffinityType *taf)
913
{
×
914
    if (iface_numa != -1) {
×
915
        return FindCPUInNumaNode(iface_numa, taf);
×
916
    }
×
917
    return -1;
×
918
}
×
919

920
static int16_t CPUSelectAlternative(int iface_numa, ThreadsAffinityType *taf)
921
{
×
922
    for (int nid = 0; nid < MAX_NUMA_NODES; nid++) {
×
923
        if (iface_numa == nid) {
×
924
            continue;
×
925
        }
×
926

927
        int16_t cpu = FindCPUInNumaNode(nid, taf);
×
928
        if (cpu != -1) {
×
929
            SCLogPerf("CPU %d from NUMA %d assigned to a network interface located on NUMA %d", cpu,
×
930
                    nid, iface_numa);
×
931
            return cpu;
×
932
        }
×
933
    }
×
934
    return -1;
×
935
}
×
936

937
/**
938
 * \brief Select the next available CPU for the given affinity type.
939
 * taf->cpu_set is a bit array where each bit represents a CPU core.
940
 * The function iterates over the bit array and returns the first available CPU.
941
 * If last used CPU core index is higher than the indexes of available cores,
942
 * we reach the end of the array, and we reset the CPU selection.
943
 * On the second reset attempt, the function bails out with a default value.
944
 * The second attempt should only happen with an empty CPU set.
945
 */
946
static uint16_t CPUSelectDefault(ThreadsAffinityType *taf)
947
{
×
948
    uint16_t cpu = taf->lcpu[0];
×
949
    int attempts = 0;
×
950
    uint16_t num_procs = UtilCpuGetNumProcessorsOnline();
×
951
    if (num_procs > 0) {
×
952
        while (!CPU_ISSET(cpu, &taf->cpu_set) && attempts < 2) {
×
953
            cpu = (cpu + 1) % num_procs;
×
954
            if (cpu == 0) {
×
955
                attempts++;
×
956
            }
×
957
        }
×
958
    }
×
959

960
    taf->lcpu[0] = cpu + 1;
×
961
    return cpu;
×
962
}
×
963

964
static uint16_t CPUSelectFromNumaOrDefault(int iface_numa, ThreadsAffinityType *taf)
965
{
×
966
    uint16_t attempts = 0;
×
967
    int16_t cpu = -1;
×
968
    while (attempts < 2) {
×
969
        cpu = CPUSelectFromNuma(iface_numa, taf);
×
970
        if (cpu == -1) {
×
971
            cpu = CPUSelectAlternative(iface_numa, taf);
×
972
            if (cpu == -1) {
×
973
                // All CPUs from all NUMAs are used at this point
974
                ResetCPUs(taf);
×
975
                attempts++;
×
976
            }
×
977
        }
×
978

979
        if (cpu >= 0) {
×
980
            return (uint16_t)cpu;
×
981
        }
×
982
    }
×
983
    return CPUSelectDefault(taf);
×
984
}
×
985

986
static uint16_t GetNextAvailableCPU(int iface_numa, ThreadsAffinityType *taf)
987
{
×
988
    if (iface_numa < 0) {
×
989
        return CPUSelectDefault(taf);
×
990
    }
×
991

992
    return CPUSelectFromNumaOrDefault(iface_numa, taf);
×
993
}
×
994

995
static bool AutopinEnabled(void)
996
{
×
997
    int autopin = 0;
×
998
    if (SCConfGetBool("threading.autopin", &autopin) != 1) {
×
999
        return false;
×
1000
    }
×
1001
    return (bool)autopin;
×
1002
}
×
1003

1004
#endif /* OS_WIN32 and __OpenBSD__ */
1005

1006
uint16_t AffinityGetNextCPU(ThreadVars *tv, ThreadsAffinityType *taf)
1007
{
×
1008
    uint16_t ncpu = 0;
×
1009
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
×
1010
    int iface_numa = -1;
×
1011
    if (AutopinEnabled()) {
×
1012
#ifdef HAVE_HWLOC
1013
        if (TopologyShouldAutopin(tv, taf)) {
1014
            if (TopologyInitialize() < 0) {
1015
                SCLogError("Failed to initialize topology for CPU affinity");
1016
                return ncpu;
1017
            }
1018
            iface_numa = InterfaceGetNumaNode(tv);
1019
        }
1020
#else
1021
        static bool printed = false;
×
1022
        if (!printed) {
×
1023
            printed = true;
×
1024
            SCLogWarning(
×
1025
                    "threading.autopin option is enabled but hwloc support is not compiled in. "
×
1026
                    "Make sure to pass --enable-hwloc to configure when building Suricata.");
×
1027
        }
×
1028
#endif /* HAVE_HWLOC */
×
1029
    }
×
1030

1031
    SCMutexLock(&taf->taf_mutex);
×
1032
    ncpu = GetNextAvailableCPU(iface_numa, taf);
×
1033
    SCLogDebug("Setting affinity on CPU %d", ncpu);
×
1034
    SCMutexUnlock(&taf->taf_mutex);
×
1035
#endif /* OS_WIN32 and __OpenBSD__ */
×
1036
    return ncpu;
×
1037
}
×
1038

1039
/**
1040
 * \brief Return the total number of CPUs in a given affinity
1041
 * \retval the number of affined CPUs
1042
 */
1043
uint16_t UtilAffinityGetAffinedCPUNum(ThreadsAffinityType *taf)
1044
{
×
1045
    uint16_t ncpu = 0;
×
1046
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
×
1047
    SCMutexLock(&taf->taf_mutex);
×
1048
    for (int i = UtilCpuGetNumProcessorsOnline(); i >= 0; i--)
×
1049
        if (CPU_ISSET(i, &taf->cpu_set)) {
×
1050
            ncpu++;
×
1051
        }
×
1052
    SCMutexUnlock(&taf->taf_mutex);
×
1053
#endif
×
1054
    return ncpu;
×
1055
}
×
1056

1057
#ifdef HAVE_DPDK
1058
/**
1059
 * Find if CPU sets overlap
1060
 * \return 1 if CPUs overlap, 0 otherwise
1061
 */
1062
uint16_t UtilAffinityCpusOverlap(ThreadsAffinityType *taf1, ThreadsAffinityType *taf2)
1063
{
1064
    ThreadsAffinityType tmptaf;
1065
    CPU_ZERO(&tmptaf);
1066
    SCMutexInit(&tmptaf.taf_mutex, NULL);
1067

1068
    cpu_set_t tmpcset;
1069

1070
    SCMutexLock(&taf1->taf_mutex);
1071
    SCMutexLock(&taf2->taf_mutex);
1072
    CPU_AND(&tmpcset, &taf1->cpu_set, &taf2->cpu_set);
1073
    SCMutexUnlock(&taf2->taf_mutex);
1074
    SCMutexUnlock(&taf1->taf_mutex);
1075

1076
    for (int i = UtilCpuGetNumProcessorsOnline(); i >= 0; i--)
1077
        if (CPU_ISSET(i, &tmpcset)) {
1078
            return 1;
1079
        }
1080
    return 0;
1081
}
1082

1083
/**
1084
 * Function makes sure that CPUs of different types don't overlap by excluding
1085
 * one affinity type from the other
1086
 * \param mod_taf - CPU set to be modified
1087
 * \param static_taf - static CPU set to be used only for evaluation
1088
 */
1089
void UtilAffinityCpusExclude(ThreadsAffinityType *mod_taf, ThreadsAffinityType *static_taf)
1090
{
1091
    SCMutexLock(&mod_taf->taf_mutex);
1092
    SCMutexLock(&static_taf->taf_mutex);
1093
    int max_cpus = UtilCpuGetNumProcessorsOnline();
1094
    for (int cpu = 0; cpu < max_cpus; cpu++) {
1095
        if (CPU_ISSET(cpu, &mod_taf->cpu_set) && CPU_ISSET(cpu, &static_taf->cpu_set)) {
1096
            CPU_CLR(cpu, &mod_taf->cpu_set);
1097
        }
1098
    }
1099
    SCMutexUnlock(&static_taf->taf_mutex);
1100
    SCMutexUnlock(&mod_taf->taf_mutex);
1101
}
1102
#endif /* HAVE_DPDK */
1103

1104
#ifdef UNITTESTS
1105
// avoiding Darwin/MacOS as it does not support bitwise CPU affinity
1106
#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
1107

1108
/**
1109
 * \brief Helper function to reset affinity state for unit tests
1110
 * This properly clears CPU sets without destroying initialized mutexes
1111
 */
1112
static void ResetAffinityForTest(void)
1113
{
27✔
1114
    thread_affinity_init_done = 0;
27✔
1115
    for (int i = 0; i < MAX_CPU_SET; i++) {
135✔
1116
        ThreadsAffinityType *taf = &thread_affinity[i];
108✔
1117
        CPU_ZERO(&taf->cpu_set);
108✔
1118
        CPU_ZERO(&taf->lowprio_cpu);
108✔
1119
        CPU_ZERO(&taf->medprio_cpu);
108✔
1120
        CPU_ZERO(&taf->hiprio_cpu);
108✔
1121
        taf->nb_threads = 0;
108✔
1122
        taf->prio = PRIO_LOW;
108✔
1123
        taf->mode_flag = BALANCED_AFFINITY;
108✔
1124

1125
        for (int j = 0; j < MAX_NUMA_NODES; j++) {
1,836✔
1126
            taf->lcpu[j] = 0;
1,728✔
1127
        }
1,728✔
1128

1129
        if (taf->children) {
108✔
1130
            for (uint32_t j = 0; j < taf->nb_children; j++) {
16✔
1131
                if (taf->children[j]) {
10✔
1132
                    SCFree((void *)taf->children[j]->name);
10✔
1133
                    SCFree(taf->children[j]);
10✔
1134
                }
10✔
1135
            }
10✔
1136
            SCFree(taf->children);
6✔
1137
            taf->children = NULL;
6✔
1138
        }
6✔
1139
        taf->nb_children = 0;
108✔
1140
        taf->nb_children_capacity = 0;
108✔
1141

1142
        if (i == MANAGEMENT_CPU_SET) {
108✔
1143
            taf->name = "management-cpu-set";
27✔
1144
        } else if (i == WORKER_CPU_SET) {
81✔
1145
            taf->name = "worker-cpu-set";
27✔
1146
        } else if (i == VERDICT_CPU_SET) {
54✔
1147
            taf->name = "verdict-cpu-set";
27✔
1148
        } else if (i == RECEIVE_CPU_SET) {
27✔
1149
            taf->name = "receive-cpu-set";
27✔
1150
        } else {
27✔
1151
            taf->name = NULL;
1152
        }
1153

1154
        // Don't touch the mutex - it should remain initialized
1155
    }
108✔
1156
}
27✔
1157

1158
/**
1159
 * \brief Test basic CPU affinity parsing in new format
1160
 */
1161
static int ThreadingAffinityTest01(void)
1162
{
1✔
1163
    SCConfCreateContextBackup();
1✔
1164
    SCConfInit();
1✔
1165
    ResetAffinityForTest();
1✔
1166
    const char *config = "%YAML 1.1\n"
1✔
1167
                         "---\n"
1✔
1168
                         "threading:\n"
1✔
1169
                         "  cpu-affinity:\n"
1✔
1170
                         "    management-cpu-set:\n"
1✔
1171
                         "      cpu: [ 0 ]\n"
1✔
1172
                         "    worker-cpu-set:\n"
1✔
1173
                         "      cpu: [ 1, 2, 3 ]\n";
1✔
1174

1175
    SCConfYamlLoadString(config, strlen(config));
1✔
1176

1177
    AffinitySetupLoadFromConfig();
1✔
1178
    FAIL_IF_NOT(AffinityConfigIsLegacy() == false);
1✔
1179

1180
    ThreadsAffinityType *mgmt_taf = &thread_affinity[MANAGEMENT_CPU_SET];
1✔
1181
    FAIL_IF_NOT(CPU_ISSET(0, &mgmt_taf->cpu_set));
1✔
1182
    FAIL_IF_NOT(CPU_COUNT(&mgmt_taf->cpu_set) == 1);
1✔
1183

1184
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1185
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
1✔
1186
    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->cpu_set));
1✔
1187
    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->cpu_set));
1✔
1188
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set));
1✔
1189

1190
    SCConfDeInit();
1✔
1191
    SCConfRestoreContextBackup();
1✔
1192
    PASS;
1✔
1193
}
1✔
1194

1195
/**
1196
 * \brief Test deprecated CPU affinity format parsing
1197
 */
1198
static int ThreadingAffinityTest02(void)
1199
{
1✔
1200
    SCConfCreateContextBackup();
1✔
1201
    SCConfInit();
1✔
1202
    ResetAffinityForTest();
1✔
1203

1204
    const char *config = "%YAML 1.1\n"
1✔
1205
                         "---\n"
1✔
1206
                         "threading:\n"
1✔
1207
                         "  cpu-affinity:\n"
1✔
1208
                         "    - worker-cpu-set:\n"
1✔
1209
                         "        cpu: [ 1, 2 ]\n";
1✔
1210

1211
    SCConfYamlLoadString(config, strlen(config));
1✔
1212
    AffinitySetupLoadFromConfig();
1✔
1213
    FAIL_IF_NOT(AffinityConfigIsLegacy() == true);
1✔
1214

1215
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1216
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
1✔
1217
    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->cpu_set));
1✔
1218
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 2);
1✔
1219

1220
    SCConfDeInit();
1✔
1221
    SCConfRestoreContextBackup();
1✔
1222
    PASS;
1✔
1223
}
1✔
1224

1225
/**
1226
 * \brief Test CPU range parsing ("0-3")
1227
 */
1228
static int ThreadingAffinityTest03(void)
1229
{
1✔
1230
    SCConfCreateContextBackup();
1✔
1231
    SCConfInit();
1✔
1232
    ResetAffinityForTest();
1✔
1233

1234
    const char *config = "%YAML 1.1\n"
1✔
1235
                         "---\n"
1✔
1236
                         "threading:\n"
1✔
1237
                         "  cpu-affinity:\n"
1✔
1238
                         "    worker-cpu-set:\n"
1✔
1239
                         "      cpu: [ \"0-3\" ]\n";
1✔
1240

1241
    SCConfYamlLoadString(config, strlen(config));
1✔
1242
    AffinitySetupLoadFromConfig();
1✔
1243

1244
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1245
    FAIL_IF_NOT(CPU_ISSET(0, &worker_taf->cpu_set));
1✔
1246
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
1✔
1247
    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->cpu_set));
1✔
1248
    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->cpu_set));
1✔
1249
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 4);
1✔
1250

1251
    SCConfDeInit();
1✔
1252
    SCConfRestoreContextBackup();
1✔
1253
    PASS;
1✔
1254
}
1✔
1255

1256
/**
1257
 * \brief Test mixed CPU specification parsing (individual CPUs in list)
1258
 */
1259
static int ThreadingAffinityTest04(void)
1260
{
1✔
1261
    SCConfCreateContextBackup();
1✔
1262
    SCConfInit();
1✔
1263
    ResetAffinityForTest();
1✔
1264

1265
    const char *config = "%YAML 1.1\n"
1✔
1266
                         "---\n"
1✔
1267
                         "threading:\n"
1✔
1268
                         "  cpu-affinity:\n"
1✔
1269
                         "    worker-cpu-set:\n"
1✔
1270
                         "      cpu: [ 1, 3, 5 ]\n";
1✔
1271

1272
    SCConfYamlLoadString(config, strlen(config));
1✔
1273
    AffinitySetupLoadFromConfig();
1✔
1274

1275
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1276
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
1✔
1277
    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->cpu_set));
1✔
1278
    FAIL_IF_NOT(CPU_ISSET(5, &worker_taf->cpu_set));
1✔
1279
    FAIL_IF(CPU_ISSET(0, &worker_taf->cpu_set));
1✔
1280
    FAIL_IF(CPU_ISSET(2, &worker_taf->cpu_set));
1✔
1281
    FAIL_IF(CPU_ISSET(4, &worker_taf->cpu_set));
1✔
1282
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 3);
1✔
1283

1284
    SCConfDeInit();
1✔
1285
    SCConfRestoreContextBackup();
1✔
1286
    PASS;
1✔
1287
}
1✔
1288

1289
/**
1290
 * \brief Test "all" CPU specification
1291
 */
1292
static int ThreadingAffinityTest05(void)
1293
{
1✔
1294
    SCConfCreateContextBackup();
1✔
1295
    SCConfInit();
1✔
1296
    ResetAffinityForTest();
1✔
1297

1298
    const char *config = "%YAML 1.1\n"
1✔
1299
                         "---\n"
1✔
1300
                         "threading:\n"
1✔
1301
                         "  cpu-affinity:\n"
1✔
1302
                         "    worker-cpu-set:\n"
1✔
1303
                         "      cpu: [ \"all\" ]\n";
1✔
1304

1305
    SCConfYamlLoadString(config, strlen(config));
1✔
1306
    AffinitySetupLoadFromConfig();
1✔
1307

1308
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1309
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == UtilCpuGetNumProcessorsOnline());
1✔
1310

1311
    SCConfDeInit();
1✔
1312
    SCConfRestoreContextBackup();
1✔
1313
    PASS;
1✔
1314
}
1✔
1315

1316
/**
1317
 * \brief Test priority settings parsing
1318
 */
1319
static int ThreadingAffinityTest06(void)
1320
{
1✔
1321
    SCConfCreateContextBackup();
1✔
1322
    SCConfInit();
1✔
1323
    ResetAffinityForTest();
1✔
1324

1325
    const char *config = "%YAML 1.1\n"
1✔
1326
                         "---\n"
1✔
1327
                         "threading:\n"
1✔
1328
                         "  cpu-affinity:\n"
1✔
1329
                         "    worker-cpu-set:\n"
1✔
1330
                         "      cpu: [ 0, 1, 2, 3 ]\n"
1✔
1331
                         "      prio:\n"
1✔
1332
                         "        low: [ 0 ]\n"
1✔
1333
                         "        medium: [ \"1-2\" ]\n"
1✔
1334
                         "        high: [ 3 ]\n"
1✔
1335
                         "        default: \"medium\"\n";
1✔
1336

1337
    SCConfYamlLoadString(config, strlen(config));
1✔
1338
    AffinitySetupLoadFromConfig();
1✔
1339

1340
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1341
    FAIL_IF_NOT(CPU_ISSET(0, &worker_taf->lowprio_cpu));
1✔
1342
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->medprio_cpu));
1✔
1343
    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->medprio_cpu));
1✔
1344
    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->hiprio_cpu));
1✔
1345
    FAIL_IF_NOT(worker_taf->prio == PRIO_MEDIUM);
1✔
1346

1347
    SCConfDeInit();
1✔
1348
    SCConfRestoreContextBackup();
1✔
1349
    PASS;
1✔
1350
}
1✔
1351

1352
/**
1353
 * \brief Test mode settings (exclusive/balanced)
1354
 */
1355
static int ThreadingAffinityTest07(void)
1356
{
1✔
1357
    SCConfCreateContextBackup();
1✔
1358
    SCConfInit();
1✔
1359
    ResetAffinityForTest();
1✔
1360

1361
    const char *config = "%YAML 1.1\n"
1✔
1362
                         "---\n"
1✔
1363
                         "threading:\n"
1✔
1364
                         "  cpu-affinity:\n"
1✔
1365
                         "    worker-cpu-set:\n"
1✔
1366
                         "      cpu: [ 0, 1 ]\n"
1✔
1367
                         "      mode: \"exclusive\"\n";
1✔
1368

1369
    SCConfYamlLoadString(config, strlen(config));
1✔
1370
    AffinitySetupLoadFromConfig();
1✔
1371

1372
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1373
    FAIL_IF_NOT(worker_taf->mode_flag == EXCLUSIVE_AFFINITY);
1✔
1374

1375
    SCConfDeInit();
1✔
1376
    SCConfRestoreContextBackup();
1✔
1377
    PASS;
1✔
1378
}
1✔
1379

1380
/**
1381
 * \brief Test threads count parsing
1382
 */
1383
static int ThreadingAffinityTest08(void)
1384
{
1✔
1385
    SCConfCreateContextBackup();
1✔
1386
    SCConfInit();
1✔
1387
    ResetAffinityForTest();
1✔
1388

1389
    const char *config = "%YAML 1.1\n"
1✔
1390
                         "---\n"
1✔
1391
                         "threading:\n"
1✔
1392
                         "  cpu-affinity:\n"
1✔
1393
                         "    worker-cpu-set:\n"
1✔
1394
                         "      cpu: [ 0, 1, 2 ]\n"
1✔
1395
                         "      threads: 4\n";
1✔
1396

1397
    SCConfYamlLoadString(config, strlen(config));
1✔
1398
    AffinitySetupLoadFromConfig();
1✔
1399

1400
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1401
    FAIL_IF_NOT(worker_taf->nb_threads == 4);
1✔
1402

1403
    SCConfDeInit();
1✔
1404
    SCConfRestoreContextBackup();
1✔
1405
    PASS;
1✔
1406
}
1✔
1407

1408
/**
1409
 * \brief Test interface-specific CPU set parsing
1410
 */
1411
static int ThreadingAffinityTest09(void)
1412
{
1✔
1413
    SCConfCreateContextBackup();
1✔
1414
    SCConfInit();
1✔
1415
    ResetAffinityForTest();
1✔
1416

1417
    const char *config = "%YAML 1.1\n"
1✔
1418
                         "---\n"
1✔
1419
                         "threading:\n"
1✔
1420
                         "  cpu-affinity:\n"
1✔
1421
                         "    worker-cpu-set:\n"
1✔
1422
                         "      cpu: [ 0, 1 ]\n"
1✔
1423
                         "      interface-specific-cpu-set:\n"
1✔
1424
                         "        - interface: \"eth0\"\n"
1✔
1425
                         "          cpu: [ 2, 3 ]\n"
1✔
1426
                         "          mode: \"exclusive\"\n";
1✔
1427

1428
    SCConfYamlLoadString(config, strlen(config));
1✔
1429
    AffinitySetupLoadFromConfig();
1✔
1430

1431
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1432
    FAIL_IF_NOT(worker_taf->nb_children == 1);
1✔
1433

1434
    ThreadsAffinityType *iface_taf = worker_taf->children[0];
1✔
1435
    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
1✔
1436
    FAIL_IF_NOT(CPU_ISSET(2, &iface_taf->cpu_set));
1✔
1437
    FAIL_IF_NOT(CPU_ISSET(3, &iface_taf->cpu_set));
1✔
1438
    FAIL_IF_NOT(CPU_COUNT(&iface_taf->cpu_set) == 2);
1✔
1439
    FAIL_IF_NOT(iface_taf->mode_flag == EXCLUSIVE_AFFINITY);
1✔
1440

1441
    SCConfDeInit();
1✔
1442
    SCConfRestoreContextBackup();
1✔
1443
    PASS;
1✔
1444
}
1✔
1445

1446
/**
1447
 * \brief Test multiple interface-specific CPU sets
1448
 */
1449
static int ThreadingAffinityTest10(void)
1450
{
1✔
1451
    SCConfCreateContextBackup();
1✔
1452
    SCConfInit();
1✔
1453
    ResetAffinityForTest();
1✔
1454

1455
    const char *config = "%YAML 1.1\n"
1✔
1456
                         "---\n"
1✔
1457
                         "threading:\n"
1✔
1458
                         "  cpu-affinity:\n"
1✔
1459
                         "    receive-cpu-set:\n"
1✔
1460
                         "      cpu: [ 0 ]\n"
1✔
1461
                         "      interface-specific-cpu-set:\n"
1✔
1462
                         "        - interface: \"eth0\"\n"
1✔
1463
                         "          cpu: [ 1, 2 ]\n"
1✔
1464
                         "        - interface: \"eth1\"\n"
1✔
1465
                         "          cpu: [ 3, 4 ]\n";
1✔
1466

1467
    SCConfYamlLoadString(config, strlen(config));
1✔
1468
    AffinitySetupLoadFromConfig();
1✔
1469

1470
    ThreadsAffinityType *receive_taf = &thread_affinity[RECEIVE_CPU_SET];
1✔
1471
    FAIL_IF_NOT(receive_taf->nb_children == 2);
1✔
1472

1473
    bool eth0_found = false, eth1_found = false;
1✔
1474

1475
    for (uint32_t i = 0; i < receive_taf->nb_children; i++) {
3✔
1476
        ThreadsAffinityType *iface_taf = receive_taf->children[i];
2✔
1477
        if (strcmp(iface_taf->name, "eth0") == 0) {
2✔
1478
            if (CPU_ISSET(1, &iface_taf->cpu_set) && CPU_ISSET(2, &iface_taf->cpu_set) &&
1✔
1479
                    CPU_COUNT(&iface_taf->cpu_set) == 2) {
1✔
1480
                eth0_found = true;
1✔
1481
            }
1✔
1482
        } else if (strcmp(iface_taf->name, "eth1") == 0) {
1✔
1483
            if (CPU_ISSET(3, &iface_taf->cpu_set) && CPU_ISSET(4, &iface_taf->cpu_set) &&
1✔
1484
                    CPU_COUNT(&iface_taf->cpu_set) == 2) {
1✔
1485
                eth1_found = true;
1✔
1486
            }
1✔
1487
        }
1✔
1488
    }
2✔
1489

1490
    FAIL_IF_NOT(eth0_found && eth1_found);
1✔
1491

1492
    SCConfDeInit();
1✔
1493
    SCConfRestoreContextBackup();
1✔
1494
    PASS;
1✔
1495
}
1✔
1496

1497
/**
1498
 * \brief Test interface-specific priority settings
1499
 */
1500
static int ThreadingAffinityTest11(void)
1501
{
1✔
1502
    SCConfCreateContextBackup();
1✔
1503
    SCConfInit();
1✔
1504
    ResetAffinityForTest();
1✔
1505

1506
    const char *config = "%YAML 1.1\n"
1✔
1507
                         "---\n"
1✔
1508
                         "threading:\n"
1✔
1509
                         "  cpu-affinity:\n"
1✔
1510
                         "    worker-cpu-set:\n"
1✔
1511
                         "      cpu: [ 0 ]\n"
1✔
1512
                         "      interface-specific-cpu-set:\n"
1✔
1513
                         "        - interface: \"eth0\"\n"
1✔
1514
                         "          cpu: [ 1, 2, 3 ]\n"
1✔
1515
                         "          prio:\n"
1✔
1516
                         "            high: [ \"all\" ]\n"
1✔
1517
                         "            default: \"high\"\n";
1✔
1518

1519
    SCConfYamlLoadString(config, strlen(config));
1✔
1520
    AffinitySetupLoadFromConfig();
1✔
1521

1522
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1523
    FAIL_IF_NOT(worker_taf->nb_children == 1);
1✔
1524

1525
    ThreadsAffinityType *iface_taf = worker_taf->children[0];
1✔
1526
    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
1✔
1527
    FAIL_IF_NOT(CPU_ISSET(1, &iface_taf->hiprio_cpu));
1✔
1528
    FAIL_IF_NOT(CPU_ISSET(2, &iface_taf->hiprio_cpu));
1✔
1529
    FAIL_IF_NOT(CPU_ISSET(3, &iface_taf->hiprio_cpu));
1✔
1530
    FAIL_IF_NOT(iface_taf->prio == PRIO_HIGH);
1✔
1531

1532
    SCConfDeInit();
1✔
1533
    SCConfRestoreContextBackup();
1✔
1534
    PASS;
1✔
1535
}
1✔
1536

1537
/**
1538
 * \brief Test complete configuration with all CPU sets
1539
 */
1540
static int ThreadingAffinityTest12(void)
1541
{
1✔
1542
    SCConfCreateContextBackup();
1✔
1543
    SCConfInit();
1✔
1544
    ResetAffinityForTest();
1✔
1545

1546
    const char *config = "%YAML 1.1\n"
1✔
1547
                         "---\n"
1✔
1548
                         "threading:\n"
1✔
1549
                         "  cpu-affinity:\n"
1✔
1550
                         "    management-cpu-set:\n"
1✔
1551
                         "      cpu: [ 0 ]\n"
1✔
1552
                         "    receive-cpu-set:\n"
1✔
1553
                         "      cpu: [ 1 ]\n"
1✔
1554
                         "    worker-cpu-set:\n"
1✔
1555
                         "      cpu: [ 2, 3 ]\n"
1✔
1556
                         "      interface-specific-cpu-set:\n"
1✔
1557
                         "        - interface: \"eth0\"\n"
1✔
1558
                         "          cpu: [ \"5-7\" ]\n"
1✔
1559
                         "          prio:\n"
1✔
1560
                         "            high: [ \"all\" ]\n"
1✔
1561
                         "            default: \"high\"\n"
1✔
1562
                         "    verdict-cpu-set:\n"
1✔
1563
                         "      cpu: [ 4 ]\n";
1✔
1564

1565
    SCConfYamlLoadString(config, strlen(config));
1✔
1566
    AffinitySetupLoadFromConfig();
1✔
1567

1568
    FAIL_IF_NOT(CPU_ISSET(0, &thread_affinity[MANAGEMENT_CPU_SET].cpu_set));
1✔
1569
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[MANAGEMENT_CPU_SET].cpu_set) == 1);
1✔
1570
    FAIL_IF_NOT(CPU_ISSET(1, &thread_affinity[RECEIVE_CPU_SET].cpu_set));
1✔
1571
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[RECEIVE_CPU_SET].cpu_set) == 1);
1✔
1572
    FAIL_IF_NOT(CPU_ISSET(4, &thread_affinity[VERDICT_CPU_SET].cpu_set));
1✔
1573
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[VERDICT_CPU_SET].cpu_set) == 1);
1✔
1574
    FAIL_IF_NOT(CPU_ISSET(2, &thread_affinity[WORKER_CPU_SET].cpu_set));
1✔
1575
    FAIL_IF_NOT(CPU_ISSET(3, &thread_affinity[WORKER_CPU_SET].cpu_set));
1✔
1576

1577
    FAIL_IF_NOT(thread_affinity[WORKER_CPU_SET].nb_children == 1);
1✔
1578
    ThreadsAffinityType *iface_taf = thread_affinity[WORKER_CPU_SET].children[0];
1✔
1579
    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
1✔
1580
    FAIL_IF_NOT(CPU_ISSET(1, &iface_taf->hiprio_cpu));
1✔
1581
    FAIL_IF_NOT(CPU_ISSET(2, &iface_taf->hiprio_cpu));
1✔
1582
    FAIL_IF_NOT(CPU_ISSET(3, &iface_taf->hiprio_cpu));
1✔
1583
    FAIL_IF_NOT(iface_taf->prio == PRIO_HIGH);
1✔
1584
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[WORKER_CPU_SET].cpu_set) == 2);
1✔
1585

1586
    SCConfDeInit();
1✔
1587
    SCConfRestoreContextBackup();
1✔
1588
    PASS;
1✔
1589
}
1✔
1590

1591
/**
1592
 * \brief Test error handling for malformed CPU specification
1593
 */
1594
static int ThreadingAffinityTest13(void)
1595
{
1✔
1596
    SCConfCreateContextBackup();
1✔
1597
    SCConfInit();
1✔
1598
    ResetAffinityForTest();
1✔
1599

1600
    const char *config = "%YAML 1.1\n"
1✔
1601
                         "---\n"
1✔
1602
                         "threading:\n"
1✔
1603
                         "  cpu-affinity:\n"
1✔
1604
                         "    worker-cpu-set:\n"
1✔
1605
                         "      cpu: [ \"invalid-cpu\" ]\n";
1✔
1606

1607
    SCConfYamlLoadString(config, strlen(config));
1✔
1608
    AffinitySetupLoadFromConfig();
1✔
1609

1610
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1611
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 0);
1✔
1612

1613
    SCConfDeInit();
1✔
1614
    SCConfRestoreContextBackup();
1✔
1615
    PASS;
1✔
1616
}
1✔
1617

1618
/**
1619
 * \brief Test empty configuration handling
1620
 */
1621
static int ThreadingAffinityTest14(void)
1622
{
1✔
1623
    SCConfCreateContextBackup();
1✔
1624
    SCConfInit();
1✔
1625
    ResetAffinityForTest();
1✔
1626

1627
    const char *config = "%YAML 1.1\n"
1✔
1628
                         "---\n"
1✔
1629
                         "threading:\n"
1✔
1630
                         "  cpu-affinity:\n";
1✔
1631

1632
    SCConfYamlLoadString(config, strlen(config));
1✔
1633
    AffinitySetupLoadFromConfig();
1✔
1634

1635
    FAIL_IF_NOT(
1✔
1636
            CPU_COUNT(&thread_affinity[WORKER_CPU_SET].cpu_set) == UtilCpuGetNumProcessorsOnline());
1✔
1637

1638
    SCConfDeInit();
1✔
1639
    SCConfRestoreContextBackup();
1✔
1640
    PASS;
1✔
1641
}
1✔
1642

1643
/**
1644
 * \brief Test CPU range parsing with invalid order (high-low)
1645
 * CPU ranges specified in reverse order should be handled
1646
 */
1647
static int ThreadingAffinityTest15(void)
1648
{
1✔
1649
    SCConfCreateContextBackup();
1✔
1650
    SCConfInit();
1✔
1651
    ResetAffinityForTest();
1✔
1652

1653
    const char *config = "%YAML 1.1\n"
1✔
1654
                         "---\n"
1✔
1655
                         "threading:\n"
1✔
1656
                         "  cpu-affinity:\n"
1✔
1657
                         "    - management-cpu-set:\n"
1✔
1658
                         "        cpu: [ \"3-1\" ]\n"; // Invalid reverse range
1✔
1659

1660
    SCConfYamlLoadString(config, strlen(config));
1✔
1661
    AffinitySetupLoadFromConfig();
1✔
1662

1663
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[MANAGEMENT_CPU_SET].cpu_set) == 0);
1✔
1664

1665
    SCConfDeInit();
1✔
1666
    SCConfRestoreContextBackup();
1✔
1667
    PASS;
1✔
1668
}
1✔
1669

1670
/**
1671
 * \brief Test invalid priority values in SetupDefaultPriority
1672
 * Invalid priority strings should return errors but pass
1673
 */
1674
static int ThreadingAffinityTest16(void)
1675
{
1✔
1676
    SCConfCreateContextBackup();
1✔
1677
    SCConfInit();
1✔
1678
    ResetAffinityForTest();
1✔
1679

1680
    const char *config = "%YAML 1.1\n"
1✔
1681
                         "---\n"
1✔
1682
                         "threading:\n"
1✔
1683
                         "  cpu-affinity:\n"
1✔
1684
                         "    - management-cpu-set:\n"
1✔
1685
                         "        prio:\n"
1✔
1686
                         "          default: invalid_priority\n";
1✔
1687

1688
    SCConfYamlLoadString(config, strlen(config));
1✔
1689
    AffinitySetupLoadFromConfig();
1✔
1690

1691
    SCConfDeInit();
1✔
1692
    SCConfRestoreContextBackup();
1✔
1693
    PASS;
1✔
1694
}
1✔
1695

1696
/**
1697
 * \brief Test invalid CPU affinity mode values
1698
 * Invalid mode strings should return errors but pass
1699
 */
1700
static int ThreadingAffinityTest17(void)
1701
{
1✔
1702
    SCConfCreateContextBackup();
1✔
1703
    SCConfInit();
1✔
1704
    ResetAffinityForTest();
1✔
1705

1706
    const char *config = "%YAML 1.1\n"
1✔
1707
                         "---\n"
1✔
1708
                         "threading:\n"
1✔
1709
                         "  cpu-affinity:\n"
1✔
1710
                         "    - management-cpu-set:\n"
1✔
1711
                         "        mode: invalid_mode\n";
1✔
1712

1713
    SCConfYamlLoadString(config, strlen(config));
1✔
1714
    AffinitySetupLoadFromConfig();
1✔
1715

1716
    SCConfDeInit();
1✔
1717
    SCConfRestoreContextBackup();
1✔
1718
    PASS;
1✔
1719
}
1✔
1720

1721
/**
1722
 * \brief Test invalid thread count values
1723
 * Invalid thread counts
1724
 */
1725
static int ThreadingAffinityTest18(void)
1726
{
1✔
1727
    SCConfCreateContextBackup();
1✔
1728
    SCConfInit();
1✔
1729
    ResetAffinityForTest();
1✔
1730

1731
    const char *config = "%YAML 1.1\n"
1✔
1732
                         "---\n"
1✔
1733
                         "threading:\n"
1✔
1734
                         "  cpu-affinity:\n"
1✔
1735
                         "    - management-cpu-set:\n"
1✔
1736
                         "        threads: 0\n";
1✔
1737

1738
    SCConfYamlLoadString(config, strlen(config));
1✔
1739
    AffinitySetupLoadFromConfig();
1✔
1740

1741
    SCConfDeInit();
1✔
1742
    SCConfRestoreContextBackup();
1✔
1743
    PASS;
1✔
1744
}
1✔
1745

1746
/**
1747
 * \brief Test CPU specification with negative numbers
1748
 * Negative CPU numbers should be rejected
1749
 */
1750
static int ThreadingAffinityTest19(void)
1751
{
1✔
1752
    SCConfCreateContextBackup();
1✔
1753
    SCConfInit();
1✔
1754
    ResetAffinityForTest();
1✔
1755

1756
    const char *config = "%YAML 1.1\n"
1✔
1757
                         "---\n"
1✔
1758
                         "threading:\n"
1✔
1759
                         "  cpu-affinity:\n"
1✔
1760
                         "    - management-cpu-set:\n"
1✔
1761
                         "        cpu: [ -1 ]\n";
1✔
1762

1763
    SCConfYamlLoadString(config, strlen(config));
1✔
1764
    AffinitySetupLoadFromConfig();
1✔
1765

1766
    SCConfDeInit();
1✔
1767
    SCConfRestoreContextBackup();
1✔
1768
    PASS;
1✔
1769
}
1✔
1770

1771
/**
1772
 * \brief Test invalid thread count with non-numeric values
1773
 * Non-numeric thread counts should be handled
1774
 */
1775
static int ThreadingAffinityTest20(void)
1776
{
1✔
1777
    SCConfCreateContextBackup();
1✔
1778
    SCConfInit();
1✔
1779
    ResetAffinityForTest();
1✔
1780

1781
    const char *config = "%YAML 1.1\n"
1✔
1782
                         "---\n"
1✔
1783
                         "threading:\n"
1✔
1784
                         "  cpu-affinity:\n"
1✔
1785
                         "    - management-cpu-set:\n"
1✔
1786
                         "        threads: invalid_number\n";
1✔
1787

1788
    SCConfYamlLoadString(config, strlen(config));
1✔
1789
    AffinitySetupLoadFromConfig();
1✔
1790

1791
    SCConfDeInit();
1✔
1792
    SCConfRestoreContextBackup();
1✔
1793
    PASS;
1✔
1794
}
1✔
1795

1796
/**
1797
 * \brief Test extremely large CPU ranges
1798
 * Very large CPU range specifications should be handled
1799
 */
1800
static int ThreadingAffinityTest21(void)
1801
{
1✔
1802
    SCConfCreateContextBackup();
1✔
1803
    SCConfInit();
1✔
1804
    ResetAffinityForTest();
1✔
1805

1806
    const char *config = "%YAML 1.1\n"
1✔
1807
                         "---\n"
1✔
1808
                         "threading:\n"
1✔
1809
                         "  cpu-affinity:\n"
1✔
1810
                         "    - management-cpu-set:\n"
1✔
1811
                         "        cpu: [ 0-99999 ]\n";
1✔
1812

1813
    SCConfYamlLoadString(config, strlen(config));
1✔
1814
    AffinitySetupLoadFromConfig();
1✔
1815

1816
    SCConfDeInit();
1✔
1817
    SCConfRestoreContextBackup();
1✔
1818
    PASS;
1✔
1819
}
1✔
1820

1821
/**
1822
 * \brief Test deeply nested interface configurations
1823
 * Prevent infinite loops in configuration parsing
1824
 */
1825
static int ThreadingAffinityTest22(void)
1826
{
1✔
1827
    SCConfCreateContextBackup();
1✔
1828
    SCConfInit();
1✔
1829
    ResetAffinityForTest();
1✔
1830

1831
    const char *config = "%YAML 1.1\n"
1✔
1832
                         "---\n"
1✔
1833
                         "threading:\n"
1✔
1834
                         "  cpu-affinity:\n"
1✔
1835
                         "    worker-cpu-set:\n"
1✔
1836
                         "      interface-specific-cpu-set:\n"
1✔
1837
                         "        - interface: eth0\n"
1✔
1838
                         "          cpu: [ 1 ]\n"
1✔
1839
                         "          interface-specific-cpu-set:\n" // Nested interface-specific
1✔
1840
                         "            - interface: eth1\n"
1✔
1841
                         "              cpu: [ 2 ]\n";
1✔
1842

1843
    SCConfYamlLoadString(config, strlen(config));
1✔
1844
    AffinitySetupLoadFromConfig();
1✔
1845

1846
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1847
    FAIL_IF_NOT(worker_taf->nb_children == 1);
1✔
1848
    ThreadsAffinityType *iface_taf = worker_taf->children[0];
1✔
1849
    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
1✔
1850
    FAIL_IF_NOT(CPU_ISSET(1, &iface_taf->cpu_set));
1✔
1851
    FAIL_IF_NOT(iface_taf->nb_children == 0);
1✔
1852

1853
    SCConfDeInit();
1✔
1854
    SCConfRestoreContextBackup();
1✔
1855
    PASS;
1✔
1856
}
1✔
1857

1858
/**
1859
 * \brief Test GetAffinityTypeForNameAndIface with NULL and empty string parameters
1860
 * Comprehensive NULL parameter testing
1861
 */
1862
static int ThreadingAffinityTest23(void)
1863
{
1✔
1864
    SCConfCreateContextBackup();
1✔
1865
    SCConfInit();
1✔
1866
    ResetAffinityForTest();
1✔
1867

1868
    const char *config = "%YAML 1.1\n"
1✔
1869
                         "---\n"
1✔
1870
                         "threading:\n"
1✔
1871
                         "  cpu-affinity:\n"
1✔
1872
                         "    worker-cpu-set:\n"
1✔
1873
                         "      cpu: [ 1, 2, 3 ]\n";
1✔
1874

1875
    SCConfYamlLoadString(config, strlen(config));
1✔
1876
    AffinitySetupLoadFromConfig();
1✔
1877

1878
    ThreadsAffinityType *result = GetAffinityTypeForNameAndIface(NULL, "eth0");
1✔
1879
    FAIL_IF_NOT(result == NULL);
1✔
1880

1881
    result = GetAffinityTypeForNameAndIface("", "eth0");
1✔
1882
    FAIL_IF_NOT(result == NULL);
1✔
1883

1884
    result = GetAffinityTypeForNameAndIface("worker-cpu-set", NULL);
1✔
1885
    FAIL_IF(result == NULL);
1✔
1886
    FAIL_IF_NOT(strcmp(result->name, "worker-cpu-set") == 0);
1✔
1887

1888
    result = GetAffinityTypeForNameAndIface("worker-cpu-set", "");
1✔
1889
    FAIL_IF_NOT(result == NULL); // Returns NULL as no child with an empty name exists
1✔
1890

1891
    SCConfDeInit();
1✔
1892
    SCConfRestoreContextBackup();
1✔
1893
    PASS;
1✔
1894
}
1✔
1895

1896
/**
1897
 * \brief Test interface-specific configuration with missing interface field
1898
 * Interface-specific configs with malformed structure
1899
 */
1900
static int ThreadingAffinityTest24(void)
1901
{
1✔
1902
    SCConfCreateContextBackup();
1✔
1903
    SCConfInit();
1✔
1904
    ResetAffinityForTest();
1✔
1905

1906
    const char *config = "%YAML 1.1\n"
1✔
1907
                         "---\n"
1✔
1908
                         "threading:\n"
1✔
1909
                         "  cpu-affinity:\n"
1✔
1910
                         "    - worker-cpu-set:\n"
1✔
1911
                         "        interface-specific-cpu-set:\n"
1✔
1912
                         "          - cpu: [ 1 ]\n" // Missing interface field
1✔
1913
                         "            mode: exclusive\n"
1✔
1914
                         "          - interface_name: eth0\n" // Wrong field name
1✔
1915
                         "            cpu: [ 2 ]\n";
1✔
1916

1917
    SCConfYamlLoadString(config, strlen(config));
1✔
1918
    AffinitySetupLoadFromConfig();
1✔
1919

1920
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
1921
    FAIL_IF_NOT(worker_taf->nb_children == 0);
1✔
1922

1923
    SCConfDeInit();
1✔
1924
    SCConfRestoreContextBackup();
1✔
1925
    PASS;
1✔
1926
}
1✔
1927

1928
/**
1929
 * \brief Test AllocAndInitAffinityType with multiple allocations to test realloc paths
1930
 * Test dynamic array reallocation in parent-child relationships
1931
 */
1932
static int ThreadingAffinityTest25(void)
1933
{
1✔
1934
    ResetAffinityForTest();
1✔
1935

1936
    ThreadsAffinityType *parent = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", NULL);
1✔
1937
    FAIL_IF(parent == NULL);
1✔
1938

1939
    ThreadsAffinityType *child1 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface1");
1✔
1940
    ThreadsAffinityType *child2 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface2");
1✔
1941
    ThreadsAffinityType *child3 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface3");
1✔
1942
    ThreadsAffinityType *child4 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface4");
1✔
1943

1944
    FAIL_IF_NOT(child1 && child2 && child3 && child4);
1✔
1945
    FAIL_IF_NOT(parent->nb_children == 4);
1✔
1946

1947
    ThreadsAffinityType *found = FindAffinityByInterface(parent, "iface2");
1✔
1948
    FAIL_IF_NOT(found == child2);
1✔
1949

1950
    found = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface2");
1✔
1951
    FAIL_IF_NOT(found == child2);
1✔
1952

1953
    PASS;
1✔
1954
}
1✔
1955

1956
/**
1957
 * \brief Test AffinityGetYamlPath with very long name
1958
 * Path building with long string lengths to test for buffer overflows
1959
 */
1960
static int ThreadingAffinityTest26(void)
1961
{
1✔
1962
    ResetAffinityForTest();
1✔
1963

1964
    ThreadsAffinityType test_taf;
1✔
1965
    memset(&test_taf, 0, sizeof(test_taf));
1✔
1966

1967
    char long_name[1024];
1✔
1968
    memset(long_name, 'a', sizeof(long_name) - 1);
1✔
1969
    long_name[sizeof(long_name) - 1] = '\0';
1✔
1970
    test_taf.name = long_name;
1✔
1971

1972
    char *path = AffinityGetYamlPath(&test_taf);
1✔
1973
    FAIL_IF_NOT(path == NULL); // overflows the internal buffer and return NULL
1✔
1974

1975
    path = AffinityGetYamlPath(NULL); // returns root path
1✔
1976
    FAIL_IF(path == NULL || strcmp(path, "threading.cpu-affinity") != 0);
1✔
1977

1978
    PASS;
1✔
1979
}
1✔
1980

1981
/**
1982
 * \brief Test mixed format configurations in same file
1983
 * Combination of new and deprecated formats
1984
 */
1985
static int ThreadingAffinityTest27(void)
1986
{
1✔
1987
    SCConfCreateContextBackup();
1✔
1988
    SCConfInit();
1✔
1989
    ResetAffinityForTest();
1✔
1990

1991
    const char *config = "%YAML 1.1\n"
1✔
1992
                         "---\n"
1✔
1993
                         "threading:\n"
1✔
1994
                         "  cpu-affinity:\n"
1✔
1995
                         "    management-cpu-set:\n" // New format
1✔
1996
                         "      cpu: [ 0 ]\n"
1✔
1997
                         "    - worker-cpu-set:\n" // Deprecated format
1✔
1998
                         "        cpu: [ 1, 2 ]\n";
1✔
1999

2000
    SCConfYamlLoadString(config, strlen(config));
1✔
2001
    AffinitySetupLoadFromConfig();
1✔
2002

2003
    ThreadsAffinityType *mgmt_taf = &thread_affinity[MANAGEMENT_CPU_SET];
1✔
2004
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1✔
2005
    // The first format should be picked-up and the other should be ignored
2006
    // For ignored formats, CPU_SET is initliazed as all cores
2007
    FAIL_IF(CPU_COUNT(&mgmt_taf->cpu_set) != 1 ||
1✔
2008
            CPU_COUNT(&worker_taf->cpu_set) != UtilCpuGetNumProcessorsOnline());
1✔
2009

2010
    SCConfDeInit();
1✔
2011
    SCConfRestoreContextBackup();
1✔
2012
    PASS;
1✔
2013
}
1✔
2014

2015
#endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) */
2016

2017
/**
2018
 * \brief Register all threading affinity unit tests
2019
 */
2020
void ThreadingAffinityRegisterTests(void)
2021
{
1✔
2022
#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
1✔
2023
    UtRegisterTest("ThreadingAffinityTest01", ThreadingAffinityTest01);
1✔
2024
    UtRegisterTest("ThreadingAffinityTest02", ThreadingAffinityTest02);
1✔
2025
    UtRegisterTest("ThreadingAffinityTest03", ThreadingAffinityTest03);
1✔
2026
    UtRegisterTest("ThreadingAffinityTest04", ThreadingAffinityTest04);
1✔
2027
    UtRegisterTest("ThreadingAffinityTest05", ThreadingAffinityTest05);
1✔
2028
    UtRegisterTest("ThreadingAffinityTest06", ThreadingAffinityTest06);
1✔
2029
    UtRegisterTest("ThreadingAffinityTest07", ThreadingAffinityTest07);
1✔
2030
    UtRegisterTest("ThreadingAffinityTest08", ThreadingAffinityTest08);
1✔
2031
    UtRegisterTest("ThreadingAffinityTest09", ThreadingAffinityTest09);
1✔
2032
    UtRegisterTest("ThreadingAffinityTest10", ThreadingAffinityTest10);
1✔
2033
    UtRegisterTest("ThreadingAffinityTest11", ThreadingAffinityTest11);
1✔
2034
    UtRegisterTest("ThreadingAffinityTest12", ThreadingAffinityTest12);
1✔
2035
    UtRegisterTest("ThreadingAffinityTest13", ThreadingAffinityTest13);
1✔
2036
    UtRegisterTest("ThreadingAffinityTest14", ThreadingAffinityTest14);
1✔
2037
    UtRegisterTest("ThreadingAffinityTest15", ThreadingAffinityTest15);
1✔
2038
    UtRegisterTest("ThreadingAffinityTest16", ThreadingAffinityTest16);
1✔
2039
    UtRegisterTest("ThreadingAffinityTest17", ThreadingAffinityTest17);
1✔
2040
    UtRegisterTest("ThreadingAffinityTest18", ThreadingAffinityTest18);
1✔
2041
    UtRegisterTest("ThreadingAffinityTest19", ThreadingAffinityTest19);
1✔
2042
    UtRegisterTest("ThreadingAffinityTest20", ThreadingAffinityTest20);
1✔
2043
    UtRegisterTest("ThreadingAffinityTest21", ThreadingAffinityTest21);
1✔
2044
    UtRegisterTest("ThreadingAffinityTest22", ThreadingAffinityTest22);
1✔
2045
    UtRegisterTest("ThreadingAffinityTest23", ThreadingAffinityTest23);
1✔
2046
    UtRegisterTest("ThreadingAffinityTest24", ThreadingAffinityTest24);
1✔
2047
    UtRegisterTest("ThreadingAffinityTest25", ThreadingAffinityTest25);
1✔
2048
    UtRegisterTest("ThreadingAffinityTest26", ThreadingAffinityTest26);
1✔
2049
    UtRegisterTest("ThreadingAffinityTest27", ThreadingAffinityTest27);
1✔
2050
#endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) */
1✔
2051
}
1✔
2052

2053
#endif /* UNITTESTS */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc