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