2 * SPDX-License-Identifier: GPL-2.0-or-later
4 * lsmem - Show memory configuration
6 * Copyright IBM Corp. 2016
7 * Copyright (C) 2016 Karel Zak <kzak@redhat.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
18 #include <closestream.h>
28 #include <libsmartcols.h>
30 #define _PATH_SYS_MEMORY "/sys/devices/system/memory"
32 #define MEMORY_STATE_ONLINE 0
33 #define MEMORY_STATE_OFFLINE 1
34 #define MEMORY_STATE_GOING_OFFLINE 2
35 #define MEMORY_STATE_UNKNOWN 3
55 int zones
[MAX_NR_ZONES
];
56 unsigned int removable
:1;
60 struct path_cxt
*sysmem
; /* _PATH_SYS_MEMORY directory handler */
63 struct memory_block
*blocks
;
69 struct libscols_table
*table
;
70 unsigned int have_nodes
: 1,
82 split_by_removable
: 1,
98 static char *zone_names
[] = {
100 [ZONE_DMA32
] = "DMA32",
101 [ZONE_NORMAL
] = "Normal",
102 [ZONE_HIGHMEM
] = "Highmem",
103 [ZONE_MOVABLE
] = "Movable",
104 [ZONE_DEVICE
] = "Device",
105 [ZONE_NONE
] = "None", /* block contains more than one zone, can't be offlined */
106 [ZONE_UNKNOWN
] = "Unknown",
111 const char *name
; /* header */
112 double whint
; /* width hint (N < 1 is in percent of termwidth) */
113 int flags
; /* SCOLS_FL_* */
117 /* columns descriptions */
118 static struct coldesc coldescs
[] = {
119 [COL_RANGE
] = { "RANGE", 0, 0, N_("start and end address of the memory range")},
120 [COL_SIZE
] = { "SIZE", 5, SCOLS_FL_RIGHT
, N_("size of the memory range")},
121 [COL_STATE
] = { "STATE", 0, SCOLS_FL_RIGHT
, N_("online status of the memory range")},
122 [COL_REMOVABLE
] = { "REMOVABLE", 0, SCOLS_FL_RIGHT
, N_("memory is removable")},
123 [COL_BLOCK
] = { "BLOCK", 0, SCOLS_FL_RIGHT
, N_("memory block number or blocks range")},
124 [COL_NODE
] = { "NODE", 0, SCOLS_FL_RIGHT
, N_("numa node of memory")},
125 [COL_ZONES
] = { "ZONES", 0, SCOLS_FL_RIGHT
, N_("valid zones for the memory range")},
128 /* columns[] array specifies all currently wanted output column. The columns
129 * are defined by coldescs[] array and you can specify (on command line) each
130 * column twice. That's enough, dynamically allocated array of the columns is
131 * unnecessary overkill and over-engineering in this case */
132 static int columns
[ARRAY_SIZE(coldescs
) * 2];
133 static size_t ncolumns
;
135 static inline size_t err_columns_index(size_t arysz
, size_t idx
)
138 errx(EXIT_FAILURE
, _("too many columns specified, "
139 "the limit is %zu columns"),
145 * name must be null-terminated
147 static int zone_name_to_id(const char *name
)
151 for (i
= 0; i
< ARRAY_SIZE(zone_names
); i
++) {
152 if (!strcasecmp(name
, zone_names
[i
]))
158 #define add_column(ary, n, id) \
159 ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
161 static int column_name_to_id(const char *name
, size_t namesz
)
165 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++) {
166 const char *cn
= coldescs
[i
].name
;
168 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
))
171 warnx(_("unknown column: %s"), name
);
175 static inline int get_column_id(int num
)
178 assert((size_t) num
< ncolumns
);
179 assert(columns
[num
] < (int) ARRAY_SIZE(coldescs
));
184 static inline struct coldesc
*get_column_desc(int num
)
186 return &coldescs
[ get_column_id(num
) ];
189 static inline void reset_split_policy(struct lsmem
*l
, int enable
)
191 l
->split_by_state
= enable
;
192 l
->split_by_node
= enable
;
193 l
->split_by_removable
= enable
;
194 l
->split_by_zones
= enable
;
197 static void set_split_policy(struct lsmem
*l
, int cols
[], size_t ncols
)
201 reset_split_policy(l
, 0);
203 for (i
= 0; i
< ncols
; i
++) {
206 l
->split_by_state
= 1;
209 l
->split_by_node
= 1;
212 l
->split_by_removable
= 1;
215 l
->split_by_zones
= 1;
223 static void add_scols_line(struct lsmem
*lsmem
, struct memory_block
*blk
)
226 struct libscols_line
*line
;
228 line
= scols_table_new_line(lsmem
->table
, NULL
);
232 for (i
= 0; i
< ncolumns
; i
++) {
235 switch (get_column_id(i
)) {
238 uint64_t start
= blk
->index
* lsmem
->block_size
;
239 uint64_t size
= blk
->count
* lsmem
->block_size
;
240 xasprintf(&str
, "0x%016"PRIx64
"-0x%016"PRIx64
, start
, start
+ size
- 1);
245 xasprintf(&str
, "%"PRId64
, (uint64_t) blk
->count
* lsmem
->block_size
);
247 str
= size_to_human_string(SIZE_SUFFIX_1LETTER
,
248 (uint64_t) blk
->count
* lsmem
->block_size
);
252 blk
->state
== MEMORY_STATE_ONLINE
? _("online") :
253 blk
->state
== MEMORY_STATE_OFFLINE
? _("offline") :
254 blk
->state
== MEMORY_STATE_GOING_OFFLINE
? _("on->off") :
258 if (blk
->state
== MEMORY_STATE_ONLINE
)
259 str
= xstrdup(blk
->removable
? _("yes") : _("no"));
263 xasprintf(&str
, "%"PRId64
, blk
->index
);
265 xasprintf(&str
, "%"PRId64
"-%"PRId64
,
266 blk
->index
, blk
->index
+ blk
->count
- 1);
269 if (lsmem
->have_nodes
)
270 xasprintf(&str
, "%d", blk
->node
);
273 if (lsmem
->have_zones
) {
274 char valid_zones
[BUFSIZ
];
277 valid_zones
[0] = '\0';
278 for (j
= 0; j
< blk
->nr_zones
; j
++) {
279 zone_id
= blk
->zones
[j
];
280 if (strlen(valid_zones
) +
281 strlen(zone_names
[zone_id
]) > BUFSIZ
- 2)
283 strcat(valid_zones
, zone_names
[zone_id
]);
284 if (j
+ 1 < blk
->nr_zones
)
285 strcat(valid_zones
, "/");
287 str
= xstrdup(valid_zones
);
292 if (str
&& scols_line_refer_data(line
, i
, str
) != 0)
297 static void fill_scols_table(struct lsmem
*lsmem
)
301 for (i
= 0; i
< lsmem
->nblocks
; i
++)
302 add_scols_line(lsmem
, &lsmem
->blocks
[i
]);
305 static void print_summary(struct lsmem
*lsmem
)
308 printf("%-23s %15"PRId64
"\n",_("Memory block size:"), lsmem
->block_size
);
309 printf("%-23s %15"PRId64
"\n",_("Total online memory:"), lsmem
->mem_online
);
310 printf("%-23s %15"PRId64
"\n",_("Total offline memory:"), lsmem
->mem_offline
);
314 if ((p
= size_to_human_string(SIZE_SUFFIX_1LETTER
, lsmem
->block_size
)))
315 printf("%-23s %5s\n",_("Memory block size:"), p
);
318 if ((p
= size_to_human_string(SIZE_SUFFIX_1LETTER
, lsmem
->mem_online
)))
319 printf("%-23s %5s\n",_("Total online memory:"), p
);
322 if ((p
= size_to_human_string(SIZE_SUFFIX_1LETTER
, lsmem
->mem_offline
)))
323 printf("%-23s %5s\n",_("Total offline memory:"), p
);
328 static int memory_block_get_node(struct lsmem
*lsmem
, char *name
)
334 dir
= ul_path_opendir(lsmem
->sysmem
, name
);
336 err(EXIT_FAILURE
, _("Failed to open %s"), name
);
339 while ((de
= readdir(dir
)) != NULL
) {
340 if (strncmp("node", de
->d_name
, 4) != 0)
342 if (!isdigit_string(de
->d_name
+ 4))
345 node
= strtol(de
->d_name
+ 4, NULL
, 10);
354 static int memory_block_read_attrs(struct lsmem
*lsmem
, char *name
,
355 struct memory_block
*blk
)
358 int i
, x
= 0, rc
= 0;
360 memset(blk
, 0, sizeof(*blk
));
364 blk
->state
= MEMORY_STATE_UNKNOWN
;
365 blk
->index
= strtoumax(name
+ 6, NULL
, 10); /* get <num> of "memory<num>" */
370 if (ul_path_readf_s32(lsmem
->sysmem
, &x
, "%s/removable", name
) == 0)
371 blk
->removable
= x
== 1;
373 if (ul_path_readf_string(lsmem
->sysmem
, &line
, "%s/state", name
) > 0 && line
) {
374 if (strcmp(line
, "offline") == 0)
375 blk
->state
= MEMORY_STATE_OFFLINE
;
376 else if (strcmp(line
, "online") == 0)
377 blk
->state
= MEMORY_STATE_ONLINE
;
378 else if (strcmp(line
, "going-offline") == 0)
379 blk
->state
= MEMORY_STATE_GOING_OFFLINE
;
383 if (lsmem
->have_nodes
)
384 blk
->node
= memory_block_get_node(lsmem
, name
);
387 if (lsmem
->have_zones
388 && ul_path_readf_string(lsmem
->sysmem
, &line
, "%s/valid_zones", name
) > 0
391 char *token
= strtok(line
, " ");
393 for (i
= 0; token
&& i
< MAX_NR_ZONES
; i
++) {
394 blk
->zones
[i
] = zone_name_to_id(token
);
396 token
= strtok(NULL
, " ");
404 static int is_mergeable(struct lsmem
*lsmem
, struct memory_block
*blk
)
406 struct memory_block
*curr
;
411 curr
= &lsmem
->blocks
[lsmem
->nblocks
- 1];
414 if (curr
->index
+ curr
->count
!= blk
->index
)
416 if (lsmem
->split_by_state
&& curr
->state
!= blk
->state
)
418 if (lsmem
->split_by_removable
&& curr
->removable
!= blk
->removable
)
420 if (lsmem
->split_by_node
&& lsmem
->have_nodes
) {
421 if (curr
->node
!= blk
->node
)
424 if (lsmem
->split_by_zones
&& lsmem
->have_zones
) {
425 if (curr
->nr_zones
!= blk
->nr_zones
)
427 for (i
= 0; i
< curr
->nr_zones
; i
++) {
428 if (curr
->zones
[i
] == ZONE_UNKNOWN
||
429 curr
->zones
[i
] != blk
->zones
[i
])
436 static void free_info(struct lsmem
*lsmem
)
443 for (i
= 0; i
< lsmem
->ndirs
; i
++)
444 free(lsmem
->dirs
[i
]);
448 static void read_info(struct lsmem
*lsmem
)
450 struct memory_block blk
;
454 if (ul_path_read_buffer(lsmem
->sysmem
, buf
, sizeof(buf
), "block_size_bytes") <= 0)
455 err(EXIT_FAILURE
, _("failed to read memory block size"));
458 lsmem
->block_size
= strtoumax(buf
, NULL
, 16);
460 err(EXIT_FAILURE
, _("failed to read memory block size"));
462 for (i
= 0; i
< lsmem
->ndirs
; i
++) {
463 memory_block_read_attrs(lsmem
, lsmem
->dirs
[i
]->d_name
, &blk
);
464 if (blk
.state
== MEMORY_STATE_ONLINE
)
465 lsmem
->mem_online
+= lsmem
->block_size
;
467 lsmem
->mem_offline
+= lsmem
->block_size
;
468 if (is_mergeable(lsmem
, &blk
)) {
469 lsmem
->blocks
[lsmem
->nblocks
- 1].count
++;
473 lsmem
->blocks
= xreallocarray(lsmem
->blocks
, lsmem
->nblocks
, sizeof(blk
));
474 *&lsmem
->blocks
[lsmem
->nblocks
- 1] = blk
;
478 static int memory_block_filter(const struct dirent
*de
)
480 if (strncmp("memory", de
->d_name
, 6) != 0)
482 return isdigit_string(de
->d_name
+ 6);
485 static void read_basic_info(struct lsmem
*lsmem
)
489 if (ul_path_access(lsmem
->sysmem
, F_OK
, "block_size_bytes") != 0)
490 errx(EXIT_FAILURE
, _("This system does not support memory blocks"));
492 ul_path_get_abspath(lsmem
->sysmem
, dir
, sizeof(dir
), NULL
);
494 lsmem
->ndirs
= scandir(dir
, &lsmem
->dirs
, memory_block_filter
, versionsort
);
495 if (lsmem
->ndirs
<= 0)
496 err(EXIT_FAILURE
, _("Failed to read %s"), dir
);
498 if (memory_block_get_node(lsmem
, lsmem
->dirs
[0]->d_name
) != -1)
499 lsmem
->have_nodes
= 1;
501 /* The valid_zones sysmem attribute was introduced with kernel 3.18 */
502 if (ul_path_access(lsmem
->sysmem
, F_OK
, "memory0/valid_zones") == 0)
503 lsmem
->have_zones
= 1;
506 static void __attribute__((__noreturn__
)) usage(void)
511 fputs(USAGE_HEADER
, out
);
512 fprintf(out
, _(" %s [options]\n"), program_invocation_short_name
);
514 fputs(USAGE_SEPARATOR
, out
);
515 fputs(_("List the ranges of available memory with their online status.\n"), out
);
517 fputs(USAGE_OPTIONS
, out
);
518 fputs(_(" -J, --json use JSON output format\n"), out
);
519 fputs(_(" -P, --pairs use key=\"value\" output format\n"), out
);
520 fputs(_(" -a, --all list each individual memory block\n"), out
);
521 fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out
);
522 fputs(_(" -n, --noheadings don't print headings\n"), out
);
523 fputs(_(" -o, --output <list> output columns\n"), out
);
524 fputs(_(" --output-all output all columns\n"), out
);
525 fputs(_(" -r, --raw use raw output format\n"), out
);
526 fputs(_(" -S, --split <list> split ranges by specified columns\n"), out
);
527 fputs(_(" -s, --sysroot <dir> use the specified directory as system root\n"), out
);
528 fputs(_(" --summary[=when] print summary information (never,always or only)\n"), out
);
530 fputs(USAGE_SEPARATOR
, out
);
531 fprintf(out
, USAGE_HELP_OPTIONS(22));
533 fputs(USAGE_COLUMNS
, out
);
534 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++)
535 fprintf(out
, " %10s %s\n", coldescs
[i
].name
, _(coldescs
[i
].help
));
537 fprintf(out
, USAGE_MAN_TAIL("lsmem(1)"));
542 int main(int argc
, char **argv
)
544 struct lsmem _lsmem
= {
549 const char *outarg
= NULL
, *splitarg
= NULL
, *prefix
= NULL
;
554 LSMEM_OPT_SUMARRY
= CHAR_MAX
+ 1,
558 static const struct option longopts
[] = {
559 {"all", no_argument
, NULL
, 'a'},
560 {"bytes", no_argument
, NULL
, 'b'},
561 {"help", no_argument
, NULL
, 'h'},
562 {"json", no_argument
, NULL
, 'J'},
563 {"noheadings", no_argument
, NULL
, 'n'},
564 {"output", required_argument
, NULL
, 'o'},
565 {"output-all", no_argument
, NULL
, OPT_OUTPUT_ALL
},
566 {"pairs", no_argument
, NULL
, 'P'},
567 {"raw", no_argument
, NULL
, 'r'},
568 {"sysroot", required_argument
, NULL
, 's'},
569 {"split", required_argument
, NULL
, 'S'},
570 {"version", no_argument
, NULL
, 'V'},
571 {"summary", optional_argument
, NULL
, LSMEM_OPT_SUMARRY
},
574 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
579 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
581 setlocale(LC_ALL
, "");
582 bindtextdomain(PACKAGE
, LOCALEDIR
);
584 close_stdout_atexit();
586 while ((c
= getopt_long(argc
, argv
, "abhJno:PrS:s:V", longopts
, NULL
)) != -1) {
588 err_exclusive_options(c
, longopts
, excl
, excl_st
);
599 lsmem
->want_summary
= 0;
602 lsmem
->noheadings
= 1;
608 for (ncolumns
= 0; (size_t)ncolumns
< ARRAY_SIZE(coldescs
); ncolumns
++)
609 columns
[ncolumns
] = ncolumns
;
613 lsmem
->want_summary
= 0;
617 lsmem
->want_summary
= 0;
625 case LSMEM_OPT_SUMARRY
:
627 if (strcmp(optarg
, "never") == 0)
628 lsmem
->want_summary
= 0;
629 else if (strcmp(optarg
, "only") == 0)
630 lsmem
->want_table
= 0;
631 else if (strcmp(optarg
, "always") == 0)
632 lsmem
->want_summary
= 1;
634 errx(EXIT_FAILURE
, _("unsupported --summary argument"));
636 lsmem
->want_table
= 0;
642 print_version(EXIT_SUCCESS
);
644 errtryhelp(EXIT_FAILURE
);
648 if (argc
!= optind
) {
649 warnx(_("bad usage"));
650 errtryhelp(EXIT_FAILURE
);
653 if (lsmem
->want_table
+ lsmem
->want_summary
== 0)
654 errx(EXIT_FAILURE
, _("options --{raw,json,pairs} and --summary=only are mutually exclusive"));
656 ul_path_init_debug();
658 lsmem
->sysmem
= ul_new_path(_PATH_SYS_MEMORY
);
660 err(EXIT_FAILURE
, _("failed to initialize %s handler"), _PATH_SYS_MEMORY
);
661 if (prefix
&& ul_path_set_prefix(lsmem
->sysmem
, prefix
) != 0)
662 err(EXIT_FAILURE
, _("invalid argument to --sysroot"));
663 if (!ul_path_is_accessible(lsmem
->sysmem
))
664 err(EXIT_FAILURE
, _("cannot open %s"), _PATH_SYS_MEMORY
);
666 /* Shortcut to avoid scols machinery on --summary=only */
667 if (lsmem
->want_table
== 0 && lsmem
->want_summary
) {
668 read_basic_info(lsmem
);
670 print_summary(lsmem
);
678 add_column(columns
, ncolumns
++, COL_RANGE
);
679 add_column(columns
, ncolumns
++, COL_SIZE
);
680 add_column(columns
, ncolumns
++, COL_STATE
);
681 add_column(columns
, ncolumns
++, COL_REMOVABLE
);
682 add_column(columns
, ncolumns
++, COL_BLOCK
);
685 if (outarg
&& string_add_to_idarray(outarg
, columns
, ARRAY_SIZE(columns
),
686 &ncolumns
, column_name_to_id
) < 0)
694 if (!(lsmem
->table
= scols_new_table()))
695 errx(EXIT_FAILURE
, _("failed to initialize output table"));
696 scols_table_enable_raw(lsmem
->table
, lsmem
->raw
);
697 scols_table_enable_export(lsmem
->table
, lsmem
->export
);
698 scols_table_enable_json(lsmem
->table
, lsmem
->json
);
699 scols_table_enable_noheadings(lsmem
->table
, lsmem
->noheadings
);
702 scols_table_set_name(lsmem
->table
, "memory");
704 for (i
= 0; i
< ncolumns
; i
++) {
705 struct coldesc
*ci
= get_column_desc(i
);
706 struct libscols_column
*cl
;
708 cl
= scols_table_new_column(lsmem
->table
, ci
->name
, ci
->whint
, ci
->flags
);
710 err(EXIT_FAILURE
, _("Failed to initialize output column"));
713 int id
= get_column_id(i
);
721 scols_column_set_json_type(cl
, SCOLS_JSON_NUMBER
);
724 scols_column_set_json_type(cl
, SCOLS_JSON_BOOLEAN
);
731 int split
[ARRAY_SIZE(coldescs
)] = { 0 };
732 static size_t nsplits
= 0;
734 if (strcasecmp(splitarg
, "none") == 0)
736 else if (string_add_to_idarray(splitarg
, split
, ARRAY_SIZE(split
),
737 &nsplits
, column_name_to_id
) < 0)
740 set_split_policy(lsmem
, split
, nsplits
);
743 /* follow output columns */
744 set_split_policy(lsmem
, columns
, ncolumns
);
747 * Read data and print output
749 read_basic_info(lsmem
);
752 if (lsmem
->want_table
) {
753 fill_scols_table(lsmem
);
754 scols_print_table(lsmem
->table
);
756 if (lsmem
->want_summary
)
760 if (lsmem
->want_summary
)
761 print_summary(lsmem
);
763 scols_unref_table(lsmem
->table
);
764 ul_unref_path(lsmem
->sysmem
);