~ubuntu-branches/ubuntu/utopic/numactl/utopic-proposed

« back to all changes in this revision

Viewing changes to numastat.c

  • Committer: Package Import Robot
  • Author(s): Ian Wienand
  • Date: 2012-11-15 12:20:29 UTC
  • mfrom: (1.3.9)
  • Revision ID: package-import@ubuntu.com-20121115122029-df53pmew2v1ydcsg
Tags: 2.0.8-1
New upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 
 
3
numastat - NUMA monitoring tool to show per-node usage of memory
 
4
Copyright (C) 2012 Bill Gray (bgray@redhat.com), Red Hat Inc
 
5
 
 
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.
 
9
 
 
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.
 
13
 
 
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
 
17
 
 
18
*/
 
19
 
 
20
 
 
21
/*
 
22
 
 
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
 
29
end of this file.
 
30
 
 
31
*/
 
32
 
 
33
 
 
34
// Compile with: gcc -O -std=gnu99 -Wall -o numastat numastat.c
 
35
 
 
36
 
 
37
#define __USE_MISC
 
38
#include <ctype.h>
 
39
#include <dirent.h>
 
40
#include <errno.h>
 
41
#include <getopt.h>
 
42
#include <stdint.h>
 
43
#include <stdio.h>
 
44
#include <stdlib.h>
 
45
#include <string.h>
 
46
#include <sys/types.h>
 
47
#include <unistd.h>
 
48
 
 
49
 
 
50
#define STRINGIZE(s) #s
 
51
#define STRINGIFY(s) STRINGIZE(s)
 
52
 
 
53
#define KILOBYTE (1024)
 
54
#define MEGABYTE (1024 * 1024)
 
55
 
 
56
#define BUF_SIZE 2048
 
57
#define SMALL_BUF_SIZE 128
 
58
 
 
59
 
 
60
// Don't assume nodes are sequential or contiguous.
 
61
// Need to discover and map node numbers.
 
62
 
 
63
int *node_ix_map = NULL;
 
64
char **node_header;
 
65
 
 
66
 
 
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.
 
70
 
 
71
typedef struct meminfo {
 
72
        int index;
 
73
        char *token;
 
74
        char *label;
 
75
} meminfo_t, *meminfo_p;
 
76
 
 
77
#define PROCESS_HUGE_INDEX    0
 
78
#define PROCESS_PRIVATE_INDEX 3
 
79
 
 
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" }
 
85
};
 
86
 
 
87
#define PROCESS_MEMINFO_ROWS (sizeof(process_meminfo) / sizeof(process_meminfo[0]))
 
88
 
 
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" },
 
96
};
 
97
 
 
98
#define NUMASTAT_MEMINFO_ROWS (sizeof(numastat_meminfo) / sizeof(numastat_meminfo[0]))
 
99
 
 
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" }
 
134
};
 
135
 
 
136
#define SYSTEM_MEMINFO_ROWS (sizeof(system_meminfo) / sizeof(system_meminfo[0]))
 
137
 
 
138
 
 
139
 
 
140
 
 
141
 
 
142
 
 
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.
 
148
 
 
149
#define HASH_TABLE_SIZE 151
 
150
int hash_collisions = 0;
 
151
 
 
152
struct hash_entry {
 
153
        char *name;
 
154
        int index;
 
155
} hash_table[HASH_TABLE_SIZE];
 
156
 
 
157
 
 
158
void init_hash_table() {
 
159
        memset(hash_table, 0, sizeof(hash_table));
 
160
}
 
161
 
 
162
 
 
163
int hash_ix(char *s) {
 
164
        unsigned int h = 17;
 
165
        while (*s) {
 
166
                // h * 33 + *s++
 
167
                h = ((h << 5) + h) + *s++;
 
168
        }
 
169
        return (h % HASH_TABLE_SIZE);
 
170
}
 
171
 
 
172
 
 
173
int hash_lookup(char *s) {
 
174
        int ix = hash_ix(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
 
178
                }
 
179
                ix += 1;
 
180
                if (ix >= HASH_TABLE_SIZE) {
 
181
                        ix = 0;
 
182
                }
 
183
        }
 
184
        return -1;
 
185
}
 
186
 
 
187
 
 
188
int hash_insert(char *s, int i) {
 
189
        int ix = hash_ix(s);
 
190
        while (hash_table[ix].name) {   // assumes no duplicate entries
 
191
                hash_collisions += 1;
 
192
                ix += 1;
 
193
                if (ix >= HASH_TABLE_SIZE) {
 
194
                        ix = 0;
 
195
                }
 
196
        }
 
197
        hash_table[ix].name = s;
 
198
        hash_table[ix].index = i;
 
199
        return ix;
 
200
}
 
201
 
 
202
 
 
203
 
 
204
 
 
205
 
 
206
 
 
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.
 
212
 
 
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
 
219
 
 
220
#define CELL_FLAG_FREEABLE (1 << 0)
 
221
#define CELL_FLAG_ROWSPAN  (1 << 1)
 
