From cb77faeae785da047fe0f3754f676bd11dd7a05c Mon Sep 17 00:00:00 2001 From: Peter Oberparleiter Date: Mon, 5 Feb 2024 14:40:25 +0100 Subject: [PATCH] chpstat: add tool to display channel-path statistics Add a new tool named chpstat that can be used to view channel-path statistics such as utilization and I/O throughput, and to query and control the status of the channel-path statistics function. Note: Channel-path statistics are only available on systems running in an LPAR or DPM partition. When run without further options, data for all channel-paths is displayed repeatedly with a 5 second delay in table format. Example output: CHANNEL-PATH UTILIZATION(%) READ(B/s) WRITE(B/s) ID TYP CMG SHR SPEED PART TOTAL BUS PART TOTAL PART TOTAL 1d 25 2 1 - 7.16 7.50 7.50 129M 129M 0.00 161K 21 1b 2 1 32G 0.00 0.00 0.00 0.00 0.00 0.00 0.00 34 1b 2 1 32G 0.00 0.00 0.00 0.00 0.00 0.00 0.00 61 25 2 1 - 0.00 0.01 0.00 0.00 2.00K 0.00 307K 63 25 2 1 - 0.00 0.01 0.00 0.00 0.00 0.00 381K bd 11 2 1 10G - - - 529.8 532.1 616.3 616.3 Reviewed-by: Vineeth Vijayan Signed-off-by: Peter Oberparleiter Signed-off-by: Steffen Eiden --- .gitignore | 1 + zconf/chp/Makefile | 5 +- zconf/chp/chpstat/Makefile | 19 + zconf/chp/chpstat/chpstat.8 | 739 +++++++++++ zconf/chp/chpstat/chpstat.c | 2153 ++++++++++++++++++++++++++++++++ zconf/chp/chpstat/cmg.c | 101 ++ zconf/chp/chpstat/cmg.h | 115 ++ zconf/chp/chpstat/cmg1.c | 143 +++ zconf/chp/chpstat/cmg2.c | 343 +++++ zconf/chp/chpstat/cmg3.c | 316 +++++ zconf/chp/chpstat/cmg_helper.c | 109 ++ zconf/chp/chpstat/cmg_helper.h | 81 ++ zconf/chp/chpstat/column.c | 430 +++++++ zconf/chp/chpstat/column.h | 84 ++ zconf/chp/chpstat/key.c | 227 ++++ zconf/chp/chpstat/key.h | 89 ++ zconf/chp/chpstat/misc.h | 20 + 17 files changed, 4974 insertions(+), 1 deletion(-) create mode 100644 zconf/chp/chpstat/Makefile create mode 100644 zconf/chp/chpstat/chpstat.8 create mode 100644 zconf/chp/chpstat/chpstat.c create mode 100644 zconf/chp/chpstat/cmg.c create mode 100644 zconf/chp/chpstat/cmg.h create mode 100644 zconf/chp/chpstat/cmg1.c create mode 100644 zconf/chp/chpstat/cmg2.c create mode 100644 zconf/chp/chpstat/cmg3.c create mode 100644 zconf/chp/chpstat/cmg_helper.c create mode 100644 zconf/chp/chpstat/cmg_helper.h create mode 100644 zconf/chp/chpstat/column.c create mode 100644 zconf/chp/chpstat/column.h create mode 100644 zconf/chp/chpstat/key.c create mode 100644 zconf/chp/chpstat/key.h create mode 100644 zconf/chp/chpstat/misc.h diff --git a/.gitignore b/.gitignore index 41a44a34..42e9d9eb 100644 --- a/.gitignore +++ b/.gitignore @@ -92,6 +92,7 @@ vmcp/vmcp vmur/vmur zconf/chp/chchp zconf/chp/lschp +zconf/chp/chpstat/chpstat zconf/css/lscss zconf/qeth/lsqeth zconf/scm/lsscm diff --git a/zconf/chp/Makefile b/zconf/chp/Makefile index 2e3065fc..0654dea6 100644 --- a/zconf/chp/Makefile +++ b/zconf/chp/Makefile @@ -1,6 +1,7 @@ include ../../common.mak -all: chchp lschp +all: chchp lschp chpstat + $(MAKE) -C chpstat all libs = $(rootdir)/libutil/libutil.a @@ -14,8 +15,10 @@ install: all $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 644 -c chchp.8 $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 644 -c lschp.8 $(DESTDIR)$(MANDIR)/man8 + $(MAKE) -C chpstat install clean: rm -f *.o chchp lschp + $(MAKE) -C chpstat clean .PHONY: all install clean diff --git a/zconf/chp/chpstat/Makefile b/zconf/chp/chpstat/Makefile new file mode 100644 index 00000000..53cfe49e --- /dev/null +++ b/zconf/chp/chpstat/Makefile @@ -0,0 +1,19 @@ +include ../../../common.mak + +all: chpstat + +libs = $(rootdir)/libutil/libutil.a + +chpstat: chpstat.o $(libs) column.o key.o cmg.o cmg_helper.o \ + cmg1.o cmg2.o cmg3.o + +install: all + $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 chpstat $(DESTDIR)$(BINDIR) + $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man8 + $(INSTALL) -m 644 -c chpstat.8 $(DESTDIR)$(MANDIR)/man8 + +clean: + rm -f *.o chpstat + +.PHONY: all install clean diff --git a/zconf/chp/chpstat/chpstat.8 b/zconf/chp/chpstat/chpstat.8 new file mode 100644 index 00000000..c02eae5f --- /dev/null +++ b/zconf/chp/chpstat/chpstat.8 @@ -0,0 +1,739 @@ +.\" Copyright 2024 IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.TH chpstat 8 "" s390-tools chpstat + + +.SH NAME +chpstat - Display channel-path statistics + + +.SH SYNOPSIS +.B chpstat +.RI [ OPTIONS ] +.RI [ ACTIONS ] +.RI [ CHPIDS ] + + +.SH DESCRIPTION +Use +.B chpstat +to view channel-path statistics such as utilization and throughput, +and to query and control the status of the channel-path statistics function. +.br + +When run without further options, data for all channel-paths is displayed +repeatedly with a 5 second delay in table format. You can limit output to +specific channel-paths by listing the associated CHPIDs on the command line. +.br + +Note: Channel-path statistics are only available on systems running in an +LPAR or DPM partition. +.br + +.SS "Output options" + +Without options, +.B chpstat +displays performance statistics data in table format. Use options +.BR \-\-columns ", " \-\-all " and " \-\-cmg +to change the list of columns to display. +.br + +You can use option +.B \-\-format +to select a machine-readable list output format (JSON, key-value pairs, or +comma-separated values), and option +.B \-\-keys +to restrict output to only a specific set of data keys. +.br + +Additional options can be used to display the raw source data used for +calculating channel-path statistics: +.br + +.IP \(bu 3 +.B Characteristics: +Static data describing base characteristics of a channel-path. +.br + +Use option +.B \-\-chars +to view characteristics data. +.PP +.IP \(bu 3 +.B Utilization: +Raw data about a channel-paths current utilization. This data is updated +regularly at model-dependent intervals that typically last a few seconds +(see key "interval"). + +Use option +.B \-\-util +to view raw utilization data. +.PP +.IP \(bu 3 +.B Metrics: +Performance statistics derived from utilization and characteristics data. +.br + +Note: Metrics data is calculated as averages over all utilization update +intervals that fall within the selected chpstat update interval. +.br + +Use option +.B \-\-metrics +to view metrics data. +.PP + +.SS Authorization + +A special authorization setting needs to be enabled for a system to be able +to access channel-path statistics data. + +.B Classic mode LPAR + +.IP 1. 3 +Logon on to the Hardware Management Console (HMC) +.PP +.IP 2. 3 +Select the target LPAR +.PP +.IP 3. 3 +Start "Customize Activation Profiles" HMC Task +.PP +.IP 4. 3 +Enable "Security/Global performance data control" setting +.PP + +Note: The LPAR needs to be deactivated/re-activated for the change to +become effective. + +.B DPM Partition +.IP 1. 3 +Logon on to the Hardware Management Console (HMC) +.PP +.IP 2. 3 +Select the target partition +.PP +.IP 3. 3 +Start "Partition Details" HMC Task +.PP +.IP 4. 3 +Enable "Controls/Access global performance data" setting +.PP +.IP 5. 3 +Press "Apply" - the change will be active immediately +.PP + +.SS "Channel-Measurement Groups" + +A Channel-Measurement Group (CMG) is a number associated with each channel-path +that determines the type of available statistics data for that channel-path. + + +.SH ACTIONS +.BR \-s ", " \-\-status +.br +.RS +Show channel-path statistics status + +Possible status values are: +.IP \(bu 3 +.B enabled: +Statistics facility is active +.PP +.IP \(bu 3 +.B disabled: +Statistics facility is inactive +.PP +.IP \(bu 3 +.B unsupported: +Statistics facility is not supported. +.br + +Note that channel-path statistics are only available when running in LPAR or +DPM partition. +.PP +.RE + +.BR \-e ", " \-\-enable +.br +.RS +Enable channel-path statistics +.br + +After booting Linux, the channel-path statistics facility starts in disabled +state. Use option +.B \-\-enable +to enable it. +.RE + +.BR \-d ", " \-\-disable +.br +.RS +Disable channel-path statistics +.RE + +.BR \-l ", " \-\-list\-columns +.br +.RS +List available table columns +.br + +Use this option to get a list of available table column names and associated +short description. A comma-separated list of column names can be used with +option +.B \-\-columns +to select the columns to display in table output format. +.RE + +.BR \-L ", " \-\-list\-keys +.br +.RS +List available data keys +.br + +Use this option to get a list of available keys that can be used with option +.B \-\-keys +to select data pairs to display in machine-readable output format. +.RE + +.BR \-h ", " \-\-help +.br +.RS +Print usage information, then exit +.RE + +.BR \-v ", " \-\-version +.br +.RS +Print version information, then exit +.RE + + +.SH OPTIONS +.BR \-n ", " \-\-iterations +.I NUM +.br +.RS +Display NUM reports before ending +.br + +By default, +.B chpstat +shows output repeatedly until interrupted. Use option +.B \-\-iterations +to specify how many times output should be updated before exiting. A value of +0 indicates an unlimited number of iterations. +.RE + +.BR \-i ", " \-\-interval +.I NUM +.br +.RS +Pause NUM seconds between display +.br + +Use this option to specify the number of seconds to wait between output updates. +Valid values are between 1 and 2140. + +Note: It is recommended to use interval values of at least the model-dependent +statistics update interval (see key "interval"). +.br +.RE + +.BR \-c ", " \-\-columns +.IR COL ,.. +.br +.RS +Select table columns to show in table output format +.br + +To get a list of available columns, use option +.BR \-\-list\-columns . +If a channel-path does not provide data for a selected column, the +corresponding table field is set to '\-'. +.RE + +.BR \-k ", " \-\-keys +.IR KEY ,.. +.br +.RS +Select keys to show in machine-readable output format +.br + +Use this option to select the data to show in machine-readable output +format. To get a list of available keys, use option +.BR \-\-list\-keys . +If a channel-path does not provide data for a selected key, the +corresponding value is set to "". +.RE + +.BR \-a ", " \-\-all +.br +.RS +Show all table columns and key data +.br + +Use this option to select all supported columns and keys for output. +.RE + +.BR \-\-scale +.I UNIT +.br +.RS +Scale BPS values by UNIT + +Use this option to specify a value by which bytes-per-seconds (BPS) values - +such as read and write throughput - are scaled in table output format. Accepted +values are: + +.IP \(bu 3 +.B auto: +Scale automatically to fit value into each column. This is the default. +.PP +.IP \(bu 3 +.IR number : +Scale by +.I number +.PP +.IP \(bu 3 +.B K: +Scale by 1024 (KiB) +.PP +.IP \(bu 3 +.B M: +Scale by 1,048,576 (MiB) +.PP +.IP \(bu 3 +.B G: +Scale by 1,073,741,824 (GiB) +.PP +.IP \(bu 3 +.B T: +Scale by 1,099,511,627,776 (TiB) +.PP +.RE + +.BR \-\-cmg +.IR CMG ,.. +.br +.RS +Show data for specified CMGs only +.br + +Use this option to limit output to CHPIDs with the specified +Channel-Measurement-Groups (CMG). This option also selects table columns +suitable for the specified CMGs. +.RE + +.BR \-\-format +.I FORMAT +.br +.RS +Show data in specified FORMAT + +Use this option to show output in a machine-readable format. +.I FORMAT +can be either of: + +.IP \(bu 3 +.B json: +Single JavaScript Object Notation (JSON) data structure + +Data for all iterations is formatted as one JSON data structure formatted +in multiple lines to make them more readable by humans. +.br + +See section "OUTPUT FORMAT" for more details. +.br +.PP +.IP \(bu 3 +.B json\-seq: +Sequence of JSON data structures + +Data for each iteration is formatted as separate JSON data structure prefixed +with an ASCII Record Separator character (0x1e) and suffixed with an ASCII Line +Feed character (0x0a) in accordance with RFC7464. +.br + +See section "OUTPUT FORMAT" for more details. +.br +.PP +.IP \(bu 3 +.B pairs: +Textual key=value pairs + +By default, keys have a prefix that makes them unique across one tool +invocation. This prefix can be removed by specifying option +.BR \-\-no\-prefix . +.PP +.IP \(bu 3 +.B csv: +Comma-Separated-Value (CSV) list +.br + +All values are quoted with double-quotes and separated by commas. The first +line of output contains a list of headings. Subsequent lines each represent +data for one CHPID in one iteration. +.PP +.RE + +.BR \-\-chars +.br +.RS +List channel-path measurement characteristics + +Use this option to display static data describing base characteristics of a +channel-path. This option implies a machine-readable format. +.RE + +.BR \-\-util +.br +.RS +List unprocessed utilization data + +Use this option to display raw channel-path utilization data that is updated +regularly by firmware at model-dependent intervals that typically last a few +seconds (see key "interval"). This option implies machine-readable output +format. +.RE + +.BR \-\-metrics +.br +.RS +List performance metrics +.br + +Use this option to display performance statistics data derived from utilization +and characteristics data. This option implies machine-readable output format. +.br + +Note: Metrics data is calculated as averages over all utilization update +intervals that fall within the selected chpstat update interval. +.RE + +.BR \-\-no\-ansi +.br +.RS +Do not use ANSI terminal codes in output +.br + +When specified, this option suppresses the use of ANSI terminal control +characters in table output format. Such characters are used to clear the +screen, and to invert the colors for table heading display. Use this option +when an output terminal does not support these control characters. +.RE + +.BR \-\-no\-prefix +.br +.RS +Hide key prefix in pairs output format + +By default, keys that are shown in the "pairs" machine-readable output format +have a prefix that makes them unique across a tool invocation. Use option +.B \-\-no\-prefix +to remove this prefix. +.RE + + +.SH "OUTPUT FORMAT" +This section contains additional information for some of the supported +output formats. + +.SS json + +JSON output consists of a top-level object with the following properties +(key-value pairs): + +.IP \(bu 3 +.BR meta : +Tool meta-data including API level, version, host name, and time of invocation +.PP +.IP \(bu 3 +.BR chpstat : +Channel-path statistics data +.PP + +Note: For a given API level, the output format is guaranteed to remain +compatible, that is: + +.IP \(bu 3 +required child-objects are not removed +.PP +.IP \(bu 3 +format and contents of existing objects and properties are retained +.PP +.IP \(bu 3 +new child-objects and properties may be added +.PP + +Channel-path statistics data is stored as an array of iteration objects under +the "chpstat" property in the top-level object. + +Each iteration object contains a property named "channel_paths" the value of +which consists of an array of objects representing data for one channel-path +during one iteration. Objects for a single channel-path contain further +child-objects that group related properties together. +.br + +The following object properties are required and will always be part of +JSON output: + +.IP \(bu 3 +For iteration objects: "iteration", "time", "time_epoch", and "channel_paths" +.br +.PP + +.IP \(bu 3 +For channel-path objects: "chpid", "type", "cmg", "shared" +.br +.PP + +All other properties are optional and will be omitted from JSON output if the +associated value is unavailable. If option +.BR --all +is specified, unavailable properties are also listed as either empty strings or +negative values, depending on the value type. + +Example JSON output for single iteration and channel-path with all properties: +.br + +{ +.br + "meta": { +.br + "api_level": 1, +.br + "version": "2.32.0", +.br + "host": "localhost", +.br + "time_epoch": 1714663282, +.br + "time": "2024-05-02 17:21:22+0200" +.br + }, +.br + "chpstat": [ +.br + { +.br + "iteration": 0, +.br + "time_epoch": 1714663282, +.br + "time": "2024-05-02 17:21:22+0200", +.br + "channel_paths": [ +.br + { +.br + "chpid": "0.00", +.br + "type": 0, +.br + "cmg": 0, +.br + "shared": 0, +.br + "speed": "", +.br + "characteristics": { +.br + "max_bus_cycles": 0, +.br + "max_channel_work_units": 0, +.br + "max_write_data_units": 0, +.br + "max_read_data_units": 0, +.br + "data_unit_size": 0, +.br + "data_unit_size_cpc": 0, +.br + "msg_unit_size": 0, +.br + "msg_unit_size_cpc": 0, +.br + }, +.br + "utilization": { +.br + "timestamp": 0, +.br + "bus_cycles_cpc": 0, +.br + "channel_work_units_cpc": 0, +.br + "channel_work_units": 0, +.br + "data_units_written_cpc": 0, +.br + "data_units_written": 0, +.br + "data_units_read_cpc": 0, +.br + "data_units_read": 0, +.br + "total_ficon_ops_cpc": 0, +.br + "total_deferred_ficon_ops_cpc": 0, +.br + "sum_ficon_ops_cpc": 0, +.br + "total_hpf_ops_cpc": 0, +.br + "total_deferred_hpf_ops_cpc": 0, +.br + "sum_hpf_ops_cpc": 0, +.br + "channel_path_busy_time_cpc": 0, +.br + "channel_path_busy_time": 0, +.br + "msg_units_sent": 0, +.br + "msg_units_sent_cpc": 0, +.br + "unsuccessful_attempts_to_send": 0, +.br + "unavailable_receive_buffers": 0, +.br + "unavailable_receive_buffers_cpc": 0, +.br + "data_units_sent": 0, +.br + "data_units_sent_cpc": 0, +.br + }, +.br + "metrics": { +.br + "interval": 0.0, +.br + "util_total": 0.0, +.br + "util_part": 0.0, +.br + "util_bus": 0.0, +.br + "read_total": 0.0, +.br + "read_part": 0.0, +.br + "write_total": 0.0, +.br + "write_part": 0.0, +.br + "ficon_rate": 0.0, +.br + "ficon_active": 0.0, +.br + "ficon_defer": 0.0, +.br + "hpf_rate": 0.0, +.br + "hpf_active": 0.0, +.br + "hpf_defer": 0.0, +.br + "msg_rate_part": 0.0, +.br + "msg_rate_total": 0.0, +.br + "msg_size_part": 0.0, +.br + "msg_size_total": 0.0, +.br + "send_fail_part": 0.0, +.br + "rcv_fail_part": 0.0, +.br + "rcv_fail_total": 0.0, +.br + } +.br + } +.br + ] +.br + } +.br + ] +.br +} +.br + + +.SS json\-seq + +The json\-seq output format is a variation of the JSON output format described +above with the following differences: + +.IP \(bu 3 +Output consists of a sequence of top-level JSON objects, each contained in +single line with no indentation +.br + +.IP \(bu 3 +Each top-level object is prefixed by an ASCII Record Separator character (0x1e) +and suffixed with an ASCII Line Feed character (0x0a) in accordance with +RFC7464 +.br +.PP + +.IP \(bu 3 +The first object contains tool meta-data properties defined in the previous +section +.br +.PP + +.IP \(bu 3 +Subsequent objects each represent channel-path statistics data for one iteration +.br +.PP + + +.SH "EXIT CODES" +.TP +.B 0 +Program finished successfully +.PP +.TP +.B 1 +Usage error +.PP +.TP +.B 2 +A run\-time error occurred +.PP + + +.SH EXAMPLES +Display current channel-path statistics status in JSON format: +.RS 4 +$ +.B chpstat \-\-status \-\-format json +.br +.RE +.PP +. +Determine the model-dependent update interval for CHPID 0.f0: +.RS 4 +$ +.B chpstat \-\-keys interval \-n 1 \-\-no\-prefix 0.f0 \-\-format pairs +.br +.RE +.PP +. +Collect partition write throughput statistics for 1 hour in CSV format: +.RS 4 +$ +.B chpstat \-n 60 \-i 60 \-\-format csv \-\-key time,chpid,write_part +.br + + +.SH "SEE ALSO" +.BR lschp "(8), " chchp (8) diff --git a/zconf/chp/chpstat/chpstat.c b/zconf/chp/chpstat/chpstat.c new file mode 100644 index 00000000..51e53318 --- /dev/null +++ b/zconf/chp/chpstat/chpstat.c @@ -0,0 +1,2153 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + + while ((name = strsep(&arg, ","))) { + cmg_t = cmg_get(atoi(name)); + 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. + */ +static void read_cmcb(int chpid, cmcb_t *cmcb) +{ + char *path; + + memset(cmcb, 0, sizeof(*cmcb)); + path = get_chpid_path(chpid, "measurement_chars"); + read_bin(path, cmcb, sizeof(*cmcb), false); + free(path); +} + +/* + * 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) + 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; +} diff --git a/zconf/chp/chpstat/cmg.c b/zconf/chp/chpstat/cmg.c new file mode 100644 index 00000000..5b4fee8a --- /dev/null +++ b/zconf/chp/chpstat/cmg.c @@ -0,0 +1,101 @@ +/* + * Registry for CMG types + * + * 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 +#include +#include +#include + +#include "cmg.h" +#include "misc.h" + +#include "lib/util_libc.h" + +static struct cmg_t **cmgs; +static unsigned int num_cmgs; + +/* Register new CMG-object @cmg. */ +void cmg_add(struct cmg_t *cmg) +{ + unsigned int i; + + for (i = 0; i < num_cmgs; i++) { + if (cmgs[i]->cmg == cmg->cmg) + errx(EXIT_RUNTIME, "Internal error: CMG %d registered " + "multiple times", cmg->cmg); + } + + util_expand_array(&cmgs, &num_cmgs); + cmgs[num_cmgs - 1] = cmg; +} + +void cmg_exit(void) +{ + free(cmgs); +} + +/* Return CMG-object for @cmg. */ +struct cmg_t *cmg_get(int cmg) +{ + unsigned int i; + + for (i = 0; i < num_cmgs; i++) { + if (cmg == cmgs[i]->cmg) + return cmgs[i]; + } + return NULL; +} + +struct cmg_t *_cmg_get_by_index(unsigned int i) +{ + return (i < num_cmgs) ? cmgs[i] : NULL; +} + +void cmg_free_keys(char **keys) +{ + int i; + + if (!keys) + return; + for (i = 0; keys[i]; i++) + free(keys[i]); + free(keys); +} + +void cmg_free_pairs(struct cmg_pair_t *pairs) +{ + int i; + + if (!pairs) + return; + for (i = 0; pairs[i].key; i++) + free(pairs[i].key); + free(pairs); +} + +char **cmg_get_keys(struct cmg_t *cmg, int groups) +{ + struct cmg_pair_t *pairs; + struct cmg_data_t data; + char **keys = NULL; + unsigned int i, num = 0; + + /* Get key-value pairs for dummy data. */ + memset(&data, 0, sizeof(data)); + pairs = cmg->get_values(&data, groups); + + /* Convert pair array to key array. */ + for (i = 0; pairs[i].key; i++) + util_add_array(&keys, &num, util_strdup(pairs[i].key)); + util_add_array(&keys, &num, NULL); + + cmg_free_pairs(pairs); + + return keys; +} diff --git a/zconf/chp/chpstat/cmg.h b/zconf/chp/chpstat/cmg.h new file mode 100644 index 00000000..bd9e58c9 --- /dev/null +++ b/zconf/chp/chpstat/cmg.h @@ -0,0 +1,115 @@ +/* + * Registry for CMG types + * + * 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. + */ + +#ifndef CMG_H +#define CMG_H + +#include + +#include "lib/util_base.h" +#include "lib/util_list.h" +#include "column.h" + +#define CMCB_SIZE (5 * sizeof(u32)) +#define CUE_SIZE (8 * sizeof(u32)) +#define EXT_CUE_SIZE (16 * sizeof(u32)) +#define METRICS_SIZE (17 * sizeof(double)) + +/* Bit-width of CUE timestamp field. */ +#define CUE_TS_WIDTH 24 + +typedef union { + struct { + u8 cuiv; + u32 timestamp:24; + } common __packed; + u8 data[CUE_SIZE]; +} cue_t; +typedef u8 ext_cue_t[EXT_CUE_SIZE]; +typedef u8 cmcb_t[CMCB_SIZE]; +typedef u8 metrics_t[METRICS_SIZE]; + +struct util_t { + cue_t cue; + ext_cue_t ext_cue; + bool extended; +}; + +/* CMG-specific CHPID data. */ +struct cmg_data_t { + cmcb_t cmcb; + struct util_t util_a; + struct util_t util_b; + metrics_t metrics; +}; + +enum cmg_unit_t { + CMG_NUMBER, + CMG_PERCENT, + CMG_BPS, +}; + +enum cmg_value_type_t { + CMG_U32, + CMG_U64, + CMG_FLOAT, +}; + +struct cmg_pair_t { + /* Key. */ + char *key; + enum column_id_t col; + /* Value. */ + bool valid; + enum cmg_unit_t unit; + enum cmg_value_type_t type; + union { + u32 value_u32; + u64 value_u64; + double value_double; + }; +}; + +struct cmg_t { + int cmg; + + /* Flag indicating whether this CMG was selected on the command line. */ + bool selected; + + /* Counter indicating number of CHPIDs found with this CMG. */ + int found; + + /* Indicator whether this CMG requires a CMCB. */ + const bool has_cmcb; + + /* Array of default column IDs to be displayed for this CMG. */ + const enum column_id_t *default_column_ids; + + /* Return array of key-value pairs. */ + struct cmg_pair_t *(*get_values)(struct cmg_data_t *data, int groups); + + /* Update the metrics found in @data. */ + void (*update_metrics)(struct cmg_data_t *data); + +}; + +/* Iterate over all supported CMG data types. */ +#define cmg_for_each(c) \ + for (unsigned int __i = 0; ((c) = _cmg_get_by_index(__i)); __i++) + +void cmg_add(struct cmg_t *cmg); +struct cmg_t *cmg_get(int cmg); +struct cmg_t *_cmg_get_by_index(unsigned int i); + +void cmg_exit(void); +void cmg_free_keys(char **keys); +void cmg_free_pairs(struct cmg_pair_t *pairs); +char **cmg_get_keys(struct cmg_t *cmg, int groups); + +#endif /* CMG_H */ diff --git a/zconf/chp/chpstat/cmg1.c b/zconf/chp/chpstat/cmg1.c new file mode 100644 index 00000000..2c4cf173 --- /dev/null +++ b/zconf/chp/chpstat/cmg1.c @@ -0,0 +1,143 @@ +/* + * Support for CMG 1 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 "cmg.h" +#include "cmg_helper.h" +#include "column.h" +#include "key.h" + +#include "lib/util_base.h" +#include "lib/zt_common.h" + +/* Macros to convert generic cmg_data_t into CMG-specific ones. */ +#define get_cue(d, x) ((struct cue1_t *)&((d)->x.cue)) +#define get_metrics(d) ((struct metrics1_t *)&((d)->metrics)) + +/* CMG 1 format Channel-Utilization-Entry. */ +struct cue1_t { + u8 cuiv; + u32 timestamp:24; + u32 channel_path_busy_time_cpc; + u32 channel_path_busy_time; +} __packed; + +STATIC_ASSERT(sizeof(struct cue1_t) <= sizeof(cue_t)); + +/* Metrics based on CMG 1 format CUEs. */ +struct metrics1_t { + double interval; + double util_total; + double util_part; +}; + +STATIC_ASSERT(sizeof(struct metrics1_t) <= sizeof(metrics_t)); + +/* IDs of columns that should be shown by default in table output. */ +static const enum column_id_t default_column_ids[] = { + COL_CHPID, + COL_TYPE, + COL_CMG, + COL_SHARED, + COL_SPEED, + COL_UTIL_PART, + COL_UTIL_TOTAL, + /* End of list. */ + COL_END +}; + +static void pr_cue(struct cmg_pair_t **a, unsigned int *n, + struct cmg_data_t *data) +{ + struct cue1_t *cue = get_cue(data, util_b); + + pr_u32(a, n, cue, timestamp); + pr_cue_u32(a, n, cue, channel_path_busy_time_cpc); + pr_cue_u32(a, n, cue, channel_path_busy_time); +} + +static void pr_metrics(struct cmg_pair_t **a, unsigned int *n, + struct cmg_data_t *data) +{ + struct metrics1_t *metrics = get_metrics(data); + + pr_metric(a, n, metrics, interval, CMG_NUMBER, COL_INTERVAL); + pr_metric(a, n, metrics, util_total, CMG_PERCENT, COL_UTIL_TOTAL); + pr_metric(a, n, metrics, util_part, CMG_PERCENT, COL_UTIL_PART); +} + +static struct cmg_pair_t *get_values(struct cmg_data_t *data, int groups) +{ + struct cmg_pair_t *array = NULL; + unsigned int num = 0; + + if (groups & KEY_GRP_UTIL) + pr_cue(&array, &num, data); + if (groups & KEY_GRP_METRICS) + pr_metrics(&array, &num, data); + + /* Add terminating null-element. */ + util_expand_array(&array, &num); + array[num - 1].key = NULL; + + return array; +} + +/* Initialize metrics in @m. */ +static void init_metrics(struct metrics1_t *m) +{ + m->interval = METRICS_INIT; + m->util_total = METRICS_INIT; + m->util_part = METRICS_INIT; +} + +/* Recalculate metrics in @data. */ +static void update_metrics(struct cmg_data_t *data) +{ + struct metrics1_t *m = get_metrics(data); + struct cue1_t *a = get_cue(data, util_a); + struct cue1_t *b = get_cue(data, util_b); + u32 ticks, delta; + + init_metrics(m); + + ticks = get_delta(a->timestamp, b->timestamp, CUE_TS_WIDTH); + if (ticks == 0) + return; + + /* interval = t2 - t1 */ + m->interval = tick_to_s(ticks); + + /* util_total = 100.0 * ticks_busy_cpc / ticks_total */ + if (cue_valid2(a, b, channel_path_busy_time_cpc)) { + delta = field_delta(channel_path_busy_time_cpc, a, b); + m->util_total = 100.0 * delta / ticks; + } + /* util_part = 100.0 * ticks_busy / ticks_total */ + if (cue_valid2(a, b, channel_path_busy_time)) { + delta = field_delta(channel_path_busy_time, a, b); + m->util_part = 100.0 * delta / ticks; + } +} + +/* Object defining this CMG. */ +static struct cmg_t cmg1 = { + .cmg = 1, + .selected = false, + .found = 0, + .has_cmcb = false, + .default_column_ids = default_column_ids, + .get_values = &get_values, + .update_metrics = &update_metrics, +}; + +/* Add to CMG registry. */ +static void __attribute__((constructor)) cmg1_ctr(void) +{ + cmg_add(&cmg1); +} diff --git a/zconf/chp/chpstat/cmg2.c b/zconf/chp/chpstat/cmg2.c new file mode 100644 index 00000000..84ec037b --- /dev/null +++ b/zconf/chp/chpstat/cmg2.c @@ -0,0 +1,343 @@ +/* + * Support for CMG 2 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 "cmg.h" +#include "cmg_helper.h" +#include "column.h" +#include "key.h" + +#include "lib/util_base.h" + +/* Macros to convert generic cmg_data_t into CMG-specific ones. */ +#define get_cmcb(d) ((struct cmcb2_t *)&((d)->cmcb)) +#define get_cue(d, x) ((struct cue2_t *)&((d)->x.cue)) +#define get_ext_cue(d, x) ((struct ext_cue2_t *)&((d)->x.ext_cue)) +#define get_metrics(d) ((struct metrics2_t *)&((d)->metrics)) + +/* CMG 2 format Channel-Measurement-Characteristics Block (CMCB). */ +struct cmcb2_t { + u32 max_bus_cycles; + u32 max_channel_work_units; + u32 max_write_data_units; + u32 max_read_data_units; + u32 data_unit_size; +} __packed; + +STATIC_ASSERT(sizeof(struct cmcb2_t) <= sizeof(cmcb_t)); + +/* CMG 2 format Channel-Utilization-Entry (CUE). */ +struct cue2_t { + u8 cuiv; + u32 timestamp:24; + u32 bus_cycles_cpc; + u32 channel_work_units_cpc; + u32 channel_work_units; + u32 data_units_written_cpc; + u32 data_units_written; + u32 data_units_read_cpc; + u32 data_units_read; +} __packed; + +STATIC_ASSERT(sizeof(struct cue2_t) <= sizeof(cue_t)); + +/* CMG 2 format Extended Channel-Utilization-Entry. */ +struct ext_cue2_t { + u32 total_ficon_ops_cpc; + u32 total_deferred_ficon_ops_cpc; + u64 sum_ficon_ops_cpc; + u32 total_hpf_ops_cpc; + u32 total_deferred_hpf_ops_cpc; + u64 sum_hpf_ops_cpc; +} __packed; + +STATIC_ASSERT(sizeof(struct ext_cue2_t) <= sizeof(ext_cue_t)); + +/* Metrics based on CMG 2 format CUEs. */ +struct metrics2_t { + double interval; + double util_total; + double util_part; + double util_bus; + double read_total; + double read_part; + double write_total; + double write_part; + /* Extended CUE metrics. */ + double ficon_rate; + double ficon_active; + double ficon_defer; + double hpf_rate; + double hpf_active; + double hpf_defer; +}; + +STATIC_ASSERT(sizeof(struct metrics2_t) <= sizeof(metrics_t)); + +/* IDs of columns that should be shown by default in table output. */ +static const enum column_id_t default_column_ids[] = { + COL_CHPID, + COL_TYPE, + COL_CMG, + COL_SHARED, + COL_SPEED, + COL_UTIL_PART, + COL_UTIL_TOTAL, + COL_UTIL_BUS, + COL_READ_PART, + COL_READ_TOTAL, + COL_WRITE_PART, + COL_WRITE_TOTAL, + COL_FICON_RATE, + COL_FICON_ACTIVE, + COL_FICON_DEFER, + COL_HPF_RATE, + COL_HPF_ACTIVE, + COL_HPF_DEFER, + /* End of list. */ + COL_END +}; + +static void pr_chars(struct cmg_pair_t **a, unsigned int *n, + struct cmg_data_t *data) +{ + struct cmcb2_t *cmcb = get_cmcb(data); + + pr_u32(a, n, cmcb, max_bus_cycles); + pr_u32(a, n, cmcb, max_channel_work_units); + pr_u32(a, n, cmcb, max_write_data_units); + pr_u32(a, n, cmcb, max_read_data_units); + pr_u32(a, n, cmcb, data_unit_size); +} + +static void pr_cue(struct cmg_pair_t **a, unsigned int *n, + struct cmg_data_t *data) +{ + struct cue2_t *cue = get_cue(data, util_b); + + pr_u32(a, n, cue, timestamp); + pr_cue_u32(a, n, cue, bus_cycles_cpc); + pr_cue_u32(a, n, cue, channel_work_units_cpc); + pr_cue_u32(a, n, cue, channel_work_units); + pr_cue_u32(a, n, cue, data_units_written_cpc); + pr_cue_u32(a, n, cue, data_units_written); + pr_cue_u32(a, n, cue, data_units_read_cpc); + pr_cue_u32(a, n, cue, data_units_read); +} + +static void pr_ext_cue(struct cmg_pair_t **a, unsigned int *n, + struct cmg_data_t *data) +{ + struct ext_cue2_t *ext_cue = get_ext_cue(data, util_b); + bool v = data->util_b.extended; + + pr_cond_u32(a, n, v, ext_cue, total_ficon_ops_cpc); + pr_cond_u32(a, n, v, ext_cue, total_deferred_ficon_ops_cpc); + pr_cond_u64(a, n, v, ext_cue, sum_ficon_ops_cpc); + pr_cond_u32(a, n, v, ext_cue, total_hpf_ops_cpc); + pr_cond_u32(a, n, v, ext_cue, total_deferred_hpf_ops_cpc); + pr_cond_u64(a, n, v, ext_cue, sum_hpf_ops_cpc); +} + +static void pr_metrics(struct cmg_pair_t **a, unsigned int *n, + struct cmg_data_t *data) +{ + struct metrics2_t *metrics = get_metrics(data); + + pr_metric(a, n, metrics, interval, CMG_NUMBER, COL_INTERVAL); + pr_metric(a, n, metrics, util_total, CMG_PERCENT, COL_UTIL_TOTAL); + pr_metric(a, n, metrics, util_part, CMG_PERCENT, COL_UTIL_PART); + pr_metric(a, n, metrics, util_bus, CMG_PERCENT, COL_UTIL_BUS); + pr_metric(a, n, metrics, read_total, CMG_BPS, COL_READ_TOTAL); + pr_metric(a, n, metrics, read_part, CMG_BPS, COL_READ_PART); + pr_metric(a, n, metrics, write_total, CMG_BPS, COL_WRITE_TOTAL); + pr_metric(a, n, metrics, write_part, CMG_BPS, COL_WRITE_PART); + pr_metric(a, n, metrics, ficon_rate, CMG_NUMBER, COL_FICON_RATE); + pr_metric(a, n, metrics, ficon_active, CMG_NUMBER, COL_FICON_ACTIVE); + pr_metric(a, n, metrics, ficon_defer, CMG_NUMBER, COL_FICON_DEFER); + pr_metric(a, n, metrics, hpf_rate, CMG_NUMBER, COL_HPF_RATE); + pr_metric(a, n, metrics, hpf_active, CMG_NUMBER, COL_HPF_ACTIVE); + pr_metric(a, n, metrics, hpf_defer, CMG_NUMBER, COL_HPF_DEFER); +} + +static struct cmg_pair_t *get_values(struct cmg_data_t *data, int groups) +{ + struct cmg_pair_t *array = NULL; + unsigned int num = 0; + + if (groups & KEY_GRP_CHARS) + pr_chars(&array, &num, data); + if (groups & KEY_GRP_UTIL) { + pr_cue(&array, &num, data); + pr_ext_cue(&array, &num, data); + } + if (groups & KEY_GRP_METRICS) + pr_metrics(&array, &num, data); + + /* Add terminating null-element. */ + util_expand_array(&array, &num); + array[num - 1].key = NULL; + + return array; +} + +/* Initialize metrics in @m. */ +static void init_metrics(struct metrics2_t *m) +{ + m->interval = METRICS_INIT; + m->util_total = METRICS_INIT; + m->util_part = METRICS_INIT; + m->util_bus = METRICS_INIT; + m->read_total = METRICS_INIT; + m->read_part = METRICS_INIT; + m->write_total = METRICS_INIT; + m->write_part = METRICS_INIT; + /* Extended CUE metrics. */ + m->ficon_rate = METRICS_INIT; + m->ficon_active = METRICS_INIT; + m->ficon_defer = METRICS_INIT; + m->hpf_rate = METRICS_INIT; + m->hpf_active = METRICS_INIT; + m->hpf_defer = METRICS_INIT; +} + +/* Calculate metrics base on CMG 2 CUEs. */ +static void calc_metrics(struct cmg_data_t *data, double seconds) +{ + struct metrics2_t *m = get_metrics(data); + struct cmcb2_t *cmcb = get_cmcb(data); + struct cue2_t *a = get_cue(data, util_a); + struct cue2_t *b = get_cue(data, util_b); + double delta, max; + + /* util_total = 100.0 * work_units_cpc / max_work_units */ + if (cue_valid2(a, b, channel_work_units_cpc)) { + delta = field_delta(channel_work_units_cpc, a, b); + max = cmcb->max_channel_work_units * seconds; + if (max != 0.0) + m->util_total = 100.0 * delta / max; + } + /* util_part = 100.0 * work_units / max_work_units */ + if (cue_valid2(a, b, channel_work_units)) { + delta = field_delta(channel_work_units, a, b); + max = cmcb->max_channel_work_units * seconds; + if (max != 0.0) + m->util_part = 100.0 * delta / max; + } + /* util_bus = 100.0 * bus_cycles_cpc / max_bus_cycles */ + if (cue_valid2(a, b, bus_cycles_cpc)) { + delta = field_delta(bus_cycles_cpc, a, b); + max = cmcb->max_bus_cycles * seconds; + if (max != 0.0) + m->util_bus = 100.0 * delta / max; + } + /* read_total = data_units_read_cpc * unit_size / seconds */ + if (cue_valid2(a, b, data_units_read_cpc)) { + delta = field_delta(data_units_read_cpc, a, b); + m->read_total = (double)delta * cmcb->data_unit_size / seconds; + } + /* read_part = data_units_read * unit_size / seconds */ + if (cue_valid2(a, b, data_units_read)) { + delta = field_delta(data_units_read, a, b); + m->read_part = (double)delta * cmcb->data_unit_size / seconds; + } + /* write_total = data_units_written_cpc * unit_size / seconds */ + if (cue_valid2(a, b, data_units_written_cpc)) { + delta = field_delta(data_units_written_cpc, a, b); + m->write_total = (double)delta * cmcb->data_unit_size / + seconds; + } + /* write_part = data_units_written * unit_size / seconds */ + if (cue_valid2(a, b, data_units_written)) { + delta = field_delta(data_units_written, a, b); + m->write_part = (double)delta * cmcb->data_unit_size / seconds; + } +} + +/* Calculate metrics base on CMG 2 extended CUEs. */ +static void calc_ext_metrics(struct cmg_data_t *data, double seconds) +{ + struct metrics2_t *m = get_metrics(data); + struct ext_cue2_t *a = get_ext_cue(data, util_a); + struct ext_cue2_t *b = get_ext_cue(data, util_b); + double delta, delta2; + + /* These metrics require extended CUEs. */ + if (!data->util_a.extended || !data->util_b.extended) + return; + + /* ficon_rate = total_ficon_ops_cpc / seconds */ + delta = field_delta(total_ficon_ops_cpc, a, b); + m->ficon_rate = delta / seconds; + + /* ficon_active = sum_ficon_ops_cpc / total_ficon_ops_cpc */ + if (delta != 0.0) { + delta2 = (double)field_delta64(sum_ficon_ops_cpc, a, b); + m->ficon_active = delta2 / delta; + } else { + m->ficon_active = 0.0; + } + + /* ficon_defer = total_deferred_ficon_ops_cpc / seconds */ + delta = field_delta(total_deferred_ficon_ops_cpc, a, b); + m->ficon_defer = delta / seconds; + + /* hpf_rate = total_hpf_ops_cpc / seconds */ + delta = field_delta(total_hpf_ops_cpc, a, b); + m->hpf_rate = delta / seconds; + + /* hpf_active = sum_hpf_ops_cpc / total_hpf_ops_cpc */ + if (delta != 0.0) { + delta2 = (double)field_delta64(sum_hpf_ops_cpc, a, b); + m->hpf_active = delta2 / delta; + } else { + m->hpf_active = 0.0; + } + + /* hpf_defer = total_deferred_hpf_ops_cpc / seconds */ + delta = field_delta(total_deferred_hpf_ops_cpc, a, b); + m->hpf_defer = delta / seconds; +} + +/* Recalculate metrics in @data. */ +static void update_metrics(struct cmg_data_t *data) +{ + struct metrics2_t *m = get_metrics(data); + struct cue2_t *a = get_cue(data, util_a); + struct cue2_t *b = get_cue(data, util_b); + u32 ticks; + + init_metrics(m); + + ticks = get_delta(a->timestamp, b->timestamp, CUE_TS_WIDTH); + if (ticks == 0) + return; + + /* interval = t2 - t1 */ + m->interval = tick_to_s(ticks); + + calc_metrics(data, m->interval); + calc_ext_metrics(data, m->interval); +} + +/* Object defining this CMG. */ +static struct cmg_t cmg2 = { + .cmg = 2, + .selected = false, + .found = 0, + .has_cmcb = true, + .default_column_ids = default_column_ids, + .get_values = &get_values, + .update_metrics = &update_metrics, +}; + +/* Add to CMG registry. */ +static void __attribute__((constructor)) cmg2_ctr(void) +{ + cmg_add(&cmg2); +} diff --git a/zconf/chp/chpstat/cmg3.c b/zconf/chp/chpstat/cmg3.c new file mode 100644 index 00000000..4ab38504 --- /dev/null +++ b/zconf/chp/chpstat/cmg3.c @@ -0,0 +1,316 @@ +/* + * Support for CMG 3 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 "cmg.h" +#include "cmg_helper.h" +#include "column.h" +#include "key.h" + +#include "lib/util_base.h" + +/* Macros to convert generic cmg_data_t into CMG-specific ones. */ +#define get_cmcb(d) ((struct cmcb3_t *)&((d)->cmcb)) +#define get_cue(d, x) ((struct cue3_t *)&((d)->x.cue)) +#define get_metrics(d) ((struct metrics3_t *)&((d)->metrics)) + +/* CMG 3 format Channel-Measurement-Characteristics Block (CMCB). */ +struct cmcb3_t { + u32 data_unit_size; + u32 data_unit_size_cpc; + u32 msg_unit_size; + u32 msg_unit_size_cpc; +} __packed; + +STATIC_ASSERT(sizeof(struct cmcb3_t) <= sizeof(cmcb_t)); + +/* CMG 3 format Channel-Utilization-Entry (CUE). */ +struct cue3_t { + u8 cuiv; + u32 timestamp:24; + u32 msg_units_sent; + u32 msg_units_sent_cpc; + u32 unsuccessful_attempts_to_send; + u32 unavailable_receive_buffers; + u32 unavailable_receive_buffers_cpc; + u32 data_units_sent; + u32 data_units_sent_cpc; +} __packed; + +STATIC_ASSERT(sizeof(struct cue3_t) <= sizeof(cue_t)); + +/* Metrics based on CMG 3 format CUEs. */ +struct metrics3_t { + double interval; + double write_total; + double write_part; + double msg_rate_total; + double msg_rate_part; + double msg_size_total; + double msg_size_part; + double send_fail_part; + double rcv_fail_total; + double rcv_fail_part; +}; + +STATIC_ASSERT(sizeof(struct metrics3_t) <= sizeof(metrics_t)); + +/* IDs of columns that should be shown by default in table output. */ +static const enum column_id_t default_column_ids[] = { + COL_CHPID, + COL_TYPE, + COL_CMG, + COL_SHARED, + COL_SPEED, + COL_WRITE_PART, + COL_WRITE_TOTAL, + COL_MSG_RATE_PART, + COL_MSG_RATE_TOTAL, + COL_MSG_SIZE_PART, + COL_MSG_SIZE_TOTAL, + COL_SEND_FAIL_PART, + COL_RCV_FAIL_PART, + COL_RCV_FAIL_TOTAL, + /* End of list. */ + COL_END +}; + +static void pr_chars(struct cmg_pair_t **a, unsigned int *n, + struct cmg_data_t *data) +{ + struct cmcb3_t *cmcb = get_cmcb(data); + + pr_u32(a, n, cmcb, data_unit_size); + pr_u32(a, n, cmcb, data_unit_size_cpc); + pr_u32(a, n, cmcb, msg_unit_size); + pr_u32(a, n, cmcb, msg_unit_size_cpc); +} + +static void pr_cue(struct cmg_pair_t **a, unsigned int *n, + struct cmg_data_t *data) +{ + struct cue3_t *cue = get_cue(data, util_b); + + pr_u32(a, n, cue, timestamp); + pr_cue_u32(a, n, cue, msg_units_sent); + pr_cue_u32(a, n, cue, msg_units_sent_cpc); + pr_cue_u32(a, n, cue, unsuccessful_attempts_to_send); + pr_cue_u32(a, n, cue, unavailable_receive_buffers); + pr_cue_u32(a, n, cue, unavailable_receive_buffers_cpc); + pr_cue_u32(a, n, cue, data_units_sent); + pr_cue_u32(a, n, cue, data_units_sent_cpc); +} + +static void pr_metrics(struct cmg_pair_t **a, unsigned int *n, + struct cmg_data_t *data) +{ + struct metrics3_t *m = get_metrics(data); + + pr_metric(a, n, m, write_part, CMG_BPS, COL_WRITE_PART); + pr_metric(a, n, m, write_total, CMG_BPS, COL_WRITE_TOTAL); + pr_metric(a, n, m, msg_rate_part, CMG_NUMBER, COL_MSG_RATE_PART); + pr_metric(a, n, m, msg_rate_total, CMG_NUMBER, COL_MSG_RATE_TOTAL); + pr_metric(a, n, m, msg_size_part, CMG_NUMBER, COL_MSG_SIZE_PART); + pr_metric(a, n, m, msg_size_total, CMG_NUMBER, COL_MSG_SIZE_TOTAL); + pr_metric(a, n, m, send_fail_part, CMG_NUMBER, COL_SEND_FAIL_PART); + pr_metric(a, n, m, rcv_fail_part, CMG_NUMBER, COL_RCV_FAIL_PART); + pr_metric(a, n, m, rcv_fail_total, CMG_NUMBER, COL_RCV_FAIL_TOTAL); +} + +static struct cmg_pair_t *get_values(struct cmg_data_t *data, int groups) +{ + struct cmg_pair_t *array = NULL; + unsigned int num = 0; + + if (groups & KEY_GRP_CHARS) + pr_chars(&array, &num, data); + if (groups & KEY_GRP_UTIL) + pr_cue(&array, &num, data); + if (groups & KEY_GRP_METRICS) + pr_metrics(&array, &num, data); + + /* Add terminating null-element. */ + util_expand_array(&array, &num); + array[num - 1].key = NULL; + + return array; +} + +/* Initialize metrics in @m. */ +static void init_metrics(struct metrics3_t *m) +{ + m->interval = METRICS_INIT; + m->write_total = METRICS_INIT; + m->write_part = METRICS_INIT; + m->msg_rate_total = METRICS_INIT; + m->msg_rate_part = METRICS_INIT; + m->msg_size_total = METRICS_INIT; + m->msg_size_part = METRICS_INIT; + m->send_fail_part = METRICS_INIT; + m->rcv_fail_total = METRICS_INIT; + m->rcv_fail_part = METRICS_INIT; +} + +/* Calculate metrics base on CMG 3 CUEs. */ +static void calc_metrics(struct cmg_data_t *data, double seconds) +{ + u32 data_size, data_size_cpc, msg_size, msg_size_cpc; + struct metrics3_t *m = get_metrics(data); + struct cmcb3_t *cmcb = get_cmcb(data); + struct cue3_t *a = get_cue(data, util_a); + struct cue3_t *b = get_cue(data, util_b); + double delta, delta2; + + /* When not valid (reported as 0) some CMCB fields have default values + * or can be derived from related fields. */ + data_size = cmcb->data_unit_size ? : 1; + data_size_cpc = cmcb->data_unit_size_cpc ? : data_size; + msg_size = cmcb->msg_unit_size ? : 1; + msg_size_cpc = cmcb->msg_unit_size_cpc ? : msg_size; + + /* + * Amount of bytes per second sent in messages by all partitions + * + * write_total = data_units_sent_cpc * data_unit_size_cpc / seconds + */ + if (cue_valid2(a, b, data_units_sent_cpc)) { + delta = field_delta(data_units_sent_cpc, a, b); + m->write_total = delta * data_size_cpc / seconds; + } + + /* + * Amount of bytes per second sent in messages by this partition + * + * write_part = data_units_sent * data_unit_size / seconds + */ + if (cue_valid2(a, b, data_units_sent)) { + delta = field_delta(data_units_sent, a, b); + m->write_part = delta * data_size / seconds; + } + + /* + * Rate of messages sent per second by all partitions + * + * msg_rate_total = msg_units_sent_cpc * msg_unit_size_cpc / seconds + */ + if (cue_valid2(a, b, msg_units_sent_cpc)) { + delta = field_delta(msg_units_sent_cpc, a, b); + m->msg_rate_total = delta * msg_size_cpc / seconds; + } + + /* + * Rate of messages sent per second by this partition + * + * msg_rate_part = msg_units_sent * msg_unit_size / seconds + */ + if (cue_valid2(a, b, msg_units_sent)) { + delta = field_delta(msg_units_sent, a, b); + m->msg_rate_part = delta * msg_size / seconds; + } + + /* + * Average size of messages sent by all partitions + * + * msg_size_total = data_units_sent_cpc * data_unit_size_cpc / + * (msg_units_sent_cpc * msg_unit_size_cpc) + */ + if (cue_valid2(a, b, data_units_sent_cpc) && + cue_valid2(a, b, msg_units_sent_cpc)) { + delta = field_delta(data_units_sent_cpc, a, b); + delta2 = field_delta(msg_units_sent_cpc, a, b); + if (delta2 != 0.0) { + m->msg_size_total = delta * data_size_cpc / + (delta2 * msg_size_cpc); + } + } + + /* + * Average size of messages sent by this partition + * + * msg_size_part = data_units_sent * data_unit_size / + * (msg_units_sent * msg_unit_size) + */ + if (cue_valid2(a, b, data_units_sent) && + cue_valid2(a, b, msg_units_sent)) { + delta = field_delta(data_units_sent, a, b); + delta2 = field_delta(msg_units_sent, a, b); + if (delta2 != 0.0) { + m->msg_size_part = delta * data_size / + (delta2 * msg_size); + } + } + + /* + * Number of failed message send attempts per second by this partition + * + * send_fail_part = unsuccessful_attempts_to_send / seconds + */ + if (cue_valid2(a, b, unsuccessful_attempts_to_send)) { + delta = field_delta(unsuccessful_attempts_to_send, a, b); + m->send_fail_part = delta / seconds; + } + + /* + * Rate of messages per second that could not be received by all + * partitions due to unavailable receive buffers + * + * rcv_fail_total = unavailable_receive_buffers_cpc / seconds + */ + if (cue_valid2(a, b, unavailable_receive_buffers_cpc)) { + delta = field_delta(unavailable_receive_buffers_cpc, a, b); + m->rcv_fail_total = delta / seconds; + } + + /* + * Rate of messages per second that could not be received by this + * partition due to unavailable receive buffers + * + * rcv_fail_part = unavailable_receive_buffers / seconds + */ + if (cue_valid2(a, b, unavailable_receive_buffers)) { + delta = field_delta(unavailable_receive_buffers, a, b); + m->rcv_fail_part = delta / seconds; + } +} + +/* Recalculate metrics in @data. */ +static void update_metrics(struct cmg_data_t *data) +{ + struct metrics3_t *m = get_metrics(data); + struct cue3_t *a = get_cue(data, util_a); + struct cue3_t *b = get_cue(data, util_b); + u32 ticks; + + init_metrics(m); + + ticks = get_delta(a->timestamp, b->timestamp, CUE_TS_WIDTH); + if (ticks == 0) + return; + + /* interval = t2 - t1 */ + m->interval = tick_to_s(ticks); + + calc_metrics(data, m->interval); +} + +/* Object defining this CMG. */ +static struct cmg_t cmg3 = { + .cmg = 3, + .selected = false, + .found = 0, + .has_cmcb = true, + .default_column_ids = default_column_ids, + .get_values = &get_values, + .update_metrics = &update_metrics, +}; + +/* Add to CMG registry. */ +static void __attribute__((constructor)) cmg3_ctr(void) +{ + cmg_add(&cmg3); +} diff --git a/zconf/chp/chpstat/cmg_helper.c b/zconf/chp/chpstat/cmg_helper.c new file mode 100644 index 00000000..6f7fd27f --- /dev/null +++ b/zconf/chp/chpstat/cmg_helper.c @@ -0,0 +1,109 @@ +/* + * Helper functions for implementing CMG types + * + * 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 +#include +#include + +#include "cmg_helper.h" +#include "cmg.h" + +#include "lib/util_base.h" +#include "lib/util_libc.h" + +/* Convert time ticks to seconds. */ +double tick_to_s(u32 t) +{ + return CM_TICK * t; +} + +/* Get 32 bit delta of counters @curr and @last. Take into account that both + * counters might have wrapped. Counter width is @width bits. */ +u32 get_delta(u32 last, u32 curr, int width) +{ + if (curr >= last) + return curr - last; + + /* Counter wrapped - add [last..max] + [0..curr] */ + return (u32)((u64)(1ULL << width) - last + curr); +} + +/* Get 64 bit delta of counters @curr and @last. Take into account that both + * counters might have wrapped. Counter width is @width bits. */ +u64 get_delta64(u64 last, u64 curr, int width) +{ + u64 max; + + if (curr >= last) + return curr - last; + + /* Counter wrapped - add [last..max] + [0..curr] */ + max = (width == 64) ? ULLONG_MAX : (1ULL << width) - 1; + return (max - last + curr) + 1; +} + +/* Check whether word at @offset is valid according to @cuiv. */ +bool _cue_valid(u8 cuiv, int offset) +{ + int num; + u8 mask; + + num = offset / (int)sizeof(u32); + mask = 0x80 >> num; + + return (cuiv & mask) != 0; +} + +static struct cmg_pair_t *add_pair(struct cmg_pair_t **array, unsigned int *num, + bool valid, const char *key, + enum column_id_t col, enum cmg_unit_t unit, + enum cmg_value_type_t type) +{ + struct cmg_pair_t pair; + + memset(&pair, 0, sizeof(pair)); + pair.valid = valid; + pair.key = util_strdup(key); + pair.col = col; + pair.unit = unit; + pair.type = type; + util_add_array(array, num, pair); + + return &((*array)[*num - 1]); +} + +void _pr_u32(struct cmg_pair_t **array, unsigned int *num, bool valid, + const char *key, enum column_id_t col, enum cmg_unit_t unit, + u32 value) +{ + struct cmg_pair_t *pair; + + pair = add_pair(array, num, valid, key, col, unit, CMG_U32); + pair->value_u32 = value; +} + +void _pr_u64(struct cmg_pair_t **array, unsigned int *num, bool valid, + const char *key, enum column_id_t col, enum cmg_unit_t unit, + u64 value) +{ + struct cmg_pair_t *pair; + + pair = add_pair(array, num, valid, key, col, unit, CMG_U64); + pair->value_u64 = value; +} + +void _pr_double(struct cmg_pair_t **array, unsigned int *num, bool valid, + const char *key, enum column_id_t col, enum cmg_unit_t unit, + double value) +{ + struct cmg_pair_t *pair; + + pair = add_pair(array, num, valid, key, col, unit, CMG_FLOAT); + pair->value_double = value; +} diff --git a/zconf/chp/chpstat/cmg_helper.h b/zconf/chp/chpstat/cmg_helper.h new file mode 100644 index 00000000..aac1fba0 --- /dev/null +++ b/zconf/chp/chpstat/cmg_helper.h @@ -0,0 +1,81 @@ +/* + * Helper functions for implementing CMG types + * + * 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. + */ + +#ifndef CMG_HELPER_H +#define CMG_HELPER_H + +#include + +#include "cmg.h" +#include "column.h" + +#include "lib/util_base.h" + +/* Duration of channel-path measurement timestamp tick. */ +#define CM_TICK ((double)0.000128) + +/* Initialization value for metrics fields. */ +#define METRICS_INIT -1.0 + +/* Check whether member @field of struct @s is valid according to s->cuiv. */ +#define cue_valid(s, field) \ + (_cue_valid((s)->cuiv, offsetof(__typeof__(*s), field))) + +/* Check whether member @field of struct @a and @b is valid according to + * a->cuiv and b->cuiv. */ +#define cue_valid2(a, b, field) \ + ((_cue_valid((a)->cuiv, offsetof(__typeof__(*a), field))) && \ + (_cue_valid((b)->cuiv, offsetof(__typeof__(*b), field)))) + +#define field_delta(field, a, b) \ + get_delta((a)->field, (b)->field, 32) + +#define field_delta64(field, a, b) \ + get_delta64((a)->field, (b)->field, 64) + +#define pr_u32(a, n, s, field) \ + _pr_u32((a), (n), true, STRINGIFY(field), COL_NONE, CMG_NUMBER, \ + (s)->field) + +#define pr_cond_u32(a, n, v, s, field) \ + _pr_u32((a), (n), (v), STRINGIFY(field), COL_NONE, CMG_NUMBER, \ + (s)->field) + +#define pr_cue_u32(a, n, s, field) \ + pr_cond_u32(a, n, cue_valid(s, field), s, field) + +#define pr_u64(a, n, s, field) \ + _pr_u64((a), (n), true, STRINGIFY(field), COL_NONE, CMG_NUMBER, \ + (s)->field) + +#define pr_cond_u64(a, n, v, s, field) \ + _pr_u64((a), (n), (v), STRINGIFY(field), COL_NONE, CMG_NUMBER, \ + (s)->field) + +/* pr_metric(array_ptr, num_ptr, struct, field, unit, column_id) */ +#define pr_metric(a, n, s, field, u, c) \ + _pr_double((a), (n), ((s)->field != METRICS_INIT), STRINGIFY(field), \ + c, u, (s)->field) + +double tick_to_s(u32 t); +u32 get_delta(u32 last, u32 curr, int width); +u64 get_delta64(u64 last, u64 curr, int width); +bool _cue_valid(u8 cuiv, int offset); + +void _pr_u32(struct cmg_pair_t **a, unsigned int *n, bool valid, + const char *key, enum column_id_t col, enum cmg_unit_t unit, + u32 value); +void _pr_u64(struct cmg_pair_t **a, unsigned int *n, bool valid, + const char *key, enum column_id_t col, enum cmg_unit_t unit, + u64 value); +void _pr_double(struct cmg_pair_t **a, unsigned int *n, bool valid, + const char *key, enum column_id_t col, enum cmg_unit_t unit, + double value); + +#endif /* CMG_HELPER_H */ diff --git a/zconf/chp/chpstat/column.c b/zconf/chp/chpstat/column.c new file mode 100644 index 00000000..1c1e5d13 --- /dev/null +++ b/zconf/chp/chpstat/column.c @@ -0,0 +1,430 @@ +/* + * Registry for supported table columns + * + * 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 +#include + +#include "column.h" + +#include "lib/util_base.h" +#include "lib/util_libc.h" + +/* + * Use group header for adjacent columns of the same type to improve + * readability. + * + * Single header: UTIL Group header: UTILIZATION + * PART PART TOTAL + */ +#define HDR1_CHP_SINGLE "" +#define HDR1_CHP_GROUP "CHANNEL-PATH" +#define HDR1_UTIL_SINGLE "UTIL" +#define HDR1_UTIL_GROUP "UTILIZATION(%)" +#define HDR1_READ_SINGLE "READ" +#define HDR1_READ_GROUP "READ" +#define HDR1_WRITE_SINGLE "WRITE" +#define HDR1_WRITE_GROUP "WRITE" +#define HDR1_FICON_SINGLE "FICON" +#define HDR1_FICON_GROUP "FICON-OPS" +#define HDR1_HPF_SINGLE "HPF" +#define HDR1_HPF_GROUP "HPF-OPS" +#define HDR1_MSGR_SINGLE "RATE" +#define HDR1_MSGR_GROUP "MSG-RATE" +#define HDR1_MSGSZ_SINGLE "SIZE" +#define HDR1_MSGSZ_GROUP "MSG-SIZE" +#define HDR1_MSG_RCVF_SINGLE "RFAIL" +#define HDR1_MSG_RCVF_GROUP "RECEIVE-FAIL" + +static struct column_t columns[] = { + { + COL_CHPID, + "chpid", + COL_OTHER, + "Channel-path ID", + "ID", + HDR1_CHP_SINGLE, + HDR1_CHP_GROUP, + 0, + }, + { + COL_TYPE, + "type", + COL_OTHER, + "Channel-path type", + "TYP", + HDR1_CHP_SINGLE, + HDR1_CHP_GROUP, + 0, + }, + { + COL_CMG, + "cmg", + COL_OTHER, + "Channel-measurement group", + "CMG", + HDR1_CHP_SINGLE, + HDR1_CHP_GROUP, + 0, + }, + { + COL_SPEED, + "speed", + COL_OTHER, + "Operational speed", + "SPEED", + HDR1_CHP_SINGLE, + HDR1_CHP_GROUP, + 0, + }, + { + COL_SHARED, + "shared", + COL_OTHER, + "Shared channel-path indicator", + "SHR", + HDR1_CHP_SINGLE, + HDR1_CHP_GROUP, + 0, + }, + { + COL_INTERVAL, + "interval", + COL_OTHER, + "Cumulated statistics update interval", + "INT", + HDR1_CHP_SINGLE, + HDR1_CHP_GROUP, + 0, + }, + { + COL_UTIL_PART, + "util_part", + COL_PERCENT, + "Partition channel-path utilization in %", + "PART", + HDR1_UTIL_SINGLE, + HDR1_UTIL_GROUP, + 0, + }, + { + COL_UTIL_TOTAL, + "util_total", + COL_PERCENT, + "Total channel-path utilization in %", + "TOTAL", + HDR1_UTIL_SINGLE, + HDR1_UTIL_GROUP, + 0, + }, + { + COL_UTIL_BUS, + "util_bus", + COL_PERCENT, + "Bus utilization in %", + "BUS", + HDR1_UTIL_SINGLE, + HDR1_UTIL_GROUP, + 0, + }, + { + COL_READ_PART, + "read_part", + COL_BPS, + "Partition read throughput in B/s", + "PART", + HDR1_READ_SINGLE, + HDR1_READ_GROUP, + 0, + }, + { + COL_READ_TOTAL, + "read_total", + COL_BPS, + "Total read throughput in B/s", + "TOTAL", + HDR1_READ_SINGLE, + HDR1_READ_GROUP, + 0, + }, + { + COL_WRITE_PART, + "write_part", + COL_BPS, + "Partition write throughput in B/s", + "PART", + HDR1_WRITE_SINGLE, + HDR1_WRITE_GROUP, + 0, + }, + { + COL_WRITE_TOTAL, + "write_total", + COL_BPS, + "Total write throughput in B/s", + "TOTAL", + HDR1_WRITE_SINGLE, + HDR1_WRITE_GROUP, + 0, + }, + { + COL_FICON_RATE, + "ficon_rate", + COL_NUMBER, + "FICON operations per second", + "RATE", + HDR1_FICON_SINGLE, + HDR1_FICON_GROUP, + 0, + }, + { + COL_FICON_ACTIVE, + "ficon_active", + COL_NUMBER, + "Avg. concurrently active FICON operations", + "ACTV", + HDR1_FICON_SINGLE, + HDR1_FICON_GROUP, + 0, + }, + { + COL_FICON_DEFER, + "ficon_defer", + COL_NUMBER, + "Deferred FICON operations per second", + "DEFER", + HDR1_FICON_SINGLE, + HDR1_FICON_GROUP, + 0, + }, + { + COL_HPF_RATE, + "hpf_rate", + COL_NUMBER, + "HPF operations per second", + "RATE", + HDR1_HPF_SINGLE, + HDR1_HPF_GROUP, + 0, + }, + { + COL_HPF_ACTIVE, + "hpf_active", + COL_NUMBER, + "Avg. concurrently active HPF operations", + "ACTV", + HDR1_HPF_SINGLE, + HDR1_HPF_GROUP, + 0, + }, + { + COL_HPF_DEFER, + "hpf_defer", + COL_NUMBER, + "Deferred HPF operations per second", + "DEFER", + HDR1_HPF_SINGLE, + HDR1_HPF_GROUP, + 0, + }, + { + COL_MSG_RATE_PART, + "msg_rate_part", + COL_NUMBER, + "Partition message send rate per second", + "PART", + HDR1_MSGR_SINGLE, + HDR1_MSGR_GROUP, + 0, + }, + { + COL_MSG_RATE_TOTAL, + "msg_rate_total", + COL_NUMBER, + "Total message send rate per second", + "TOTAL", + HDR1_MSGR_SINGLE, + HDR1_MSGR_GROUP, + 0, + }, + { + COL_MSG_SIZE_PART, + "msg_size_part", + COL_NUMBER, + "Partition avg. send message size", + "PART", + HDR1_MSGSZ_SINGLE, + HDR1_MSGSZ_GROUP, + 0, + }, + { + COL_MSG_SIZE_TOTAL, + "msg_size_total", + COL_NUMBER, + "Total avg. send message size", + "TOTAL", + HDR1_MSGSZ_SINGLE, + HDR1_MSGSZ_GROUP, + 0, + }, + { + COL_SEND_FAIL_PART, + "send_fail_part", + COL_NUMBER, + "Partition message send fail rate per second", + "PART", + "SNDFAIL", + "SNDFAIL", + 0, + }, + { + COL_RCV_FAIL_PART, + "rcv_fail_part", + COL_NUMBER, + "Partition message receive fail rate per second", + "PART", + HDR1_MSG_RCVF_SINGLE, + HDR1_MSG_RCVF_GROUP, + 0, + }, + { + COL_RCV_FAIL_TOTAL, + "rcv_fail_total", + COL_NUMBER, + "Total message receive fail rate per second", + "TOTAL", + HDR1_MSG_RCVF_SINGLE, + HDR1_MSG_RCVF_GROUP, + 0, + }, +}; + +/* Columns selected by default. */ +static const int default_columns[] = { + COL_CHPID, + COL_TYPE, + COL_CMG, + COL_SHARED, + COL_SPEED, + COL_UTIL_PART, + COL_UTIL_TOTAL, + COL_UTIL_BUS, + COL_READ_PART, + COL_READ_TOTAL, + COL_WRITE_PART, + COL_WRITE_TOTAL, + /* End of list. */ + COL_END +}; + +static struct column_t **selected_cols; +static unsigned int num_selected_cols; +static bool hdr_updated; + +struct column_t *column_get_by_index(unsigned int i, bool selected) +{ + if (selected) + return i < num_selected_cols ? selected_cols[i] : NULL; + return i < ARRAY_SIZE(columns) ? &columns[i] : NULL; +} + +/* + * Retrieve column object by @name or %NULL if column does not exist. + */ +struct column_t *column_get_by_name(const char *name) +{ + struct column_t *col; + + column_for_each(col) { + if (strcasecmp(col->name, name) == 0) + return col; + } + + return NULL; +} + +/* + * Add the specified column to the list of selected columns. Discard duplicate + * selections of the same column. + */ +void column_select(struct column_t *col) +{ + struct column_t *c; + + /* Silently filter out double selection of columns to prevent problems + * with non-unique util_rec name fields. */ + column_for_each_selected(c) { + if (c == col) + return; + } + + util_add_array(&selected_cols, &num_selected_cols, col); +} + +void column_select_id_list(const int *ids) +{ + struct column_t *col; + int i; + + for (i = 0; ids[i] != COL_END; i++) { + column_for_each(col) { + if (col->id == ids[i]) { + column_select(col); + break; + } + } + } +} + +void column_select_default(void) +{ + column_select_id_list(default_columns); +} + +void column_select_all(void) +{ + struct column_t *col; + + column_for_each(col) + column_select(col); +} + +void column_update_bps_suffix(bool auto_scale, char suffix_char) +{ + struct column_t *col; + char *str; + + if (auto_scale) + util_asprintf(&str, "(B/s)"); + else if (suffix_char) + util_asprintf(&str, "(%ciB/s)", suffix_char); + else + str = util_strdup("(*)"); + + column_for_each(col) { + if (col->unit != COL_BPS) { + col->hdr1_group = util_strdup(col->hdr1_group); + continue; + } + util_asprintf(&col->hdr1_group, "%s%s", col->hdr1_group, str); + } + free(str); + + hdr_updated = true; +} + +void column_exit(void) +{ + struct column_t *col; + + if (hdr_updated) { + column_for_each(col) + free(col->hdr1_group); + } + free(selected_cols); +} diff --git a/zconf/chp/chpstat/column.h b/zconf/chp/chpstat/column.h new file mode 100644 index 00000000..46cf539d --- /dev/null +++ b/zconf/chp/chpstat/column.h @@ -0,0 +1,84 @@ +/* + * Registry for supported table columns + * + * 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. + */ + +#ifndef COLUMN_H +#define COLUMN_H + +#include + +enum column_id_t { + COL_NONE = -1, + COL_CHPID, + COL_TYPE, + COL_CMG, + COL_SHARED, + COL_SPEED, + COL_INTERVAL, + COL_TIMESTAMP, + COL_UTIL_PART, + COL_UTIL_TOTAL, + COL_UTIL_BUS, + COL_READ_PART, + COL_READ_TOTAL, + COL_WRITE_PART, + COL_WRITE_TOTAL, + COL_FICON_RATE, + COL_FICON_ACTIVE, + COL_FICON_DEFER, + COL_HPF_RATE, + COL_HPF_ACTIVE, + COL_HPF_DEFER, + COL_MSG_RATE_PART, + COL_MSG_RATE_TOTAL, + COL_MSG_SIZE_PART, + COL_MSG_SIZE_TOTAL, + COL_SEND_FAIL_PART, + COL_RCV_FAIL_PART, + COL_RCV_FAIL_TOTAL, + /* Special value indicating no column. */ + COL_END +}; + +enum col_unit_t { + COL_OTHER, + COL_NUMBER, + COL_PERCENT, + COL_BPS, +}; + +struct column_t { + enum column_id_t id; + const char *name; + enum col_unit_t unit; + const char *desc; + const char *hdr2; + const char *hdr1_single; + char *hdr1_group; + unsigned int width; +}; + +#define column_for_each(c) \ + for (unsigned int __i = 0; ((c) = column_get_by_index(__i, false)); \ + __i++) +#define column_for_each_selected(c) \ + for (unsigned int __i = 0; ((c) = column_get_by_index(__i, true)); \ + __i++) + +struct column_t *column_get_by_index(unsigned int i, bool selected); +struct column_t *column_get_by_name(const char *name); + +void column_select(struct column_t *col); +void column_select_id_list(const int *ids); +void column_select_default(void); +void column_select_all(void); + +void column_update_bps_suffix(bool auto_scale, char suffix_char); +void column_exit(void); + +#endif /* COLUMN_H */ diff --git a/zconf/chp/chpstat/key.c b/zconf/chp/chpstat/key.c new file mode 100644 index 00000000..fe65819f --- /dev/null +++ b/zconf/chp/chpstat/key.c @@ -0,0 +1,227 @@ +/* + * Registry for supported data keys + * + * 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 "key.h" + +#include +#include +#include + +#include "cmg.h" +#include "lib/util_libc.h" +#include "lib/zt_common.h" + +static struct key_t *keys; +static unsigned int num_keys; + +static struct key_t **selected_keys; +static unsigned int num_selected_keys; + +#define GET_CMG_MASK(c) ((u32)(1 << (c))) +#define CMG_ALL_MASK ((u32)0xffffffff) +#define IS_CMG_MASK(c, n) ((c) & GET_CMG_MASK(n)) + +static char *mask_to_cmg_str(u32 mask) +{ + char *str; + int i; + + if (mask == CMG_ALL_MASK) + return util_strdup("all"); + str = util_strdup(""); + for (i = 1; i <= 32; i++) { + if (!IS_CMG_MASK(mask, i)) + continue; + if (*str) + util_concatf(&str, ","); + util_concatf(&str, "%d", i); + } + + return str; +} + +const char *key_group_to_str(enum key_group_t group) +{ + switch (group) { + case KEY_GRP_META: + return "meta"; + case KEY_GRP_ITERATION: + return "iteration"; + case KEY_GRP_CHP: + return "channel_path"; + case KEY_GRP_CHARS: + return "characteristics"; + case KEY_GRP_UTIL: + return "utilization"; + case KEY_GRP_METRICS: + return "metrics"; + default: + return ""; + } +} + +static void add_key(const char *name, enum key_group_t group, bool found, + u32 cmg_mask) +{ + struct key_t *key; + + key = key_get_by_name(name); + if (!key) { + util_expand_array(&keys, &num_keys); + key = &keys[num_keys - 1]; + key->name = util_strdup(name); + key->group = group; + } + key->found |= found; + key->cmg_mask |= cmg_mask; + free(key->cmg_str); + key->cmg_str = mask_to_cmg_str(key->cmg_mask); +} + +static void add_generic_keys(void) +{ + add_key(KEY_META_API_LEVEL, KEY_GRP_META, true, CMG_ALL_MASK); + add_key(KEY_META_VERSION, KEY_GRP_META, true, CMG_ALL_MASK); + add_key(KEY_META_HOST, KEY_GRP_META, true, CMG_ALL_MASK); + add_key(KEY_META_TIME, KEY_GRP_META, true, CMG_ALL_MASK); + add_key(KEY_META_TIME_EPOCH, KEY_GRP_META, true, CMG_ALL_MASK); + add_key(KEY_ITERATION, KEY_GRP_ITERATION, true, CMG_ALL_MASK); + add_key(KEY_TIME, KEY_GRP_ITERATION, true, CMG_ALL_MASK); + add_key(KEY_TIME_EPOCH, KEY_GRP_ITERATION, true, CMG_ALL_MASK); + add_key(KEY_CHPID, KEY_GRP_CHP, true, CMG_ALL_MASK); + add_key(KEY_TYPE, KEY_GRP_CHP, true, CMG_ALL_MASK); + add_key(KEY_CMG, KEY_GRP_CHP, true, CMG_ALL_MASK); + add_key(KEY_SHARED, KEY_GRP_CHP, true, CMG_ALL_MASK); + add_key(KEY_SPEED, KEY_GRP_CHP, true, CMG_ALL_MASK); +} + +static void add_cmg_keys(bool all) +{ + enum key_group_t groups[] = { KEY_GRP_UTIL, KEY_GRP_CHARS, + KEY_GRP_METRICS }; + unsigned int i, j; + struct cmg_t *cmg; + char **cmg_keys; + + cmg_for_each(cmg) { + for (i = 0; i < ARRAY_SIZE(groups); i++) { + cmg_keys = cmg_get_keys(cmg, groups[i]); + for (j = 0; cmg_keys[j]; j++) { + add_key(cmg_keys[j], groups[i], + cmg->found || all, + GET_CMG_MASK(cmg->cmg)); + } + cmg_free_keys(cmg_keys); + } + } +} + +void key_init(bool all) +{ + add_generic_keys(); + add_cmg_keys(all); +} + +void key_exit(void) +{ + unsigned int i; + + for (i = 0; i < num_keys; i++) { + free(keys[i].name); + free(keys[i].cmg_str); + } + free(keys); + free(selected_keys); +} + +static int cmp_keys(const void *a, const void *b) +{ + const struct key_t * const *a_key = a; + const struct key_t * const *b_key = b; + + return strcmp((*a_key)->name, (*b_key)->name); +} + +void key_sort_selected(void) +{ + qsort(selected_keys, num_selected_keys, sizeof(struct key_t *), + cmp_keys); +} + +struct key_t *key_get_by_index(unsigned int i, bool selected) +{ + if (selected) + return i < num_selected_keys ? selected_keys[i] : NULL; + return i < num_keys ? &keys[i] : NULL; +} + +struct key_t *key_get_by_name(const char *name) +{ + struct key_t *key; + + key_for_each(key) { + if (strcmp(key->name, name) == 0) + return key; + } + return NULL; +} + +void key_select(struct key_t *key) +{ + struct key_t *k; + + /* Prevent duplicates. */ + key_for_each_selected(k) { + if (k == key) + return; + } + util_add_array(&selected_keys, &num_selected_keys, key); +} + +void key_select_by_groups(int groups, bool found) +{ + struct key_t *key; + + key_for_each(key) { + if (found && !key->found) + continue; + if (!(groups & (int)key->group)) + continue; + key_select(key); + } +} + +void key_select_by_cmg(int cmg) +{ + struct key_t *key; + + key_for_each(key) { + if (IS_CMG_MASK(key->cmg_mask, cmg)) + key_select(key); + } +} + +void key_select_all(void) +{ + struct key_t *key; + + key_for_each(key) + key_select(key); +} + +int key_get_selected_groups(void) +{ + struct key_t *key; + int groups = 0; + + key_for_each_selected(key) + groups |= (int)key->group; + + return groups; +} diff --git a/zconf/chp/chpstat/key.h b/zconf/chp/chpstat/key.h new file mode 100644 index 00000000..3849ba06 --- /dev/null +++ b/zconf/chp/chpstat/key.h @@ -0,0 +1,89 @@ +/* + * Registry for supported data keys + * + * 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. + */ + +#ifndef KEY_H +#define KEY_H + +#include + +#include "lib/zt_common.h" + +#define KEY_META_API_LEVEL "meta.api_level" +#define KEY_META_VERSION "meta.version" +#define KEY_META_HOST "meta.host" +#define KEY_META_TIME "meta.time" +#define KEY_META_TIME_EPOCH "meta.time_epoch" +#define KEY_ITERATION "iteration" +#define KEY_TIME "time" +#define KEY_TIME_EPOCH "time_epoch" +#define KEY_CHPID "chpid" +#define KEY_TYPE "type" +#define KEY_CMG "cmg" +#define KEY_SHARED "shared" +#define KEY_SPEED "speed" + +/** + * enum util_fmt_t - Key group identifiers. + * @KEY_GRP_META - Tool-related meta data + * @KEY_GRP_ITERATION - Iteration-related data + * @KEY_GRP_CHP - Channel-path related data + * @KEY_GRP_CHARS - Group for channel-patch measurement characteristics data + * @KEY_GRP_UTIL - Group for unprocessed utilization data + * @KEY_GRP_METRICS - Group for performance metrics + */ +enum key_group_t { + KEY_GRP_META = (1 << 0), + KEY_GRP_ITERATION = (1 << 1), + KEY_GRP_CHP = (1 << 2), + KEY_GRP_CHARS = (1 << 3), + KEY_GRP_UTIL = (1 << 4), + KEY_GRP_METRICS = (1 << 5), +}; + +#define KEY_GRP_ALL (KEY_GRP_META | KEY_GRP_ITERATION | KEY_GRP_CHP | \ + KEY_GRP_CHARS | KEY_GRP_UTIL | KEY_GRP_METRICS) + +/** + * struct key_t - A single data key + * @name: Key name + * @cmg_mask: List (bitmask) of CMGs for which this key is defined + * @cmg_str: List (text) of CMGs for which this key is defined + * @group: Key group this key belongs to + * @found: Flag indicating whether key is provided by CMGS of selected CHPIDs + */ +struct key_t { + char *name; + u32 cmg_mask; + char *cmg_str; + enum key_group_t group; + bool found; +}; + +#define key_for_each(c) \ + for (unsigned int __i = 0; ((c) = key_get_by_index(__i, false)); \ + __i++) +#define key_for_each_selected(c) \ + for (unsigned int __i = 0; ((c) = key_get_by_index(__i, true)); \ + __i++) + +const char *key_group_to_str(enum key_group_t group); +struct key_t *key_get_by_index(unsigned int i, bool selected); +struct key_t *key_get_by_name(const char *name); +void key_select(struct key_t *key); +void key_select_by_groups(int groups, bool found); +void key_select_by_cmg(int cmg); +void key_select_all(void); +void key_sort_selected(void); +int key_get_selected_groups(void); + +void key_init(bool all); +void key_exit(void); +struct key_t *_get_key(int i); + +#endif /* KEY_H */ diff --git a/zconf/chp/chpstat/misc.h b/zconf/chp/chpstat/misc.h new file mode 100644 index 00000000..9a1e83a4 --- /dev/null +++ b/zconf/chp/chpstat/misc.h @@ -0,0 +1,20 @@ +/* + * Miscellaneous definitions + * + * 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. + */ + +#ifndef MISC_H +#define MISC_H + +/* Program exit codes. */ +enum exit_code_t { + EXIT_OK = 0, /* Program finished successfully */ + EXIT_USAGE = 1, /* Usage error */ + EXIT_RUNTIME = 2, /* Run-time error */ +}; + +#endif /* MISC_H */