2 * lsmem - Show memory configuration
4 * Copyright IBM Corp. 2016
5 * Copyright (C) 2016 Karel Zak <kzak@redhat.com>
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.
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.
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.
25 #include <closestream.h>
35 #include <libsmartcols.h>
37 #define _PATH_SYS_MEMORY "/sys/devices/system/memory"
39 #define MEMORY_STATE_ONLINE 0
40 #define MEMORY_STATE_OFFLINE 1
41 #define MEMORY_STATE_GOING_OFFLINE 2
42 #define MEMORY_STATE_UNKNOWN 3
62 int zones
[MAX_NR_ZONES
];
63 unsigned int removable
:1;
67 struct path_cxt
*sysmem
; /* _PATH_SYS_MEMORY directory handler */
70 struct memory_block
*blocks
;
76 struct libscols_table
*table
;
77 unsigned int have_nodes
: 1,
89 split_by_removable
: 1,
105 static char *zone_names
[] = {
107 [ZONE_DMA32
] = "DMA32",
108 [ZONE_NORMAL
] = "Normal",
109 [ZONE_HIGHMEM
] = "Highmem",
110 [ZONE_MOVABLE
] = "Movable",
111 [ZONE_DEVICE
] = "Device",
112 [ZONE_NONE
] = "None", /* block contains more than one zone, can't be offlined */
113 [ZONE_UNKNOWN
] = "Unknown",
118 const char *name
; /* header */
119 double whint
; /* width hint (N < 1 is in percent of termwidth) */
120 int flags
; /* SCOLS_FL_* */
124 /* columns descriptions */
125 static struct coldesc coldescs
[] = {
126 [COL_RANGE
] = { "RANGE", 0, 0, N_("start and end address of the memory range")},
127 [COL_SIZE
] = { "SIZE", 5, SCOLS_FL_RIGHT
, N_("size of the memory range")},
128 [COL_STATE
] = { "STATE", 0, SCOLS_FL_RIGHT
, N_("online status of the memory range")},
129 [COL_REMOVABLE
] = { "REMOVABLE", 0, SCOLS_FL_RIGHT
, N_("memory is removable")},
130 [COL_BLOCK
] = { "BLOCK", 0, SCOLS_FL_RIGHT
, N_("memory block number or blocks range")},
131 [COL_NODE
] = { "NODE", 0, SCOLS_FL_RIGHT
, N_("numa node of memory")},
132 [COL_ZONES
] = { "ZONES", 0, SCOLS_FL_RIGHT
, N_("valid zones for the memory range")},
135 /* columns[] array specifies all currently wanted output column. The columns
136 * are defined by coldescs[] array and you can specify (on command line) each
137 * column twice. That's enough, dynamically allocated array of the columns is
138 * unnecessary overkill and over-engineering in this case */
139 static int columns
[ARRAY_SIZE(coldescs
) * 2];
140 static size_t ncolumns
;
142 static inline size_t err_columns_index(size_t arysz
, size_t idx
)
145 errx(EXIT_FAILURE
, _("too many columns specified, "
146 "the limit is %zu columns"),
152 * name must be null-terminated
154 static int zone_name_to_id(const char *name
)
158 for (i
= 0; i
< ARRAY_SIZE(zone_names
); i
++) {
159 if (!strcasecmp(name
, zone_names
[i
]))
165 #define add_column(ary, n, id) \
166 ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
168 static int column_name_to_id(const char *name
, size_t namesz
)
172 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++) {
173 const char *cn
= coldescs
[i
].name
;
175 if (!strncasecmp(name
, cn
, namesz
) && !*(cn
+ namesz
))
178 warnx(_("unknown column: %s"), name
);
182 static inline int get_column_id(int num
)
185 assert((size_t) num
< ncolumns
);
186 assert(columns
[num
] < (int) ARRAY_SIZE(coldescs
));
191 static inline struct coldesc
*get_column_desc(int num
)
193 return &coldescs
[ get_column_id(num
) ];
196 static inline void reset_split_policy(struct lsmem
*l
, int enable
)
198 l
->split_by_state
= enable
;
199 l
->split_by_node
= enable
;
200 l
->split_by_removable
= enable
;
201 l
->split_by_zones
= enable
;
204 static void set_split_policy(struct lsmem
*l
, int cols
[], size_t ncols
)
208 reset_split_policy(l
, 0);
210 for (i
= 0; i
< ncols
; i
++) {
213 l
->split_by_state
= 1;
216 l
->split_by_node
= 1;
219 l
->split_by_removable
= 1;
222 l
->split_by_zones
= 1;
230 static void add_scols_line(struct lsmem
*lsmem
, struct memory_block
*blk
)
233 struct libscols_line
*line
;
235 line
= scols_table_new_line(lsmem
->table
, NULL
);
239 for (i
= 0; i
< ncolumns
; i
++) {
242 switch (get_column_id(i
)) {
245 uint64_t start
= blk
->index
* lsmem
->block_size
;
246 uint64_t size
= blk
->count
* lsmem
->block_size
;
247 xasprintf(&str
, "0x%016"PRIx64
"-0x%016"PRIx64
, start
, start
+ size
- 1);
252 xasprintf(&str
, "%"PRId64
, (uint64_t) blk
->count
* lsmem
->block_size
);
254 str
= size_to_human_string(SIZE_SUFFIX_1LETTER
,
255 (uint64_t) blk
->count
* lsmem
->block_size
);
259 blk
->state
== MEMORY_STATE_ONLINE
? _("online") :
260 blk
->state
== MEMORY_STATE_OFFLINE
? _("offline") :
261 blk
->state
== MEMORY_STATE_GOING_OFFLINE
? _("on->off") :
265 if (blk
->state
== MEMORY_STATE_ONLINE
)
266 str
= xstrdup(blk
->removable
? _("yes") : _("no"));
270 xasprintf(&str
, "%"PRId64
, blk
->index
);
272 xasprintf(&str
, "%"PRId64
"-%"PRId64
,
273 blk
->index
, blk
->index
+ blk
->count
- 1);
276 if (lsmem
->have_nodes
)
277 xasprintf(&str
, "%d", blk
->node
);
280 if (lsmem
->have_zones
) {
281 char valid_zones
[BUFSIZ
];
284 valid_zones
[0] = '\0';
285 for (j
= 0; j
< blk
->nr_zones
; j
++) {
286 zone_id
= blk
->zones
[j
];
287 if (strlen(valid_zones
) +
288 strlen(zone_names
[zone_id
]) > BUFSIZ
- 2)
290 strcat(valid_zones
, zone_names
[zone_id
]);
291 if (j
+ 1 < blk
->nr_zones
)
292 strcat(valid_zones
, "/");
294 str
= xstrdup(valid_zones
);
299 if (str
&& scols_line_refer_data(line
, i
, str
) != 0)
304 static void fill_scols_table(struct lsmem
*lsmem
)
308 for (i
= 0; i
< lsmem
->nblocks
; i
++)
309 add_scols_line(lsmem
, &lsmem
->blocks
[i
]);
312 static void print_summary(struct lsmem
*lsmem
)
315 printf("%-23s %15"PRId64
"\n",_("Memory block size:"), lsmem
->block_size
);
316 printf("%-23s %15"PRId64
"\n",_("Total online memory:"), lsmem
->mem_online
);
317 printf("%-23s %15"PRId64
"\n",_("Total offline memory:"), lsmem
->mem_offline
);
321 if ((p
= size_to_human_string(SIZE_SUFFIX_1LETTER
, lsmem
->block_size
)))
322 printf("%-23s %5s\n",_("Memory block size:"), p
);
325 if ((p
= size_to_human_string(SIZE_SUFFIX_1LETTER
, lsmem
->mem_online
)))
326 printf("%-23s %5s\n",_("Total online memory:"), p
);
329 if ((p
= size_to_human_string(SIZE_SUFFIX_1LETTER
, lsmem
->mem_offline
)))
330 printf("%-23s %5s\n",_("Total offline memory:"), p
);
335 static int memory_block_get_node(struct lsmem
*lsmem
, char *name
)
341 dir
= ul_path_opendir(lsmem
->sysmem
, name
);
343 err(EXIT_FAILURE
, _("Failed to open %s"), name
);
346 while ((de
= readdir(dir
)) != NULL
) {
347 if (strncmp("node", de
->d_name
, 4))
349 if (!isdigit_string(de
->d_name
+ 4))
351 node
= strtol(de
->d_name
+ 4, NULL
, 10);
358 static void memory_block_read_attrs(struct lsmem
*lsmem
, char *name
,
359 struct memory_block
*blk
)
364 memset(blk
, 0, sizeof(*blk
));
367 blk
->state
= MEMORY_STATE_UNKNOWN
;
368 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) {
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) {
390 char *token
= strtok(line
, " ");
392 for (i
= 0; token
&& i
< MAX_NR_ZONES
; i
++) {
393 blk
->zones
[i
] = zone_name_to_id(token
);
395 token
= strtok(NULL
, " ");
402 static int is_mergeable(struct lsmem
*lsmem
, struct memory_block
*blk
)
404 struct memory_block
*curr
;
409 curr
= &lsmem
->blocks
[lsmem
->nblocks
- 1];
412 if (curr
->index
+ curr
->count
!= blk
->index
)
414 if (lsmem
->split_by_state
&& curr
->state
!= blk
->state
)
416 if (lsmem
->split_by_removable
&& curr
->removable
!= blk
->removable
)
418 if (lsmem
->split_by_node
&& lsmem
->have_nodes
) {
419 if (curr
->node
!= blk
->node
)
422 if (lsmem
->split_by_zones
&& lsmem
->have_zones
) {
423 if (curr
->nr_zones
!= blk
->nr_zones
)
425 for (i
= 0; i
< curr
->nr_zones
; i
++) {
426 if (curr
->zones
[i
] == ZONE_UNKNOWN
||
427 curr
->zones
[i
] != blk
->zones
[i
])
434 static void read_info(struct lsmem
*lsmem
)
436 struct memory_block blk
;
440 if (ul_path_read_buffer(lsmem
->sysmem
, buf
, sizeof(buf
), "block_size_bytes") <= 0)
441 err(EXIT_FAILURE
, _("failed to read memory block size"));
442 lsmem
->block_size
= strtoumax(buf
, NULL
, 16);
444 for (i
= 0; i
< lsmem
->ndirs
; i
++) {
445 memory_block_read_attrs(lsmem
, lsmem
->dirs
[i
]->d_name
, &blk
);
446 if (blk
.state
== MEMORY_STATE_ONLINE
)
447 lsmem
->mem_online
+= lsmem
->block_size
;
449 lsmem
->mem_offline
+= lsmem
->block_size
;
450 if (is_mergeable(lsmem
, &blk
)) {
451 lsmem
->blocks
[lsmem
->nblocks
- 1].count
++;
455 lsmem
->blocks
= xrealloc(lsmem
->blocks
, lsmem
->nblocks
* sizeof(blk
));
456 *&lsmem
->blocks
[lsmem
->nblocks
- 1] = blk
;
460 static int memory_block_filter(const struct dirent
*de
)
462 if (strncmp("memory", de
->d_name
, 6))
464 return isdigit_string(de
->d_name
+ 6);
467 static void read_basic_info(struct lsmem
*lsmem
)
471 if (ul_path_access(lsmem
->sysmem
, F_OK
, "block_size_bytes") != 0)
472 errx(EXIT_FAILURE
, _("This system does not support memory blocks"));
474 ul_path_get_abspath(lsmem
->sysmem
, dir
, sizeof(dir
), NULL
);
476 lsmem
->ndirs
= scandir(dir
, &lsmem
->dirs
, memory_block_filter
, versionsort
);
477 if (lsmem
->ndirs
<= 0)
478 err(EXIT_FAILURE
, _("Failed to read %s"), dir
);
480 if (memory_block_get_node(lsmem
, lsmem
->dirs
[0]->d_name
) != -1)
481 lsmem
->have_nodes
= 1;
483 /* The valid_zones sysmem attribute was introduced with kernel 3.18 */
484 if (ul_path_access(lsmem
->sysmem
, F_OK
, "memory0/valid_zones") == 0)
485 lsmem
->have_zones
= 1;
488 static void __attribute__((__noreturn__
)) usage(void)
493 fputs(USAGE_HEADER
, out
);
494 fprintf(out
, _(" %s [options]\n"), program_invocation_short_name
);
496 fputs(USAGE_SEPARATOR
, out
);
497 fputs(_("List the ranges of available memory with their online status.\n"), out
);
499 fputs(USAGE_OPTIONS
, out
);
500 fputs(_(" -J, --json use JSON output format\n"), out
);
501 fputs(_(" -P, --pairs use key=\"value\" output format\n"), out
);
502 fputs(_(" -a, --all list each individual memory block\n"), out
);
503 fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out
);
504 fputs(_(" -n, --noheadings don't print headings\n"), out
);
505 fputs(_(" -o, --output <list> output columns\n"), out
);
506 fputs(_(" --output-all output all columns\n"), out
);
507 fputs(_(" -r, --raw use raw output format\n"), out
);
508 fputs(_(" -S, --split <list> split ranges by specified columns\n"), out
);
509 fputs(_(" -s, --sysroot <dir> use the specified directory as system root\n"), out
);
510 fputs(_(" --summary[=when] print summary information (never,always or only)\n"), out
);
512 fputs(USAGE_SEPARATOR
, out
);
513 printf(USAGE_HELP_OPTIONS(22));
515 fputs(USAGE_COLUMNS
, out
);
516 for (i
= 0; i
< ARRAY_SIZE(coldescs
); i
++)
517 fprintf(out
, " %10s %s\n", coldescs
[i
].name
, _(coldescs
[i
].help
));
519 printf(USAGE_MAN_TAIL("lsmem(1)"));
524 int main(int argc
, char **argv
)
526 struct lsmem _lsmem
= {
531 const char *outarg
= NULL
, *splitarg
= NULL
, *prefix
= NULL
;
536 LSMEM_OPT_SUMARRY
= CHAR_MAX
+ 1,
540 static const struct option longopts
[] = {
541 {"all", no_argument
, NULL
, 'a'},
542 {"bytes", no_argument
, NULL
, 'b'},
543 {"help", no_argument
, NULL
, 'h'},
544 {"json", no_argument
, NULL
, 'J'},
545 {"noheadings", no_argument
, NULL
, 'n'},
546 {"output", required_argument
, NULL
, 'o'},
547 {"output-all", no_argument
, NULL
, OPT_OUTPUT_ALL
},
548 {"pairs", no_argument
, NULL
, 'P'},
549 {"raw", no_argument
, NULL
, 'r'},
550 {"sysroot", required_argument
, NULL
, 's'},
551 {"split", required_argument
, NULL
, 'S'},
552 {"version", no_argument
, NULL
, 'V'},
553 {"summary", optional_argument
, NULL
, LSMEM_OPT_SUMARRY
},
556 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
561 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
563 setlocale(LC_ALL
, "");
564 bindtextdomain(PACKAGE
, LOCALEDIR
);
566 close_stdout_atexit();
568 while ((c
= getopt_long(argc
, argv
, "abhJno:PrS:s:V", longopts
, NULL
)) != -1) {
570 err_exclusive_options(c
, longopts
, excl
, excl_st
);
581 lsmem
->want_summary
= 0;
584 lsmem
->noheadings
= 1;
590 for (ncolumns
= 0; (size_t)ncolumns
< ARRAY_SIZE(coldescs
); ncolumns
++)
591 columns
[ncolumns
] = ncolumns
;
595 lsmem
->want_summary
= 0;
599 lsmem
->want_summary
= 0;
607 case LSMEM_OPT_SUMARRY
:
609 if (strcmp(optarg
, "never") == 0)
610 lsmem
->want_summary
= 0;
611 else if (strcmp(optarg
, "only") == 0)
612 lsmem
->want_table
= 0;
613 else if (strcmp(optarg
, "always") == 0)
614 lsmem
->want_summary
= 1;
616 errx(EXIT_FAILURE
, _("unsupported --summary argument"));
618 lsmem
->want_table
= 0;
624 print_version(EXIT_SUCCESS
);
626 errtryhelp(EXIT_FAILURE
);
630 if (argc
!= optind
) {
631 warnx(_("bad usage"));
632 errtryhelp(EXIT_FAILURE
);
635 if (lsmem
->want_table
+ lsmem
->want_summary
== 0)
636 errx(EXIT_FAILURE
, _("options --{raw,json,pairs} and --summary=only are mutually exclusive"));
638 ul_path_init_debug();
640 lsmem
->sysmem
= ul_new_path(_PATH_SYS_MEMORY
);
642 err(EXIT_FAILURE
, _("failed to initialize %s handler"), _PATH_SYS_MEMORY
);
643 if (prefix
&& ul_path_set_prefix(lsmem
->sysmem
, prefix
) != 0)
644 err(EXIT_FAILURE
, _("invalid argument to --sysroot"));
646 /* Shortcut to avoid scols machinery on --summary=only */
647 if (lsmem
->want_table
== 0 && lsmem
->want_summary
) {
648 read_basic_info(lsmem
);
650 print_summary(lsmem
);
658 add_column(columns
, ncolumns
++, COL_RANGE
);
659 add_column(columns
, ncolumns
++, COL_SIZE
);
660 add_column(columns
, ncolumns
++, COL_STATE
);
661 add_column(columns
, ncolumns
++, COL_REMOVABLE
);
662 add_column(columns
, ncolumns
++, COL_BLOCK
);
665 if (outarg
&& string_add_to_idarray(outarg
, columns
, ARRAY_SIZE(columns
),
666 &ncolumns
, column_name_to_id
) < 0)
674 if (!(lsmem
->table
= scols_new_table()))
675 errx(EXIT_FAILURE
, _("failed to initialize output table"));
676 scols_table_enable_raw(lsmem
->table
, lsmem
->raw
);
677 scols_table_enable_export(lsmem
->table
, lsmem
->export
);
678 scols_table_enable_json(lsmem
->table
, lsmem
->json
);
679 scols_table_enable_noheadings(lsmem
->table
, lsmem
->noheadings
);
682 scols_table_set_name(lsmem
->table
, "memory");
684 for (i
= 0; i
< ncolumns
; i
++) {
685 struct coldesc
*ci
= get_column_desc(i
);
686 struct libscols_column
*cl
;
688 cl
= scols_table_new_column(lsmem
->table
, ci
->name
, ci
->whint
, ci
->flags
);
690 err(EXIT_FAILURE
, _("Failed to initialize output column"));
693 int id
= get_column_id(i
);
701 scols_column_set_json_type(cl
, SCOLS_JSON_NUMBER
);
704 scols_column_set_json_type(cl
, SCOLS_JSON_BOOLEAN
);
711 int split
[ARRAY_SIZE(coldescs
)] = { 0 };
712 static size_t nsplits
= 0;
714 if (strcasecmp(splitarg
, "none") == 0)
716 else if (string_add_to_idarray(splitarg
, split
, ARRAY_SIZE(split
),
717 &nsplits
, column_name_to_id
) < 0)
720 set_split_policy(lsmem
, split
, nsplits
);
723 /* follow output columns */
724 set_split_policy(lsmem
, columns
, ncolumns
);
727 * Read data and print output
729 read_basic_info(lsmem
);
732 if (lsmem
->want_table
) {
733 fill_scols_table(lsmem
);
734 scols_print_table(lsmem
->table
);
736 if (lsmem
->want_summary
)
740 if (lsmem
->want_summary
)
741 print_summary(lsmem
);
743 scols_unref_table(lsmem
->table
);
744 ul_unref_path(lsmem
->sysmem
);