222
#define CELL_FLAG_COLSPAN  (1 << 2)
 
223
 
 
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)
 
231
 
 
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
 
235
 
 
236
typedef struct cell {
 
237
        uint32_t type;
 
238
        uint32_t flags;
 
239
        union {
 
240
                char *s;
 
241
                double d;
 
242
                int64_t l;
 
243
                char c[8];
 
244
        };
 
245
} cell_t, *cell_p;
 
246
 
 
247
typedef struct vtab {
 
248
        int header_rows;
 
249
        int header_cols;
 
250
        int data_rows;
 
251
        int data_cols;
 
252
        cell_p cell;
 
253
        int *row_ix_map;
 
254
        uint8_t *row_flags;
 
255
        uint8_t *col_flags;
 
256
        uint8_t *col_width;
 
257
        uint8_t *col_decimal_places;
 
258
} vtab_t, *vtab_p;
 
259
 
 
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])
 
263
 
 
264
#define USUAL_GUTTER_WIDTH 1
 
265
 
 
266
 
 
267
void set_row_flag(vtab_p table, int row, int flag) {
 
268
        table->row_flags[row] |= (uint8_t)flag;
 
269
}
 
270
 
 
271
void set_col_flag(vtab_p table, int col, int flag) {
 
272
        table->col_flags[col] |= (uint8_t)flag;
 
273
}
 
274
 
 
275
void clear_row_flag(vtab_p table, int row, int flag) {
 
276
        table->row_flags[row] &= (uint8_t)~flag;
 
277
}
 
278
 
 
279
void clear_col_flag(vtab_p table, int col, int flag) {
 
280
        table->col_flags[col] &= (uint8_t)~flag;
 
281
}
 
282
 
 
283
int test_row_flag(vtab_p table, int row, int flag) {
 
284
        return ((table->row_flags[row] & (uint8_t)flag) != 0);
 
285
}
 
286
 
 
287
int test_col_flag(vtab_p table, int col, int flag) {
 
288
        return ((table->col_flags[col] & (uint8_t)flag) != 0);
 
289
}
 
290
 
 
291
 
 
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);
 
295
}
 
296
 
 
297
 
 
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;
 
301
        }
 
302
        table->col_width[col] = width;
 
303
}
 
304
 
 
305
 
 
306
void set_col_decimal_places(vtab_p table, int col, uint8_t places) {
 
307
        table->col_decimal_places[col] = places;
 
308
}
 
309
 
 
310
 
 
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;
 
314
}
 
315
 
 
316
 
 
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;
 
320
}
 
321
 
 
322
 
 
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);
 
326
}
 
327
 
 
328
 
 
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;
 
332
        c_ptr->s = s;
 
333
}
 
334
 
 
335
 
 
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;
 
339
        c_ptr->c[0] = c;
 
340
}
 
341
 
 
342
 
 
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;
 
346
        c_ptr->d = d;
 
347
}
 
348
 
 
349
 
 
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;
 
353
        c_ptr->l = l;
 
354
}
 
355
 
 
356
 
 
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;
 
360
        c_ptr->d += d;
 
361
}
 
362
 
 
363
 
 
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;
 
367
        c_ptr->l += l;
 
368
}
 
369
 
 
370
 
 
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));
 
374
}
 
375
 
 
376
 
 
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));
 
383
                        c_ptr->type = type;
 
384
                }
 
385
        }
 
386
}
 
387
 
 
388
 
 
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++) {
 
393
                int biggest_ix = 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;
 
399
                                biggest_ix = iy;
 
400
                        }
 
401
                }
 
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;
 
406
                }
 
407
        }
 
408
}
 
409
 
 
410
 
 
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?
 
413
}
 
414
 
 
415
 
 
416
void init_table(vtab_p table, int header_rows, int header_cols, int data_rows, int data_cols) {
 
417
        // init table sizes
 
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__));
 
427
                exit(EXIT_FAILURE);
 
428
        }
 
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__));
 
435
                exit(EXIT_FAILURE);
 
436
        }
 
437
        for (int row = 0; (row < ALL_TABLE_ROWS); row++) {
 
438
                table->row_ix_map[row] = row;
 
439
        }
 
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__));
 
445
                exit(EXIT_FAILURE);
 
446
        }
 
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__));
 
453
                exit(EXIT_FAILURE);
 
454
        }
 
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__));
 
461
                exit(EXIT_FAILURE);
 
462
        }
 
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__));
 
469
                exit(EXIT_FAILURE);
 
470
        }
 
471
        memset(table->col_decimal_places, 0, alloc_size);
 
472
}
 
473
 
 
474
 
 
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)) {
 
480
                free(c_ptr->s);
 
481
        }
 
482
        memset(c_ptr, 0, sizeof(cell_t));
 
483
}
 
484
 
 
485
 
 
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);
 
491
                        }
 
492
                }
 
493
                free(table->cell);
 
494
        }
 
495
        if (table->row_ix_map != NULL) {
 
496
                free(table->row_ix_map);
 
497
        }
 
