]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/lsmem.c
misc: consolidate macro style USAGE_HELP_OPTIONS
[thirdparty/util-linux.git] / sys-utils / lsmem.c
1 /*
2 * lsmem - Show memory configuration
3 *
4 * Copyright IBM Corp. 2016
5 * Copyright (C) 2016 Karel Zak <kzak@redhat.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it would be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21 #include <c.h>
22 #include <nls.h>
23 #include <path.h>
24 #include <strutils.h>
25 #include <closestream.h>
26 #include <xalloc.h>
27 #include <getopt.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <dirent.h>
31 #include <fcntl.h>
32 #include <inttypes.h>
33 #include <assert.h>
34 #include <optutils.h>
35 #include <libsmartcols.h>
36
37 #define _PATH_SYS_MEMORY "/sys/devices/system/memory"
38 #define _PATH_SYS_MEMORY_BLOCK_SIZE _PATH_SYS_MEMORY "/block_size_bytes"
39
40 #define MEMORY_STATE_ONLINE 0
41 #define MEMORY_STATE_OFFLINE 1
42 #define MEMORY_STATE_GOING_OFFLINE 2
43 #define MEMORY_STATE_UNKNOWN 3
44
45 struct memory_block {
46 uint64_t index;
47 uint64_t count;
48 int state;
49 int node;
50 unsigned int removable:1;
51 };
52
53 struct lsmem {
54 struct dirent **dirs;
55 int ndirs;
56 struct memory_block *blocks;
57 int nblocks;
58 uint64_t block_size;
59 uint64_t mem_online;
60 uint64_t mem_offline;
61
62 struct libscols_table *table;
63 unsigned int have_nodes : 1,
64 raw : 1,
65 export : 1,
66 json : 1,
67 noheadings : 1,
68 summary : 1,
69 list_all : 1,
70 bytes : 1,
71 want_node : 1,
72 want_state : 1,
73 want_removable : 1,
74 want_summary : 1,
75 want_table : 1;
76 };
77
78 enum {
79 COL_RANGE,
80 COL_SIZE,
81 COL_STATE,
82 COL_REMOVABLE,
83 COL_BLOCK,
84 COL_NODE,
85 };
86
87 /* column names */
88 struct coldesc {
89 const char *name; /* header */
90 double whint; /* width hint (N < 1 is in percent of termwidth) */
91 int flags; /* SCOLS_FL_* */
92 const char *help;
93
94 int sort_type; /* SORT_* */
95 };
96
97 /* columns descriptions */
98 static struct coldesc coldescs[] = {
99 [COL_RANGE] = { "RANGE", 0, 0, N_("start and end address of the memory range")},
100 [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the memory range")},
101 [COL_STATE] = { "STATE", 0, SCOLS_FL_RIGHT, N_("online status of the memory range")},
102 [COL_REMOVABLE] = { "REMOVABLE", 0, SCOLS_FL_RIGHT, N_("memory is removable")},
103 [COL_BLOCK] = { "BLOCK", 0, SCOLS_FL_RIGHT, N_("memory block number or blocks range")},
104 [COL_NODE] = { "NODE", 0, SCOLS_FL_RIGHT, N_("numa node of memory")},
105 };
106
107 /* columns[] array specifies all currently wanted output column. The columns
108 * are defined by coldescs[] array and you can specify (on command line) each
109 * column twice. That's enough, dynamically allocated array of the columns is
110 * unnecessary overkill and over-engineering in this case */
111 static int columns[ARRAY_SIZE(coldescs) * 2];
112 static size_t ncolumns;
113
114 static inline size_t err_columns_index(size_t arysz, size_t idx)
115 {
116 if (idx >= arysz)
117 errx(EXIT_FAILURE, _("too many columns specified, "
118 "the limit is %zu columns"),
119 arysz - 1);
120 return idx;
121 }
122
123 #define add_column(ary, n, id) \
124 ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
125
126 static int column_name_to_id(const char *name, size_t namesz)
127 {
128 size_t i;
129
130 for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
131 const char *cn = coldescs[i].name;
132
133 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
134 return i;
135 }
136 warnx(_("unknown column: %s"), name);
137 return -1;
138 }
139
140 static inline int get_column_id(int num)
141 {
142 assert(num >= 0);
143 assert((size_t) num < ncolumns);
144 assert(columns[num] < (int) ARRAY_SIZE(coldescs));
145
146 return columns[num];
147 }
148
149 static inline struct coldesc *get_column_desc(int num)
150 {
151 return &coldescs[ get_column_id(num) ];
152 }
153
154 static inline int has_column(int id)
155 {
156 size_t i;
157
158 for (i = 0; i < ncolumns; i++)
159 if (columns[i] == id)
160 return 1;
161 return 0;
162 }
163
164 static void add_scols_line(struct lsmem *lsmem, struct memory_block *blk)
165 {
166 size_t i;
167 struct libscols_line *line;
168
169 line = scols_table_new_line(lsmem->table, NULL);
170 if (!line)
171 err_oom();
172
173 for (i = 0; i < ncolumns; i++) {
174 char *str = NULL;
175
176 switch (get_column_id(i)) {
177 case COL_RANGE:
178 {
179 uint64_t start = blk->index * lsmem->block_size;
180 uint64_t size = blk->count * lsmem->block_size;
181 xasprintf(&str, "0x%016"PRIx64"-0x%016"PRIx64, start, start + size - 1);
182 break;
183 }
184 case COL_SIZE:
185 if (lsmem->bytes)
186 xasprintf(&str, "%"PRId64, (uint64_t) blk->count * lsmem->block_size);
187 else
188 str = size_to_human_string(SIZE_SUFFIX_1LETTER,
189 (uint64_t) blk->count * lsmem->block_size);
190 break;
191 case COL_STATE:
192 str = xstrdup(
193 blk->state == MEMORY_STATE_ONLINE ? _("online") :
194 blk->state == MEMORY_STATE_OFFLINE ? _("offline") :
195 blk->state == MEMORY_STATE_GOING_OFFLINE ? _("on->off") :
196 "?");
197 break;
198 case COL_REMOVABLE:
199 if (blk->state == MEMORY_STATE_ONLINE)
200 str = xstrdup(blk->removable ? _("yes") : _("no"));
201 else
202 str = xstrdup("-");
203 break;
204 case COL_BLOCK:
205 if (blk->count == 1)
206 xasprintf(&str, "%"PRId64, blk->index);
207 else
208 xasprintf(&str, "%"PRId64"-%"PRId64,
209 blk->index, blk->index + blk->count - 1);
210 break;
211 case COL_NODE:
212 if (lsmem->have_nodes)
213 xasprintf(&str, "%d", blk->node);
214 else
215 str = xstrdup("-");
216 break;
217 }
218
219 if (str && scols_line_refer_data(line, i, str) != 0)
220 err_oom();
221 }
222 }
223
224 static void fill_scols_table(struct lsmem *lsmem)
225 {
226 int i;
227
228 for (i = 0; i < lsmem->nblocks; i++)
229 add_scols_line(lsmem, &lsmem->blocks[i]);
230 }
231
232 static void print_summary(struct lsmem *lsmem)
233 {
234 if (lsmem->bytes) {
235 printf("%-23s %15"PRId64"\n",_("Memory block size:"), lsmem->block_size);
236 printf("%-23s %15"PRId64"\n",_("Total online memory:"), lsmem->mem_online);
237 printf("%-23s %15"PRId64"\n",_("Total offline memory:"), lsmem->mem_offline);
238 } else {
239 printf("%-23s %5s\n",_("Memory block size:"),
240 size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->block_size));
241 printf("%-23s %5s\n",_("Total online memory:"),
242 size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->mem_online));
243 printf("%-23s %5s\n",_("Total offline memory:"),
244 size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->mem_offline));
245 }
246 }
247
248 static int memory_block_get_node(char *name)
249 {
250 struct dirent *de;
251 const char *path;
252 DIR *dir;
253 int node;
254
255 path = path_get(_PATH_SYS_MEMORY"/%s", name);
256 if (!path || !(dir= opendir(path)))
257 err(EXIT_FAILURE, _("Failed to open %s"), path ? path : name);
258
259 node = -1;
260 while ((de = readdir(dir)) != NULL) {
261 if (strncmp("node", de->d_name, 4))
262 continue;
263 if (!isdigit_string(de->d_name + 4))
264 continue;
265 node = strtol(de->d_name + 4, NULL, 10);
266 break;
267 }
268 closedir(dir);
269 return node;
270 }
271
272 static void memory_block_read_attrs(struct lsmem *lsmem, char *name,
273 struct memory_block *blk)
274 {
275 char line[BUFSIZ];
276
277 blk->count = 1;
278 blk->index = strtoumax(name + 6, NULL, 10); /* get <num> of "memory<num>" */
279 blk->removable = path_read_u64(_PATH_SYS_MEMORY"/%s/%s", name, "removable");
280 blk->state = MEMORY_STATE_UNKNOWN;
281 path_read_str(line, sizeof(line), _PATH_SYS_MEMORY"/%s/%s", name, "state");
282 if (strcmp(line, "offline") == 0)
283 blk->state = MEMORY_STATE_OFFLINE;
284 else if (strcmp(line, "online") == 0)
285 blk->state = MEMORY_STATE_ONLINE;
286 else if (strcmp(line, "going-offline") == 0)
287 blk->state = MEMORY_STATE_GOING_OFFLINE;
288 if (lsmem->have_nodes)
289 blk->node = memory_block_get_node(name);
290 }
291
292 static int is_mergeable(struct lsmem *lsmem, struct memory_block *blk)
293 {
294 struct memory_block *curr;
295
296 if (!lsmem->nblocks)
297 return 0;
298 curr = &lsmem->blocks[lsmem->nblocks - 1];
299 if (lsmem->list_all)
300 return 0;
301 if (curr->index + curr->count != blk->index)
302 return 0;
303 if (lsmem->want_state && curr->state != blk->state)
304 return 0;
305 if (lsmem->want_removable && curr->removable != blk->removable)
306 return 0;
307 if (lsmem->want_node && lsmem->have_nodes) {
308 if (curr->node != blk->node)
309 return 0;
310 }
311 return 1;
312 }
313
314 static void read_info(struct lsmem *lsmem)
315 {
316 struct memory_block blk;
317 char line[BUFSIZ];
318 int i;
319
320 path_read_str(line, sizeof(line), _PATH_SYS_MEMORY_BLOCK_SIZE);
321 lsmem->block_size = strtoumax(line, NULL, 16);
322
323 for (i = 0; i < lsmem->ndirs; i++) {
324 memory_block_read_attrs(lsmem, lsmem->dirs[i]->d_name, &blk);
325 if (is_mergeable(lsmem, &blk)) {
326 lsmem->blocks[lsmem->nblocks - 1].count++;
327 continue;
328 }
329 lsmem->nblocks++;
330 lsmem->blocks = xrealloc(lsmem->blocks, lsmem->nblocks * sizeof(blk));
331 *&lsmem->blocks[lsmem->nblocks - 1] = blk;
332 }
333 for (i = 0; i < lsmem->nblocks; i++) {
334 if (lsmem->blocks[i].state == MEMORY_STATE_ONLINE)
335 lsmem->mem_online += lsmem->block_size * lsmem->blocks[i].count;
336 else
337 lsmem->mem_offline += lsmem->block_size * lsmem->blocks[i].count;
338 }
339 }
340
341 static int memory_block_filter(const struct dirent *de)
342 {
343 if (strncmp("memory", de->d_name, 6))
344 return 0;
345 return isdigit_string(de->d_name + 6);
346 }
347
348 static void read_basic_info(struct lsmem *lsmem)
349 {
350 const char *dir;
351
352 if (!path_exist(_PATH_SYS_MEMORY_BLOCK_SIZE))
353 errx(EXIT_FAILURE, _("This system does not support memory blocks"));
354
355 dir = path_get(_PATH_SYS_MEMORY);
356 if (!dir)
357 err(EXIT_FAILURE, _("Failed to read %s"), _PATH_SYS_MEMORY);
358
359 lsmem->ndirs = scandir(dir, &lsmem->dirs, memory_block_filter, versionsort);
360 if (lsmem->ndirs <= 0)
361 err(EXIT_FAILURE, _("Failed to read %s"), _PATH_SYS_MEMORY);
362
363 if (memory_block_get_node(lsmem->dirs[0]->d_name) != -1)
364 lsmem->have_nodes = 1;
365 }
366
367 static void __attribute__((__noreturn__)) usage(void)
368 {
369 FILE *out = stdout;
370 unsigned int i;
371
372 fputs(USAGE_HEADER, out);
373 fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
374
375 fputs(USAGE_SEPARATOR, out);
376 fputs(_("List the ranges of available memory with their online status.\n"), out);
377
378 fputs(USAGE_OPTIONS, out);
379 fputs(_(" -J, --json use JSON output format\n"), out);
380 fputs(_(" -P, --pairs use key=\"value\" output format\n"), out);
381 fputs(_(" -a, --all list each individual memory block\n"), out);
382 fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out);
383 fputs(_(" -n, --noheadings don't print headings\n"), out);
384 fputs(_(" -o, --output <list> output columns\n"), out);
385 fputs(_(" -r, --raw use raw output format\n"), out);
386 fputs(_(" -s, --sysroot <dir> use the specified directory as system root\n"), out);
387 fputs(_(" --summary[=when] print summary information (never,always or only)\n"), out);
388
389 fputs(USAGE_SEPARATOR, out);
390 printf(USAGE_HELP_OPTIONS(22));
391
392 fputs(USAGE_COLUMNS, out);
393 for (i = 0; i < ARRAY_SIZE(coldescs); i++)
394 fprintf(out, " %10s %s\n", coldescs[i].name, _(coldescs[i].help));
395
396 printf(USAGE_MAN_TAIL("lsmem(1)"));
397
398 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
399 }
400
401 int main(int argc, char **argv)
402 {
403 struct lsmem _lsmem = {
404 .want_table = 1,
405 .want_summary = 1
406 }, *lsmem = &_lsmem;
407
408 const char *outarg = NULL;
409 int c;
410 size_t i;
411
412 enum {
413 LSMEM_OPT_SUMARRY = CHAR_MAX + 1
414 };
415
416 static const struct option longopts[] = {
417 {"all", no_argument, NULL, 'a'},
418 {"bytes", no_argument, NULL, 'b'},
419 {"help", no_argument, NULL, 'h'},
420 {"json", no_argument, NULL, 'J'},
421 {"noheadings", no_argument, NULL, 'n'},
422 {"output", required_argument, NULL, 'o'},
423 {"pairs", no_argument, NULL, 'P'},
424 {"raw", no_argument, NULL, 'r'},
425 {"sysroot", required_argument, NULL, 's'},
426 {"version", no_argument, NULL, 'V'},
427 {"summary", optional_argument, NULL, LSMEM_OPT_SUMARRY },
428 {NULL, 0, NULL, 0}
429 };
430 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
431 { 'J', 'P', 'r' },
432 { 0 }
433 };
434 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
435
436 setlocale(LC_ALL, "");
437 bindtextdomain(PACKAGE, LOCALEDIR);
438 textdomain(PACKAGE);
439 atexit(close_stdout);
440
441 while ((c = getopt_long(argc, argv, "abhJno:Prs:V", longopts, NULL)) != -1) {
442
443 err_exclusive_options(c, longopts, excl, excl_st);
444
445 switch (c) {
446 case 'a':
447 lsmem->list_all = 1;
448 break;
449 case 'b':
450 lsmem->bytes = 1;
451 break;
452 case 'h':
453 usage();
454 break;
455 case 'J':
456 lsmem->json = 1;
457 lsmem->want_summary = 0;
458 break;
459 case 'n':
460 lsmem->noheadings = 1;
461 break;
462 case 'o':
463 outarg = optarg;
464 break;
465 case 'P':
466 lsmem->export = 1;
467 lsmem->want_summary = 0;
468 break;
469 case 'r':
470 lsmem->raw = 1;
471 lsmem->want_summary = 0;
472 break;
473 case 's':
474 if(path_set_prefix(optarg))
475 err(EXIT_FAILURE, _("invalid argument to %s"), "--sysroot");
476 break;
477 case 'V':
478 printf(UTIL_LINUX_VERSION);
479 return 0;
480 case LSMEM_OPT_SUMARRY:
481 if (optarg) {
482 if (strcmp(optarg, "never") == 0)
483 lsmem->want_summary = 0;
484 else if (strcmp(optarg, "only") == 0)
485 lsmem->want_table = 0;
486 else if (strcmp(optarg, "always") == 0)
487 lsmem->want_summary = 1;
488 else
489 errx(EXIT_FAILURE, _("unsupported --summary argument"));
490 } else
491 lsmem->want_table = 0;
492 break;
493 default:
494 errtryhelp(EXIT_FAILURE);
495 }
496 }
497
498 if (argc != optind) {
499 warnx(_("bad usage"));
500 errtryhelp(EXIT_FAILURE);
501 }
502
503 if (lsmem->want_table + lsmem->want_summary == 0)
504 errx(EXIT_FAILURE, _("options --{raw,json,pairs} and --summary=only are mutually exclusive"));
505
506 /* Shortcut to avoid scols machinery on --summary=only */
507 if (lsmem->want_table == 0 && lsmem->want_summary) {
508 read_basic_info(lsmem);
509 read_info(lsmem);
510 print_summary(lsmem);
511 return EXIT_SUCCESS;
512 }
513
514 /*
515 * Default columns
516 */
517 if (!ncolumns) {
518 add_column(columns, ncolumns++, COL_RANGE);
519 add_column(columns, ncolumns++, COL_SIZE);
520 add_column(columns, ncolumns++, COL_STATE);
521 add_column(columns, ncolumns++, COL_REMOVABLE);
522 add_column(columns, ncolumns++, COL_BLOCK);
523 }
524
525 if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
526 &ncolumns, column_name_to_id) < 0)
527 return EXIT_FAILURE;
528
529 /*
530 * Initialize output
531 */
532 scols_init_debug(0);
533
534 if (!(lsmem->table = scols_new_table()))
535 errx(EXIT_FAILURE, _("failed to initialize output table"));
536 scols_table_enable_raw(lsmem->table, lsmem->raw);
537 scols_table_enable_export(lsmem->table, lsmem->export);
538 scols_table_enable_json(lsmem->table, lsmem->json);
539 scols_table_enable_noheadings(lsmem->table, lsmem->noheadings);
540
541 if (lsmem->json)
542 scols_table_set_name(lsmem->table, "memory");
543
544 for (i = 0; i < ncolumns; i++) {
545 struct coldesc *ci = get_column_desc(i);
546 if (!scols_table_new_column(lsmem->table, ci->name, ci->whint, ci->flags))
547 err(EXIT_FAILURE, _("Failed to initialize output column"));
548 }
549
550 if (has_column(COL_STATE))
551 lsmem->want_state = 1;
552 if (has_column(COL_NODE))
553 lsmem->want_node = 1;
554 if (has_column(COL_REMOVABLE))
555 lsmem->want_removable = 1;
556
557 /*
558 * Read data and print output
559 */
560 read_basic_info(lsmem);
561 read_info(lsmem);
562
563 if (lsmem->want_table) {
564 fill_scols_table(lsmem);
565 scols_print_table(lsmem->table);
566
567 if (lsmem->want_summary)
568 fputc('\n', stdout);
569 }
570
571 if (lsmem->want_summary)
572 print_summary(lsmem);
573
574 scols_unref_table(lsmem->table);
575 return 0;
576 }