diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | configure.ac | 7 | ||||
-rw-r--r-- | sys-utils/Makemodule.am | 8 | ||||
-rw-r--r-- | sys-utils/lsmem.1 | 95 | ||||
-rw-r--r-- | sys-utils/lsmem.c | 494 |
5 files changed, 605 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index 064010b20..6ead2f1dd 100644 --- a/.gitignore +++ b/.gitignore @@ -110,6 +110,7 @@ update.log /lscpu /lslocks /lslogins +/lsmem /lsns /mcookie /mesg diff --git a/configure.ac b/configure.ac index 1bd7d2e89..505abdaac 100644 --- a/configure.ac +++ b/configure.ac @@ -1475,6 +1475,13 @@ UL_BUILD_INIT([flock], [check]) UL_REQUIRES_HAVE([flock], [timer], [timer_create function]) AM_CONDITIONAL([BUILD_FLOCK], [test "x$build_flock" = xyes]) +AC_ARG_ENABLE([lsmem], + AS_HELP_STRING([--disable-lsmem], [do not build lsmem]), + [], [UL_DEFAULT_ENABLE([lsmem], [yes])] +) +UL_BUILD_INIT([lsmem]) +AM_CONDITIONAL([BUILD_LSMEM], [test "x$build_lsmem" = xyes]) + UL_BUILD_INIT([ipcmk], [yes]) AM_CONDITIONAL([BUILD_IPCMK], [test "x$build_ipcmk" = xyes]) diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index 94003031f..5de02e1fb 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -1,3 +1,11 @@ +if BUILD_LSMEM +usrbin_exec_PROGRAMS += lsmem +dist_man_MANS += sys-utils/lsmem.1 +lsmem_SOURCES = sys-utils/lsmem.c +lsmem_LDADD = $(LDADD) libcommon.la libsmartcols.la +lsmem_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) +endif + if BUILD_FLOCK usrbin_exec_PROGRAMS += flock dist_man_MANS += sys-utils/flock.1 diff --git a/sys-utils/lsmem.1 b/sys-utils/lsmem.1 new file mode 100644 index 000000000..bb457dd74 --- /dev/null +++ b/sys-utils/lsmem.1 @@ -0,0 +1,95 @@ +.TH LSMEM 1 "October 2016" "util-linux" "User Commands" +.SH NAME +lsmem \- list the ranges of available memory with their online status +.SH SYNOPSIS +.B lsmem +.RB [ \-h "] [" \-V "] [" \-a "] [" \-s " \fIdirectory\fP] [" \-e [=\fIlist\fP]| \-p [=\fIlist\fP]] +.br +.SH DESCRIPTION +The \fBlsmem\fP command lists the ranges of available memory with their online +status. The listed memory blocks correspond to the memory block representation +in sysfs. The command also shows the memory block size and the amount of memory +in online and offline state. +.sp +By default \fBlsmem\fP prints a human readable output table with the RANGE, +SIZE, STATE, REMOVABLE, and BLOCK columns. +.sp +Use the \fB--extended\fP or \fB--parse\fP option to customize the output +table. You can change the table format and limit, extend, or rearrange the +table columns. +.sp +Not all columns are supported on all systems. If an unsupported column is +specified, \fBlsmem\fP prints the column but does not provide any data for it. + +.SS COLUMNS +.TP +.B RANGE +Start and end address of the memory range. +.TP +.B SIZE +Size of the memory range. +.TP +.B STATE +Indication of the online status of the memory range. State "on->off" means +that the address range is in transition from online to offline. +.TP +.B REMOVABLE +"yes" if the memory range can be set offline, "no" if it cannot be set offline. +A dash ("\-") means that the range is already offline. +.TP +.B BLOCK +Memory block number or numbers that correspond to the memory range. +.TP +.B NODE +Numa node of memory. +.SH OPTIONS +.TP +.BR \-a ", " \-\-all +List each individual memory block, instead of combining memory blocks with +similar attributes. +.TP +.BR \-e , " \-\-extended" [=\fIlist\fP] +Display extended memory information in human-readable format. + +If the \fIlist\fP argument is omitted, all columns for which data is available +are included in the command output. + +The \fIlist\fP argument is a comma-separated list of column labels that specifies +the columns to be included in the output table and their sequence in the +table. See \fBCOLUMNS\fP for valid column labels. Column labels are not case +sensitive. The specification consisting of option, equal sign (=), and \fIlist\fP +must not contain any white space. + +Examples: '\fB-e=range,size,state\fP' or '\fB--extended=NODE,state,size,RANGE\fP'. +.TP +.BR \-h ", " \-\-help +Display help text and exit. +.TP +.BR \-p , " \-\-parse" [=\fIlist\fP] +Optimize the command output for easy parsing. + +If the \fIlist\fP argument is omitted, all columns for which data is available +are included in the command output. + +The \fIlist\fP argument is a comma-separated list of column labels that specifies +the columns to be included in the output table and their sequence in the +table. See \fBCOLUMNS\fP for valid column labels. Column labels are not case +sensitive. The specification consisting of option, equal sign (=), and \fIlist\fP +must not contain any white space. + +Examples: '\fB-p=range,size,state\fP' or '\fB--parse=NODE,state,size,RANGE\fP'. +.TP +.BR \-s , " \-\-sysroot " \fIdirectory\fP +Gather memory data for a Linux instance other than the instance from which the +\fBlsmem\fP command is issued. The specified \fIdirectory\fP is the system +root of the Linux instance to be inspected. +.TP +.BR \-V ", " \-\-version +Display version information and exit. +.SH SEE ALSO +.BR chmem (8) +.SH AVAILABILITY +The \fBlsmem\fP command is part of the util-linux package and is available from +.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/sys-utils/lsmem.c b/sys-utils/lsmem.c new file mode 100644 index 000000000..13e2414d8 --- /dev/null +++ b/sys-utils/lsmem.c @@ -0,0 +1,494 @@ +/* + * lsmem - Show memory configuration + * + * Copyright IBM Corp. 2016 + * + * 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 <c.h> +#include <nls.h> +#include <path.h> +#include <strutils.h> +#include <closestream.h> +#include <xalloc.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <dirent.h> +#include <strutils.h> +#include <fcntl.h> +#include <inttypes.h> +#include <assert.h> +#include <optutils.h> +#include <libsmartcols.h> + +#define _PATH_SYS_MEMORY "/sys/devices/system/memory" +#define _PATH_SYS_MEMORY_BLOCK_SIZE _PATH_SYS_MEMORY "/block_size_bytes" + +#define MEMORY_STATE_ONLINE 0 +#define MEMORY_STATE_OFFLINE 1 +#define MEMORY_STATE_GOING_OFFLINE 2 +#define MEMORY_STATE_UNKNOWN 3 + +struct memory_block { + uint64_t index; + uint64_t count; + int state; + int node; + unsigned int removable:1; +}; + +enum { + OUTPUT_READABLE = 0, /* default */ + OUTPUT_PARSABLE, /* -p */ +}; + +struct lsmem_desc { + struct dirent **dirs; + int ndirs; + struct memory_block *blocks; + int nblocks; + unsigned int have_nodes : 1; + uint64_t block_size; + uint64_t mem_online; + uint64_t mem_offline; +}; + +struct lsmem_modifier { + int mode; /* OUTPUT_* */ + unsigned int compat : 1; + unsigned int list_all_blocks : 1; +}; + +enum { + COL_RANGE, + COL_SIZE, + COL_STATE, + COL_REMOVABLE, + COL_BLOCK, + COL_NODE, +}; + +struct lsmem_coldesc { + const int flags; + const char *name; + const char *help; +}; + +static struct lsmem_coldesc coldescs[] = { + [COL_RANGE] = { 0, "RANGE", N_("adress range")}, + [COL_SIZE] = { SCOLS_FL_RIGHT, "SIZE", N_("size of memory")}, + [COL_STATE] = { 0, "STATE", N_("state of memory")}, + [COL_REMOVABLE] = { 0, "REMOVABLE", N_("memory is removable")}, + [COL_BLOCK] = { 0, "BLOCK", N_("memory block")}, + [COL_NODE] = { 0, "NODE", N_("node information")}, +}; + +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 char *get_cell_header(int col, char *buf, size_t bufsz) +{ + snprintf(buf, bufsz, "%s", coldescs[col].name); + return buf; +} + +static char *get_cell_data(struct lsmem_desc *desc, int idx, int col, + struct lsmem_modifier *mod, + char *buf, size_t bufsz) +{ + struct memory_block *blk; + uint64_t start, size; + + blk = &desc->blocks[idx]; + start = blk->index * desc->block_size; + size = blk->count * desc->block_size; + + switch (col) { + case COL_RANGE: + snprintf(buf, bufsz, "0x%016"PRIx64"-0x%016"PRIx64, start, start + size - 1); + break; + case COL_SIZE: + if (mod->mode == OUTPUT_PARSABLE) + snprintf(buf, bufsz, "%"PRId64, size); + else + snprintf(buf, bufsz, "%s", size_to_human_string(SIZE_SUFFIX_1LETTER, size)); + break; + case COL_STATE: + if (blk->state == MEMORY_STATE_ONLINE) + snprintf(buf, bufsz, _("online")); + else if (blk->state == MEMORY_STATE_OFFLINE) + snprintf(buf, bufsz, _("offline")); + else if (blk->state == MEMORY_STATE_GOING_OFFLINE) + snprintf(buf, bufsz, _("on->off")); + else /* unknown */ + snprintf(buf, bufsz, "?"); + break; + case COL_REMOVABLE: + if (blk->state == MEMORY_STATE_ONLINE) + snprintf(buf, bufsz, "%s", blk->removable ? _("yes") : _("no")); + else + snprintf(buf, bufsz, "-"); + break; + case COL_BLOCK: + if (blk->count == 1) + snprintf(buf, bufsz, "%"PRId64, blk->index); + else + snprintf(buf, bufsz, "%"PRId64"-%"PRId64, + blk->index, blk->index + blk->count - 1); + break; + case COL_NODE: + if (desc->have_nodes) + snprintf(buf, bufsz, "%d", blk->node); + else + snprintf(buf, bufsz, "-"); + break; + } + return buf; +} + +static void print_parsable(struct lsmem_desc *desc, int *cols, int ncols, + struct lsmem_modifier *mod) +{ + char buf[BUFSIZ], *data; + int c, i; + + fputs("# ", stdout); + for (i = 0; i < ncols; i++) { + data = get_cell_header(cols[i], buf, sizeof(buf)); + if (i > 0) + fputc(',', stdout); + fputs(data && *data ? data : "", stdout); + } + fputc('\n', stdout); + + for (i = 0; i < desc->nblocks; i++) { + for (c = 0; c < ncols; c++) { + if (c > 0) + putchar(','); + data = get_cell_data(desc, i, cols[c], mod, buf, sizeof(buf)); + fputs(data && *data ? data : "", stdout); + } + fputc('\n', stdout); + } +} + +static void print_readable_table(struct lsmem_desc *desc, int *cols, int ncols, + struct lsmem_modifier *mod) +{ + struct libscols_table *table; + char buf[BUFSIZ], *data; + int c, i; + + scols_init_debug(0); + table = scols_new_table(); + if (!table) + err(EXIT_FAILURE, _("Failed to initialize output table")); + for (i = 0; i < ncols; i++) { + data = get_cell_header(cols[i], buf, sizeof(buf)); + if (!scols_table_new_column(table, xstrdup(data), 0, coldescs[i].flags)) + err(EXIT_FAILURE, _("Failed to initialize output column")); + } + for (i = 0; i < desc->nblocks; i++) { + struct libscols_line *line; + + line = scols_table_new_line(table, NULL); + if (!line) + err(EXIT_FAILURE, _("Failed to initialize output line")); + + for (c = 0; c < ncols; c++) { + data = get_cell_data(desc, i, cols[c], mod, buf, sizeof(buf)); + if (!data || !*data) + data = "-"; + scols_line_set_data(line, c, data); + } + } + scols_print_table(table); + scols_unref_table(table); +} + +static void print_readable(struct lsmem_desc *desc, int *cols, int ncols, + struct lsmem_modifier *mod) +{ + print_readable_table(desc, cols, ncols, mod); + fputc('\n', stdout); + fprintf(stdout, _("Memory block size : %8s\n"), + size_to_human_string(SIZE_SUFFIX_1LETTER, desc->block_size)); + fprintf(stdout, _("Total online memory : %8s\n"), + size_to_human_string(SIZE_SUFFIX_1LETTER, desc->mem_online)); + fprintf(stdout, _("Total offline memory: %8s\n"), + size_to_human_string(SIZE_SUFFIX_1LETTER, desc->mem_offline)); +} + +static int memory_block_get_node(char *name) +{ + struct dirent *de; + char *path; + DIR *dir; + int node; + + path = path_strdup(_PATH_SYS_MEMORY"/%s", name); + dir = opendir(path); + free(path); + if (!dir) + err(EXIT_FAILURE, _("Failed to open %s"), path); + 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); + } + closedir(dir); + return node; +} + +static void memory_block_read_attrs(struct lsmem_desc *desc, char *name, + struct memory_block *blk) +{ + char line[BUFSIZ]; + + blk->count = 1; + blk->index = strtoumax(name + 6, NULL, 10); /* get <num> of "memory<num>" */ + blk->removable = path_read_u64(_PATH_SYS_MEMORY"/%s/%s", name, "removable"); + blk->state = MEMORY_STATE_UNKNOWN; + path_read_str(line, sizeof(line), _PATH_SYS_MEMORY"/%s/%s", name, "state"); + 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; + if (desc->have_nodes) + blk->node = memory_block_get_node(name); +} + +static int is_mergeable(struct lsmem_desc *desc, char *enabled, + struct memory_block *blk, + struct lsmem_modifier *mod) +{ + struct memory_block *curr; + + if (!desc->nblocks) + return 0; + curr = &desc->blocks[desc->nblocks - 1]; + if (mod->list_all_blocks) + return 0; + if (curr->index + curr->count != blk->index) + return 0; + if (enabled[COL_STATE] && (curr->state != blk->state)) + return 0; + if (enabled[COL_REMOVABLE] && (curr->removable != blk->removable)) + return 0; + if (enabled[COL_NODE] && desc->have_nodes) { + if (curr->node != blk->node) + return 0; + } + return 1; +} + +static void read_info(struct lsmem_desc *desc, int *cols, int ncols, + struct lsmem_modifier *mod) +{ + char enabled[ARRAY_SIZE(coldescs)]; + struct memory_block blk; + char line[BUFSIZ]; + int i; + + path_read_str(line, sizeof(line), _PATH_SYS_MEMORY_BLOCK_SIZE); + desc->block_size = strtoumax(line, NULL, 16); + + memset(enabled, 0, sizeof(enabled)); + for (i = 0; i < ncols; i++) + enabled[cols[i]] = 1; + for (i = 0; i < desc->ndirs; i++) { + memory_block_read_attrs(desc, desc->dirs[i]->d_name, &blk); + if (is_mergeable(desc, enabled, &blk, mod)) { + desc->blocks[desc->nblocks - 1].count++; + continue; + } + desc->nblocks++; + desc->blocks = xrealloc(desc->blocks, desc->nblocks * sizeof(blk)); + *&desc->blocks[desc->nblocks - 1] = blk; + } + for (i = 0; i < desc->nblocks; i++) { + if (desc->blocks[i].state == MEMORY_STATE_ONLINE) + desc->mem_online += desc->block_size * desc->blocks[i].count; + else + desc->mem_offline += desc->block_size * desc->blocks[i].count; + } +} + +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_desc *desc) +{ + char *dir; + + if (!path_exist(_PATH_SYS_MEMORY_BLOCK_SIZE)) + errx(EXIT_FAILURE, _("This system does not support memory blocks")); + + dir = path_strdup(_PATH_SYS_MEMORY); + desc->ndirs = scandir(dir, &desc->dirs, memory_block_filter, versionsort); + free(dir); + if (desc->ndirs <= 0) + err(EXIT_FAILURE, _("Failed to read %s"), _PATH_SYS_MEMORY); + + if (memory_block_get_node(desc->dirs[0]->d_name) != -1) + desc->have_nodes = 1; +} + +static void __attribute__((__noreturn__)) lsmem_usage(FILE *out) +{ + unsigned int 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(_(" -a, --all list each individiual memory block\n"), out); + fputs(_(" -e, --extended[=<list>] print customized output in a readable format\n"), out); + fputs(_(" -p, --parse[=<list>] print customized output in a parsable format\n"), out); + fputs(_(" -s, --sysroot <dir> use the specified directory as system root\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + + fputs(_("\nAvailable columns:\n"), out); + + for (i = 0; i < ARRAY_SIZE(coldescs); i++) + fprintf(out, " %10s %s\n", coldescs[i].name, coldescs[i].help); + + fprintf(out, USAGE_MAN_TAIL("lsmem(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + struct lsmem_modifier _mod = { .mode = OUTPUT_READABLE, .compat = 1 }, *mod = &_mod; + struct lsmem_desc _desc = { }, *desc = &_desc; + int columns[ARRAY_SIZE(coldescs)], ncolumns = 0; + int c; + + static const struct option longopts[] = { + {"all", no_argument, NULL, 'a'}, + {"extended", optional_argument, NULL, 'e'}, + {"help", no_argument, NULL, 'h'}, + {"parse", optional_argument, NULL, 'p'}, + {"sysroot", required_argument, NULL, 's'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} + }; + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'e','p' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "ae::hp::s:V", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch (c) { + case 'a': + mod->list_all_blocks = 1; + break; + case 'h': + lsmem_usage(stdout); + break; + case 'p': + case 'e': + if (optarg) { + if (*optarg == '=') + optarg++; + ncolumns = string_to_idarray(optarg, + columns, ARRAY_SIZE(columns), + column_name_to_id); + if (ncolumns < 0) + return EXIT_FAILURE; + } + mod->mode = c == 'p' ? OUTPUT_PARSABLE : OUTPUT_READABLE; + mod->compat = 0; + break; + case 's': + path_set_prefix(optarg); + break; + case 'V': + printf(UTIL_LINUX_VERSION); + return 0; + default: + lsmem_usage(stderr); + } + } + + if (argc != optind) + lsmem_usage(stderr); + + read_basic_info(desc); + + if (!ncolumns) { + /* No list was given. Print the following lines by default */ + columns[ncolumns++] = COL_RANGE; + columns[ncolumns++] = COL_SIZE; + columns[ncolumns++] = COL_STATE; + columns[ncolumns++] = COL_REMOVABLE; + columns[ncolumns++] = COL_BLOCK; + if (!mod->compat) { + /* Print everything else what is there if the + * extended or parsable mode has been specified */ + if (desc->have_nodes) + columns[ncolumns++] = COL_NODE; + } + } + + read_info(desc, columns, ncolumns, mod); + + switch (mod->mode) { + case OUTPUT_READABLE: + print_readable(desc, columns, ncolumns, mod); + break; + case OUTPUT_PARSABLE: + print_parsable(desc, columns, ncolumns, mod); + break; + } + return 0; +} |