498
        if (table->row_flags != NULL) {
 
499
                free(table->row_flags);
 
500
        }
 
501
        if (table->col_flags != NULL) {
 
502
                free(table->col_flags);
 
503
        }
 
504
        if (table->col_width != NULL) {
 
505
                free(table->col_width);
 
506
        }
 
507
        if (table->col_decimal_places != NULL) {
 
508
                free(table->col_decimal_places);
 
509
        }
 
510
}
 
511
 
 
512
 
 
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) {
 
519
        case CELL_TYPE_NULL:
 
520
                buf[0] = '\0';
 
521
                break;
 
522
        case CELL_TYPE_LONG:
 
523
                snprintf(buf, SMALL_BUF_SIZE, "%ld", c_ptr->l);
 
524
                break;
 
525
        case CELL_TYPE_DOUBLE:
 
526
                snprintf(buf, SMALL_BUF_SIZE, "%.*f", decimal_places, c_ptr->d);
 
527
                break;
 
528
        case CELL_TYPE_STRING:
 
529
                snprintf(buf, SMALL_BUF_SIZE, "%s", c_ptr->s);
 
530
                break;
 
531
        case CELL_TYPE_CHAR8:
 
532
                strncpy(buf, c_ptr->c, 8);
 
533
                buf[8] = '\0';
 
534
                break;
 
535
        case CELL_TYPE_REPCHAR:
 
536
                memset(buf, c_ptr->c[0], rep_width);
 
537
                buf[rep_width] = '\0';
 
538
                break;
 
539
        default:
 
540
                strcpy(buf, "Unknown");
 
541
                break;
 
542
        }
 
543
        buf[max_width] = '\0';
 
544
        return buf;
 
545
}
 
546
 
 
547
 
 
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) {
 
553
                        continue;
 
554
                }
 
555
                char *p = fmt_cell_data(c_ptr, max_width, (int)(table->col_decimal_places[col]));
 
556
                int l = strlen(p);
 
557
                if (width < l) {
 
558
                        width = l;
 
559
                }
 
560
        }
 
561
        width += USUAL_GUTTER_WIDTH;
 
562
        if (width > max_width) {
 
563
                width = max_width;
 
564
        }
 
565
        table->col_width[col] = (uint8_t)width;
 
566
}
 
567
 
 
568
 
 
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);
 
571
        int l = strlen(p);
 
572
        char buf[SMALL_BUF_SIZE];
 
573
        switch (col_flags & COL_JUSTIFY_MASK) {
 
574
        case COL_JUSTIFY_LEFT:
 
575
                memcpy(buf, p, l);
 
576
                if (l < width) {
 
577
                        memset(&buf[l], ' ', width - l);
 
578
                }
 
579
                break;
 
580
        case COL_JUSTIFY_RIGHT:
 
581
                if (l < width) {
 
582
                        memset(buf, ' ', width - l);
 
583
                }
 
584
                memcpy(&buf[width - l], p, l);
 
585
                break;
 
586
        case COL_JUSTIFY_CENTER:
 
587
        default:
 
588
                memset(buf, ' ', width);
 
589
                memcpy(&buf[(width - l + 1) / 2], p, l);
 
590
                break;
 
591
        }
 
592
        buf[width] = '\0';
 
593
        printf("%s", buf);
 
594
}
 
595
 
 
596
 
 
597
void display_table(vtab_p table,
 
598
                      int screen_width,
 
599
                      int show_unseen_rows,
 
600
                      int show_unseen_cols,
 
601
                      int show_zero_rows,
 
602
                      int show_zero_cols)
 
603
{
 
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)) {
 
614
                                some_seen_data = 1;
 
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);
 
626
                                }
 
627
                        }
 
628
                }
 
629
        }
 
630
        if (!some_seen_data) {
 
631
                printf("Table has no data.\n");
 
632
                return;
 
633
        }
 
634
        if (!some_non_zero_data && !show_zero_rows && !show_zero_cols) {
 
635
                printf("Table has no non-zero data.\n");
 
636
                return;
 
637
        }
 
638
        // Start with first data column and try to display table,
 
639
        // folding lines as necessary per screen_width
 
640
        int col = -1;
 
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))))) {
 
647
                        data_col += 1;
 
648
                        continue;
 
649
                }
 
650
                // Display blank line between table sections
 
651
                if (col > 0) {
 
652
                        printf("\n");
 
653
                }
 
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)))) {
 
662
                                        continue;
 
663
                                }
 
664
                        }
 
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]);
 
675
                        }
 
676
                        // Reset column index to starting data column for each new row
 
677
                        col = data_col;
 
678
                        // Try to display as many data columns as possible in every section
 
679
                        for (;;) {
 
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]);
 
690
                                }
 
691
                                col += 1;
 
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)) {
 
695
                                        break;
 
696
                                }
 
697
                        }
 
698
                        printf("\n");
 
699
                }
 
700
                // Remember next starting data column for next section
 
701
                data_col = col;
 
702
        }
 
