Files
s390-tools/zconf/chp/chpstat/cmg2.c
Peter Oberparleiter cb77faeae7 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 <vneethv@linux.ibm.com>
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2024-05-27 16:52:27 +02:00

344 lines
10 KiB
C

/*
* 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);
}