summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--configure.ac7
-rw-r--r--sys-utils/Makemodule.am8
-rw-r--r--sys-utils/lsmem.195
-rw-r--r--sys-utils/lsmem.c494
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;
+}