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