3
numastat - NUMA monitoring tool to show per-node usage of memory
4
Copyright (C) 2012 Bill Gray (bgray@redhat.com), Red Hat Inc
6
numastat is free software; you can redistribute it and/or modify it under the
7
terms of the GNU Lesser General Public License as published by the Free
8
Software Foundation; version 2.1.
10
numastat is distributed in the hope that it will be useful, but WITHOUT ANY
11
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
14
You should find a copy of v2.1 of the GNU Lesser General Public License
15
somewhere on your Linux system; if not, write to the Free Software Foundation,
16
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
Historical note: From approximately 2003 to 2012, numastat was a perl script
24
written by Andi Kleen to display the /sys/devices/system/node/node<N>/numastat
25
statistics. In 2012, numastat was rewritten as a C program by Red Hat to
26
display per-node memory data for applications and the system in general,
27
while also remaining strictly compatible by default with the original numastat.
28
A copy of the original numastat perl script is included for reference at the
34
// Compile with: gcc -O -std=gnu99 -Wall -o numastat numastat.c
46
#include <sys/types.h>
50
#define STRINGIZE(s) #s
51
#define STRINGIFY(s) STRINGIZE(s)
53
#define KILOBYTE (1024)
54
#define MEGABYTE (1024 * 1024)
57
#define SMALL_BUF_SIZE 128
60
// Don't assume nodes are sequential or contiguous.
61
// Need to discover and map node numbers.
63
int *node_ix_map = NULL;
67
// Structure to organize memory info from /proc/<PID>/numa_maps for a specific
68
// process, or from /sys/devices/system/node/node?/meminfo for system-wide
69
// data. Tables are defined below for each process and for system-wide data.
71
typedef struct meminfo {
75
} meminfo_t, *meminfo_p;
77
#define PROCESS_HUGE_INDEX 0
78
#define PROCESS_PRIVATE_INDEX 3
80
meminfo_t process_meminfo[] = {
81
{ PROCESS_HUGE_INDEX, "huge", "Huge" },
82
{ 1, "heap", "Heap" },
83
{ 2, "stack", "Stack" },
84
{ PROCESS_PRIVATE_INDEX, "N", "Private" }
87
#define PROCESS_MEMINFO_ROWS (sizeof(process_meminfo) / sizeof(process_meminfo[0]))
89
meminfo_t numastat_meminfo[] = {
90
{ 0, "numa_hit", "Numa_Hit" },
91
{ 1, "numa_miss", "Numa_Miss" },
92
{ 2, "numa_foreign", "Numa_Foreign" },
93
{ 3, "interleave_hit", "Interleave_Hit" },
94
{ 4, "local_node", "Local_Node" },
95
{ 5, "other_node", "Other_Node" },
98
#define NUMASTAT_MEMINFO_ROWS (sizeof(numastat_meminfo) / sizeof(numastat_meminfo[0]))
100
meminfo_t system_meminfo[] = {
101
{ 0, "MemTotal", "MemTotal" },
102
{ 1, "MemFree", "MemFree" },
103
{ 2, "MemUsed", "MemUsed" },
104
{ 3, "HighTotal", "HighTotal" },
105
{ 4, "HighFree", "HighFree" },
106
{ 5, "LowTotal", "LowTotal" },
107
{ 6, "LowFree", "LowFree" },
108
{ 7, "Active", "Active" },
109
{ 8, "Inactive", "Inactive" },
110
{ 9, "Active(anon)", "Active(anon)" },
111
{ 10, "Inactive(anon)", "Inactive(anon)" },
112
{ 11, "Active(file)", "Active(file)" },
113
{ 12, "Inactive(file)", "Inactive(file)" },
114
{ 13, "Unevictable", "Unevictable" },
115
{ 14, "Mlocked", "Mlocked" },
116
{ 15, "Dirty", "Dirty" },
117
{ 16, "Writeback", "Writeback" },
118
{ 17, "FilePages", "FilePages" },
119
{ 18, "Mapped", "Mapped" },
120
{ 19, "AnonPages", "AnonPages" },
121
{ 20, "Shmem", "Shmem" },
122
{ 21, "KernelStack", "KernelStack" },
123
{ 22, "PageTables", "PageTables" },
124
{ 23, "NFS_Unstable", "NFS_Unstable" },
125
{ 24, "Bounce", "Bounce" },
126
{ 25, "WritebackTmp", "WritebackTmp" },
127
{ 26, "Slab", "Slab" },
128
{ 27, "SReclaimable", "SReclaimable" },
129
{ 28, "SUnreclaim", "SUnreclaim" },
130
{ 29, "AnonHugePages", "AnonHugePages" },
131
{ 30, "HugePages_Total", "HugePages_Total" },
132
{ 31, "HugePages_Free", "HugePages_Free" },
133
{ 32, "HugePages_Surp", "HugePages_Surp" }
136
#define SYSTEM_MEMINFO_ROWS (sizeof(system_meminfo) / sizeof(system_meminfo[0]))
143
// To allow re-ordering the meminfo memory categories in system_meminfo and
144
// numastat_meminfo relative to order in /proc, etc., a simple hash index is
145
// used to look up the meminfo categories. The allocated hash table size must
146
// be bigger than necessary to reduce collisions (and because these specific
147
// hash algorithms depend on having some unused buckets.
149
#define HASH_TABLE_SIZE 151
150
int hash_collisions = 0;
155
} hash_table[HASH_TABLE_SIZE];
158
void init_hash_table() {
159
memset(hash_table, 0, sizeof(hash_table));
163
int hash_ix(char *s) {
167
h = ((h << 5) + h) + *s++;
169
return (h % HASH_TABLE_SIZE);
173
int hash_lookup(char *s) {
175
while (hash_table[ix].name) { // Assumes big table with blank entries
176
if (!strcmp(s, hash_table[ix].name)) {
177
return hash_table[ix].index; // found it
180
if (ix >= HASH_TABLE_SIZE) {
188
int hash_insert(char *s, int i) {
190
while (hash_table[ix].name) { // assumes no duplicate entries
191
hash_collisions += 1;
193
if (ix >= HASH_TABLE_SIZE) {
197
hash_table[ix].name = s;
198
hash_table[ix].index = i;
207
// To decouple details of table display (e.g. column width, line folding for
208
// display screen width, et cetera) from acquiring the data and populating the
209
// tables, this semi-general table handling code is used. There are various
210
// routines to set table attributes, assign and test some cell contents,
211
// initialize and actually display the table.
213
#define CELL_TYPE_NULL 0
214
#define CELL_TYPE_LONG 1
215
#define CELL_TYPE_DOUBLE 2
216
#define CELL_TYPE_STRING 3
217
#define CELL_TYPE_CHAR8 4
218
#define CELL_TYPE_REPCHAR 5
220
#define CELL_FLAG_FREEABLE (1 << 0)
221
#define CELL_FLAG_ROWSPAN (1 << 1)
222
#define CELL_FLAG_COLSPAN (1 << 2)
224
#define COL_JUSTIFY_LEFT (1 << 0)
225
#define COL_JUSTIFY_RIGHT (1 << 1)
226
#define COL_JUSTIFY_CENTER 3
227
#define COL_JUSTIFY_MASK 0x3
228
#define COL_FLAG_SEEN_DATA (1 << 2)
229
#define COL_FLAG_NON_ZERO_DATA (1 << 3)
230
#define COL_FLAG_ALWAYS_SHOW (1 << 4)
232
#define ROW_FLAG_SEEN_DATA COL_FLAG_SEEN_DATA
233
#define ROW_FLAG_NON_ZERO_DATA COL_FLAG_NON_ZERO_DATA
234
#define ROW_FLAG_ALWAYS_SHOW COL_FLAG_ALWAYS_SHOW
236
typedef struct cell {
247
typedef struct vtab {
257
uint8_t *col_decimal_places;
260
#define ALL_TABLE_ROWS (table->header_rows + table->data_rows)
261
#define ALL_TABLE_COLS (table->header_cols + table->data_cols)
262
#define GET_CELL_PTR(row, col) (&table->cell[(row * ALL_TABLE_COLS) + col])
264
#define USUAL_GUTTER_WIDTH 1
267
void set_row_flag(vtab_p table, int row, int flag) {
268
table->row_flags[row] |= (uint8_t)flag;
271
void set_col_flag(vtab_p table, int col, int flag) {
272
table->col_flags[col] |= (uint8_t)flag;
275
void clear_row_flag(vtab_p table, int row, int flag) {
276
table->row_flags[row] &= (uint8_t)~flag;
279
void clear_col_flag(vtab_p table, int col, int flag) {
280
table->col_flags[col] &= (uint8_t)~flag;
283
int test_row_flag(vtab_p table, int row, int flag) {
284
return ((table->row_flags[row] & (uint8_t)flag) != 0);
287
int test_col_flag(vtab_p table, int col, int flag) {
288
return ((table->col_flags[col] & (uint8_t)flag) != 0);
292
void set_col_justification(vtab_p table, int col, int justify) {
293
table->col_flags[col] &= (uint8_t)~COL_JUSTIFY_MASK;
294
table->col_flags[col] |= (uint8_t)(justify & COL_JUSTIFY_MASK);
298
void set_col_width(vtab_p table, int col, uint8_t width) {
299
if (width >= SMALL_BUF_SIZE) {
300
width = SMALL_BUF_SIZE - 1;
302
table->col_width[col] = width;
306
void set_col_decimal_places(vtab_p table, int col, uint8_t places) {
307
table->col_decimal_places[col] = places;
311
void set_cell_flag(vtab_p table, int row, int col, int flag) {
312
cell_p c_ptr = GET_CELL_PTR(row, col);
313
c_ptr->flags |= (uint32_t)flag;
317
void clear_cell_flag(vtab_p table, int row, int col, int flag) {
318
cell_p c_ptr = GET_CELL_PTR(row, col);
319
c_ptr->flags &= (uint32_t)~flag;
323
int test_cell_flag(vtab_p table, int row, int col, int flag) {
324
cell_p c_ptr = GET_CELL_PTR(row, col);
325
return ((c_ptr->flags & (uint32_t)flag) != 0);
329
void string_assign(vtab_p table, int row, int col, char *s) {
330
cell_p c_ptr = GET_CELL_PTR(row, col);
331
c_ptr->type = CELL_TYPE_STRING;
336
void repchar_assign(vtab_p table, int row, int col, char c) {
337
cell_p c_ptr = GET_CELL_PTR(row, col);
338
c_ptr->type = CELL_TYPE_REPCHAR;
343
void double_assign(vtab_p table, int row, int col, double d) {
344
cell_p c_ptr = GET_CELL_PTR(row, col);
345
c_ptr->type = CELL_TYPE_DOUBLE;
350
void long_assign(vtab_p table, int row, int col, int64_t l) {
351
cell_p c_ptr = GET_CELL_PTR(row, col);
352
c_ptr->type = CELL_TYPE_LONG;
357
void double_addto(vtab_p table, int row, int col, double d) {
358
cell_p c_ptr = GET_CELL_PTR(row, col);
359
c_ptr->type = CELL_TYPE_DOUBLE;
364
void long_addto(vtab_p table, int row, int col, int64_t l) {
365
cell_p c_ptr = GET_CELL_PTR(row, col);
366
c_ptr->type = CELL_TYPE_LONG;
371
void clear_assign(vtab_p table, int row, int col) {
372
cell_p c_ptr = GET_CELL_PTR(row, col);
373
memset(c_ptr, 0, sizeof(cell_t));
377
void zero_table_data(vtab_p table, int type) {
378
// Sets data area of table to zeros of specified type
379
for (int row = table->header_rows; (row < ALL_TABLE_ROWS); row++) {
380
for (int col = table->header_cols; (col < ALL_TABLE_COLS); col++) {
381
cell_p c_ptr = GET_CELL_PTR(row, col);
382
memset(c_ptr, 0, sizeof(cell_t));
389
void sort_rows_descending_by_col(vtab_p table, int start_row, int stop_row, int col) {
390
// Rearrange row_ix_map[] indices so the rows will be in
391
// descending order by the value in the specified column
392
for (int ix = start_row; (ix <= stop_row); ix++) {
394
cell_p biggest_ix_c_ptr = GET_CELL_PTR(table->row_ix_map[ix], col);
395
for (int iy = ix + 1; (iy <= stop_row); iy++) {
396
cell_p iy_c_ptr = GET_CELL_PTR(table->row_ix_map[iy], col);
397
if (biggest_ix_c_ptr->d < iy_c_ptr->d) {
398
biggest_ix_c_ptr = iy_c_ptr;
402
if (biggest_ix != ix) {
403
int tmp = table->row_ix_map[ix];
404
table->row_ix_map[ix] = table->row_ix_map[biggest_ix];
405
table->row_ix_map[biggest_ix] = tmp;
411
void span(vtab_p table, int first_row, int first_col, int last_row, int last_col) {
412
// FIXME: implement row / col spannnig someday?
416
void init_table(vtab_p table, int header_rows, int header_cols, int data_rows, int data_cols) {
418
table->header_rows = header_rows;
419
table->header_cols = header_cols;
420
table->data_rows = data_rows;
421
table->data_cols = data_cols;
422
// allocate memory for all the cells
423
int alloc_size = ALL_TABLE_ROWS * ALL_TABLE_COLS * sizeof(cell_t);
424
table->cell = malloc(alloc_size);
425
if (table->cell == NULL) {
426
perror("malloc failed line: " STRINGIFY(__LINE__));
429
memset(table->cell, 0, alloc_size);
430
// allocate memory for the row map vector
431
alloc_size = ALL_TABLE_ROWS * sizeof(int);
432
table->row_ix_map = malloc(alloc_size);
433
if (table->row_ix_map == NULL) {
434
perror("malloc failed line: " STRINGIFY(__LINE__));
437
for (int row = 0; (row < ALL_TABLE_ROWS); row++) {
438
table->row_ix_map[row] = row;
440
// allocate memory for the row flags vector
441
alloc_size = ALL_TABLE_ROWS * sizeof(uint8_t);
442
table->row_flags = malloc(alloc_size);
443
if (table->row_flags == NULL) {
444
perror("malloc failed line: " STRINGIFY(__LINE__));
447
memset(table->row_flags, 0, alloc_size);
448
// allocate memory for the column flags vector
449
alloc_size = ALL_TABLE_COLS * sizeof(uint8_t);
450
table->col_flags = malloc(alloc_size);
451
if (table->col_flags == NULL) {
452
perror("malloc failed line: " STRINGIFY(__LINE__));
455
memset(table->col_flags, 0, alloc_size);
456
// allocate memory for the column width vector
457
alloc_size = ALL_TABLE_COLS * sizeof(uint8_t);
458
table->col_width = malloc(alloc_size);
459
if (table->col_width == NULL) {
460
perror("malloc failed line: " STRINGIFY(__LINE__));
463
memset(table->col_width, 0, alloc_size);
464
// allocate memory for the column precision vector
465
alloc_size = ALL_TABLE_COLS * sizeof(uint8_t);
466
table->col_decimal_places = malloc(alloc_size);
467
if (table->col_decimal_places == NULL) {
468
perror("malloc failed line: " STRINGIFY(__LINE__));
471
memset(table->col_decimal_places, 0, alloc_size);
475
void free_cell(vtab_p table, int row, int col) {
476
cell_p c_ptr = GET_CELL_PTR(row, col);
477
if ((c_ptr->type == CELL_TYPE_STRING)
478
&& (c_ptr->flags & CELL_FLAG_FREEABLE)
479
&& (c_ptr->s != NULL)) {
482
memset(c_ptr, 0, sizeof(cell_t));
486
void free_table(vtab_p table) {
487
if (table->cell != NULL) {
488
for (int row = 0; (row < ALL_TABLE_ROWS); row++) {
489
for (int col = 0; (col < ALL_TABLE_COLS); col++) {
490
free_cell(table, row, col);
495
if (table->row_ix_map != NULL) {
496
free(table->row_ix_map);
498
if (table->row_flags != NULL) {
499
free(table->row_flags);
501
if (table->col_flags != NULL) {
502
free(table->col_flags);
504
if (table->col_width != NULL) {
505
free(table->col_width);
507
if (table->col_decimal_places != NULL) {
508
free(table->col_decimal_places);
513
char *fmt_cell_data(cell_p c_ptr, int max_width, int decimal_places) {
514
// Returns pointer to a static buffer, expecting caller to
515
// immediately use or copy the contents before calling again.
516
int rep_width = max_width - USUAL_GUTTER_WIDTH;
517
static char buf[SMALL_BUF_SIZE];
518
switch (c_ptr->type) {
523
snprintf(buf, SMALL_BUF_SIZE, "%ld", c_ptr->l);
525
case CELL_TYPE_DOUBLE:
526
snprintf(buf, SMALL_BUF_SIZE, "%.*f", decimal_places, c_ptr->d);
528
case CELL_TYPE_STRING:
529
snprintf(buf, SMALL_BUF_SIZE, "%s", c_ptr->s);
531
case CELL_TYPE_CHAR8:
532
strncpy(buf, c_ptr->c, 8);
535
case CELL_TYPE_REPCHAR:
536
memset(buf, c_ptr->c[0], rep_width);
537
buf[rep_width] = '\0';
540
strcpy(buf, "Unknown");
543
buf[max_width] = '\0';
548
void auto_set_col_width(vtab_p table, int col, int min_width, int max_width) {
549
int width = min_width;
550
for (int row = 0; (row < ALL_TABLE_ROWS); row++) {
551
cell_p c_ptr = GET_CELL_PTR(row, col);
552
if (c_ptr->type == CELL_TYPE_REPCHAR) {
555
char *p = fmt_cell_data(c_ptr, max_width, (int)(table->col_decimal_places[col]));
561
width += USUAL_GUTTER_WIDTH;
562
if (width > max_width) {
565
table->col_width[col] = (uint8_t)width;
569
void display_justified_cell(cell_p c_ptr, int row_flags, int col_flags, int width, int decimal_places) {
570
char *p = fmt_cell_data(c_ptr, width, decimal_places);
572
char buf[SMALL_BUF_SIZE];
573
switch (col_flags & COL_JUSTIFY_MASK) {
574
case COL_JUSTIFY_LEFT:
577
memset(&buf[l], ' ', width - l);
580
case COL_JUSTIFY_RIGHT:
582
memset(buf, ' ', width - l);
584
memcpy(&buf[width - l], p, l);
586
case COL_JUSTIFY_CENTER:
588
memset(buf, ' ', width);
589
memcpy(&buf[(width - l + 1) / 2], p, l);
597
void display_table(vtab_p table,
599
int show_unseen_rows,
600
int show_unseen_cols,
604
// Set row and column flags according to whether data in rows and cols
605
// has been assigned, and is currently non-zero.
606
int some_seen_data = 0;
607
int some_non_zero_data = 0;
608
for (int row = table->header_rows; (row < ALL_TABLE_ROWS); row++) {
609
for (int col = table->header_cols; (col < ALL_TABLE_COLS); col++) {
610
cell_p c_ptr = GET_CELL_PTR(row, col);
611
// Currently, "seen data" includes not only numeric data, but also
612
// any strings, etc -- anything non-NULL (other than rephcars).
613
if ((c_ptr->type != CELL_TYPE_NULL) && (c_ptr->type != CELL_TYPE_REPCHAR)) {
615
set_row_flag(table, row, ROW_FLAG_SEEN_DATA);
616
set_col_flag(table, col, COL_FLAG_SEEN_DATA);
617
// Currently, "non-zero data" includes not only numeric data,
618
// but also any strings, etc -- anything non-zero (other than
619
// repchars, which are already excluded above). So, note a
620
// valid non-NULL pointer to an empty string would still be
621
// counted as non-zero data.
622
if (c_ptr->l != (int64_t)0) {
623
some_non_zero_data = 1;
624
set_row_flag(table, row, ROW_FLAG_NON_ZERO_DATA);
625
set_col_flag(table, col, COL_FLAG_NON_ZERO_DATA);
630
if (!some_seen_data) {
631
printf("Table has no data.\n");
634
if (!some_non_zero_data && !show_zero_rows && !show_zero_cols) {
635
printf("Table has no non-zero data.\n");
638
// Start with first data column and try to display table,
639
// folding lines as necessary per screen_width
641
int data_col = table->header_cols;
642
while (data_col < ALL_TABLE_COLS) {
643
// Skip data columns until we have one to display
644
if ((!test_col_flag(table, data_col, COL_FLAG_ALWAYS_SHOW)) &&
645
(((!show_unseen_cols) && (!test_col_flag(table, data_col, COL_FLAG_SEEN_DATA))) ||
646
((!show_zero_cols) && (!test_col_flag(table, data_col, COL_FLAG_NON_ZERO_DATA))))) {
650
// Display blank line between table sections
654
// For each row, display as many columns as possible
655
for (int row_ix = 0; (row_ix < ALL_TABLE_ROWS); row_ix++) {
656
int row = table->row_ix_map[row_ix];
657
// If past the header rows, conditionally skip rows
658
if ((row >= table->header_rows) && (!test_row_flag(table, row, ROW_FLAG_ALWAYS_SHOW))) {
659
// Optionally skip row if no data seen or if all zeros
660
if (((!show_unseen_rows) && (!test_row_flag(table, row, ROW_FLAG_SEEN_DATA))) ||
661
((!show_zero_rows) && (!test_row_flag(table, row, ROW_FLAG_NON_ZERO_DATA)))) {
665
// Begin a new row...
666
int cur_line_width = 0;
667
// All lines start with the left header columns
668
for (col = 0; (col < table->header_cols); col++) {
669
display_justified_cell(GET_CELL_PTR(row, col),
670
(int)(table->row_flags[row]),
671
(int)(table->col_flags[col]),
672
(int)(table->col_width[col]),
673
(int)(table->col_decimal_places[col]));
674
cur_line_width += (int)(table->col_width[col]);
676
// Reset column index to starting data column for each new row
678
// Try to display as many data columns as possible in every section
680
// See if we should print this column
681
if (test_col_flag(table, col, COL_FLAG_ALWAYS_SHOW) ||
682
(((show_unseen_cols) || (test_col_flag(table, col, COL_FLAG_SEEN_DATA))) &&
683
((show_zero_cols) || (test_col_flag(table, col, COL_FLAG_NON_ZERO_DATA))))) {
684
display_justified_cell(GET_CELL_PTR(row, col),
685
(int)(table->row_flags[row]),
686
(int)(table->col_flags[col]),
687
(int)(table->col_width[col]),
688
(int)(table->col_decimal_places[col]));
689
cur_line_width += (int)(table->col_width[col]);
692
// End the line if no more columns or next column would exceed screen width
693
if ((col >= ALL_TABLE_COLS) ||
694
((cur_line_width + (int)(table->col_width[col])) > screen_width)) {
700
// Remember next starting data column for next section
713
int screen_width = 0;
714
int show_zero_data = 1;
715
int compress_display = 0;
717
int sort_table_node = -1;
718
int compatibility_mode = 0;
719
int pid_array_max_pids = 0;
720
int *pid_array = NULL;
721
char *prog_name = NULL;
722
double page_size_in_bytes = 0;
723
double huge_page_size_in_bytes = 0;
726
void display_version_and_exit() {
727
char *version_string = "20120821";
728
printf("%s version: %s: %s\n", prog_name, version_string, __DATE__);
733
void display_usage_and_exit() {
734
fprintf(stderr, "Usage: %s [-c] [-m] [-n] [-p <PID>|<pattern>] [-s[<node>]] [-v] [-V] [-z] [ <PID>|<pattern>... ]\n", prog_name);
735
fprintf(stderr, "-c to minimize column widths\n");
736
fprintf(stderr, "-m to show meminfo-like system-wide memory usage\n");
737
fprintf(stderr, "-n to show the numastat statistics info\n");
738
fprintf(stderr, "-p <PID>|<pattern> to show process info\n");
739
fprintf(stderr, "-s[<node>] to sort data by total column or <node>\n");
740
fprintf(stderr, "-v to make some reports more verbose\n");
741
fprintf(stderr, "-V to show the %s code version\n", prog_name);
742
fprintf(stderr, "-z to skip rows and columns of zeros\n");
747
int get_screen_width() {
749
char *p = getenv("NUMASTAT_WIDTH");
752
if ((width < 1) || (width > 10000000)) {
755
} else if (isatty(fileno(stdout))) {
756
FILE *fs = popen("resize 2>/dev/null", "r");
759
fgets(columns, sizeof(columns), fs);
761
if (strncmp(columns, "COLUMNS=", 8) == 0) {
762
width = atoi(&columns[8]);
763
if ((width < 1) || (width > 10000000)) {
769
// Not a tty, so allow a really long line
779
char *command_name_for_pid(int pid) {
780
// Get the PID command name field from /proc/PID/status file. Return
781
// pointer to a static buffer, expecting caller to immediately copy result.
782
static char buf[SMALL_BUF_SIZE];
784
snprintf(fname, sizeof(fname), "/proc/%d/status", pid);
785
FILE *fs = fopen(fname, "r");
789
while (fgets(buf, SMALL_BUF_SIZE, fs)) {
790
if (strstr(buf, "Name:") == buf) {
792
while (isspace(*p)) {
795
if (p[strlen(p) - 1] == '\n') {
796
p[strlen(p) - 1] = '\0';
808
void show_info_from_system_file(char *file, meminfo_p meminfo, int meminfo_rows, int tok_offset) {
809
// Setup and init table
811
int header_rows = 2 - compatibility_mode;
813
// Add an extra data column for a total column
814
init_table(&table, header_rows, header_cols, meminfo_rows, num_nodes + 1);
815
int total_col_ix = header_cols + num_nodes;
816
// Insert token mapping in hash table and assign left header column label for each row in table
818
for (int row = 0; (row < meminfo_rows); row++) {
819
hash_insert(meminfo[row].token, meminfo[row].index);
820
if (compatibility_mode) {
821
string_assign(&table, (header_rows + row), 0, meminfo[row].token);
823
string_assign(&table, (header_rows + row), 0, meminfo[row].label);
826
// printf("There are %d table hash collisions.\n", hash_collisions);
827
// Set left header column width and left justify it
828
set_col_width(&table, 0, 16);
829
set_col_justification(&table, 0, COL_JUSTIFY_LEFT);
830
// Open /sys/devices/system/node/node?/<file> for each node and store data
831
// in table. If not compatibility_mode, do approximately first third of
832
// this loop also for (node_ix == num_nodes) to get "Total" column header.
833
for (int node_ix = 0; (node_ix < (num_nodes + (1 - compatibility_mode))); node_ix++) {
834
int col = header_cols + node_ix;
835
// Assign header row label and horizontal line for this column...
836
string_assign(&table, 0, col, node_header[node_ix]);
837
if (!compatibility_mode) {
838
repchar_assign(&table, 1, col, '-');
839
int decimal_places = 2;
840
if (compress_display) {
843
set_col_decimal_places(&table, col, decimal_places);
845
// Set column width and right justify data
846
set_col_width(&table, col, 16);
847
set_col_justification(&table, col, COL_JUSTIFY_RIGHT);
848
if (node_ix == num_nodes) {
851
// Open /sys/.../node<N>/numstast file for this node...
852
char buf[SMALL_BUF_SIZE];
854
snprintf(fname, sizeof(fname), "/sys/devices/system/node/node%d/%s", node_ix_map[node_ix], file);
855
FILE *fs = fopen(fname, "r");
857
sprintf(buf, "cannot open %s", fname);
861
// Get table values for this node...
862
while (fgets(buf, SMALL_BUF_SIZE, fs)) {
865
const char *delimiters = " \t\r\n:";
866
char *p = strtok(buf, delimiters);
868
continue; // Skip blank lines;
872
p = strtok(NULL, delimiters);
874
// example line from numastat file: "numa_miss 16463"
875
// example line from meminfo file: "Node 3 Inactive: 210680 kB"
876
int index = hash_lookup(tok[0 + tok_offset]);
878
printf("Token %s not in hash table.\n", tok[0]);
880
double value = (double)atol(tok[1 + tok_offset]);
881
if (!compatibility_mode) {
882
double multiplier = 1.0;
884
multiplier = page_size_in_bytes;
885
} else if (!strncmp("HugePages", tok[2], 9)) {
886
multiplier = huge_page_size_in_bytes;
887
} else if (!strncmp("kB", tok[4], 2)) {
888
multiplier = KILOBYTE;
891
value /= (double)MEGABYTE;
893
double_assign(&table, header_rows + index, col, value);
894
double_addto(&table, header_rows + index, total_col_ix, value);
899
// Crompress display column widths, if requested
900
if (compress_display) {
901
for (int col = 0; (col < header_cols + num_nodes + 1); col++) {
902
auto_set_col_width(&table, col, 4, 16);
905
// Optionally sort the table data
908
if ((sort_table_node < 0) || (sort_table_node >= num_nodes)) {
909
sort_col = total_col_ix;
911
sort_col = header_cols + node_ix_map[sort_table_node];
913
sort_rows_descending_by_col(&table, header_rows, header_rows + meminfo_rows - 1, sort_col);
915
// Actually display the table now, doing line-folding as necessary
916
display_table(&table, screen_width, 0, 0, show_zero_data, show_zero_data);
921
void show_numastat_info() {
922
if (!compatibility_mode) {
923
printf("\nPer-node numastat info (in MBs):\n");
925
show_info_from_system_file("numastat", numastat_meminfo, NUMASTAT_MEMINFO_ROWS, 0);
929
void show_system_info() {
930
printf("\nPer-node system memory usage (in MBs):\n");
931
show_info_from_system_file("meminfo", system_meminfo, SYSTEM_MEMINFO_ROWS, 2);
935
void show_process_info() {
940
int show_sub_categories = (verbose || (num_pids == 1));
941
if (show_sub_categories) {
942
data_rows = PROCESS_MEMINFO_ROWS;
944
data_rows = num_pids;
946
// Add two extra rows for a horizontal rule followed by a total row
947
// Add one extra data column for a total column
948
init_table(&table, header_rows, header_cols, data_rows + 2, num_nodes + 1);
949
int total_col_ix = header_cols + num_nodes;
950
int total_row_ix = header_rows + data_rows + 1;
951
string_assign(&table, total_row_ix, 0, "Total");
952
if (show_sub_categories) {
953
// Assign left header column label for each row in table
954
for (int row = 0; (row < PROCESS_MEMINFO_ROWS); row++) {
955
string_assign(&table, (header_rows + row), 0, process_meminfo[row].label);
958
string_assign(&table, 0, 0, "PID");
959
repchar_assign(&table, 1, 0, '-');
960
printf("\nPer-node process memory usage (in MBs)\n");
962
// Set left header column width and left justify it
963
set_col_width(&table, 0, 16);
964
set_col_justification(&table, 0, COL_JUSTIFY_LEFT);
965
// Set up "Node <N>" column headers over data columns, plus "Total" column
966
for (int node_ix = 0; (node_ix <= num_nodes); node_ix++) {
967
int col = header_cols + node_ix;
968
// Assign header row label and horizontal line for this column...
969
string_assign(&table, 0, col, node_header[node_ix]);
970
repchar_assign(&table, 1, col, '-');
971
// Set column width, decimal places, and right justify data
972
set_col_width(&table, col, 16);
973
int decimal_places = 2;
974
if (compress_display) {
977
set_col_decimal_places(&table, col, decimal_places);
978
set_col_justification(&table, col, COL_JUSTIFY_RIGHT);
980
// Initialize data in table to all zeros
981
zero_table_data(&table, CELL_TYPE_DOUBLE);
982
// If (show_sub_categories), show individual process tables for each PID,
983
// Otherwise show one big table of process total lines from all the PIDs.
984
for (int pid_ix = 0; (pid_ix < num_pids); pid_ix++) {
985
int pid = pid_array[pid_ix];
986
if (show_sub_categories) {
987
printf("\nPer-node process memory usage (in MBs) for PID %d (%s)\n", pid, command_name_for_pid(pid));
989
// Re-initialize show_sub_categories table, because we re-use it for each PID.
990
zero_table_data(&table, CELL_TYPE_DOUBLE);
993
// Put this row's "PID (cmd)" label in left header column for this PID total row
995
snprintf(tmp_buf, sizeof(tmp_buf), "%d (%s)", pid, command_name_for_pid(pid));
996
char *p = strdup(tmp_buf);
998
perror("malloc failed line: " STRINGIFY(__LINE__));
1001
string_assign(&table, header_rows + pid_ix, 0, p);
1002
set_cell_flag(&table, header_rows + pid_ix, 0, CELL_FLAG_FREEABLE);
1004
// Open numa_map for this PID to get per-node data
1006
snprintf(fname, sizeof(fname), "/proc/%d/numa_maps", pid);
1008
FILE *fs = fopen(fname, "r");
1010
sprintf(buf, "Can't read /proc/%d/numa_maps", pid);
1014
// Add up sub-category memory used from each node. Must go line by line
1015
// through the numa_map figuring out which category memory, node, and the
1017
while (fgets(buf, BUF_SIZE, fs)) {
1018
int category = PROCESS_PRIVATE_INDEX; // init category to the catch-all...
1019
const char *delimiters = " \t\r\n";
1020
char *p = strtok(buf, delimiters);
1022
// If the memory category for this line is still the catch-all
1023
// (i.e. private), then see if the current token is a special
1024
// keyword for a specific memory sub-category.
1025
if (category == PROCESS_PRIVATE_INDEX) {
1026
for (int ix = 0; (ix < PROCESS_PRIVATE_INDEX); ix++) {
1027
if (!strncmp(p, process_meminfo[ix].token, strlen(process_meminfo[ix].token))) {
1033
// If the current token is a per-node pages quantity, parse the
1034
// node number and accumulate the number of pages in the specific
1035
// category (and also add to the total).
1037
int node_num = (int)strtol(&p[1], &p, 10);
1039
perror("node value parse error");
1042
double value = (double)strtol(&p[1], &p, 10);
1043
double multiplier = page_size_in_bytes;
1044
if (category == PROCESS_HUGE_INDEX) {
1045
multiplier = huge_page_size_in_bytes;
1047
value *= multiplier;
1048
value /= (double)MEGABYTE;
1049
// Add value to data cell, total_col, and total_row
1051
if (show_sub_categories) {
1052
tmp_row = header_rows + category;
1054
tmp_row = header_rows + pid_ix;
1056
int tmp_col = header_cols + node_num;
1057
double_addto(&table, tmp_row, tmp_col, value);
1058
double_addto(&table, tmp_row, total_col_ix, value);
1059
double_addto(&table, total_row_ix, tmp_col, value);
1060
double_addto(&table, total_row_ix, total_col_ix, value);
1062
// Get next token on the line
1063
p = strtok(NULL, delimiters);
1066
// Currently, a non-root user can open some numa_map files successfully
1067
// without error, but can't actually read the contents -- despite the
1068
// 444 file permissions. So, use ferror() to check here to see if we
1069
// actually got a read error, and if so, alert the user so they know
1070
// not to trust the zero in the table.
1072
sprintf(buf, "Can't read /proc/%d/numa_maps", pid);
1076
// If showing individual tables, or we just added the last total line,
1077
// prepare the table for display and display it...
1078
if ((show_sub_categories) || (pid_ix + 1 == num_pids)) {
1079
// Crompress display column widths, if requested
1080
if (compress_display) {
1081
for (int col = 0; (col < header_cols + num_nodes + 1); col++) {
1082
auto_set_col_width(&table, col, 4, 16);
1085
// Since not compressing the display, allow the left header
1086
// column to be wider. Otherwise, sometimes process command
1087
// name instance numbers can be truncated in an annoying way.
1088
auto_set_col_width(&table, 0, 16, 24);
1090
// Put dashes above Total line...
1091
set_row_flag(&table, total_row_ix - 1, COL_FLAG_ALWAYS_SHOW);
1092
for (int col = 0; (col < header_cols + num_nodes + 1); col++) {
1093
repchar_assign(&table, total_row_ix - 1, col, '-');
1095
// Optionally sort the table data
1098
if ((sort_table_node < 0) || (sort_table_node >= num_nodes)) {
1099
sort_col = total_col_ix;
1101
sort_col = header_cols + node_ix_map[sort_table_node];
1103
sort_rows_descending_by_col(&table, header_rows, header_rows + data_rows - 1, sort_col);
1105
// Actually show the table
1106
display_table(&table, screen_width, 0, 0, show_zero_data, show_zero_data);
1108
} // END OF FOR_EACH-PID loop
1110
} // show_process_info()
1113
int node_and_digits(const struct dirent *dptr) {
1114
char *p = (char *)(dptr->d_name);
1115
if (*p++ != 'n') return 0;
1116
if (*p++ != 'o') return 0;
1117
if (*p++ != 'd') return 0;
1118
if (*p++ != 'e') return 0;
1120
if (!isdigit(*p++)) return 0;
1121
} while (*p != '\0');
1126
void init_node_ix_map_and_header(int compatibility_mode) {
1127
// Count directory names of the form: /sys/devices/system/node/node<N>
1128
struct dirent **namelist;
1129
num_nodes = scandir("/sys/devices/system/node", &namelist, node_and_digits, NULL);
1130
if (num_nodes < 1) {
1131
if (compatibility_mode) {
1132
perror("sysfs not mounted or system not NUMA aware");
1134
perror("Couldn't open /sys/devices/system/node");
1138
node_ix_map = malloc(num_nodes * sizeof(int));
1139
if (node_ix_map == NULL) {
1140
perror("malloc failed line: " STRINGIFY(__LINE__));
1143
// For each "node<N>" filename present, save <N> in node_ix_map
1144
for (int ix = 0; (ix < num_nodes); ix++) {
1145
node_ix_map[ix] = atoi(&namelist[ix]->d_name[4]);
1149
// Now, sort the node map in increasing order. Use a simplistic sort
1150
// since we expect a relatively short (and maybe pre-ordered) list.
1151
for (int ix = 0; (ix < num_nodes); ix++) {
1152
int smallest_ix = ix;
1153
for (int iy = ix + 1; (iy < num_nodes); iy++) {
1154
if (node_ix_map[smallest_ix] > node_ix_map[iy]) {
1158
if (smallest_ix != ix) {
1159
int tmp = node_ix_map[ix];
1160
node_ix_map[ix] = node_ix_map[smallest_ix];
1161
node_ix_map[smallest_ix] = tmp;
1164
// Construct vector of "Node <N>" and "Total" column headers. Allocate
1165
// one for each NUMA node, plus one on the end for the "Total" column
1166
node_header = malloc((num_nodes + 1) * sizeof(char *));
1167
if (node_header == NULL) {
1168
perror("malloc failed line: " STRINGIFY(__LINE__));
1171
for (int node_ix = 0; (node_ix <= num_nodes); node_ix++) {
1172
char node_label[64];
1173
if (node_ix == num_nodes) {
1174
strcpy(node_label, "Total");
1175
} else if (compatibility_mode) {
1176
snprintf(node_label, sizeof(node_label), "node%d", node_ix_map[node_ix]);
1178
snprintf(node_label, sizeof(node_label), "Node %d", node_ix_map[node_ix]);
1180
char *s = strdup(node_label);
1182
perror("malloc failed line: " STRINGIFY(__LINE__));
1185
node_header[node_ix] = s;
1191
void free_node_ix_map_and_header() {
1192
if (node_ix_map != NULL) {
1196
if (node_header != NULL) {
1197
for (int ix = 0; (ix <= num_nodes); ix++) {
1198
free(node_header[ix]);
1206
double get_huge_page_size_in_bytes() {
1207
double huge_page_size = 0;;
1208
FILE *fs = fopen("/proc/meminfo", "r");
1210
perror("Can't open /proc/meminfo");
1213
char buf[SMALL_BUF_SIZE];
1214
while (fgets(buf, SMALL_BUF_SIZE, fs)) {
1215
if (!strncmp("Hugepagesize", buf, 12)) {
1217
while ((!isdigit(*p)) && (p < buf + SMALL_BUF_SIZE)) {
1220
huge_page_size = strtod(p, NULL);
1225
return huge_page_size * KILOBYTE;
1229
int all_digits(char *p) {
1233
while (*p != '\0') {
1234
if (!isdigit(*p++)) return 0;
1240
int starts_with_digit(const struct dirent *dptr) {
1241
return (isdigit(dptr->d_name[0]));
1245
void add_pid_to_list(int pid) {
1246
if (num_pids < pid_array_max_pids) {
1247
pid_array[num_pids++] = pid;
1249
if (pid_array_max_pids == 0) {
1250
pid_array_max_pids = 32;
1252
int *tmp_int_ptr = realloc(pid_array, 2 * pid_array_max_pids * sizeof(int));
1253
if (tmp_int_ptr == NULL) {
1254
char buf[SMALL_BUF_SIZE];
1255
sprintf(buf, "Too many PIDs, skipping %d", pid);
1258
pid_array = tmp_int_ptr;
1259
pid_array_max_pids *= 2;
1260
pid_array[num_pids++] = pid;
1266
int ascending(const void *p1, const void *p2) {
1267
return *(int *)p1 - *(int *) p2;
1270
void sort_pids_and_remove_duplicates() {
1272
qsort(pid_array, num_pids, sizeof(int), ascending);
1274
for (int ix2 = 1; (ix2 < num_pids); ix2++) {
1275
if (pid_array[ix2] == pid_array[ix1]) {
1280
pid_array[ix1] = pid_array[ix2];
1288
void add_pids_from_pattern_search(char *pattern) {
1289
// Search all /proc/<PID>/cmdline files and /proc/<PID>/status:Name fields
1290
// for matching patterns. Show the memory details for matching PIDs.
1291
int num_matches_found = 0;
1292
struct dirent **namelist;
1293
int files = scandir("/proc", &namelist, starts_with_digit, NULL);
1295
perror("Couldn't open /proc");
1297
for (int ix = 0; (ix < files); ix++) {
1299
// First get Name field from status file
1300
int pid = atoi(namelist[ix]->d_name);
1301
char *p = command_name_for_pid(pid);
1307
// Next copy cmdline file contents onto end of buffer. Do it a
1308
// character at a time to convert nulls to spaces.
1310
snprintf(fname, sizeof(fname), "/proc/%s/cmdline", namelist[ix]->d_name);
1311
FILE *fs = fopen(fname, "r");
1314
while (*p != '\0') {
1319
while (((c = fgetc(fs)) != EOF) && (p < buf + BUF_SIZE - 1)) {
1328
if (strstr(buf, pattern)) {
1329
if (pid != getpid()) {
1330
add_pid_to_list(pid);
1331
num_matches_found += 1;
1337
if (num_matches_found == 0) {
1338
printf("Found no processes containing pattern: \"%s\"\n", pattern);
1343
int main(int argc, char **argv) {
1344
prog_name = argv[0];
1345
int show_the_system_info = 0;
1346
int show_the_numastat_info = 0;
1347
static struct option long_options[] = {
1348
{"help", 0, 0, '?'},
1351
int long_option_index = 0;
1353
while ((opt = getopt_long(argc, argv, "cmnp:s::vVz?", long_options, &long_option_index)) != -1) {
1356
printf("Unexpected long option %s", long_options[long_option_index].name);
1358
printf(" with arg %s", optarg);
1361
display_usage_and_exit();
1364
compress_display = 1;
1367
show_the_system_info = 1;
1370
show_the_numastat_info = 1;
1373
if ((optarg) && (all_digits(optarg))) {
1374
add_pid_to_list(atoi(optarg));
1376
add_pids_from_pattern_search(optarg);
1381
if ((optarg) && (all_digits(optarg))) {
1382
sort_table_node = atoi(optarg);
1389
display_version_and_exit();
1396
display_usage_and_exit();
1400
// Figure out the display width, which is used to format the tables
1401
// and limit the output columns per row
1402
screen_width = get_screen_width();
1403
// Any remaining arguments are assumed to be additional process specifiers
1404
while (optind < argc) {
1405
if (all_digits(argv[optind])) {
1406
add_pid_to_list(atoi(argv[optind]));
1408
add_pids_from_pattern_search(argv[optind]);
1412
// If there are no program options or arguments, be extremely compatible
1413
// with the old numastat perl script (which is included at the end of this
1414
// file for reference)
1415
compatibility_mode = (argc == 1);
1416
init_node_ix_map_and_header(compatibility_mode); // enumarate the NUMA nodes
1417
if (compatibility_mode) {
1418
show_numastat_info();
1419
free_node_ix_map_and_header();
1422
// Figure out page sizes
1423
page_size_in_bytes = (double)sysconf(_SC_PAGESIZE);
1424
huge_page_size_in_bytes = get_huge_page_size_in_bytes();
1425
// Display the info for the process specifiers
1427
sort_pids_and_remove_duplicates();
1428
show_process_info();
1430
if (pid_array != NULL) {
1433
// Display the system-wide memory usage info
1434
if (show_the_system_info) {
1437
// Display the numastat statistics info
1438
if ((show_the_numastat_info) || ((num_pids == 0) && (!show_the_system_info))) {
1439
show_numastat_info();
1441
free_node_ix_map_and_header();
1455
# Print numa statistics for all nodes
1456
# Copyright (C) 2003,2004 Andi Kleen, SuSE Labs.
1458
# numastat is free software; you can redistribute it and/or
1459
# modify it under the terms of the GNU General Public
1460
# License as published by the Free Software Foundation; version
1463
# numastat is distributed in the hope that it will be useful,
1464
# but WITHOUT ANY WARRANTY; without even the implied warranty of
1465
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1466
# General Public License for more details.
1468
# You should find a copy of v2 of the GNU General Public License somewhere
1469
# on your Linux system; if not, write to the Free Software Foundation,
1470
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1472
# Example: NUMASTAT_WIDTH=80 watch -n1 numastat
1477
if (defined($ENV{'NUMASTAT_WIDTH'})) {
1478
$WIDTH=$ENV{'NUMASTAT_WIDTH'};
1481
if (POSIX::isatty(fileno(STDOUT))) {
1482
if (open(R, "resize |")) {
1484
$WIDTH=$1 if /COLUMNS=(\d+)/;
1489
# don't split it up for easier parsing
1493
$WIDTH = 32 if $WIDTH < 32;
1495
if (! -d "/sys/devices/system/node" ) {
1496
print STDERR "sysfs not mounted or system not NUMA aware\n";
1503
opendir(NODES, "/sys/devices/system/node") || exit 1;
1504
foreach $nd (readdir(NODES)) {
1505
next unless $nd =~ /node(\d+)/;
1506
# On newer kernels, readdir may enumerate the 'node(\d+) subdirs
1507
# in opposite order from older kernels--e.g., node{0,1,2,...}
1508
# as opposed to node{N,N-1,N-2,...}. Accomodate this by
1509
# switching to new mode so that the stats get emitted in
1511
#print "readdir(NODES) returns $nd\n";
1512
if (!$title && $nd =~ /node0/) {
1515
open(STAT, "/sys/devices/system/node/$nd/numastat") ||
1516
die "cannot open $nd: $!\n";
1518
$title = sprintf("%16s",$nd) . $title;
1520
$title = $title . sprintf("%16s",$nd);
1524
($name, $val) = split;
1526
$stat{$name} = sprintf("%16u", $val) . $stat{$name};
1528
$stat{$name} = $stat{$name} . sprintf("%16u", $val);
1530
push(@fields, $name);
1536
$numfields = int(($WIDTH - 16) / 16);
1537
$l = 16 * $numfields;
1538
for ($i = 0; $i < length($title); $i += $l) {
1539
print "\n" if $i > 0;
1540
printf "%16s%s\n","",substr($title,$i,$l);
1542
printf "%-16s%s\n",$_,substr($stat{$_},$i,$l);