suricata
util-hugepages.c
Go to the documentation of this file.
1/* Copyright (C) 2023 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/**
19 * \file
20 *
21 * \author Lukas Sismis <lsismis@oisf.net>
22 */
23
24#include "suricata.h"
25#include "util-debug.h"
26#include "util-hugepages.h"
27#include "util-path.h"
28
29static uint16_t SystemHugepageSizesCntPerNodeGet(uint16_t node_index);
30static uint16_t SystemNodeCountGet(void);
31static void SystemHugepagePerNodeGetHugepageSizes(
32 uint16_t node_index, uint16_t hp_sizes_cnt, uint32_t *hp_sizes);
33static HugepageInfo *SystemHugepageHugepageInfoCreate(uint16_t hp_size_cnt);
34static int16_t SystemHugepagePerNodeGetHugepageInfo(uint16_t node_index, NodeInfo *node);
35static void SystemHugepageHugepageInfoDestroy(HugepageInfo *h);
36static void SystemHugepageNodeInfoDestroy(NodeInfo *n);
37static void SystemHugepageNodeInfoDump(NodeInfo *n);
38static void SystemHugepageSnapshotDump(SystemHugepageSnapshot *s);
39
44
45static OSHugepageAction SystemHugepageDetermineOS(void)
46{
47 // try Linux
48 if (SCPathExists("/sys/devices/system/node/")) {
50 }
51
52 return OS_UNKNOWN;
53}
54
55static bool SystemHugepageSupported(void)
56{
57 if (SystemHugepageDetermineOS() != OS_UNKNOWN)
58 return true;
59 return false;
60}
61
62/**
63 * \brief Linux-specific function to detect number of NUMA nodes on the system
64 * \returns number of NUMA nodes, 0 on error
65 */
66static uint16_t SystemNodeCountGetLinux(void)
67{
68 char dir_path[] = "/sys/devices/system/node/";
69 DIR *dir = opendir(dir_path);
70 if (dir == NULL)
71 FatalError("unable to open %s", dir_path);
72
73 uint16_t count = 0;
74 struct dirent *entry;
75 while ((entry = readdir(dir)) != NULL) {
76 char d_name[] = "node";
77 if (SCIsRegularDirectory(entry) && strncmp(entry->d_name, d_name, strlen(d_name)) == 0)
78 count++;
79 }
80 closedir(dir);
81 return count;
82}
83
84/**
85 * \brief Linux-specific function to detect number of unique hugepage sizes
86 * \param[in] node_index index of the NUMA node
87 * \returns number of hugepage sizes, 0 on error
88 */
89static uint16_t SystemHugepageSizesCntPerNodeGetLinux(uint16_t node_index)
90{
91 char dir_path[256];
92 snprintf(dir_path, sizeof(dir_path), "/sys/devices/system/node/node%d/hugepages/", node_index);
93 DIR *dir = opendir(dir_path);
94 if (dir == NULL) {
95 SCLogInfo("unable to open %s", dir_path);
96 return 0;
97 }
98
99 uint16_t count = 0;
100 struct dirent *entry;
101 while ((entry = readdir(dir)) != NULL) {
102 char d_name[] = "hugepages-";
103 if (SCIsRegularDirectory(entry) && strncmp(entry->d_name, d_name, strlen(d_name)) == 0)
104 count++;
105 }
106 closedir(dir);
107 return count;
108}
109
110/**
111 * \brief Linux-specific function to detect unique hugepage sizes
112 * \note Arrays `hugepages` and `hp_sizes` are expected to have the same size
113 * \param[in] node_index index of the NUMA node
114 * \param[in] hp_sizes_cnt number of the unique hugepage sizes
115 * \param[out] hp_sizes a pointer to the array of hugepage sizes
116 */
117static void SystemHugepagePerNodeGetHugepageSizesLinux(
118 uint16_t node_index, uint16_t hp_sizes_cnt, uint32_t *hp_sizes)
119{
120 char dir_path[256];
121 snprintf(dir_path, sizeof(dir_path), "/sys/devices/system/node/node%d/hugepages/", node_index);
122 DIR *dir = opendir(dir_path);
123 if (dir == NULL)
124 FatalError("unable to open %s", dir_path);
125
126 uint16_t index = 0;
127 struct dirent *entry;
128 while ((entry = readdir(dir)) != NULL) {
129 if (SCIsRegularDirectory(entry) && strncmp(entry->d_name, "hugepages-", 10) == 0) {
130 sscanf(entry->d_name, "hugepages-%ukB", &(hp_sizes[index]));
131 index++;
132 }
133 }
134 closedir(dir);
135}
136
137/**
138 * \brief Linux-specific function to detect number of unique hugepage sizes
139 * \note Arrays `hugepages` and `hp_sizes` are expected to have the same size
140 * \param[out] hugepages a pointer to the array of hugepage info structures
141 * \param[in] hp_sizes a pointer to the array of hugepage sizes
142 * \param[in] hp_sizes_cnt number of hugepage sizes
143 * \param[in] node_index index of the NUMA node
144 * \returns 0 on success, negative number on error
145 */
146static int16_t SystemHugepagePerNodeGetHugepageInfoLinux(
147 HugepageInfo *hugepages, uint32_t *hp_sizes, uint16_t hp_sizes_cnt, uint16_t node_index)
148{
149 for (int16_t i = 0; i < hp_sizes_cnt; i++) {
150 hugepages[i].size_kb = hp_sizes[i];
151 char path[256];
152 snprintf(path, sizeof(path),
153 "/sys/devices/system/node/node%hu/hugepages/hugepages-%ukB/nr_hugepages",
154 node_index, hp_sizes[i]);
155 FILE *f = fopen(path, "r");
156 if (!f) {
157 SCLogInfo("unable to open %s", path);
158 return -SC_ENOENT;
159 }
160 if (fscanf(f, "%hu", &hugepages[i].allocated) != 1) {
161 SCLogInfo("failed to read the total number of allocated hugepages (%ukB) on node %hu",
162 hp_sizes[i], node_index);
163 fclose(f);
164 return -SC_EINVAL;
165 }
166 fclose(f);
167
168 snprintf(path, sizeof(path),
169 "/sys/devices/system/node/node%hu/hugepages/hugepages-%ukB/free_hugepages",
170 node_index, hp_sizes[i]);
171 f = fopen(path, "r");
172 if (!f) {
173 SCLogInfo("unable to open %s", path);
174 return -SC_ENOENT;
175 }
176 if (fscanf(f, "%hu", &hugepages[i].free) != 1) {
177 SCLogInfo("failed to read the total number of free hugepages (%ukB) on node %hu",
178 hp_sizes[i], node_index);
179 fclose(f);
180 return -SC_EINVAL;
181 }
182 fclose(f);
183 }
184
185 return 0;
186}
187
188/**
189 * \brief The function gathers information about hugepages on a given node
190 * \param[in] node_index index of the NUMA node
191 * \param[out] node a pointer to the structure to hold hugepage info
192 * \returns 0 on success, negative number on error
193 */
194static int16_t SystemHugepagePerNodeGetHugepageInfo(uint16_t node_index, NodeInfo *node)
195{
196 uint16_t hp_sizes_cnt = SystemHugepageSizesCntPerNodeGet(node_index);
197 if (hp_sizes_cnt == 0) {
198 SCLogInfo("hugepages not found for node %d", node_index);
199 return -SC_ENOENT;
200 }
201 uint32_t *hp_sizes = SCCalloc(hp_sizes_cnt, sizeof(*hp_sizes));
202 if (hp_sizes == NULL) {
203 FatalError("failed to allocate memory for hugepage info");
204 }
205 SystemHugepagePerNodeGetHugepageSizes(node_index, hp_sizes_cnt, hp_sizes);
206
207 node->hugepages = SystemHugepageHugepageInfoCreate(hp_sizes_cnt);
208 node->num_hugepage_sizes = hp_sizes_cnt;
209
210 int16_t ret = 0;
211 if (SystemHugepageDetermineOS() == OS_LINUX_SYS_DEVICES)
212 ret = SystemHugepagePerNodeGetHugepageInfoLinux(
213 node->hugepages, hp_sizes, node->num_hugepage_sizes, node_index);
214
215 SCFree(hp_sizes);
216 return ret;
217}
218
219/**
220 * \brief The function detects number of NUMA nodes on the system
221 * \returns 0 if detection is unsuccessful, otherwise number of detected nodes
222 */
223static uint16_t SystemNodeCountGet(void)
224{
225 if (SystemHugepageDetermineOS() == OS_LINUX_SYS_DEVICES)
226 return SystemNodeCountGetLinux();
227 return 0;
228}
229
230/**
231 * \brief The function detects the number of unique hugepage sizes
232 * \returns 0 if detection is unsuccessful, otherwise number of hugepage sizes
233 */
234static uint16_t SystemHugepageSizesCntPerNodeGet(uint16_t node_index)
235{
236 if (SystemHugepageDetermineOS() == OS_LINUX_SYS_DEVICES)
237 return SystemHugepageSizesCntPerNodeGetLinux(node_index);
238 return 0;
239}
240
241/**
242 * \brief The function fills an array with unique hugepage sizes
243 * \note Arrays `hugepages` and `hp_sizes` are expected to have the same size
244 * \param[in] node_index index of the NUMA node
245 * \param[in] hp_sizes_cnt number of hugepage sizes
246 * \param[out] hp_sizes a pointer to the array of hugepage sizes
247 */
248static void SystemHugepagePerNodeGetHugepageSizes(
249 uint16_t node_index, uint16_t hp_sizes_cnt, uint32_t *hp_sizes)
250{
251 if (SystemHugepageDetermineOS() == OS_LINUX_SYS_DEVICES)
252 SystemHugepagePerNodeGetHugepageSizesLinux(node_index, hp_sizes_cnt, hp_sizes);
253}
254
255static HugepageInfo *SystemHugepageHugepageInfoCreate(uint16_t hp_size_cnt)
256{
257 HugepageInfo *h = SCCalloc(hp_size_cnt, sizeof(*h));
258 if (h == NULL) {
259 FatalError("failed to allocate hugepage info array");
260 }
261 return h;
262}
263
264static void SystemHugepageHugepageInfoDestroy(HugepageInfo *h)
265{
266 if (h != NULL)
267 SCFree(h);
268}
269
270static void SystemHugepageNodeInfoDestroy(NodeInfo *n)
271{
272 if (n == NULL)
273 return;
274
275 SystemHugepageHugepageInfoDestroy(n->hugepages);
276}
277
278static void SystemHugepageNodeInfoDump(NodeInfo *n)
279{
280 if (n == NULL)
281 return;
282
283 for (uint16_t i = 0; i < n->num_hugepage_sizes; i++) {
284 SCLogDebug("Hugepage size - %dkB - allocated: %d free: %d", n->hugepages[i].size_kb,
285 n->hugepages[i].allocated, n->hugepages[i].free);
286 }
287}
288
289/**
290 * \brief The function prints out the hugepage snapshot
291 * \param[in] s a pointer to the snapshot
292 */
293static void SystemHugepageSnapshotDump(SystemHugepageSnapshot *s)
294{
295 if (s == NULL)
296 return;
297
298 for (uint16_t i = 0; i < s->num_nodes; i++) {
299 SCLogDebug("NUMA Node %d", i);
300 SystemHugepageNodeInfoDump(&(s->nodes[i]));
301 }
302}
303
305{
306 if (s == NULL)
307 return;
308
309 for (uint16_t i = 0; i < s->num_nodes; i++) {
310 SystemHugepageNodeInfoDestroy(&(s->nodes[i]));
311 }
312 SCFree(s->nodes);
313 SCFree(s);
314}
315
316/**
317 * \brief The function creates a snapshot of the system's hugepage usage
318 * per NUMA node and per hugepage size.
319 * The snapshot is used to evaluate the system's hugepage usage after
320 * initialization of Suricata.
321 * \returns a pointer to the snapshot, NULL on error
322 */
324{
325 if (!SystemHugepageSupported())
326 return NULL;
327
328 uint16_t node_cnt = SystemNodeCountGet();
329 if (node_cnt == 0) {
330 SCLogInfo("hugepage snapshot failed - cannot obtain number of NUMA nodes in the system");
331 return NULL;
332 }
333 NodeInfo *nodes = SCCalloc(node_cnt, sizeof(*nodes));
334 if (nodes == NULL) {
335 FatalError("failed to allocate memory for NUMA node info");
336 }
337
338 SystemHugepageSnapshot *s = SCCalloc(1, sizeof(*s));
339 if (s == NULL) {
340 SCFree(nodes);
341 FatalError("failed to allocate memory for NUMA node snapshot");
342 }
343 s->num_nodes = node_cnt;
344 s->nodes = nodes;
345
346 for (uint16_t i = 0; i < s->num_nodes; i++) {
347 int16_t ret = SystemHugepagePerNodeGetHugepageInfo(i, &s->nodes[i]);
348 if (ret != 0) {
350 return NULL;
351 }
352 }
353
354 return s;
355}
356
357/**
358 * \brief The function compares two hugepage snapshots and prints out
359 * recommendations for hugepage configuration
360 * \param[in] pre_s a pointer to the snapshot taken before Suricata initialization
361 * \param[in] post_s a pointer to the snapshot taken after Suricata initialization
362 */
364{
365 if (!SystemHugepageSupported() || pre_s == NULL || post_s == NULL)
366 return;
367
368 SCLogDebug("Hugepages before initialization");
369 SystemHugepageSnapshotDump(pre_s);
370
371 SCLogDebug("Hugepages after initialization");
372 SystemHugepageSnapshotDump(post_s);
373
374 if (pre_s->num_nodes != post_s->num_nodes)
375 FatalError("Number of NUMA nodes changed during hugepage evaluation");
376
377 for (int32_t i = 0; i < post_s->num_nodes; i++) {
378 if (pre_s->nodes[i].num_hugepage_sizes != post_s->nodes[i].num_hugepage_sizes)
379 FatalError("Number of NUMA node hugepage sizes changed during hugepage evaluation");
380
381 for (int32_t j = 0; j < post_s->nodes->num_hugepage_sizes; j++) {
382 HugepageInfo *prerun_hp = &pre_s->nodes[i].hugepages[j];
383 HugepageInfo *postrun_hp = &post_s->nodes[i].hugepages[j];
384
385 if (prerun_hp->free == 0) {
386 continue; // this HP size on this node has no HPs allocated
387 } else if (prerun_hp->free < postrun_hp->free) {
389 "Hugepage usage decreased while it should only increase/stay the same");
390 } else if (prerun_hp->free > 0 && prerun_hp->free == postrun_hp->free) {
391 SCLogPerf("%ukB hugepages on NUMA node %u are unused and can be deallocated",
392 postrun_hp->size_kb, i);
393 } else { // assumes this is an active NUMA node because at least some hugepages were
394 // used
395 // speculative hint only for 2048kB pages as e.g. 1 GB pages can leave a lot of room
396 // for additional allocations
397 if (postrun_hp->size_kb == 2048 && postrun_hp->free == 0) {
398 SCLogPerf("all %ukB hugepages used on NUMA node %d - consider increasing to "
399 "prevent memory allocation from other NUMA nodes",
400 postrun_hp->size_kb, i);
401 }
402
403 float free_hugepages_ratio = (float)postrun_hp->free / (float)prerun_hp->free;
404 if (free_hugepages_ratio > 0.5) {
405 int32_t used_hps = prerun_hp->free - postrun_hp->free;
406 SCLogPerf("Hugepages on NUMA node %u can be set to %.0lf (only using %u/%u "
407 "%ukB hugepages)",
408 i, ceil((prerun_hp->free - postrun_hp->free) * 1.15), used_hps,
409 prerun_hp->free, postrun_hp->size_kb);
410 }
411 }
412 }
413 }
414}
uint16_t allocated
uint32_t size_kb
HugepageInfo * hugepages
uint16_t num_hugepage_sizes
#define FatalError(...)
Definition util-debug.h:510
#define SCLogPerf(...)
Definition util-debug.h:234
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition util-debug.h:255
#define SCLogInfo(...)
Macro used to log INFORMATIONAL messages.
Definition util-debug.h:225
@ SC_ENOENT
Definition util-error.h:33
@ SC_EINVAL
Definition util-error.h:30
void SystemHugepageEvaluateHugepages(SystemHugepageSnapshot *pre_s, SystemHugepageSnapshot *post_s)
The function compares two hugepage snapshots and prints out recommendations for hugepage configuratio...
OSHugepageAction_
@ OS_LINUX_SYS_DEVICES
@ OS_UNKNOWN
void SystemHugepageSnapshotDestroy(SystemHugepageSnapshot *s)
SystemHugepageSnapshot * SystemHugepageSnapshotCreate(void)
The function creates a snapshot of the system's hugepage usage per NUMA node and per hugepage size....
enum OSHugepageAction_ OSHugepageAction
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
bool SCPathExists(const char *path)
Check if a path exists.
Definition util-path.c:183
bool SCIsRegularDirectory(const struct dirent *const dir_entry)
OS independent wrapper for directory check.
Definition util-path.c:200