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 
29 static uint16_t SystemHugepageSizesCntPerNodeGet(uint16_t node_index);
30 static uint16_t SystemNodeCountGet(void);
31 static void SystemHugepagePerNodeGetHugepageSizes(
32  uint16_t node_index, uint16_t hp_sizes_cnt, uint32_t *hp_sizes);
33 static HugepageInfo *SystemHugepageHugepageInfoCreate(uint16_t hp_size_cnt);
34 static int16_t SystemHugepagePerNodeGetHugepageInfo(uint16_t node_index, NodeInfo *node);
35 static void SystemHugepageHugepageInfoDestroy(HugepageInfo *h);
36 static void SystemHugepageNodeInfoDestroy(NodeInfo *n);
37 static void SystemHugepageNodeInfoDump(NodeInfo *n);
38 static void SystemHugepageSnapshotDump(SystemHugepageSnapshot *s);
39 
40 typedef enum OSHugepageAction_ {
41  OS_UNKNOWN, // unknown/unsupported OS
44 
45 static OSHugepageAction SystemHugepageDetermineOS(void)
46 {
47  // try Linux
48  if (SCPathExists("/sys/devices/system/node/")) {
49  return OS_LINUX_SYS_DEVICES;
50  }
51 
52  return OS_UNKNOWN;
53 }
54 
55 static 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  */
66 static 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  */
89 static 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  */
117 static 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  */
146 static 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  */
194 static 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  */
223 static 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  */
234 static 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  */
248 static 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 
255 static 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 
264 static void SystemHugepageHugepageInfoDestroy(HugepageInfo *h)
265 {
266  if (h != NULL)
267  SCFree(h);
268 }
269 
270 static void SystemHugepageNodeInfoDestroy(NodeInfo *n)
271 {
272  if (n == NULL)
273  return;
274 
275  SystemHugepageHugepageInfoDestroy(n->hugepages);
276 }
277 
278 static 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  */
293 static 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) {
388  SCLogWarning(
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 }
NodeInfo::hugepages
HugepageInfo * hugepages
Definition: util-hugepages.h:39
OSHugepageAction
enum OSHugepageAction_ OSHugepageAction
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:269
util-hugepages.h
HugepageInfo::free
uint16_t free
Definition: util-hugepages.h:30
SC_EINVAL
@ SC_EINVAL
Definition: util-error.h:30
OS_UNKNOWN
@ OS_UNKNOWN
Definition: util-hugepages.c:41
SystemHugepageSnapshot
Definition: util-hugepages.h:44
SystemHugepageSnapshot::num_nodes
uint16_t num_nodes
Definition: util-hugepages.h:45
SystemHugepageSnapshot::nodes
NodeInfo * nodes
Definition: util-hugepages.h:46
util-debug.h
SCIsRegularDirectory
bool SCIsRegularDirectory(const struct dirent *const dir_entry)
OS independent wrapper for directory check.
Definition: util-path.c:236
HugepageInfo::size_kb
uint32_t size_kb
Definition: util-hugepages.h:28
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:249
SC_ENOENT
@ SC_ENOENT
Definition: util-error.h:33
SCLogInfo
#define SCLogInfo(...)
Macro used to log INFORMATIONAL messages.
Definition: util-debug.h:224
OSHugepageAction_
OSHugepageAction_
Definition: util-hugepages.c:40
SCPathExists
bool SCPathExists(const char *path)
Check if a path exists.
Definition: util-path.c:219
NodeInfo::num_hugepage_sizes
uint16_t num_hugepage_sizes
Definition: util-hugepages.h:38
util-path.h
SCLogPerf
#define SCLogPerf(...)
Definition: util-debug.h:230
OS_LINUX_SYS_DEVICES
@ OS_LINUX_SYS_DEVICES
Definition: util-hugepages.c:42
FatalError
#define FatalError(...)
Definition: util-debug.h:502
HugepageInfo
Definition: util-hugepages.h:27
SystemHugepageSnapshotDestroy
void SystemHugepageSnapshotDestroy(SystemHugepageSnapshot *s)
Definition: util-hugepages.c:304
SCFree
#define SCFree(p)
Definition: util-mem.h:61
NodeInfo
Definition: util-hugepages.h:37
suricata.h
SystemHugepageSnapshotCreate
SystemHugepageSnapshot * SystemHugepageSnapshotCreate(void)
The function creates a snapshot of the system's hugepage usage per NUMA node and per hugepage size....
Definition: util-hugepages.c:323
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
SystemHugepageEvaluateHugepages
void SystemHugepageEvaluateHugepages(SystemHugepageSnapshot *pre_s, SystemHugepageSnapshot *post_s)
The function compares two hugepage snapshots and prints out recommendations for hugepage configuratio...
Definition: util-hugepages.c:363
HugepageInfo::allocated
uint16_t allocated
Definition: util-hugepages.h:29