Files
s390-tools/zconf/chp/chpstat/chpstat.c
Peter Oberparleiter 6520070a46 chpstat: Fix invalid utilization data on older kernels
On kernels without support for the new "measurement_chars_full" CHPID
sysfs attribute, chpstat displays invalid utilization numbers (nan/inf).
This is due to an invalid buffer address calculation when reading the
old "measurement_chars" attribute.

Fix this by using the correct buffer address calculation.

Fixes: 026ecbafea ("chpstat: Add support for full CMCB")
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Reviewed-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-12-19 21:45:37 +01:00

2169 lines
48 KiB
C

/*
* chpstat - Tool to display channel-path statistics
*
* Copyright IBM Corp. 2024
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "cmg.h"
#include "column.h"
#include "key.h"
#include "misc.h"
#include "lib/util_base.h"
#include "lib/util_file.h"
#include "lib/util_fmt.h"
#include "lib/util_libc.h"
#include "lib/util_opt.h"
#include "lib/util_path.h"
#include "lib/util_prg.h"
#include "lib/util_rec.h"
#include "lib/zt_common.h"
/* Output format API level. */
#define API_LEVEL 1
/* Default interval in seconds between output. */
#define DEFAULT_INTERVAL 5
/* Maximum interval in seconds after which timestamps wrap twice (approx.). */
#define MAX_INTERVAL 2140
/* Maximum number of CHPIDS. */
#define NUM_CHPIDS 256
/* Highest valid CHPID number. */
#define MAX_CHPID 255
/* Sysfs paths. */
#define SYS_CSS0 "devices/css0"
#define SYS_CM_ENABLE SYS_CSS0 "/cm_enable"
/* ANSI X3.64 terminal control codes. */
#define ANSI_CLS "\e[2J"
#define ANSI_LOCATE(x, y) "\e[" STRINGIFY(x) ";" STRINGIFY(y) "H"
#define ANSI_BOLD "\e[1m"
#define ANSI_REVERSE "\e[7m"
#define ANSI_RESET "\e[0m"
#define UNIT_DEC 1000UL
#define UNIT_BIN 1024UL
#define UNIT_AUTO 0
#define strlen_i(a) ((int)strlen(a))
#define strlen_u(a) ((unsigned int)strlen(a))
/* Channel-path measurement facility status values. */
enum cm_status_t {
CM_UNSUPPORTED,
CM_DISABLED,
CM_ENABLED,
};
/* Program information. */
static const struct util_prg prg = {
.desc = "Use chpstat to view channel-path statistics such as "
"utilization and throughput, and to query and control the "
"status of the channel-path statistics function.\n"
"\n"
"When run without further options, data for all channel-paths "
"is displayed repeatedly with a " STRINGIFY(DEFAULT_INTERVAL)
" second delay in table format. You can limit output to "
"specific channel-paths by listing the associated CHPIDs on "
"the command line.",
.copyright_vec = {
{
.owner = "IBM Corp.",
.pub_first = 2024,
.pub_last = 2024,
},
UTIL_PRG_COPYRIGHT_END
},
.args = "[CHPIDS] [ACTIONS]",
};
enum {
OPT_STATUS = 's',
OPT_ENABLE = 'e',
OPT_DISABLE = 'd',
OPT_LIST_COLUMNS = 'l',
OPT_LIST_KEYS = 'L',
OPT_ITERATIONS = 'n',
OPT_INTERVAL = 'i',
OPT_COLUMNS = 'c',
OPT_KEYS = 'k',
OPT_ALL = 'a',
/* Options without short version below. */
OPT_FORMAT = 0x80, /* First non-printable character. */
OPT_CHARS,
OPT_UTIL,
OPT_METRICS,
OPT_CMG,
OPT_SCALE,
OPT_NO_ANSI,
OPT_NO_PREFIX,
OPT_DEBUG,
};
enum command_t {
CMD_ENABLE = OPT_ENABLE,
CMD_DISABLE = OPT_DISABLE,
CMD_STATUS = OPT_STATUS,
CMD_LIST_COLUMNS = OPT_LIST_COLUMNS,
CMD_LIST_KEYS = OPT_LIST_KEYS,
CMD_TABLE,
CMD_LIST,
};
/*
* Configuration of command line options
*/
static struct util_opt opt_vec[] = {
{
.desc = "ACTIONS",
.flags = UTIL_OPT_FLAG_SECTION,
},
{
.option = { "status", no_argument, NULL, OPT_STATUS },
.desc = "Show channel-path statistics status",
},
{
.option = { "enable", no_argument, NULL, OPT_ENABLE },
.desc = "Enable channel-path statistics",
},
{
.option = { "disable", no_argument, NULL, OPT_DISABLE },
.desc = "Disable channel-path statistics",
},
{
.option = { "list-columns", no_argument, NULL,
OPT_LIST_COLUMNS},
.desc = "List available table columns",
},
{
.option = { "list-keys", no_argument, NULL, OPT_LIST_KEYS},
.desc = "List available data keys",
},
UTIL_OPT_HELP,
UTIL_OPT_VERSION,
{
.desc = "OPTIONS",
.flags = UTIL_OPT_FLAG_SECTION,
},
{
.option = { "iterations", required_argument, NULL,
OPT_ITERATIONS },
.argument = "NUM",
.desc = "Display NUM reports before ending (0 for no end)",
},
{
.option = { "interval", required_argument, NULL,
OPT_INTERVAL },
.argument = "NUM",
.desc = "Pause NUM seconds between display",
},
{
.option = { "columns", required_argument, NULL, OPT_COLUMNS },
.argument = "COL,..",
.desc = "Show only specified columns in table output",
},
{
.option = { "keys", required_argument, NULL, OPT_KEYS },
.argument = "KEY,..",
.desc = "Show only data for specified keys in list output",
},
{
.option = { "all", no_argument, NULL, OPT_ALL },
.desc = "Show all table columns and key data",
},
{
.option = { "scale", required_argument, NULL, OPT_SCALE },
.argument = "UNIT",
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "Scale BPS values by UNIT (number, suffix or auto)",
},
{
.option = { "cmg", required_argument, NULL, OPT_CMG },
.argument = "CMG,..",
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "Show data for specified CMGs only",
},
{
.option = { "format", required_argument, NULL, OPT_FORMAT },
.argument = "FORMAT",
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "List data in specified FORMAT (" FMT_TYPE_NAMES ")",
},
{
.option = { "chars", no_argument, NULL, OPT_CHARS },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "List channel-path measurement characteristics",
},
{
.option = { "util", no_argument, NULL, OPT_UTIL },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "List unprocessed utilization data",
},
{
.option = { "metrics", no_argument, NULL, OPT_METRICS },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "List performance metrics",
},
{
.option = { "no-ansi", no_argument, NULL, OPT_NO_ANSI },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "Do not use ANSI terminal codes in output",
},
{
.option = { "no-prefix", no_argument, NULL, OPT_NO_PREFIX },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "Hide key prefix in pairs output format",
},
{
.option = { "debug", no_argument, NULL, OPT_DEBUG },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "Print debugging information",
},
UTIL_OPT_END
};
/* Settings from command line options. */
static struct {
bool cmd_specified;
enum command_t cmd;
int iterations;
bool forever;
int interval;
int groups;
bool groups_specified;
bool all;
bool use_ansi;
bool use_prefix;
bool debug;
enum util_fmt_t fmt;
bool fmt_specified;
bool cmgs_specified;
bool columns_specified;
bool keys_specified;
unsigned long unit;
char unit_suffix;
bool unit_specified;
} opts;
/* Per CHPID run-time data. */
static struct chpid_data_t {
int id;
bool selected;
struct cmg_t *cmg;
int type;
int shared;
char *speed;
struct cmg_data_t data;
} chpid_data[NUM_CHPIDS];
#define chpid_for_each(c) \
for (int __i = 0; __i < NUM_CHPIDS && ((c) = &chpid_data[__i]); __i++)
/*
* Buffer should be large enough to contain a full table of output columns for
* up to 256 CHPIDs.
*/
#define OUTPUT_BUFFER_SIZE 131072
/*
* Make stdout fully buffered to enable writing a full table to terminal in a
* single flush write. This helps reduce visual flicker on output terminals.
*/
static void buffer_stdout(bool on)
{
static void *buffer;
if (on) {
free(buffer);
buffer = util_zalloc(OUTPUT_BUFFER_SIZE);
setvbuf(stdout, buffer, _IOFBF, OUTPUT_BUFFER_SIZE);
} else {
setlinebuf(stdout);
free(buffer);
buffer = NULL;
}
}
/*
* Emit ANSI terminal control @codes depending on the global use_ansi option.
*/
static void ansi(const char *codes)
{
if (opts.use_ansi)
printf("%s", codes);
}
/*
* Initialize default settings.
*/
static void init_opts(void)
{
opts.cmd = CMD_TABLE;
opts.cmd_specified = false;
opts.iterations = -1;
opts.forever = true;
opts.interval = DEFAULT_INTERVAL;
opts.groups = 0;
opts.groups_specified = false;
opts.all = false;
opts.use_ansi = isatty(STDOUT_FILENO);
opts.use_prefix = true;
opts.fmt = FMT_JSON;
opts.fmt_specified = false;
opts.cmgs_specified = false;
opts.unit = UNIT_AUTO;
opts.unit_suffix = 0;
opts.unit_specified = false;
}
/*
* Initialize chpid data array.
*/
static void init_chpid_data(void)
{
int chpid;
for (chpid = 0; chpid < NUM_CHPIDS; chpid++) {
memset(&chpid_data[chpid], 0, sizeof(chpid_data[chpid]));
chpid_data[chpid].id = chpid;
}
}
/*
* Release memory used by chpid data array.
*/
static void free_chpid_data(void)
{
int chpid;
for (chpid = 0; chpid < NUM_CHPIDS; chpid++)
free(chpid_data[chpid].speed);
}
/*
* Parse a command line argument @arg that was specified for command line
* option @name as an integer and check if the result is between @min and @max.
*
* Return the resulting integer on success. On failure, exit with an
* appropriate error message.
*/
static int parse_int(const char *name, const char *arg, long min, long max)
{
char *endptr;
long v;
v = strtol(arg, &endptr, 10);
if (*endptr || !*arg) {
errx(EXIT_USAGE, "Value for option --%s is invalid: %s",
name, arg);
}
if (v < min) {
errx(EXIT_USAGE, "Value %s for option --%s is too small "
"(min %ld)", arg, name, min);
}
if (v > max) {
errx(EXIT_USAGE, "Value %s for option --%s is too large "
"(max %ld)", arg, name, max);
}
return (int)v;
}
/*
* Parse a command line argument @arg as CHPID. Return the CHPID on success.
* On failure, exit with an error message.
*/
static int parse_chpid(const char *arg)
{
int id;
char c;
if ((sscanf(arg, "%x %c", &id, &c) == 1 ||
sscanf(arg, "0.%x %c", &id, &c) == 1) && id <= MAX_CHPID)
return id;
errx(EXIT_USAGE, "Invalid CHPID '%s'", arg);
}
/*
* Parse the comma-separated list of column names in @arg and select the
* associated columns.
*/
static void parse_columns(char *arg)
{
struct column_t *col;
char *name;
while ((name = strsep(&arg, ","))) {
col = column_get_by_name(name);
if (!col)
errx(EXIT_USAGE, "Unknown column name '%s'", name);
column_select(col);
}
}
/*
* Parse the comma-separated list of key names in @arg and add it to the
* list of specified keys.
*/
static void parse_keys(char *arg)
{
struct key_t *key;
char *name;
while ((name = strsep(&arg, ","))) {
key = key_get_by_name(name);
if (!key)
errx(EXIT_USAGE, "Unknown key '%s'", name);
key_select(key);
}
}
/*
* Parse the comma-separated list of CMG values in @arg and select the
* associated CMG.
*/
static void parse_cmgs(char *arg)
{
struct cmg_t *cmg_t;
char *name;
int cmg;
while ((name = strsep(&arg, ","))) {
cmg = parse_int("cmg", name, 1, 255);
cmg_t = cmg_get(cmg);
if (!cmg_t)
errx(EXIT_USAGE, "Unsupported CMG '%s'", name);
cmg_t->selected = true;
}
}
/*
* Parse a scale unit value in @arg and return the resulting scaling factor.
*/
static unsigned long parse_unit(char *arg)
{
unsigned long unit;
char *endptr;
if (strlen(arg) == 1) {
opts.unit_suffix = (char)toupper(*arg);
switch (opts.unit_suffix) {
case 'K':
return UNIT_BIN;
case 'M':
return UNIT_BIN * UNIT_BIN;
case 'G':
return UNIT_BIN * UNIT_BIN * UNIT_BIN;
case 'T':
return UNIT_BIN * UNIT_BIN * UNIT_BIN * UNIT_BIN;
default:
break;
}
}
opts.unit_suffix = 0;
if (strcmp(arg, "auto") == 0)
return UNIT_AUTO;
/* Parse as number. */
unit = strtoul(arg, &endptr, 10);
if (!*endptr && *arg && unit > 0 &&
!(unit == ULONG_MAX && errno == ERANGE)) {
return unit;
}
errx(EXIT_USAGE, "Unsupported scaling unit '%s'", optarg);
}
/*
* Determine the current status of the channel-path measurement facility.
*/
static enum cm_status_t get_cm_status(void)
{
enum cm_status_t result;
char *path;
int i;
path = util_path_sysfs(SYS_CM_ENABLE);
if (!util_path_exists(path)) {
result = CM_UNSUPPORTED;
goto out;
}
if (util_file_read_i(&i, 10, path))
errx(EXIT_RUNTIME, "Unable to read file '%s'", path);
if (i == 0)
result = CM_DISABLED;
else
result = CM_ENABLED;
out:
free(path);
return result;
}
/*
* Change the channel-path facility status to the value represented by @on.
*/
static void set_cm_enable(long on)
{
char *path;
if (on)
printf("Enabling channel-path statistics\n");
else
printf("Disabling channel-path statistics\n");
path = util_path_sysfs(SYS_CM_ENABLE);
if (util_file_write_l(on, 10, "%s", path) == 0) {
free(path);
return;
}
switch (errno) {
case EIO:
errx(EXIT_RUNTIME, "Unable to enable channel-path statistics\n"
"Check if your system is authorized (see section "
"AUTHORIZATION in 'man %s')",
program_invocation_short_name);
break;
default:
err(EXIT_RUNTIME, "Unable to write to file '%s'", path);
}
}
/*
* Get sysfs path for attribute @filename of CHPID @chpid. If @filename is
* not specified, return path to CHPID sysfs directory.
*/
static char *get_chpid_path(int chpid, const char *filename)
{
return util_path_sysfs(SYS_CSS0 "/chp0.%02x%s%s", chpid,
filename ? "/" : "", filename ? filename : "");
}
/*
* Check if CHPID @chpid exists.
*/
static bool chpid_exists(int chpid)
{
char *path;
bool rc;
path = get_chpid_path(chpid, NULL);
rc = util_path_exists("%s", path);
free(path);
return rc;
}
/*
* Print debugging information related to reading file @path.
*/
static void debug_text(const char *path, const char *txt)
{
if (!opts.debug)
return;
if (txt)
printf("DEBUG: read(%s)='%s'\n", path, txt);
else
printf("DEBUG: read(%s)=- errno=%d\n", path, errno);
}
/*
* Print debugging information related to reading file @path.
*/
static void debug_bin(const char *path, void *buffer, size_t expect, ssize_t rc)
{
if (!opts.debug)
return;
printf("DEBUG: read(%s)=%d/%d\n", path, (int)rc, (int)expect);
util_hexdump_grp(stdout, "DEBUG", buffer, 4, (int)rc, 0);
}
static char *read_chpid_attr_as_text(int chpid, const char *name, bool try)
{
char *path, *value = NULL;
path = get_chpid_path(chpid, name);
if (try && !util_path_exists(path))
goto out;
value = util_file_read_text_file(path, true);
debug_text(path, value);
if (!value && !try)
err(EXIT_RUNTIME, "Unable to read file '%s'", path);
out:
free(path);
return value;
}
/*
* Read and return the value of the cmg sysfs attribute for CHPID @chpid.
* Return %-1 if the CMG value is "unknown".
*/
static int read_cmg(int chpid)
{
char *value;
int cmg;
value = read_chpid_attr_as_text(chpid, "cmg", false);
if (strcmp(value, "unknown") == 0)
cmg = -1;
else
cmg = atoi(value);
free(value);
return cmg;
}
/*
* Read and return the integer value of sysfs attribute @name for CHPID @chpid.
* The integer is interpreted using base @base.
*/
static int read_chpid_attr_as_int(int chpid, const char *name, int base)
{
char *value;
long i;
value = read_chpid_attr_as_text(chpid, name, false);
i = strtol(value, NULL, base);
free(value);
return (int)i;
}
/*
* Read and return the value of sysfs attribute speed_bps for CHPID @chpid.
* Return "-" if the attribute cannot be read, or if the value is unavailable.
*/
static char *read_speed(int chpid)
{
char *value;
value = read_chpid_attr_as_text(chpid, "speed_bps", true);
/* Unavailable speed value is reported as "0". */
if (value && strcmp(value, "0") == 0) {
free(value);
value = NULL;
}
return value;
}
/* Read @count bytes of binary data from file @path to @buffer. On success,
* return %true. If @allow_eof is %true, return %false if not all data could
* be read. Otherwise exit with an error message.
*/
static bool read_bin(const char *path, void *buffer, size_t count,
bool allow_eof)
{
int fd;
ssize_t rc = -1;
fd = open(path, O_RDONLY);
if (fd == -1)
goto err;
rc = read(fd, buffer, count);
debug_bin(path, buffer, count, rc);
if (rc < 0)
goto err;
if ((size_t)rc < count && !allow_eof)
goto err_eof;
close(fd);
return (size_t)rc == count;
err_eof:
errx(EXIT_RUNTIME, "Unable to read file '%s': Unexpected end of file",
path);
err:
err(EXIT_RUNTIME, "Unable to read file '%s'", path);
}
/*
* Read the channel-measurements characteristics block for CHPID @chpid to
* @cmcb. Return %true if full CMCB was read, %false otherwise.
*/
static bool read_cmcb(int chpid, cmcb_t *cmcb)
{
bool full = false;
char *path;
memset(cmcb, 0, sizeof(*cmcb));
path = get_chpid_path(chpid, "measurement_chars_full");
if (util_path_exists(path)) {
/* Full CMCB starts at offset 0. */
read_bin(path, cmcb, CMCB_SIZE, false);
full = true;
} else {
free(path);
path = get_chpid_path(chpid, "measurement_chars");
/* Partial CMCB contains data starting at word 3. */
read_bin(path, (char *)cmcb + PARTIAL_CMCB_OFFSET,
PARTIAL_CMCB_SIZE, false);
}
free(path);
return full;
}
/*
* Read the channel-utilization entry for CHPID @chpid to @cue.
*/
static void read_cue(int chpid, int cmg, cue_t *cue)
{
char *path;
memset(cue, 0, sizeof(*cue));
path = get_chpid_path(chpid, "measurement");
read_bin(path, cue, sizeof(*cue), false);
free(path);
/* Some firmware levels incorrectly report a CUIV of 0xfe for CMG 3 -
* fix this here to enable access to the full CUE data. */
if (cmg == 3 && cue->common.cuiv == 0xfe) {
if (opts.debug)
printf("DEBUG: Fixing CMG 3 CUIV (0xfe => 0xff)\n");
cue->common.cuiv = 0xff;
}
}
/*
* Read the extended channel-utilization entry for CHPID @chpid to @ext_cue.
*/
static bool read_ext_cue(int chpid, ext_cue_t *ext_cue)
{
static bool once;
bool e = false;
char *path;
memset(ext_cue, 0, sizeof(*ext_cue));
path = get_chpid_path(chpid, "ext_measurement");
/* Older kernels might not have extended measurement support. */
if (util_path_exists(path)) {
e = read_bin(path, ext_cue, sizeof(*ext_cue), true);
} else if (!once) {
warnx("Missing kernel support for extended channel-path "
"measurement data");
once = true;
}
free(path);
return e;
}
static bool cue_modified(cue_t *a, cue_t *b)
{
return a->common.timestamp != b->common.timestamp;
}
/*
* Read utilization data @util for @chpid with CMG @cmg.
*/
static void read_util(int chpid, int cmg, struct util_t *util)
{
bool repeat = false, *e = &util->extended;
ext_cue_t *ext_cue = &util->ext_cue;
cue_t cue2, *cue = &util->cue;
/* Loop to ensure that basic and extended CUEs are in sync. */
do {
read_cue(chpid, cmg, cue);
*e = read_ext_cue(chpid, ext_cue);
if (*e) {
/* Re-read CUE to make sure it hasn't changed. */
read_cue(chpid, cmg, &cue2);
repeat = cue_modified(cue, &cue2);
}
} while (repeat);
}
/*
* Select @chpid and read initial CHPID data from sysfs. If @try is %true,
* return %true if CHPID is available, %false otherwise. If @try is %false,
* exit with an error message if CHPID is unavailable.
*/
static bool select_chpid(int chpid, bool try)
{
struct chpid_data_t *c = &chpid_data[chpid];
struct cmg_t *cmg_t;
int cmg;
if (!chpid_exists(chpid)) {
if (try)
return false;
errx(EXIT_RUNTIME, "CHPID 0.%02x does not exist", chpid);
}
cmg = read_cmg(chpid);
if (cmg == -1) {
if (try)
return false;
errx(EXIT_RUNTIME, "No statistics available for CHPID 0.%02x",
chpid);
}
cmg_t = cmg_get(cmg);
if (!cmg_t) {
if (try)
return false;
errx(EXIT_RUNTIME, "CHPID 0.%02x uses unsupported CMG %d",
chpid, cmg);
}
if (opts.cmgs_specified && !cmg_t->selected) {
if (try)
return false;
errx(EXIT_RUNTIME, "CHPID 0.%02x excluded by --cmg option",
chpid);
}
cmg_t->found++;
c->cmg = cmg_t;
c->type = read_chpid_attr_as_int(chpid, "type", 16);
c->shared = read_chpid_attr_as_int(chpid, "shared", 10);
c->speed = read_speed(chpid);
if (cmg_t->has_cmcb)
c->data.full_cmcb = read_cmcb(chpid, &c->data.cmcb);
read_util(chpid, cmg, &c->data.util_a);
c->data.util_b = c->data.util_a;
c->selected = true;
return true;
}
/*
* Select all available CHPIDs.
*/
static int select_all_chpids(void)
{
int chpid, num_selected = 0;
for (chpid = 0; chpid < NUM_CHPIDS; chpid++) {
if (select_chpid(chpid, true))
num_selected++;
}
return num_selected;
}
/*
* Parse all positional parameters in @argv starting with @first up to @argc
* and select all specified CHPIDs.
*/
static int parse_chpids(int first, int argc, char *argv[])
{
int i, chpid, num_selected = 0;
/* Parse optional CHPID selection. */
for (i = first; i < argc; i++) {
chpid = parse_chpid(argv[i]);
if (chpid_data[chpid].selected)
continue;
select_chpid(chpid, false);
num_selected++;
}
return num_selected;
}
/*
* Enable channel-path statistics.
*/
static void cmd_enable(enum cm_status_t status)
{
if (status == CM_ENABLED)
printf("Channel-path statistics already enabled\n");
else
set_cm_enable(1);
}
/*
* Disable channel-path statistics.
*/
static void cmd_disable(enum cm_status_t status)
{
if (status == CM_DISABLED)
printf("Channel-path statistics already disabled\n");
else
set_cm_enable(0);
}
/* pr_pair(key, fmt) */
#define pr_pair(k, fmt, ...) \
util_fmt_pair(FMT_PERSIST, (k), (fmt), ##__VA_ARGS__)
/* pr_pair_quoted(key, fmt) */
#define pr_pair_quoted(k, fmt, ...) \
util_fmt_pair(FMT_PERSIST | FMT_QUOTE, (k), (fmt), ##__VA_ARGS__)
/*
* Show status of the channel-path statistics function in human-readable form.
*/
static void cmd_status_default(enum cm_status_t status)
{
printf("Channel-path statistics are ");
switch (status) {
case CM_UNSUPPORTED:
printf("not supported on this system\n");
break;
case CM_DISABLED:
printf("disabled\n");
break;
case CM_ENABLED:
printf("enabled\n");
break;
}
}
/*
* Show status of the channel-path statistics function in machine-readable
* format.
*/
static void cmd_status_fmt(enum cm_status_t status)
{
const char *str = "";
switch (status) {
case CM_UNSUPPORTED:
str = "unsupported";
break;
case CM_DISABLED:
str = "disabled";
break;
case CM_ENABLED:
str = "enabled";
break;
}
util_fmt_add_key("status");
util_fmt_obj_start(FMT_ROW, "chpstat_status");
pr_pair_quoted("status", "%s", str);
util_fmt_obj_end();
}
/*
* Show status of the channel-path statistics function.
*/
static void cmd_status(enum cm_status_t status)
{
if (opts.fmt_specified)
cmd_status_fmt(status);
else
cmd_status_default(status);
}
/*
* Update channel-utilization data for CHPID @c. Return %true if new data was
* found, %false otherwise.
*/
static bool _update_util(struct chpid_data_t *c)
{
struct util_t util;
read_util(c->id, c->cmg->cmg, &util);
if (!cue_modified(&util.cue, &c->data.util_b.cue))
return false;
c->data.util_a = c->data.util_b;
c->data.util_b = util;
return true;
}
/*
* Update channel-utilization data for CHPID @c. If @wait is %true, repeat
* update attempts every second until new data is available.
*/
static void update_util(struct chpid_data_t *c, bool wait)
{
while (!_update_util(c) && wait)
sleep(1);
}
/*
* Update channel-path utilization data for all CHPIDs. If @wait is %true,
* repeat the process until all CHPIDs have new data.
*/
static void update_util_all(bool wait)
{
struct chpid_data_t *c;
chpid_for_each(c) {
if (c->selected)
update_util(c, wait);
}
}
/*
* Return hostname of local host.
*/
static char *get_host_name(void)
{
char host[HOST_NAME_MAX + 1] = { 0 }, *d;
gethostname(host, sizeof(host) - 1);
d = strchr(host, '.');
if (d)
*d = 0;
if (!host[0])
strncpy(host, "-", sizeof(host) - 1);
return util_strdup(host);
}
/*
* Print a header with iteration-specific information for iteration @iteration.
*/
static void pr_iteration_header(int iteration)
{
unsigned int quoted = FMT_PERSIST | FMT_QUOTE, unquoted = FMT_PERSIST;
char str[30], *host, *b = "", *r = "";
struct timeval tv;
struct tm *tm;
if (opts.use_ansi) {
b = ANSI_BOLD;
r = ANSI_RESET;
}
gettimeofday(&tv, NULL);
tm = localtime(&tv.tv_sec);
host = get_host_name();
if (opts.cmd == CMD_TABLE) {
printf("%sIteration:%s %d ", b, r, iteration);
if (tm) {
strftime(str, sizeof(str), "%F", tm);
printf("%sDate:%s %s ", b, r, str);
strftime(str, sizeof(str), "%T%z", tm);
printf("%sTime:%s %s ", b, r, str);
}
printf("%sHost:%s %s\n", b, r, host);
} else {
util_fmt_pair(unquoted, KEY_ITERATION, "%d", iteration);
util_fmt_pair(unquoted, KEY_TIME_EPOCH, "%llu", tv.tv_sec);
if (tm) {
strftime(str, sizeof(str), "%F %T%z", tm);
util_fmt_pair(quoted, KEY_TIME, "%s", str);
} else {
util_fmt_pair(quoted | FMT_INVAL, KEY_TIME, "", str);
}
}
free(host);
}
/*
* Print generic, non-cmg specific data for CHPID @c.
*/
static void pr_chpid(struct chpid_data_t *c)
{
pr_pair_quoted(KEY_CHPID, "0.%02x", c->id);
pr_pair(KEY_TYPE, "%d", c->type);
pr_pair(KEY_CMG, "%d", c->cmg->cmg);
pr_pair(KEY_SHARED, "%d", c->shared);
if (c->speed) {
pr_pair_quoted(KEY_SPEED, "%s", c->speed);
} else {
util_fmt_pair(FMT_PERSIST | FMT_QUOTE | FMT_INVAL, KEY_SPEED,
"");
}
}
static void pr_cmg_inval_pair(struct cmg_pair_t *p)
{
if (p->type == CMG_FLOAT)
util_fmt_pair(FMT_INVAL, p->key, "-1.0");
else
util_fmt_pair(FMT_INVAL, p->key, "-1");
}
static void pr_cmg_pair(struct cmg_pair_t *p)
{
if (!p->valid) {
pr_cmg_inval_pair(p);
return;
}
switch (p->type) {
case CMG_U32:
util_fmt_pair(FMT_DEFAULT, p->key, "%u", p->value_u32);
break;
case CMG_U64:
util_fmt_pair(FMT_DEFAULT, p->key, "%llu", p->value_u64);
break;
case CMG_FLOAT:
util_fmt_pair(FMT_DEFAULT, p->key, "%.1f", p->value_double);
break;
}
}
static void pr_cmg_pairs(struct cmg_pair_t *pairs)
{
int i;
for (i = 0; pairs[i].key; i++)
pr_cmg_pair(&pairs[i]);
}
static void pr_group(struct chpid_data_t *c, struct cmg_t *cmg,
enum key_group_t group)
{
struct cmg_pair_t *pairs = NULL;
pairs = cmg->get_values(&c->data, group);
util_fmt_obj_start(FMT_DEFAULT, key_group_to_str(group));
pr_cmg_pairs(pairs);
util_fmt_obj_end();
cmg_free_pairs(pairs);
}
/*
* Show list of channel-path statistics once.
*/
static void cmd_list_once(void)
{
struct chpid_data_t *c;
struct cmg_t *cmg;
util_fmt_obj_start(FMT_LIST, "channel_paths");
chpid_for_each(c) {
if (!c->selected)
continue;
cmg = c->cmg;
if (opts.groups & (KEY_GRP_UTIL | KEY_GRP_METRICS))
update_util(c, false);
if (opts.groups & KEY_GRP_METRICS)
cmg->update_metrics(&c->data);
util_fmt_obj_start(FMT_ROW, NULL);
if (opts.groups & KEY_GRP_CHP)
pr_chpid(c);
util_fmt_obj_start(FMT_DEFAULT, "cmg%d", cmg->cmg);
if (opts.groups & KEY_GRP_CHARS)
pr_group(c, cmg, KEY_GRP_CHARS);
if (opts.groups & KEY_GRP_UTIL)
pr_group(c, cmg, KEY_GRP_UTIL);
if (opts.groups & KEY_GRP_METRICS)
pr_group(c, cmg, KEY_GRP_METRICS);
util_fmt_obj_end();
util_fmt_obj_end();
}
util_fmt_obj_end();
}
/* Register the keys of all key-value pairs that will be printed. */
static void register_selected_keys(void)
{
struct key_t *key;
key_for_each_selected(key)
util_fmt_add_key("%s", key->name);
}
/* Report an error if a key specified on the command line is not reported by
* any available CHPID. */
static void check_key_availability(void)
{
struct key_t *key;
key_for_each_selected(key) {
if (key->found)
continue;
errx(EXIT_RUNTIME, "No available CHPID provides key '%s' "
"(needs CMG %s)", key->name, key->cmg_str);
}
}
static void apply_key_selection(void)
{
bool do_filter = (opts.cmd == CMD_LIST);
struct cmg_t *cmg_t;
if (opts.all) {
key_select_all();
} else if (opts.groups_specified || opts.cmgs_specified ||
opts.keys_specified) {
/* Apply selection options. */
if (opts.groups_specified)
key_select_by_groups(opts.groups, do_filter);
if (opts.cmgs_specified) {
cmg_for_each(cmg_t) {
if (!cmg_t->selected)
continue;
key_select_by_cmg(cmg_t->cmg);
}
}
} else {
/* Select default keys suitable for command. */
if (opts.cmd == CMD_LIST_KEYS)
key_select_all();
else
key_select_by_groups(opts.groups, do_filter);
}
if (opts.cmd == CMD_LIST_KEYS)
key_sort_selected();
}
/*
* Show channel-path statistics in list format.
*/
static void cmd_list(void)
{
int i;
if (!opts.groups_specified && !opts.keys_specified)
opts.groups = KEY_GRP_METRICS;
opts.groups |= KEY_GRP_META | KEY_GRP_ITERATION | KEY_GRP_CHP;
/* Re-initialize keys to get current key->found values. */
key_init(opts.all);
if (opts.keys_specified)
check_key_availability();
apply_key_selection();
register_selected_keys();
/* Select CMG key groups needed for selected keys. */
opts.groups |= key_get_selected_groups();
if (opts.groups & KEY_GRP_METRICS)
update_util_all(true);
if (opts.fmt != FMT_JSONSEQ)
util_fmt_obj_start(FMT_LIST, NULL);
for (i = 0; opts.forever || i < opts.iterations; i++) {
if (i > 0)
sleep((unsigned int)opts.interval);
util_fmt_obj_start(0, NULL);
if (opts.groups & KEY_GRP_ITERATION)
pr_iteration_header(i);
cmd_list_once();
util_fmt_obj_end();
/* Flush here to allow immediate consumption of full reports
* via pipes (e.g. for use with grep). */
fflush(stdout);
}
if (opts.fmt != FMT_JSONSEQ)
util_fmt_obj_end();
}
/*
* Allocate a table using the names and widths of all selected columns.
*/
static struct util_rec *define_table(void)
{
struct util_rec *rec;
struct column_t *col;
rec = util_rec_new_wide(NULL);
column_for_each_selected(col) {
util_rec_def(rec, col->name, UTIL_REC_ALIGN_RIGHT,
(int)col->width, "");
}
return rec;
}
/*
* Scale value @v by @unit and return the suffix of the applied
* unit-multiplier.
*/
static char scale_auto(struct cmg_pair_t *p, int unit)
{
char suffixes[] = "\0KMGTPE";
int i;
for (i = 0; suffixes[i + 1]; i++) {
switch (p->type) {
case CMG_U32:
if (p->value_u32 < (u32)unit)
goto out;
p->value_u32 /= (u32)unit;
break;
case CMG_U64:
if (p->value_u64 < (u64)unit)
goto out;
p->value_u64 /= (u64)unit;
break;
case CMG_FLOAT:
if (p->value_double < (double)unit)
goto out;
p->value_double /= (double)unit;
break;
default:
break;
}
}
out:
return suffixes[i];
}
static void scale_fixed(struct cmg_pair_t *p, unsigned long unit)
{
switch (p->type) {
case CMG_U32:
p->value_u32 /= (u32)unit;
break;
case CMG_U64:
p->value_u64 /= (u64)unit;
break;
case CMG_FLOAT:
p->value_double /= (double)unit;
break;
default:
break;
}
}
/*
* Add @value in formatted form to @column of @table. Scale value if defined
* for @column.
*/
static void add_pair_value(struct util_rec *table, struct column_t *col,
struct cmg_pair_t *pair)
{
char suffix = 0, str[16];
int p;
if (!pair->valid) {
util_rec_set(table, col->name, "-");
return;
}
if (pair->unit == CMG_NUMBER) {
suffix = scale_auto(pair, UNIT_DEC);
} else if (pair->unit == CMG_BPS) {
if (opts.unit == UNIT_AUTO)
suffix = scale_auto(pair, UNIT_BIN);
else
scale_fixed(pair, opts.unit);
}
switch (pair->type) {
case CMG_U32:
snprintf(str, sizeof(str), "%u%c", pair->value_u32, suffix);
break;
case CMG_U64:
snprintf(str, sizeof(str), "%llu%c", pair->value_u64, suffix);
break;
case CMG_FLOAT:
/* Find highest precision that fits into @width characters. */
for (p = 2; p >= 0; p--) {
snprintf(str, sizeof(str), "%.*f%c", p,
pair->value_double, suffix);
if (strlen(str) <= col->width)
break;
}
break;
}
util_rec_set(table, col->name, "%s", str);
}
/*
* Add generic, non-cmg specific data for column @col and CHPID @c to @table.
* Return %true if requested column was handled, %false otherwise.
*/
static bool add_column_generic(struct util_rec *table, struct column_t *col,
struct chpid_data_t *c)
{
const char *name = col->name;
switch (col->id) {
case COL_CHPID:
util_rec_set(table, name, "%02x", c->id);
break;
case COL_TYPE:
util_rec_set(table, name, "%02x", c->type);
break;
case COL_CMG:
util_rec_set(table, name, "%d", c->cmg->cmg);
break;
case COL_SHARED:
util_rec_set(table, name, "%d", c->shared);
break;
case COL_SPEED:
if (!c->speed)
return false;
util_rec_set(table, name, "%s", c->speed);
break;
default:
return false;
}
return true;
}
static bool add_column_cmg(struct util_rec *table, struct column_t *col,
struct cmg_pair_t *pairs)
{
int i;
for (i = 0; pairs[i].key; i++) {
if (pairs[i].col != col->id)
continue;
add_pair_value(table, col, &pairs[i]);
return true;
}
return false;
}
/*
* Add row data for CHPID @c to @table.
*/
static void add_table_row(struct util_rec *table, struct chpid_data_t *c,
struct cmg_pair_t *pairs)
{
struct column_t *col;
column_for_each_selected(col) {
if (add_column_generic(table, col, c))
continue;
if (add_column_cmg(table, col, pairs))
continue;
/* Default text for unavailable column values. */
util_rec_set(table, col->name, "-");
}
}
/*
* Find the longest number of consecutive columns that use the same non-empty
* group header. Start with selected column @start. Update @num_ptr to contain
* the number of consecutive columns, and @width_ptr to contain the total
* width of these columns, including separating spaces.
*/
static void get_hdr_group_size(unsigned int start, unsigned int *num_ptr,
unsigned int *width_ptr)
{
struct column_t *col;
const char *last_hdr = "";
unsigned int i, num, width;
num = 0;
width = 0;
for (i = start; (col = column_get_by_index(i, true)); i++) {
if (i > start) {
if (!*last_hdr ||
strcmp(col->hdr1_group, last_hdr) != 0)
break;
/* Account for space between columns. */
width++;
}
num++;
width += col->width;
last_hdr = col->hdr1_group;
}
*num_ptr = num;
*width_ptr = width;
}
/*
* Print @str centered in a space of @width characters.
*/
static void pr_centered(const char *str, unsigned int width)
{
unsigned int a, b, l;
l = strlen_u(str);
width = MAX(width, l);
a = (width - l) / 2;
b = width - l - a;
printf("%*s%s%*s", b, "", str, a, "");
}
/*
* Distribute @extra characters evenly to column widths for @num selected
* columns starting with @start.
*/
static void enlarge_columns(unsigned int start, unsigned int num,
unsigned int extra)
{
struct column_t *col;
unsigned int i, end, delta;
end = start + num;
for (i = start; i < end; i++) {
col = column_get_by_index(i, true);
delta = extra / (end - i);
col->width += delta;
extra -= delta;
}
}
/*
* Make sure that group header is centered over the non-spacing portions of
* line 2 headers: | HDR1 | => | HDR1 |
* | HDR2 HDR2| | HDR2 HDR2|
*/
static void pr_hdr1_spacing(struct column_t *col, unsigned int *width_ptr)
{
unsigned int w1, w2, spacing;
w1 = strlen_u(col->hdr1_group);
w2 = strlen_u(col->hdr2);
if (col->width <= w2)
return;
spacing = col->width - w2;
if (*width_ptr - spacing >= w1) {
printf("%*s", spacing, "");
*width_ptr -= spacing;
}
}
/*
* Print multi-line table header and update column widths based on header
* lengths.
*/
static void print_table_header(void)
{
struct column_t *col;
unsigned int i, next_i, num, width, hdr_width;
/* Print first header line and update column width based on heading. */
for (i = 0; (col = column_get_by_index(i, true)); i = next_i) {
if (i > 0)
printf(" ");
get_hdr_group_size(i, &num, &width);
if (num == 1) {
/* Update column width in case heading is wider. */
width = MAX(width, strlen_u(col->hdr1_single));
width = MAX(width, strlen_u(col->hdr2));
col->width = width;
printf("%*s", col->width, col->hdr1_single);
next_i = i + 1;
continue;
}
hdr_width = strlen_u(col->hdr1_group);
if (hdr_width > width) {
/* Header text is longer than sum of column widths,
* increase column widths accordingly.*/
enlarge_columns(i, num, hdr_width - width);
width = hdr_width;
}
pr_hdr1_spacing(col, &width);
pr_centered(col->hdr1_group, width);
next_i = i + num;
}
printf("\n");
/* Print second header line. */
i = 0;
column_for_each_selected(col) {
if (i++ > 0)
printf(" ");
printf("%*s", col->width, col->hdr2);
}
printf("\n");
}
/*
* Show table of channel-path statistics once.
*/
static void cmd_table_once(void)
{
struct cmg_pair_t *pairs;
struct util_rec *table;
struct chpid_data_t *c;
struct cmg_t *cmg;
ansi(ANSI_BOLD ANSI_REVERSE);
print_table_header();
ansi(ANSI_RESET);
table = define_table();
chpid_for_each(c) {
if (!c->selected)
continue;
cmg = c->cmg;
/* Update data. */
update_util(c, false);
cmg->update_metrics(&c->data);
/* Add data to table. */
pairs = cmg->get_values(&c->data, KEY_GRP_ALL);
add_table_row(table, c, pairs);
cmg_free_pairs(pairs);
util_rec_print(table);
}
util_rec_free(table);
if (opts.unit != UNIT_AUTO && !opts.unit_suffix)
printf("\n* = %lu B/s\n", opts.unit);
}
static void apply_column_selection(void)
{
struct cmg_t *cmg_t;
if (opts.all) {
column_select_all();
} else if (opts.cmgs_specified) {
/* Choose columns appropriate for CMGs specified via --cmg. */
cmg_for_each(cmg_t) {
if (!cmg_t->selected)
continue;
column_select_id_list(cmg_t->default_column_ids);
}
} else if (!opts.columns_specified) {
/* Select default columns suitable for command. */
if (opts.cmd == CMD_LIST_COLUMNS)
column_select_all();
else
column_select_default();
}
}
/* Artificial upper limit for BPS needed to determine maximum column widths. */
#define MAX_BPS (1024.0 * /* MiB/s per GFC */ 100 * (/* MiB */ 1048576))
/*
* Make sure each column fits expected values.
*/
static void calc_column_widths(void)
{
struct column_t *col;
const char *last_hdr1 = NULL;
char str[32];
unsigned int width;
double v;
column_for_each_selected(col) {
/* Make sure column fits at least a single header. */
col->width = MAX(col->width, strlen_u(col->hdr2));
/* Make sure column fits maximum value. */
width = col->width;
switch (col->unit) {
case COL_PERCENT:
width = 4; /* "99.9" */
break;
case COL_NUMBER:
width = 4; /* "999k" */
break;
case COL_BPS:
if (!opts.unit_specified || opts.unit == UNIT_AUTO) {
width = 4; /* "999K" */
} else {
v = MAX_BPS / (double)opts.unit;
snprintf(str, sizeof(str) - 1, "%.0f%c", v,
opts.unit_suffix);
width = strlen_u(str);
}
break;
default:
break;
}
col->width = MAX(col->width, width);
/* Double space between groups. */
if (last_hdr1 && strcmp(last_hdr1, col->hdr1_group) != 0)
col->width++;
last_hdr1 = col->hdr1_group;
}
}
/*
* Show table of channel-path statistics continuously as specified by
* command line options.
*/
static void cmd_table(void)
{
int i;
apply_column_selection();
calc_column_widths();
column_update_bps_suffix(opts.unit == UNIT_AUTO, opts.unit_suffix);
printf("Collecting initial utilization data\n");
update_util_all(true);
buffer_stdout(true);
for (i = 0; opts.forever || i < opts.iterations; i++) {
if (i > 0) {
sleep((unsigned int)opts.interval);
printf("\n");
}
/* Clear screen + move cursor to top-left of terminal (1,1). */
ansi(ANSI_CLS ANSI_LOCATE(1, 1));
pr_iteration_header(i);
printf("\n");
cmd_table_once();
/* Make table visible in one go to avoid screen flicker. */
fflush(stdout);
}
buffer_stdout(false);
}
#define HDR_COLUMN "column"
#define HDR_HEADING "heading"
#define HDR_DESC "description"
/*
* List available table columns in table format.
*/
static void cmd_list_columns_table(void)
{
struct util_rec *table;
struct column_t *col;
int w, w1, w2, w3;
/* Determine column widths. */
w1 = strlen_i(HDR_COLUMN);
w2 = strlen_i(HDR_HEADING);
w3 = strlen_i(HDR_DESC);
column_for_each_selected(col) {
w1 = MAX(w1, strlen_i(col->name));
w = strlen_i(col->hdr1_single) + strlen_i(col->hdr2) + 1;
w2 = MAX(w2, w);
w3 = MAX(w3, strlen_i(col->desc));
}
w1++;
w2++;
/* Print table heading. */
table = util_rec_new_wide(NULL);
util_rec_def(table, HDR_COLUMN, UTIL_REC_ALIGN_LEFT, w1, "COLUMN");
util_rec_def(table, HDR_HEADING, UTIL_REC_ALIGN_LEFT, w2, "HEADING");
util_rec_def(table, HDR_DESC, UTIL_REC_ALIGN_LEFT, w3, "DESCRIPTION");
util_rec_print_hdr(table);
/* Print rows of column data. */
column_for_each_selected(col) {
util_rec_set(table, HDR_COLUMN, col->name);
if (*col->hdr1_single) {
util_rec_set(table, HDR_HEADING, "%s %s",
col->hdr1_single, col->hdr2);
} else {
util_rec_set(table, HDR_HEADING, "%s", col->hdr2);
}
util_rec_set(table, HDR_DESC, "%s", col->desc);
util_rec_print(table);
}
util_rec_free(table);
}
#define HDR_NAME "name"
#define HDR_HEAD "heading"
/*
* List available table columns in machine-readable format.
*/
static void cmd_list_columns_fmt(void)
{
struct column_t *col;
util_fmt_add_key(HDR_NAME);
util_fmt_add_key(HDR_HEAD);
util_fmt_add_key(HDR_DESC);
util_fmt_obj_start(FMT_LIST, "chpstat_list_columns");
column_for_each_selected(col) {
util_fmt_obj_start(FMT_ROW, NULL);
pr_pair_quoted(HDR_NAME, "%s", col->name);
if (*col->hdr1_single) {
pr_pair_quoted(HDR_HEAD, "%s %s",
col->hdr1_single, col->hdr2);
} else {
pr_pair_quoted(HDR_HEAD, "%s", col->hdr2);
}
pr_pair_quoted(HDR_DESC, "%s", col->desc);
util_fmt_obj_end();
}
util_fmt_obj_end();
}
/*
* List available table columns.
*/
static void cmd_list_columns(void)
{
apply_column_selection();
if (opts.fmt_specified)
cmd_list_columns_fmt();
else
cmd_list_columns_table();
}
#define HDR_KEY "key"
#define HDR_GROUP "group"
#define HDR_CMGS "cmgs"
/*
* List available table columns in table format.
*/
static void cmd_list_key_table(void)
{
struct util_rec *table;
struct key_t *key;
int w1, w2, w3;
/* Determine column width. */
w1 = strlen_i(HDR_KEY);
w2 = strlen_i(HDR_GROUP);
w3 = strlen_i(HDR_CMGS);
key_for_each_selected(key) {
w1 = MAX(w1, strlen_i(key->name));
w2 = MAX(w2, strlen_i(key_group_to_str(key->group)));
w3 = MAX(w3, strlen_i(key->cmg_str));
}
/* Increase spacing between columns to improve readability. */
w1++;
w2++;
/* Print table heading. */
table = util_rec_new_wide(NULL);
util_rec_def(table, HDR_KEY, UTIL_REC_ALIGN_LEFT, w1, "KEY");
util_rec_def(table, HDR_GROUP, UTIL_REC_ALIGN_LEFT, w2, "GROUP");
util_rec_def(table, HDR_CMGS, UTIL_REC_ALIGN_LEFT, w3, "CMGS");
util_rec_print_hdr(table);
/* Print rows of column data. */
key_for_each_selected(key) {
util_rec_set(table, HDR_KEY, "%s", key->name);
util_rec_set(table, HDR_GROUP, "%s",
key_group_to_str(key->group));
util_rec_set(table, HDR_CMGS, "%s", key->cmg_str);
util_rec_print(table);
}
util_rec_free(table);
}
/*
* List available keys in machine-readable format.
*/
static void cmd_list_key_fmt(void)
{
struct key_t *key;
util_fmt_add_key(HDR_KEY);
util_fmt_add_key(HDR_GROUP);
util_fmt_add_key(HDR_CMGS);
util_fmt_obj_start(FMT_LIST, "chpstat_list_keys");
key_for_each_selected(key) {
util_fmt_obj_start(FMT_ROW, NULL);
util_fmt_pair(FMT_QUOTE, HDR_KEY, "%s", key->name);
util_fmt_pair(FMT_QUOTE, HDR_GROUP, "%s",
key_group_to_str(key->group));
util_fmt_pair(FMT_QUOTE, HDR_CMGS, "%s", key->cmg_str);
util_fmt_obj_end();
}
util_fmt_obj_end();
}
/*
* List available keys.
*/
static void cmd_list_keys(void)
{
apply_key_selection();
if (opts.fmt_specified)
cmd_list_key_fmt();
else
cmd_list_key_table();
}
/*
* Return long command line name for specified @cmd option.
*/
static const char *cmd_to_optstr(enum command_t cmd)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(opt_vec); i++) {
if (opt_vec[i].option.val == (int)cmd)
return opt_vec[i].option.name;
}
return "";
}
/*
* Set tool command @cmd and check for conflicting options.
*/
static void set_cmd(enum command_t cmd)
{
if (!opts.cmd_specified || opts.cmd == cmd) {
opts.cmd = cmd;
opts.cmd_specified = true;
return;
}
errx(EXIT_USAGE, "Options --%s and --%s cannot be specified together",
cmd_to_optstr(opts.cmd), cmd_to_optstr(cmd));
}
/*
* Set output format to the format specified by @name.
*/
static void set_fmt(const char *name)
{
enum util_fmt_t fmt;
if (!util_fmt_name_to_type(name, &fmt)) {
errx(EXIT_USAGE, "Unknown format '%s' - supported formats: "
FMT_TYPE_NAMES, name);
}
if (opts.fmt_specified && fmt != opts.fmt) {
errx(EXIT_USAGE, "Option --format cannot be specified multiple "
"times");
}
opts.fmt = fmt;
opts.fmt_specified = true;
}
static void init_fmt(void)
{
unsigned int flags = 0;
if (opts.keys_specified)
flags |= FMT_FILTER;
if (!opts.use_prefix)
flags |= FMT_NOPREFIX;
if (opts.fmt == FMT_JSON || opts.fmt == FMT_JSONSEQ) {
/* Ensure correct JSON even if interrupted. */
flags |= FMT_HANDLEINT;
}
if (opts.fmt == FMT_CSV) {
/* Quote all values to ensure compatibility with a multitude
* of CSV consumers. */
flags |= FMT_QUOTEALL;
}
if (opts.keys_specified || opts.all) {
/* Always show data for keys with no valid value if the key
* was specifically requested by the user. */
flags |= FMT_KEEPINVAL;
}
if (opts.debug)
flags |= FMT_WARN;
util_fmt_init(stdout, opts.fmt, flags, API_LEVEL);
util_fmt_set_indent(0, 2, ' ');
}
/*
* Parse options and execute the command
*/
int main(int argc, char *argv[])
{
enum cm_status_t status;
int c, num_selected;
util_prg_init(&prg);
util_opt_init(opt_vec, NULL);
init_opts();
init_chpid_data();
key_init(opts.all);
/* Parse command-line parameters. */
while (1) {
c = util_opt_getopt_long(argc, argv);
if (c == -1)
break;
switch (c) {
case 'h':
/* --help */
util_prg_print_help();
util_opt_print_help();
goto out;
case 'v':
/* --version */
util_prg_print_version();
goto out;
case OPT_ENABLE:
/* --enable */
set_cmd(CMD_ENABLE);
break;
case OPT_DISABLE:
/* --disable */
set_cmd(CMD_DISABLE);
break;
case OPT_STATUS:
/* --status */
set_cmd(CMD_STATUS);
break;
case OPT_LIST_COLUMNS:
/* --list-columns */
set_cmd(CMD_LIST_COLUMNS);
break;
case OPT_COLUMNS:
/* --columns */
opts.columns_specified = true;
parse_columns(optarg);
break;
case OPT_LIST_KEYS:
/* --list-keys */
set_cmd(CMD_LIST_KEYS);
break;
case OPT_KEYS:
/* --keys KEY1,.. */
opts.keys_specified = true;
parse_keys(optarg);
break;
case OPT_CMG:
/* --cmg NUM,.. */
opts.cmgs_specified = true;
parse_cmgs(optarg);
break;
case OPT_SCALE:
/* --scale UNIT */
opts.unit_specified = true;
opts.unit = parse_unit(optarg);
break;
case OPT_ITERATIONS:
/* --iterations NUM */
opts.iterations = parse_int("iterations", optarg, 0,
INT_MAX);
opts.forever = (opts.iterations < 1);
break;
case OPT_INTERVAL:
/* --interval NUM */
opts.interval = parse_int("interval", optarg, 1,
MAX_INTERVAL);
break;
case OPT_CHARS:
/* --chars */
opts.groups_specified = true;
opts.groups |= KEY_GRP_CHARS;
break;
case OPT_UTIL:
/* --util */
opts.groups_specified = true;
opts.groups |= KEY_GRP_UTIL;
break;
case OPT_METRICS:
/* --metrics */
opts.groups_specified = true;
opts.groups |= KEY_GRP_METRICS;
break;
case OPT_ALL:
/* --all */
opts.all = true;
break;
case OPT_NO_ANSI:
/* --no-ansi */
opts.use_ansi = false;
break;
case OPT_NO_PREFIX:
/* --no-prefix */
opts.use_prefix = false;
break;
case OPT_DEBUG:
/* --debug */
opts.debug = true;
break;
case OPT_FORMAT:
/* --format FORMAT */
set_fmt(optarg);
break;
default:
util_opt_print_parse_error((char)c, argv);
return EXIT_USAGE;
}
}
if (!opts.cmd_specified) {
/* List output is implied by --chars, --util, --metrics, --keys
* and --format. */
if (opts.groups_specified || opts.keys_specified ||
opts.fmt_specified)
opts.cmd = CMD_LIST;
}
if (optind != argc && opts.cmd != CMD_LIST && opts.cmd != CMD_TABLE) {
errx(EXIT_USAGE, "Unexpected parameter specified: %s",
argv[optind]);
}
/* Initialize output formatter. */
init_fmt();
/* Handle functions that work without CM support. */
status = get_cm_status();
switch (opts.cmd) {
case CMD_STATUS:
cmd_status(status);
goto out;
case CMD_LIST_COLUMNS:
cmd_list_columns();
goto out;
case CMD_LIST_KEYS:
cmd_list_keys();
goto out;
default:
break;
}
/* Ensure measurements are available beyond this point. */
if (status == CM_UNSUPPORTED) {
errx(EXIT_RUNTIME, "This system does not support channel-path "
"statistics");
}
/* Handle supplemental functions. */
switch (opts.cmd) {
case CMD_ENABLE:
cmd_enable(status);
goto out;
case CMD_DISABLE:
cmd_disable(status);
goto out;
default:
break;
}
/* Ensure measurements are enabled beyond this point. */
if (status == CM_DISABLED) {
errx(EXIT_RUNTIME, "Channel-path statistics are disabled\n"
"Use '%s --enable' to enable statistics",
program_invocation_short_name);
}
/* Determine CHPID list. */
if (optind != argc)
num_selected = parse_chpids(optind, argc, argv);
else
num_selected = select_all_chpids();
if (num_selected == 0)
errx(EXIT_RUNTIME, "No available CHPIDs found");
switch (opts.cmd) {
case CMD_LIST:
cmd_list();
break;
default:
cmd_table();
break;
}
out:
util_fmt_exit();
free_chpid_data();
key_exit();
cmg_exit();
column_exit();
return EXIT_OK;
}