703
}
 
704
 
 
705
 
 
706
 
 
707
 
 
708
 
 
709
 
 
710
int verbose = 0;
 
711
int num_pids = 0;
 
712
int num_nodes = 0;
 
713
int screen_width = 0;
 
714
int show_zero_data = 1;
 
715
int compress_display = 0;
 
716
int sort_table = 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;
 
724
 
 
725
 
 
726
void display_version_and_exit() {
 
727
        char *version_string = "20120821";
 
728
        printf("%s version: %s: %s\n", prog_name, version_string, __DATE__);
 
729
        exit(EXIT_SUCCESS);
 
730
}
 
731
 
 
732
 
 
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");
 
743
        exit(EXIT_FAILURE);
 
744
}
 
745
 
 
746
 
 
747
int get_screen_width() {
 
748
        int width = 80;
 
749
        char *p = getenv("NUMASTAT_WIDTH");
 
750
        if (p != NULL) {
 
751
                width = atoi(p);
 
752
                if ((width < 1) || (width > 10000000)) {
 
753
                        width = 80;
 
754
                }
 
755
        } else if (isatty(fileno(stdout))) {
 
756
                FILE *fs = popen("resize 2>/dev/null", "r");
 
757
                if (fs != NULL) {
 
758
                        char columns[72];
 
759
                        fgets(columns, sizeof(columns), fs);
 
760
                        pclose(fs);
 
761
                        if (strncmp(columns, "COLUMNS=", 8) == 0) {
 
762
                                width = atoi(&columns[8]);
 
763
                                if ((width < 1) || (width > 10000000)) {
 
764
                                        width = 80;
 
765
                                }
 
766
                        }
 
767
                }
 
768
        } else {
 
769
                // Not a tty, so allow a really long line
 
770
                width = 10000000;
 
771
        }
 
772
        if (width < 32) {
 
773
                width = 32;
 
774
        }
 
775
        return width;
 
776
}
 
777
 
 
778
 
 
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];
 
783
        char fname[64];
 
784
        snprintf(fname, sizeof(fname), "/proc/%d/status", pid);
 
785
        FILE *fs = fopen(fname, "r");
 
786
        if (!fs) {
 
787
                return NULL;
 
788
        } else {
 
789
                while (fgets(buf, SMALL_BUF_SIZE, fs)) {
 
790
                        if (strstr(buf, "Name:") == buf) {
 
791
                                char *p = &buf[5];
 
792
                                while (isspace(*p)) {
 
793
                                        p++;
 
794
                                }
 
795
                                if (p[strlen(p) - 1] == '\n') {
 
796
                                        p[strlen(p) - 1] = '\0';
 
797
                                }
 
798
                                fclose(fs);
 
799
                                return p;
 
800
                        }
 
801
                }
 
802
                fclose(fs);
 
803
        }
 
804
        return NULL;
 
805
}
 
806
 
 
807
 
 
808
void show_info_from_system_file(char *file, meminfo_p meminfo, int meminfo_rows, int tok_offset) {
 
809
        // Setup and init table
 
810
        vtab_t table;
 
811
        int header_rows = 2 - compatibility_mode;
 
812
        int header_cols = 1;
 
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
 
817
        init_hash_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);
 
822
                } else {
 
823
                        string_assign(&table, (header_rows + row), 0, meminfo[row].label);
 
824
                }
 
825
        }
 
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) {
 
841
                                decimal_places = 0;
 
842
                        }
 
843
                        set_col_decimal_places(&table, col, decimal_places);
 
844
                }
 
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) {
 
849
                        break;
 
850
                }
 
851
                // Open /sys/.../node<N>/numstast file for this node...
 
852
                char buf[SMALL_BUF_SIZE];
 
853
                char fname[64];
 
854
                snprintf(fname, sizeof(fname), "/sys/devices/system/node/node%d/%s", node_ix_map[node_ix], file);
 
855
                FILE *fs = fopen(fname, "r");
 
856
                if (!fs) {
 
857
                        sprintf(buf, "cannot open %s", fname);
 
858
                        perror(buf);
 
859
                        exit(EXIT_FAILURE);
 
860
                }
 
861
                // Get table values for this node...
 
862
                while (fgets(buf, SMALL_BUF_SIZE, fs)) {
 
863
                        char *tok[64];
 
864
                        int tokens = 0;
 
865
                        const char *delimiters = " \t\r\n:";
 
866
                        char *p = strtok(buf, delimiters);
 
867
                        if (p == NULL) {
 
868
                                continue;       // Skip blank lines;
 
869
                        }
 
870
                        while (p) {
 
871
                                tok[tokens++] = p;
 
872
                                p = strtok(NULL, delimiters);
 
873
                        }
 
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]);
 
