Coverage Report

Created: 2026-02-14 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/util-affinity.c
Line
Count
Source
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
#define _THREAD_AFFINITY
27
#include "util-affinity.h"
28
#include "conf.h"
29
#include "runmodes.h"
30
#include "util-cpu.h"
31
#include "util-byte.h"
32
#include "util-debug.h"
33
34
ThreadsAffinityType thread_affinity[MAX_CPU_SET] = {
35
    {
36
        .name = "receive-cpu-set",
37
        .mode_flag = EXCLUSIVE_AFFINITY,
38
        .prio = PRIO_MEDIUM,
39
        .lcpu = 0,
40
    },
41
    {
42
        .name = "worker-cpu-set",
43
        .mode_flag = EXCLUSIVE_AFFINITY,
44
        .prio = PRIO_MEDIUM,
45
        .lcpu = 0,
46
    },
47
    {
48
        .name = "verdict-cpu-set",
49
        .mode_flag = BALANCED_AFFINITY,
50
        .prio = PRIO_MEDIUM,
51
        .lcpu = 0,
52
    },
53
    {
54
        .name = "management-cpu-set",
55
        .mode_flag = BALANCED_AFFINITY,
56
        .prio = PRIO_MEDIUM,
57
        .lcpu = 0,
58
    },
59
60
};
61
62
int thread_affinity_init_done = 0;
63
64
/**
65
 * \brief find affinity by its name
66
 * \retval a pointer to the affinity or NULL if not found
67
 */
68
ThreadsAffinityType * GetAffinityTypeFromName(const char *name)
69
0
{
70
0
    int i;
71
0
    for (i = 0; i < MAX_CPU_SET; i++) {
72
0
        if (!strcmp(thread_affinity[i].name, name)) {
73
0
            return &thread_affinity[i];
74
0
        }
75
0
    }
76
0
    return NULL;
77
0
}
78
79
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
80
static void AffinitySetupInit(void)
81
0
{
82
0
    int i, j;
83
0
    int ncpu = UtilCpuGetNumProcessorsConfigured();
84
85
0
    SCLogDebug("Initialize affinity setup\n");
86
    /* be conservative relatively to OS: use all cpus by default */
87
0
    for (i = 0; i < MAX_CPU_SET; i++) {
88
0
        cpu_set_t *cs = &thread_affinity[i].cpu_set;
89
0
        CPU_ZERO(cs);
90
0
        for (j = 0; j < ncpu; j++) {
91
0
            CPU_SET(j, cs);
92
0
        }
93
0
        SCMutexInit(&thread_affinity[i].taf_mutex, NULL);
94
0
    }
95
0
    return;
96
0
}
97
98
void BuildCpusetWithCallback(const char *name, ConfNode *node,
99
                             void (*Callback)(int i, void * data),
100
                             void *data)
101
0
{
102
0
    ConfNode *lnode;
103
0
    TAILQ_FOREACH(lnode, &node->head, next) {
104
0
        int i;
105
0
        long int a,b;
106
0
        int stop = 0;
107
0
        int max = UtilCpuGetNumProcessorsOnline() - 1;
108
0
        if (!strcmp(lnode->val, "all")) {
109
0
            a = 0;
110
0
            b = max;
111
0
            stop = 1;
112
0
        } else if (strchr(lnode->val, '-') != NULL) {
113
0
            char *sep = strchr(lnode->val, '-');
114
0
            char *end;
115
0
            a = strtoul(lnode->val, &end, 10);
116
0
            if (end != sep) {
117
0
                SCLogError("%s: invalid cpu range (start invalid): \"%s\"", name, lnode->val);
118
0
                exit(EXIT_FAILURE);
119
0
            }
120
0
            b = strtol(sep + 1, &end, 10);
121
0
            if (end != sep + strlen(sep)) {
122
0
                SCLogError("%s: invalid cpu range (end invalid): \"%s\"", name, lnode->val);
123
0
                exit(EXIT_FAILURE);
124
0
            }
125
0
            if (a > b) {
126
0
                SCLogError("%s: invalid cpu range (bad order): \"%s\"", name, lnode->val);
127
0
                exit(EXIT_FAILURE);
128
0
            }
129
0
            if (b > max) {
130
0
                SCLogError("%s: upper bound (%ld) of cpu set is too high, only %d cpu(s)", name, b,
131
0
                        max + 1);
132
0
            }
133
0
        } else {
134
0
            char *end;
135
0
            a = strtoul(lnode->val, &end, 10);
136
0
            if (end != lnode->val + strlen(lnode->val)) {
137
0
                SCLogError("%s: invalid cpu range (not an integer): \"%s\"", name, lnode->val);
138
0
                exit(EXIT_FAILURE);
139
0
            }
140
0
            b = a;
141
0
        }
142
0
        for (i = a; i<= b; i++) {
143
0
            Callback(i, data);
144
0
        }
145
0
        if (stop)
146
0
            break;
147
0
    }
148
0
}
149
150
static void AffinityCallback(int i, void *data)
151
0
{
152
0
    CPU_SET(i, (cpu_set_t *)data);
153
0
}
154
155
static void BuildCpuset(const char *name, ConfNode *node, cpu_set_t *cpu)
156
0
{
157
0
    BuildCpusetWithCallback(name, node, AffinityCallback, (void *) cpu);
158
0
}
159
#endif /* OS_WIN32 and __OpenBSD__ */
160
161
/**
162
 * \brief Extract cpu affinity configuration from current config file
163
 */
