/* * lsmem - Show memory configuration * * Copyright IBM Corp. 2016 * Copyright (C) 2016 Karel Zak * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it would be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _PATH_SYS_MEMORY "/sys/devices/system/memory" #define MEMORY_STATE_ONLINE 0 #define MEMORY_STATE_OFFLINE 1 #define MEMORY_STATE_GOING_OFFLINE 2 #define MEMORY_STATE_UNKNOWN 3 enum zone_id { ZONE_DMA = 0, ZONE_DMA32, ZONE_NORMAL, ZONE_HIGHMEM, ZONE_MOVABLE, ZONE_DEVICE, ZONE_NONE, ZONE_UNKNOWN, MAX_NR_ZONES, }; struct memory_block { uint64_t index; uint64_t count; int state; int node; int nr_zones; int zones[MAX_NR_ZONES]; unsigned int removable:1; }; struct lsmem { struct path_cxt *sysmem; /* _PATH_SYS_MEMORY directory handler */ struct dirent **dirs; int ndirs; struct memory_block *blocks; int nblocks; uint64_t block_size; uint64_t mem_online; uint64_t mem_offline; struct libscols_table *table; unsigned int have_nodes : 1, raw : 1, export : 1, json : 1, noheadings : 1, summary : 1, list_all : 1, bytes : 1, want_summary : 1, want_table : 1, split_by_node : 1, split_by_state : 1, split_by_removable : 1, split_by_zones : 1, have_zones : 1; }; enum { COL_RANGE, COL_SIZE, COL_STATE, COL_REMOVABLE, COL_BLOCK, COL_NODE, COL_ZONES, }; static char *zone_names[] = { [ZONE_DMA] = "DMA", [ZONE_DMA32] = "DMA32", [ZONE_NORMAL] = "Normal", [ZONE_HIGHMEM] = "Highmem", [ZONE_MOVABLE] = "Movable", [ZONE_DEVICE] = "Device", [ZONE_NONE] = "None", /* block contains more than one zone, can't be offlined */ [ZONE_UNKNOWN] = "Unknown", }; /* column names */ struct coldesc { const char *name; /* header */ double whint; /* width hint (N < 1 is in percent of termwidth) */ int flags; /* SCOLS_FL_* */ const char *help; }; /* columns descriptions */ static struct coldesc coldescs[] = { [COL_RANGE] = { "RANGE", 0, 0, N_("start and end address of the memory range")}, [COL_SIZE] = { "SIZE", 5, SCOLS_FL_RIGHT, N_("size of the memory range")}, [COL_STATE] = { "STATE", 0, SCOLS_FL_RIGHT, N_("online status of the memory range")}, [COL_REMOVABLE] = { "REMOVABLE", 0, SCOLS_FL_RIGHT, N_("memory is removable")}, [COL_BLOCK] = { "BLOCK", 0, SCOLS_FL_RIGHT, N_("memory block number or blocks range")}, [COL_NODE] = { "NODE", 0, SCOLS_FL_RIGHT, N_("numa node of memory")}, [COL_ZONES] = { "ZONES", 0, SCOLS_FL_RIGHT, N_("valid zones for the memory range")}, }; /* columns[] array specifies all currently wanted output column. The columns * are defined by coldescs[] array and you can specify (on command line) each * column twice. That's enough, dynamically allocated array of the columns is * unnecessary overkill and over-engineering in this case */ static int columns[ARRAY_SIZE(coldescs) * 2]; static size_t ncolumns; static inline size_t err_columns_index(size_t arysz, size_t idx) { if (idx >= arysz) errx(EXIT_FAILURE, _("too many columns specified, " "the limit is %zu columns"), arysz - 1); return idx; } /* * name must be null-terminated */ static int zone_name_to_id(const char *name) { size_t i; for (i = 0; i < ARRAY_SIZE(zone_names); i++) { if (!strcasecmp(name, zone_names[i])) return i; } return ZONE_UNKNOWN; } #define add_column(ary, n, id) \ ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id)) static int column_name_to_id(const char *name, size_t namesz) { size_t i; for (i = 0; i < ARRAY_SIZE(coldescs); i++) { const char *cn = coldescs[i].name; if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) return i; } warnx(_("unknown column: %s"), name); return -1; } static inline int get_column_id(int num) { assert(num >= 0); assert((size_t) num < ncolumns); assert(columns[num] < (int) ARRAY_SIZE(coldescs)); return columns[num]; } static inline struct coldesc *get_column_desc(int num) { return &coldescs[ get_column_id(num) ]; } static inline void reset_split_policy(struct lsmem *l, int enable) { l->split_by_state = enable; l->split_by_node = enable; l->split_by_removable = enable; l->split_by_zones = enable; } static void set_split_policy(struct lsmem *l, int cols[], size_t ncols) { size_t i; reset_split_policy(l, 0); for (i = 0; i < ncols; i++) { switch (cols[i]) { case COL_STATE: l->split_by_state = 1; break; case COL_NODE: l->split_by_node = 1; break; case COL_REMOVABLE: l->split_by_removable = 1; break; case COL_ZONES: l->split_by_zones = 1; break; default: break; } } } static void add_scols_line(struct lsmem *lsmem, struct memory_block *blk) { size_t i; struct libscols_line *line; line = scols_table_new_line(lsmem->table, NULL); if (!line) err_oom(); for (i = 0; i < ncolumns; i++) { char *str = NULL; switch (get_column_id(i)) { case COL_RANGE: { uint64_t start = blk->index * lsmem->block_size; uint64_t size = blk->count * lsmem->block_size; xasprintf(&str, "0x%016"PRIx64"-0x%016"PRIx64, start, start + size - 1); break; } case COL_SIZE: if (lsmem->bytes) xasprintf(&str, "%"PRId64, (uint64_t) blk->count * lsmem->block_size); else str = size_to_human_string(SIZE_SUFFIX_1LETTER, (uint64_t) blk->count * lsmem->block_size); break; case COL_STATE: str = xstrdup( blk->state == MEMORY_STATE_ONLINE ? _("online") : blk->state == MEMORY_STATE_OFFLINE ? _("offline") : blk->state == MEMORY_STATE_GOING_OFFLINE ? _("on->off") : "?"); break; case COL_REMOVABLE: if (blk->state == MEMORY_STATE_ONLINE) str = xstrdup(blk->removable ? _("yes") : _("no")); break; case COL_BLOCK: if (blk->count == 1) xasprintf(&str, "%"PRId64, blk->index); else xasprintf(&str, "%"PRId64"-%"PRId64, blk->index, blk->index + blk->count - 1); break; case COL_NODE: if (lsmem->have_nodes) xasprintf(&str, "%d", blk->node); break; case COL_ZONES: if (lsmem->have_zones) { char valid_zones[BUFSIZ]; int j, zone_id; valid_zones[0] = '\0'; for (j = 0; j < blk->nr_zones; j++) { zone_id = blk->zones[j]; if (strlen(valid_zones) + strlen(zone_names[zone_id]) > BUFSIZ - 2) break; strcat(valid_zones, zone_names[zone_id]); if (j + 1 < blk->nr_zones) strcat(valid_zones, "/"); } str = xstrdup(valid_zones); } break; } if (str && scols_line_refer_data(line, i, str) != 0) err_oom(); } } static void fill_scols_table(struct lsmem *lsmem) { int i; for (i = 0; i < lsmem->nblocks; i++) add_scols_line(lsmem, &lsmem->blocks[i]); } static void print_summary(struct lsmem *lsmem) { if (lsmem->bytes) { printf("%-23s %15"PRId64"\n",_("Memory block size:"), lsmem->block_size); printf("%-23s %15"PRId64"\n",_("Total online memory:"), lsmem->mem_online); printf("%-23s %15"PRId64"\n",_("Total offline memory:"), lsmem->mem_offline); } else { char *p; if ((p = size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->block_size))) printf("%-23s %5s\n",_("Memory block size:"), p); free(p); if ((p = size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->mem_online))) printf("%-23s %5s\n",_("Total online memory:"), p); free(p); if ((p = size_to_human_string(SIZE_SUFFIX_1LETTER, lsmem->mem_offline))) printf("%-23s %5s\n",_("Total offline memory:"), p); free(p); } } static int memory_block_get_node(struct lsmem *lsmem, char *name) { struct dirent *de; DIR *dir; int node; dir = ul_path_opendir(lsmem->sysmem, name); if (!dir) err(EXIT_FAILURE, _("Failed to open %s"), name); node = -1; while ((de = readdir(dir)) != NULL) { if (strncmp("node", de->d_name, 4)) continue; if (!isdigit_string(de->d_name + 4)) continue; node = strtol(de->d_name + 4, NULL, 10); break; } closedir(dir); return node; } static void memory_block_read_attrs(struct lsmem *lsmem, char *name, struct memory_block *blk) { char *line = NULL; int i, x = 0; memset(blk, 0, sizeof(*blk)); blk->count = 1; blk->state = MEMORY_STATE_UNKNOWN; blk->index = strtoumax(name + 6, NULL, 10); /* get of "memory" */ if (ul_path_readf_s32(lsmem->sysmem, &x, "%s/removable", name) == 0) blk->removable = x == 1; if (ul_path_readf_string(lsmem->sysmem, &line, "%s/state", name) > 0) { if (strcmp(line, "offline") == 0) blk->state = MEMORY_STATE_OFFLINE; else if (strcmp(line, "online") == 0) blk->state = MEMORY_STATE_ONLINE; else if (strcmp(line, "going-offline") == 0) blk->state = MEMORY_STATE_GOING_OFFLINE; free(line); } if (lsmem->have_nodes) blk->node = memory_block_get_node(lsmem, name); blk->nr_zones = 0; if (lsmem->have_zones && ul_path_readf_string(lsmem->sysmem, &line, "%s/valid_zones", name) > 0) { char *token = strtok(line, " "); for (i = 0; token && i < MAX_NR_ZONES; i++) { blk->zones[i] = zone_name_to_id(token); blk->nr_zones++; token = strtok(NULL, " "); } free(line); } } static int is_mergeable(struct lsmem *lsmem, struct memory_block *blk) { struct memory_block *curr; int i; if (!lsmem->nblocks) return 0; curr = &lsmem->blocks[lsmem->nblocks - 1]; if (lsmem->list_all) return 0; if (curr->index + curr->count != blk->index) return 0; if (lsmem->split_by_state && curr->state != blk->state) return 0; if (lsmem->split_by_removable && curr->removable != blk->removable) return 0; if (lsmem->split_by_node && lsmem->have_nodes) { if (curr->node != blk->node) return 0; } if (lsmem->split_by_zones && lsmem->have_zones) { if (curr->nr_zones != blk->nr_zones) return 0; for (i = 0; i < curr->nr_zones; i++) { if (curr->zones[i] == ZONE_UNKNOWN || curr->zones[i] != blk->zones[i]) return 0; } } return 1; } static void read_info(struct lsmem *lsmem) { struct memory_block blk; char buf[128]; int i; if (ul_path_read_buffer(lsmem->sysmem, buf, sizeof(buf), "block_size_bytes") <= 0) err(EXIT_FAILURE, _("failed to read memory block size")); lsmem->block_size = strtoumax(buf, NULL, 16); for (i = 0; i < lsmem->ndirs; i++) { memory_block_read_attrs(lsmem, lsmem->dirs[i]->d_name, &blk); if (blk.state == MEMORY_STATE_ONLINE) lsmem->mem_online += lsmem->block_size; else lsmem->mem_offline += lsmem->block_size; if (is_mergeable(lsmem, &blk)) { lsmem->blocks[lsmem->nblocks - 1].count++; continue; } lsmem->nblocks++; lsmem->blocks = xrealloc(lsmem->blocks, lsmem->nblocks * sizeof(blk)); *&lsmem->blocks[lsmem->nblocks - 1] = blk; } } static int memory_block_filter(const struct dirent *de) { if (strncmp("memory", de->d_name, 6)) return 0; return isdigit_string(de->d_name + 6); } static void read_basic_info(struct lsmem *lsmem) { char dir[PATH_MAX]; if (ul_path_access(lsmem->sysmem, F_OK, "block_size_bytes") != 0) errx(EXIT_FAILURE, _("This system does not support memory blocks")); ul_path_get_abspath(lsmem->sysmem, dir, sizeof(dir), NULL); lsmem->ndirs = scandir(dir, &lsmem->dirs, memory_block_filter, versionsort); if (lsmem->ndirs <= 0) err(EXIT_FAILURE, _("Failed to read %s"), dir); if (memory_block_get_node(lsmem, lsmem->dirs[0]->d_name) != -1) lsmem->have_nodes = 1; /* The valid_zones sysmem attribute was introduced with kernel 3.18 */ if (ul_path_access(lsmem->sysmem, F_OK, "memory0/valid_zones") == 0) lsmem->have_zones = 1; } static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; size_t i; fputs(USAGE_HEADER, out); fprintf(out, _(" %s [options]\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("List the ranges of available memory with their online status.\n"), out); fputs(USAGE_OPTIONS, out); fputs(_(" -J, --json use JSON output format\n"), out); fputs(_(" -P, --pairs use key=\"value\" output format\n"), out); fputs(_(" -a, --all list each individual memory block\n"), out); fputs(_(" -b, --bytes print SIZE in bytes rather than in human readable format\n"), out); fputs(_(" -n, --noheadings don't print headings\n"), out); fputs(_(" -o, --output output columns\n"), out); fputs(_(" --output-all output all columns\n"), out); fputs(_(" -r, --raw use raw output format\n"), out); fputs(_(" -S, --split split ranges by specified columns\n"), out); fputs(_(" -s, --sysroot use the specified directory as system root\n"), out); fputs(_(" --summary[=when] print summary information (never,always or only)\n"), out); fputs(USAGE_SEPARATOR, out); printf(USAGE_HELP_OPTIONS(22)); fputs(USAGE_COLUMNS, out); for (i = 0; i < ARRAY_SIZE(coldescs); i++) fprintf(out, " %10s %s\n", coldescs[i].name, _(coldescs[i].help)); printf(USAGE_MAN_TAIL("lsmem(1)")); exit(EXIT_SUCCESS); } int main(int argc, char **argv) { struct lsmem _lsmem = { .want_table = 1, .want_summary = 1 }, *lsmem = &_lsmem; const char *outarg = NULL, *splitarg = NULL, *prefix = NULL; int c; size_t i; enum { LSMEM_OPT_SUMARRY = CHAR_MAX + 1, OPT_OUTPUT_ALL }; static const struct option longopts[] = { {"all", no_argument, NULL, 'a'}, {"bytes", no_argument, NULL, 'b'}, {"help", no_argument, NULL, 'h'}, {"json", no_argument, NULL, 'J'}, {"noheadings", no_argument, NULL, 'n'}, {"output", required_argument, NULL, 'o'}, {"output-all", no_argument, NULL, OPT_OUTPUT_ALL}, {"pairs", no_argument, NULL, 'P'}, {"raw", no_argument, NULL, 'r'}, {"sysroot", required_argument, NULL, 's'}, {"split", required_argument, NULL, 'S'}, {"version", no_argument, NULL, 'V'}, {"summary", optional_argument, NULL, LSMEM_OPT_SUMARRY }, {NULL, 0, NULL, 0} }; static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ { 'J', 'P', 'r' }, { 'S', 'a' }, { 0 } }; int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); close_stdout_atexit(); while ((c = getopt_long(argc, argv, "abhJno:PrS:s:V", longopts, NULL)) != -1) { err_exclusive_options(c, longopts, excl, excl_st); switch (c) { case 'a': lsmem->list_all = 1; break; case 'b': lsmem->bytes = 1; break; case 'J': lsmem->json = 1; lsmem->want_summary = 0; break; case 'n': lsmem->noheadings = 1; break; case 'o': outarg = optarg; break; case OPT_OUTPUT_ALL: for (ncolumns = 0; (size_t)ncolumns < ARRAY_SIZE(coldescs); ncolumns++) columns[ncolumns] = ncolumns; break; case 'P': lsmem->export = 1; lsmem->want_summary = 0; break; case 'r': lsmem->raw = 1; lsmem->want_summary = 0; break; case 's': prefix = optarg; break; case 'S': splitarg = optarg; break; case LSMEM_OPT_SUMARRY: if (optarg) { if (strcmp(optarg, "never") == 0) lsmem->want_summary = 0; else if (strcmp(optarg, "only") == 0) lsmem->want_table = 0; else if (strcmp(optarg, "always") == 0) lsmem->want_summary = 1; else errx(EXIT_FAILURE, _("unsupported --summary argument")); } else lsmem->want_table = 0; break; case 'h': usage(); case 'V': print_version(EXIT_SUCCESS); default: errtryhelp(EXIT_FAILURE); } } if (argc != optind) { warnx(_("bad usage")); errtryhelp(EXIT_FAILURE); } if (lsmem->want_table + lsmem->want_summary == 0) errx(EXIT_FAILURE, _("options --{raw,json,pairs} and --summary=only are mutually exclusive")); ul_path_init_debug(); lsmem->sysmem = ul_new_path(_PATH_SYS_MEMORY); if (!lsmem->sysmem) err(EXIT_FAILURE, _("failed to initialize %s handler"), _PATH_SYS_MEMORY); if (prefix && ul_path_set_prefix(lsmem->sysmem, prefix) != 0) err(EXIT_FAILURE, _("invalid argument to --sysroot")); /* Shortcut to avoid scols machinery on --summary=only */ if (lsmem->want_table == 0 && lsmem->want_summary) { read_basic_info(lsmem); read_info(lsmem); print_summary(lsmem); return EXIT_SUCCESS; } /* * Default columns */ if (!ncolumns) { add_column(columns, ncolumns++, COL_RANGE); add_column(columns, ncolumns++, COL_SIZE); add_column(columns, ncolumns++, COL_STATE); add_column(columns, ncolumns++, COL_REMOVABLE); add_column(columns, ncolumns++, COL_BLOCK); } if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns), &ncolumns, column_name_to_id) < 0) return EXIT_FAILURE; /* * Initialize output */ scols_init_debug(0); if (!(lsmem->table = scols_new_table())) errx(EXIT_FAILURE, _("failed to initialize output table")); scols_table_enable_raw(lsmem->table, lsmem->raw); scols_table_enable_export(lsmem->table, lsmem->export); scols_table_enable_json(lsmem->table, lsmem->json); scols_table_enable_noheadings(lsmem->table, lsmem->noheadings); if (lsmem->json) scols_table_set_name(lsmem->table, "memory"); for (i = 0; i < ncolumns; i++) { struct coldesc *ci = get_column_desc(i); struct libscols_column *cl; cl = scols_table_new_column(lsmem->table, ci->name, ci->whint, ci->flags); if (!cl) err(EXIT_FAILURE, _("Failed to initialize output column")); if (lsmem->json) { int id = get_column_id(i); switch (id) { case COL_SIZE: if (!lsmem->bytes) break; /* fallthrough */ case COL_NODE: scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); break; case COL_REMOVABLE: scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN); break; } } } if (splitarg) { int split[ARRAY_SIZE(coldescs)] = { 0 }; static size_t nsplits = 0; if (strcasecmp(splitarg, "none") == 0) ; else if (string_add_to_idarray(splitarg, split, ARRAY_SIZE(split), &nsplits, column_name_to_id) < 0) return EXIT_FAILURE; set_split_policy(lsmem, split, nsplits); } else /* follow output columns */ set_split_policy(lsmem, columns, ncolumns); /* * Read data and print output */ read_basic_info(lsmem); read_info(lsmem); if (lsmem->want_table) { fill_scols_table(lsmem); scols_print_table(lsmem->table); if (lsmem->want_summary) fputc('\n', stdout); } if (lsmem->want_summary) print_summary(lsmem); scols_unref_table(lsmem->table); ul_unref_path(lsmem->sysmem); return 0; }