877
                        if (index < 0) {
 
878
                                printf("Token %s not in hash table.\n", tok[0]);
 
879
                        } else {
 
880
                                double value = (double)atol(tok[1 + tok_offset]);
 
881
                                if (!compatibility_mode) {
 
882
                                        double multiplier = 1.0;
 
883
                                        if (tokens < 5) {
 
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;
 
889
                                        }
 
890
                                        value *= multiplier;
 
891
                                        value /= (double)MEGABYTE;
 
892
                                }
 
893
                                double_assign(&table, header_rows + index, col, value);
 
894
                                double_addto(&table, header_rows + index, total_col_ix, value);
 
895
                        }
 
896
                }
 
897
                fclose(fs);
 
898
        }
 
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);
 
903
                }
 
904
        }
 
905
        // Optionally sort the table data
 
906
        if (sort_table) {
 
907
                int sort_col;
 
908
                if ((sort_table_node < 0) || (sort_table_node >= num_nodes)) {
 
909
                        sort_col = total_col_ix;
 
910
                } else {
 
911
                        sort_col = header_cols + node_ix_map[sort_table_node];
 
912
                }
 
913
                sort_rows_descending_by_col(&table, header_rows, header_rows + meminfo_rows - 1, sort_col);
 
914
        }
 
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);
 
917
        free_table(&table);
 
918
}
 
919
 
 
920
 
 
921
void show_numastat_info() {
 
922
        if (!compatibility_mode) {
 
923
                printf("\nPer-node numastat info (in MBs):\n");
 
924
        }
 
925
        show_info_from_system_file("numastat", numastat_meminfo, NUMASTAT_MEMINFO_ROWS, 0);
 
926
}
 
927
 
 
928
 
 
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);
 
932
}
 
933
 
 
934
 
 
935
void show_process_info() {
 
936
        vtab_t table;
 
937
        int header_rows = 2;
 
938
        int header_cols = 1;
 
939
        int data_rows;
 
940
        int show_sub_categories = (verbose || (num_pids == 1));
 
941
        if (show_sub_categories) {
 
942
                data_rows = PROCESS_MEMINFO_ROWS;
 
943
        } else {
 
944
                data_rows = num_pids;
 
945
        }
 
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);
 
956
                }
 
957
        } else {
 
958
                string_assign(&table, 0, 0, "PID");
 
959
                repchar_assign(&table, 1, 0, '-');
 
960
                printf("\nPer-node process memory usage (in MBs)\n");
 
961
        }
 
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) {
 
975
                        decimal_places = 0;
 
976
                }
 
977
                set_col_decimal_places(&table, col, decimal_places);
 
978
                set_col_justification(&table, col, COL_JUSTIFY_RIGHT);
 
979
        }
 
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));
 
988
                        if (pid_ix > 0) {
 
989
                                // Re-initialize show_sub_categories table, because we re-use it for each PID.
 
990
                                zero_table_data(&table, CELL_TYPE_DOUBLE);
 
991
                        }
 
992
                } else {
 
993
                        // Put this row's "PID (cmd)" label in left header column for this PID total row
 
994
                        char tmp_buf[64];
 
995
                        snprintf(tmp_buf, sizeof(tmp_buf), "%d (%s)", pid, command_name_for_pid(pid));
 
996
                        char *p = strdup(tmp_buf);
 
997
                        if (p == NULL) {
 
998
                                perror("malloc failed line: " STRINGIFY(__LINE__));
 
999
                                exit(EXIT_FAILURE);
 
1000
                        }
 
1001
                        string_assign(&table, header_rows + pid_ix, 0, p);
 
1002
                        set_cell_flag(&table, header_rows + pid_ix, 0, CELL_FLAG_FREEABLE);
 
1003
                }
 
1004
                // Open numa_map for this PID to get per-node data
 
1005
                char fname[64];
 
1006
                snprintf(fname, sizeof(fname), "/proc/%d/numa_maps", pid);
 
1007
                char buf[BUF_SIZE];
 
1008
                FILE *fs = fopen(fname, "r");
 
1009
                if (!fs) {
 
1010
                        sprintf(buf, "Can't read /proc/%d/numa_maps", pid);
 
1011
                        perror(buf);
 
1012
                        continue;
 
1013
                }
 
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
 
1016
                // amount.
 
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);
 
1021
                        while (p) {
 
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))) {
 
1028
                                                        category = ix;
 
1029
                                                        break;
 
1030
                                                }
 
1031
                                        }
 
1032
                                }
 
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).
 
1036
                                if (p[0] == 'N') {
 
1037
                                        int node_num = (int)strtol(&p[1], &p, 10);
 
1038
                                        if (p[0] != '=') {
 
1039
                                                perror("node value parse error");
 
1040
                                                exit(EXIT_FAILURE);
 
1041
                                        }
 
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;
 
1046
                                        }
 
1047
                                        value *= multiplier;
 
1048
                                        value /= (double)MEGABYTE;
 
1049
                                        // Add value to data cell, total_col, and total_row
 
1050
                                        int tmp_row;
 
1051
                                        if (show_sub_categories) {
 
1052
                                                tmp_row = header_rows + category;
 
1053
                                        } else {
 
1054
                                                tmp_row = header_rows + pid_ix;
 
1055
                                        }
 
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);
 