164
165
void AffinitySetupLoadFromConfig(void)
166
0
{
167
0
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
168
0
    ConfNode *root = ConfGetNode("threading.cpu-affinity");
169
0
    ConfNode *affinity;
170
171
0
    if (thread_affinity_init_done == 0) {
172
0
        AffinitySetupInit();
173
0
        thread_affinity_init_done = 1;
174
0
    }
175
176
0
    SCLogDebug("Load affinity from config\n");
177
0
    if (root == NULL) {
178
0
        SCLogInfo("can't get cpu-affinity node");
179
0
        return;
180
0
    }
181
182
0
    TAILQ_FOREACH(affinity, &root->head, next) {
183
0
        if (affinity->val == NULL || strcmp(affinity->val, "decode-cpu-set") == 0 ||
184
0
                strcmp(affinity->val, "stream-cpu-set") == 0 ||
185
0
                strcmp(affinity->val, "reject-cpu-set") == 0 ||
186
0
                strcmp(affinity->val, "output-cpu-set") == 0) {
187
0
            continue;
188
0
        }
189
190
0
        const char *setname = affinity->val;
191
0
        if (strcmp(affinity->val, "detect-cpu-set") == 0)
192
0
            setname = "worker-cpu-set";
193
194
0
        ThreadsAffinityType *taf = GetAffinityTypeFromName(setname);
195
0
        ConfNode *node = NULL;
196
0
        ConfNode *nprio = NULL;
197
198
0
        if (taf == NULL) {
199
0
            FatalError("unknown cpu-affinity type");
200
0
        } else {
201
0
            SCLogConfig("Found affinity definition for \"%s\"", setname);
202
0
        }
203
204
0
        CPU_ZERO(&taf->cpu_set);
205
0
        node = ConfNodeLookupChild(affinity->head.tqh_first, "cpu");
206
0
        if (node == NULL) {
207
0
            SCLogInfo("unable to find 'cpu'");
208
0
        } else {
209
0
            BuildCpuset(setname, node, &taf->cpu_set);
210
0
        }
211
212
0
        CPU_ZERO(&taf->lowprio_cpu);
213
0
        CPU_ZERO(&taf->medprio_cpu);
214
0
        CPU_ZERO(&taf->hiprio_cpu);
215
0
        nprio = ConfNodeLookupChild(affinity->head.tqh_first, "prio");
216
0
        if (nprio != NULL) {
217
0
            node = ConfNodeLookupChild(nprio, "low");
218
0
            if (node == NULL) {
219
0
                SCLogDebug("unable to find 'low' prio using default value");
220
0
            } else {
221
0
                BuildCpuset(setname, node, &taf->lowprio_cpu);
222
0
            }
223
224
0
            node = ConfNodeLookupChild(nprio, "medium");
225
0
            if (node == NULL) {
226
0
                SCLogDebug("unable to find 'medium' prio using default value");
227
0
            } else {
228
0
                BuildCpuset(setname, node, &taf->medprio_cpu);
229
0
            }
230
231
0
            node = ConfNodeLookupChild(nprio, "high");
232
0
            if (node == NULL) {
233
0
                SCLogDebug("unable to find 'high' prio using default value");
234
0
            } else {
235
0
                BuildCpuset(setname, node, &taf->hiprio_cpu);
236
0
            }
237
0
            node = ConfNodeLookupChild(nprio, "default");
238
0
            if (node != NULL) {
239
0
                if (!strcmp(node->val, "low")) {
240
0
                    taf->prio = PRIO_LOW;
241
0
                } else if (!strcmp(node->val, "medium")) {
242
0
                    taf->prio = PRIO_MEDIUM;
243
0
                } else if (!strcmp(node->val, "high")) {
244
0
                    taf->prio = PRIO_HIGH;
245
0
                } else {
246
0
                    FatalError("unknown cpu_affinity prio");
247
0
                }
248
0
                SCLogConfig("Using default prio '%s' for set '%s'",
249
0
                        node->val, setname);
250
0
            }
251
0
        }
252
253
0
        node = ConfNodeLookupChild(affinity->head.tqh_first, "mode");
254
0
        if (node != NULL) {
255
0
            if (!strcmp(node->val, "exclusive")) {
256
0
                taf->mode_flag = EXCLUSIVE_AFFINITY;
257
0
            } else if (!strcmp(node->val, "balanced")) {
258
0
                taf->mode_flag = BALANCED_AFFINITY;
259
0
            } else {
260
0
                FatalError("unknown cpu_affinity node");
261
0
            }
262
0
        }
263
264
0
        node = ConfNodeLookupChild(affinity->head.tqh_first, "threads");
265
0
        if (node != NULL) {
266
0
            if (StringParseUint32(&taf->nb_threads, 10, 0, (const char *)node->val) < 0) {
267
0
                FatalError("invalid value for threads "
268
0
                           "count: '%s'",
269
0
                        node->val);
270
0
            }
271
0
            if (! taf->nb_threads) {
272
0
                FatalError("bad value for threads count");
273
0
            }
274
0
        }
275
0
    }
276
0
#endif /* OS_WIN32 and __OpenBSD__ */
277
0
}
278
279
/**
280
 * \brief Return next cpu to use for a given thread family
281
 * \retval the cpu to used given by its id
282
 */
