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