1061
                                }
 
1062
                                // Get next token on the line
 
1063
                                p = strtok(NULL, delimiters);
 
1064
                        }
 
1065
                }
 
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.
 
1071
                if (ferror(fs)) {
 
1072
                        sprintf(buf, "Can't read /proc/%d/numa_maps", pid);
 
1073
                        perror(buf);
 
1074
                }
 
1075
                fclose(fs);
 
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);
 
1083
                                }
 
1084
                        } else {
 
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);
 
1089
                        }
 
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, '-');
 
1094
                        }
 
1095
                        // Optionally sort the table data
 
1096
                        if (sort_table) {
 
1097
                                int sort_col;
 
1098
                                if ((sort_table_node < 0) || (sort_table_node >= num_nodes)) {
 
1099
                                        sort_col = total_col_ix;
 
1100
                                } else {
 
1101
                                        sort_col = header_cols + node_ix_map[sort_table_node];
 
1102
                                }
 
1103
                                sort_rows_descending_by_col(&table, header_rows, header_rows + data_rows - 1, sort_col);
 
1104
                        }
 
1105
                        // Actually show the table
 
1106
                        display_table(&table, screen_width, 0, 0, show_zero_data, show_zero_data);
 
1107
                }
 
1108
        }                       // END OF FOR_EACH-PID loop
 
1109
        free_table(&table);
 
1110
}                               // show_process_info()
 
1111
 
 
1112
 
 
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;
 
1119
        do {
 
1120
                if (!isdigit(*p++)) return 0;
 
1121
        } while (*p != '\0');
 
1122
        return 1;
 
1123
}
 
1124
 
 
1125
 
 
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");
 
1133
                } else {
 
1134
                        perror("Couldn't open /sys/devices/system/node");
 
1135
                }
 
1136
                exit(EXIT_FAILURE);
 
1137
        } else {
 
1138
                node_ix_map = malloc(num_nodes * sizeof(int));
 
1139
                if (node_ix_map == NULL) {
 
1140
                        perror("malloc failed line: " STRINGIFY(__LINE__));
 
1141
                        exit(EXIT_FAILURE);
 
1142
                }
 
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]);
 
1146
                        free(namelist[ix]);
 
1147
                }
 
1148
                free(namelist);
 
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]) {
 
1155
                                        smallest_ix = iy;
 
1156
                                }
 
1157
                        }
 
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;
 
1162
                        }
 
1163
                }
 
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__));
 
1169
                        exit(EXIT_FAILURE);
 
1170
                }
 
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]);
 
1177
                        } else {
 
1178
                                snprintf(node_label, sizeof(node_label), "Node %d", node_ix_map[node_ix]);
 
1179
                        }
 
1180
                        char *s = strdup(node_label);
 
1181
                        if (s == NULL) {
 
1182
                                perror("malloc failed line: " STRINGIFY(__LINE__));
 
1183
                                exit(EXIT_FAILURE);
 
1184
                        }
 
1185
                        node_header[node_ix] = s;
 
1186
                }
 
1187
        }
 
1188
}
 
1189
 
 
1190
 
 
1191
void free_node_ix_map_and_header() {
 
1192
        if (node_ix_map != NULL) {
 
1193
                free(node_ix_map);
 
1194
                node_ix_map = NULL;
 
1195
        }
 
1196
        if (node_header != NULL) {
 
1197
                for (int ix = 0; (ix <= num_nodes); ix++) {
 
1198
                        free(node_header[ix]);
 
1199
                }
 
1200
                free(node_header);
 
1201
                node_header = NULL;
 
1202
        }
 
1203
}
 
1204
 
 
1205
 
 
1206
double get_huge_page_size_in_bytes() {
 
1207
        double huge_page_size = 0;;
 
1208
        FILE *fs = fopen("/proc/meminfo", "r");
 
1209
        if (!fs) {
 
1210
                perror("Can't open /proc/meminfo");
 
1211
                exit(EXIT_FAILURE);
 
1212
        }
 
1213
        char buf[SMALL_BUF_SIZE];
 
1214
        while (fgets(buf, SMALL_BUF_SIZE, fs)) {
 
1215
                if (!strncmp("Hugepagesize", buf, 12)) {
 
1216
                        char *p = &buf[12];
 
1217
                        while ((!isdigit(*p)) && (p < buf + SMALL_BUF_SIZE)) {
 
1218
                                p++;
 
1219
                        }
 
1220
                        huge_page_size = strtod(p, NULL);
 
1221
                        break;
 
1222
                }
 
1223
        }
 
1224
        fclose(fs);
 
1225
        return huge_page_size * KILOBYTE;
 
1226
}
 
1227
 
 
1228
 
 
1229
int all_digits(char *p) {
 
1230
        if (p == NULL) {
 
1231
                return 0;
 
1232
        }
 
1233
        while (*p != '\0') {
 
1234
                if (!isdigit(*p++)) return 0;
 
1235
        }
 
1236
        return 1;
 
1237
}
 