283
uint16_t AffinityGetNextCPU(ThreadsAffinityType *taf)
284
0
{
285
0
    uint16_t ncpu = 0;
286
0
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
287
0
    int iter = 0;
288
0
    SCMutexLock(&taf->taf_mutex);
289
0
    ncpu = taf->lcpu;
290
0
    while (!CPU_ISSET(ncpu, &taf->cpu_set) && iter < 2) {
291
0
        ncpu++;
292
0
        if (ncpu >= UtilCpuGetNumProcessorsOnline()) {
293
0
            ncpu = 0;
294
0
            iter++;
295
0
        }
296
0
    }
297
0
    if (iter == 2) {
298
0
        SCLogError("cpu_set does not contain "
299
0
                   "available cpus, cpu affinity conf is invalid");
300
0
    }
301
0
    taf->lcpu = ncpu + 1;
302
0
    if (taf->lcpu >= UtilCpuGetNumProcessorsOnline())
303
0
        taf->lcpu = 0;
304
0
    SCMutexUnlock(&taf->taf_mutex);
305
0
    SCLogDebug("Setting affinity on CPU %d", ncpu);
306
0
#endif /* OS_WIN32 and __OpenBSD__ */
307
0
    return ncpu;
308
0
}
309
310
uint16_t UtilAffinityGetAffinedCPUNum(ThreadsAffinityType *taf)
311
0
{
312
0
    uint16_t ncpu = 0;
313
0
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
314
0
    SCMutexLock(&taf->taf_mutex);
315
0
    for (int i = UtilCpuGetNumProcessorsOnline(); i >= 0; i--)
316
0
        if (CPU_ISSET(i, &taf->cpu_set))
317
0
            ncpu++;
318
0
    SCMutexUnlock(&taf->taf_mutex);
319
0
#endif
320
0
    return ncpu;
321
0
}
322
323
#ifdef HAVE_DPDK
324
/**
325
 * Find if CPU sets overlap
326
 * \return 1 if CPUs overlap, 0 otherwise
327
 */
328
uint16_t UtilAffinityCpusOverlap(ThreadsAffinityType *taf1, ThreadsAffinityType *taf2)
329
{
330
    ThreadsAffinityType tmptaf;
331
    CPU_ZERO(&tmptaf);
332
    SCMutexInit(&tmptaf.taf_mutex, NULL);
333
334
    cpu_set_t tmpcset;
335
336
    SCMutexLock(&taf1->taf_mutex);
337
    SCMutexLock(&taf2->taf_mutex);
338
    CPU_AND(&tmpcset, &taf1->cpu_set, &taf2->cpu_set);
339
    SCMutexUnlock(&taf2->taf_mutex);
340
    SCMutexUnlock(&taf1->taf_mutex);
341
342
    for (int i = UtilCpuGetNumProcessorsOnline(); i >= 0; i--)
343
        if (CPU_ISSET(i, &tmpcset))
344
            return 1;
345
    return 0;
346
}
347
348
/**
349
 * Function makes sure that CPUs of different types don't overlap by excluding
350
 * one affinity type from the other
351
 * \param mod_taf - CPU set to be modified
352
 * \param static_taf - static CPU set to be used only for evaluation
353
 */
354
void UtilAffinityCpusExclude(ThreadsAffinityType *mod_taf, ThreadsAffinityType *static_taf)
355
{
356
    SCMutexLock(&mod_taf->taf_mutex);
357
    SCMutexLock(&static_taf->taf_mutex);
358
    int max_cpus = UtilCpuGetNumProcessorsOnline();
359
    for (int cpu = 0; cpu < max_cpus; cpu++) {
360
        if (CPU_ISSET(cpu, &mod_taf->cpu_set) && CPU_ISSET(cpu, &static_taf->cpu_set)) {
361
            CPU_CLR(cpu, &mod_taf->cpu_set);
362
        }
363
    }
364
    SCMutexUnlock(&static_taf->taf_mutex);
365
    SCMutexUnlock(&mod_taf->taf_mutex);
366
}
367
#endif /* HAVE_DPDK */