1238
 
 
1239
 
 
1240
int starts_with_digit(const struct dirent *dptr) {
 
1241
        return (isdigit(dptr->d_name[0]));
 
1242
}
 
1243
 
 
1244
 
 
1245
void add_pid_to_list(int pid) {
 
1246
        if (num_pids < pid_array_max_pids) {
 
1247
                pid_array[num_pids++] = pid;
 
1248
        } else {
 
1249
                if (pid_array_max_pids == 0) {
 
1250
                        pid_array_max_pids = 32;
 
1251
                }
 
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);
 
1256
                        perror(buf);
 
1257
                } else {
 
1258
                        pid_array = tmp_int_ptr;
 
1259
                        pid_array_max_pids *= 2;
 
1260
                        pid_array[num_pids++] = pid;
 
1261
                }
 
1262
        }
 
1263
}
 
1264
 
 
1265
 
 
1266
int ascending(const void *p1, const void *p2) {
 
1267
        return *(int *)p1 - *(int *) p2;
 
1268
}
 
1269
 
 
1270
void sort_pids_and_remove_duplicates() {
 
1271
        if (num_pids > 1) {
 
1272
                qsort(pid_array, num_pids, sizeof(int), ascending);
 
1273
                int ix1 = 0;
 
1274
                for (int ix2 = 1; (ix2 < num_pids); ix2++) {
 
1275
                        if (pid_array[ix2] == pid_array[ix1]) {
 
1276
                                continue;
 
1277
                        }
 
1278
                        ix1 += 1;
 
1279
                        if (ix2 > ix1) {
 
1280
                                pid_array[ix1] = pid_array[ix2];
 
1281
                        }
 
1282
                }
 
1283
                num_pids = ix1 + 1;
 
1284
        }
 
1285
}
 
1286
 
 
1287
 
 
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);
 
1294
        if (files < 0) {
 
1295
                perror("Couldn't open /proc");
 
1296
        }
 
1297
        for (int ix = 0; (ix < files); ix++) {
 
1298
                char buf[BUF_SIZE];
 
1299
                // First get Name field from status file
 
1300
                int pid = atoi(namelist[ix]->d_name);
 
1301
                char *p = command_name_for_pid(pid);
 
1302
                if (p) {
 
1303
                        strcpy(buf, p);
 
1304
                } else {
 
1305
                        buf[0] = '\0';
 
1306
                }
 
1307
                // Next copy cmdline file contents onto end of buffer.  Do it a
 
1308
                // character at a time to convert nulls to spaces.
 
1309
                char fname[64];
 
1310
                snprintf(fname, sizeof(fname), "/proc/%s/cmdline", namelist[ix]->d_name);
 
1311
                FILE *fs = fopen(fname, "r");
 
1312
                if (fs) {
 
1313
                        p = buf;
 
1314
                        while (*p != '\0') {
 
1315
                                p++;
 
1316
                        }
 
1317
                        *p++ = ' ';
 
1318
                        int c;
 
1319
                        while (((c = fgetc(fs)) != EOF) && (p < buf + BUF_SIZE - 1)) {
 
1320
                                if (c == '\0') {
 
1321
                                        c = ' ';
 
1322
                                }
 
1323
                                *p++ = c;
 
1324
                        }
 
1325
                        *p++ = '\0';
 
1326
                        fclose(fs);
 
1327
                }
 
1328
                if (strstr(buf, pattern)) {
 
1329
                        if (pid != getpid()) {
 
1330
                                add_pid_to_list(pid);
 
1331
                                num_matches_found += 1;
 
1332
                        }
 
1333
                }
 
1334
                free(namelist[ix]);
 
1335
        }
 
1336
        free(namelist);
 
1337
        if (num_matches_found == 0) {
 
1338
                printf("Found no processes containing pattern: \"%s\"\n", pattern);
 
1339
        }
 
1340
}
 
1341
 
 
1342
 
 
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, '?'},
 
1349
                {0, 0, 0, 0}
 
1350
        };
 
1351
        int long_option_index = 0;
 
1352
        int opt;
 
1353
        while ((opt = getopt_long(argc, argv, "cmnp:s::vVz?", long_options, &long_option_index)) != -1) {
 
1354
                switch (opt) {
 
1355
                case 0:
 
1356
                        printf("Unexpected long option %s", long_options[long_option_index].name);
 
1357
                        if (optarg) {
 
1358
                                printf(" with arg %s", optarg);
 
1359
                        }
 
1360
                        printf("\n");
 
1361
                        display_usage_and_exit();
 
1362
                        break;
 
1363
                case 'c':
 
1364
                        compress_display = 1;
 
1365
                        break;
 
1366
                case 'm':
 
1367
                        show_the_system_info = 1;
 
1368
                        break;
 
1369
                case 'n':
 
1370
                        show_the_numastat_info = 1;
 
1371
                        break;
 
1372
                case 'p':
 
1373
                        if ((optarg) && (all_digits(optarg))) {
 
1374
                                add_pid_to_list(atoi(optarg));
 
1375
                        } else {
 
1376
                                add_pids_from_pattern_search(optarg);
 
1377
                        }
 
1378
                        break;
 
1379
                case 's':
 
1380
                        sort_table = 1;
 
1381
                        if ((optarg) && (all_digits(optarg))) {
 
1382
                                sort_table_node = atoi(optarg);
 
1383
                        }
 
1384
                        break;
 
1385
                case 'v':
 
1386
                        verbose = 1;
 
1387
                        break;
 
1388
                case 'V':
 
1389
                        display_version_and_exit();
 
1390
                        break;
 
1391
                case 'z':
 
1392
                        show_zero_data = 0;
 
1393
                        break;
 
1394
                default:
 
1395
                case '?':
 
1396
                        display_usage_and_exit();
 
1397
                        break;
 
1398
                }
 
1399
        }
 
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]));
 
1407
                } else {
 
1408
                        add_pids_from_pattern_search(argv[optind]);
 
1409
                }
 
1410
                optind += 1;
 
1411
        }
 
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();
 
1420
                exit(EXIT_SUCCESS);
 
1421
        }
 
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
 
1426
        if (num_pids > 0) {
 
1427
                sort_pids_and_remove_duplicates();
 
1428
                show_process_info();
 
1429
        }
 
1430
        if (pid_array != NULL) {
 
1431
                free(pid_array);
 
1432
        }
 
1433
        // Display the system-wide memory usage info
 
1434
        if (show_the_system_info) {
 
1435
                show_system_info();
 
1436
        }
 
1437
        // Display the numastat statistics info
 
1438
        if ((show_the_numastat_info) || ((num_pids == 0) && (!show_the_system_info))) {
 
1439
                show_numastat_info();
 
1440
        }
 
1441
        free_node_ix_map_and_header();
 
1442
        exit(EXIT_SUCCESS);
 
1443
}
 
1444
 
 
1445
 
 
1446
 
 
1447
 
 
1448
 
 
1449
 
 
1450
#if 0
 
1451
/*
 
1452
 
 
1453
 
 
1454
#!/usr/bin/perl
 
1455
# Print numa statistics for all nodes
 
1456
# Copyright (C) 2003,2004 Andi Kleen, SuSE Labs.
 
1457
#
 
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
 
1461
# 2.
 
1462
#
 
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.
 
1467
 
 
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
 
1471
#
 
1472
# Example: NUMASTAT_WIDTH=80 watch -n1 numastat
 
1473
#
 
1474
 
 
1475
# output width
 
1476
$WIDTH=80;
 
1477
if (defined($ENV{'NUMASTAT_WIDTH'})) {
 
1478
        $WIDTH=$ENV{'NUMASTAT_WIDTH'};
 
1479
} else {
 
1480
        use POSIX;
 
1481
        if (POSIX::isatty(fileno(STDOUT))) {
 
1482
                if (open(R, "resize |")) {
 
1483
                        while (<R>) {
 
1484
                                $WIDTH=$1 if /COLUMNS=(\d+)/;
 
1485
                        }
 
1486
                        close R;
 
1487
                }
 
1488
        } else {
 
1489
                # don't split it up for easier parsing
 
1490
                $WIDTH=10000000;
 
1491
        }
 
1492
}
 
1493
$WIDTH = 32 if $WIDTH < 32;
 
1494
 
 
1495
if (! -d "/sys/devices/system/node" ) {
 
1496
        print STDERR "sysfs not mounted or system not NUMA aware\n";
 
1497
        exit 1;
 
1498
}
 
1499
 
 
1500
%stat = ();
 
1501
$title = "";
 
1502
$mode = 0;
 
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
 
1510
        # the same order.
 
1511
        #print "readdir(NODES) returns $nd\n";
 
1512
        if (!$title && $nd =~ /node0/) {
 
1513
                $mode = 1;
 
1514
        }
 
1515
        open(STAT, "/sys/devices/system/node/$nd/numastat") ||
 
1516
                        die "cannot open $nd: $!\n";
 
1517
        if (! $mode) {
 
1518
                $title = sprintf("%16s",$nd) . $title;
 
1519
        } else {
 
1520
                $title = $title . sprintf("%16s",$nd);
 
1521
        }
 
1522
        @fields = ();
 
1523
        while (<STAT>) {
 
1524
                ($name, $val) = split;
 
1525
                if (! $mode) {
 
1526
                        $stat{$name} = sprintf("%16u", $val) . $stat{$name};
 
1527
                } else {
 
1528
                        $stat{$name} = $stat{$name} . sprintf("%16u", $val);
 
1529
                }
 
1530
                push(@fields, $name);
 
1531
        }
 
1532
        close STAT;
 
1533
}
 
1534
closedir NODES;
 
1535
 
 
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);
 
1541
        foreach (@fields) {
 
1542
                printf "%-16s%s\n",$_,substr($stat{$_},$i,$l);
 
1543
        }
 
1544
}
 
1545
 
 
1546
 
 
1547
*/
 
1548
#endif