mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
This commit is based on the s390-tools-1.39.0 version. Changes on top of s390-tools-1.39.0: - Add MIT license to all source files - Add LICENSE file - Transform REAMDE to README.md (markdown) - Add AUTHORS.md file - Add CONTRIBUTING.md file - Move changelog from README to CHANGELOG.md file Reviewed-by: Stefan Haberland <sth@linux.vnet.ibm.com> Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
554 lines
15 KiB
C++
554 lines
15 KiB
C++
/*
|
|
* FCP report generators
|
|
*
|
|
* Traffic report program
|
|
*
|
|
* Copyright IBM Corp. 2008, 2017
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <getopt.h>
|
|
#include <linux/types.h>
|
|
#define __STDC_LIMIT_MACROS
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
|
|
#include <list>
|
|
|
|
#include "lib/zt_common.h"
|
|
|
|
#include "ziorep_collapser.hpp"
|
|
#include "ziorep_utils.hpp"
|
|
|
|
|
|
using std::list;
|
|
|
|
|
|
const char *toolname = "ziorep_traffic";
|
|
int verbose=0;
|
|
|
|
|
|
struct options {
|
|
__u64 begin;
|
|
__u64 end;
|
|
__u32 interval;
|
|
list<const char*> devices;
|
|
list<const char*> mp_devices;
|
|
__u64 topline;
|
|
char* filename;
|
|
bool print_summary;
|
|
bool details;
|
|
Aggregator col_crit;
|
|
list<__u32> chpids;
|
|
list<__u32> devnos;
|
|
list<__u64> wwpns;
|
|
list<__u64> luns;
|
|
bool csv_export;
|
|
};
|
|
|
|
|
|
static void init_opts(struct options *opts)
|
|
{
|
|
opts->begin = 0;
|
|
opts->end = UINT64_MAX;
|
|
opts->interval = UINT32_MAX;
|
|
opts->topline = 0;
|
|
opts->filename = NULL;
|
|
opts->print_summary = false;
|
|
opts->details = false;
|
|
opts->col_crit = none;
|
|
opts->csv_export = false;
|
|
}
|
|
|
|
|
|
static const char help_text[] =
|
|
"Usage: ziorep_traffic [-V] [-v] [-h] [-b <begin>] [-e <end>]"
|
|
" [-i <time>] [-s]\n"
|
|
" [-c <chpid>] [-u <id>] [-t <num>] [-p <port>]\n"
|
|
" [-l <lun>] [-d <fdev> ] [-m <mdev>] [-x] [-D]\n"
|
|
" [-C a|u|p|m|A] <filename>\n\n"
|
|
"-h, --help Print usage information and exit.\n"
|
|
"-v, --version Print version information and exit.\n"
|
|
"-V, --verbose Be verbose.\n"
|
|
"-b, --begin <begin> Do not consider data earlier than 'begin'.\n"
|
|
" Defaults to begin of available data.\n"
|
|
" Format is YYYY-MM-DD HH:MM[:SS],\n"
|
|
" e.g. '-b \"2008-03-21 09:08\"\n"
|
|
"-e, --end <end> Do not consider data later than 'end'.\n"
|
|
" Defaults to end of available data.\n"
|
|
" Format is YYYY-MM-DD HH:MM[:SS],\n"
|
|
" e.g. '-e \"2008-03-21 09:08:57\"\n"
|
|
"-i, --interval <time> Set aggregation interval to 'time' in seconds.\n"
|
|
" Must be a multiple of the interval size of the source\n"
|
|
" data.\n"
|
|
" Set to 0 to aggregate over all data.\n"
|
|
"-s, --summary Show a summary of the data.\n"
|
|
"-C, --collapse <val> Collapse data for multiple instances of\n"
|
|
" a device into a single one. See man page for details.\n"
|
|
"-c, --chpid <chpid> Select adapter by CHPID in hex, e.g. '-c 32'\n"
|
|
"-u, --bus-id <id> Select by bus-ID, e.g. '-u 0.0.7f1d'\n"
|
|
"-p, --port <port> Select by target port, e.g. '-p 0x500507630040710b'\n"
|
|
"-l, --lun <lun> Select by lun, e.g. '-l 0x4021402200000000'\n"
|
|
"-d, --device <fdev> Select by device, e.g. '-d sda'\n"
|
|
"-m, --mdev <mdev> Select by multipath device,\n"
|
|
" e.g. '-m 36005076303ffc1040002120'\n"
|
|
"-D, --detailed Print histograms instead of min/max/avg/stdev\n"
|
|
"-x, --export-csv Export data to files in CSV format.\n"
|
|
"-t, --topline <num> Repeat topline after every 'num' frames.\n"
|
|
" 0 for no repeat (default).\n";
|
|
|
|
|
|
static void print_help()
|
|
{
|
|
printf("%s", help_text);
|
|
}
|
|
|
|
|
|
static void print_version()
|
|
{
|
|
printf("%s: Traffic report generator version %s\n"
|
|
"Copyright IBM Corp. 2008, 2017\n", toolname, RELEASE_STRING);
|
|
}
|
|
|
|
|
|
static int parse_params(int argc, char **argv, struct options *opts)
|
|
{
|
|
int c;
|
|
int index;
|
|
int rc;
|
|
__u32 tmp32, tmp32a, tmp32b;
|
|
long long unsigned int tmp64;
|
|
long tmpl;
|
|
char mychar;
|
|
static struct option long_options[] = {
|
|
{ "version", no_argument, NULL, 'v'},
|
|
{ "help", no_argument, NULL, 'h'},
|
|
{ "verbose", no_argument, NULL, 'V'},
|
|
{ "begin", required_argument, NULL, 'b'},
|
|
{ "end", required_argument, NULL, 'e'},
|
|
{ "interval", required_argument, NULL, 'i'},
|
|
{ "summary", no_argument, NULL, 's'},
|
|
{ "collapse", required_argument, NULL, 'C'},
|
|
{ "chpid", required_argument, NULL, 'c'},
|
|
{ "bus-id", required_argument, NULL, 'u'},
|
|
{ "port", required_argument, NULL, 'p'},
|
|
{ "lun", required_argument, NULL, 'l'},
|
|
{ "device", required_argument, NULL, 'd'},
|
|
{ "mdev", required_argument, NULL, 'm'},
|
|
{ "detailed", required_argument, NULL, 'D'},
|
|
{ "export-csv", no_argument, NULL, 'x'},
|
|
{ "topline", required_argument, NULL, 't'},
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
if (argc < 2) {
|
|
print_help();
|
|
return 1;
|
|
}
|
|
|
|
assert(sizeof(long long int) == sizeof(__u64));
|
|
while ((c = getopt_long(argc, argv, "m:C:b:e:i:c:u:p:l:d:t:xDshvV",
|
|
long_options, &index)) != EOF) {
|
|
switch (c) {
|
|
case 'V':
|
|
verbose++;
|
|
break;
|
|
case 'h':
|
|
print_help();
|
|
return 1;
|
|
case 'v':
|
|
print_version();
|
|
return 1;
|
|
case 'b':
|
|
if (get_datetime_val(optarg, &opts->begin))
|
|
return -1;
|
|
break;
|
|
case 'e':
|
|
if (get_datetime_val(optarg, &opts->end))
|
|
return -1;
|
|
break;
|
|
case 'i':
|
|
if (sscanf(optarg, "%lu", &tmpl) != 1) {
|
|
fprintf(stderr, "%s:"
|
|
" Cannot parse %s as an integer value."
|
|
" Please correct and try again.\n", toolname,
|
|
optarg);
|
|
return -1;
|
|
}
|
|
if (tmpl < 0) {
|
|
fprintf(stderr, "%s:"
|
|
" Argument %s must be greater than or"
|
|
" equal to 0.", toolname, optarg);
|
|
return -1;
|
|
}
|
|
opts->interval = tmpl;
|
|
break;
|
|
case 's':
|
|
opts->print_summary = true;
|
|
break;
|
|
case 'c':
|
|
rc = sscanf(optarg, "%x%c", &tmp32, &mychar);
|
|
if (rc < 1) {
|
|
fprintf(stderr, "%s: Could"
|
|
" not read chpid %s\n", toolname, optarg);
|
|
return -1;
|
|
}
|
|
if (rc > 1) {
|
|
fprintf(stderr, "%s: %s is not a valid chpid\n", toolname, optarg);
|
|
return -1;
|
|
}
|
|
opts->chpids.push_back(tmp32);
|
|
break;
|
|
case 'p':
|
|
rc = sscanf(optarg, "0x%Lx", &tmp64);
|
|
if (rc != 1) {
|
|
fprintf(stderr, "%s: Could"
|
|
" not read port number %s\n", toolname, optarg);
|
|
return -1;
|
|
}
|
|
opts->wwpns.push_back(tmp64);
|
|
break;
|
|
case 'l':
|
|
rc = sscanf(optarg, "0x%Lx", &tmp64);
|
|
if (rc != 1) {
|
|
fprintf(stderr, "%s: Could"
|
|
" not read lun %s\n", toolname, optarg);
|
|
return -1;
|
|
}
|
|
opts->luns.push_back(tmp64);
|
|
break;
|
|
case 'u':
|
|
rc = sscanf(optarg, "%x.%x.%x", &tmp32, &tmp32a, &tmp32b);
|
|
if (rc != 3) {
|
|
fprintf(stderr, "%s: Could not read bus-ID"
|
|
" %s\n", toolname, optarg);
|
|
return -1;
|
|
}
|
|
opts->devnos.push_back(ZIOREP_BUSID_PACKED(tmp32, tmp32a, tmp32b));
|
|
break;
|
|
case 'd':
|
|
opts->devices.push_back(optarg);
|
|
break;
|
|
case 'm':
|
|
opts->mp_devices.push_back(optarg);
|
|
break;
|
|
case 'D':
|
|
opts->details = true;
|
|
break;
|
|
case 't':
|
|
if (parse_topline_arg(optarg, &opts->topline))
|
|
return -1;
|
|
break;
|
|
case 'x':
|
|
opts->csv_export = true;
|
|
break;
|
|
case 'C':
|
|
rc = 0;
|
|
switch (*optarg) {
|
|
case 'a': opts->col_crit = chpid;
|
|
break;
|
|
case 'u': opts->col_crit = devno;
|
|
break;
|
|
case 'p': opts->col_crit = wwpn;
|
|
break;
|
|
case 'm': opts->col_crit = multipath_device;
|
|
break;
|
|
case 'A': opts->col_crit = all;
|
|
break;
|
|
default:
|
|
rc = -1;
|
|
}
|
|
if (rc || strlen(optarg) > 1) {
|
|
fprintf(stderr, "%s:"
|
|
" Unrecognized switch '%s' to parameter"
|
|
" '-C'. Please check the help for a list"
|
|
" of valid switches, correct and try"
|
|
" again.\n", toolname, optarg);
|
|
return -2;
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(stderr, "%s: Try '%s --help' for"
|
|
" more information.\n", toolname, toolname);
|
|
return -1;
|
|
}
|
|
}
|
|
if (optind == argc - 1)
|
|
opts->filename = argv[optind];
|
|
if (optind < argc - 1) {
|
|
fprintf(stderr, "%s: Multiple filenames"
|
|
" specified. Specify only a single one at a time.\n", toolname);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int check_opts(struct options *opts, ConfigReader **cfg)
|
|
{
|
|
int rc = 0;
|
|
|
|
// check filename
|
|
if (!opts->filename) {
|
|
fprintf(stderr, "%s: No filename specified.\n", toolname);
|
|
return -2;
|
|
}
|
|
else {
|
|
if (strncmp(opts->filename + strlen(opts->filename) - strlen(DACC_FILE_EXT_LOG),
|
|
DACC_FILE_EXT_LOG, strlen(DACC_FILE_EXT_LOG)) == 0) {
|
|
verbose_msg("Filename carries " DACC_FILE_EXT_LOG " extension - stripping\n");
|
|
opts->filename[strlen(opts->filename) - strlen(DACC_FILE_EXT_LOG)] = '\0';
|
|
}
|
|
if (strncmp(opts->filename + strlen(opts->filename) - strlen(DACC_FILE_EXT_AGG),
|
|
DACC_FILE_EXT_AGG, strlen(DACC_FILE_EXT_AGG)) == 0) {
|
|
verbose_msg("Filename carries " DACC_FILE_EXT_AGG " extension - stripping\n");
|
|
opts->filename[strlen(opts->filename) - strlen(DACC_FILE_EXT_AGG)] = '\0';
|
|
}
|
|
verbose_msg("Filename is %s\n", opts->filename);
|
|
}
|
|
|
|
// check config
|
|
*cfg = new ConfigReader(&rc, opts->filename);
|
|
if (rc)
|
|
return -1;
|
|
|
|
for (list<__u32>::const_iterator i = opts->chpids.begin();
|
|
i != opts->chpids.end(); ++i) {
|
|
if (!(*cfg)->verify_chpid(*i)) {
|
|
fprintf(stderr, "Error: Could not find chpid %x in"
|
|
" configuration.\n", *i);
|
|
rc = -2;
|
|
}
|
|
}
|
|
for (list<__u32>::const_iterator i = opts->devnos.begin();
|
|
i != opts->devnos.end(); ++i) {
|
|
if (!(*cfg)->verify_devno(*i)) {
|
|
fprintf(stderr, "Error: Could not find bus-ID %x.%x.%04x in"
|
|
" configuration.\n", ZIOREP_BUSID_UNPACKED(*i));
|
|
rc = -3;
|
|
}
|
|
}
|
|
for (list<__u64>::const_iterator i = opts->wwpns.begin();
|
|
i != opts->wwpns.end(); ++i) {
|
|
if (!(*cfg)->verify_wwpn(*i)) {
|
|
fprintf(stderr, "Error: Could not find WWPN %016Lx in"
|
|
" configuration.\n", (long long unsigned int)*i);
|
|
rc = -4;
|
|
}
|
|
}
|
|
for (list<__u64>::const_iterator i = opts->luns.begin();
|
|
i != opts->luns.end(); ++i) {
|
|
if (!(*cfg)->verify_lun(*i)) {
|
|
fprintf(stderr, "Error: Could not find LUN %016Lx in"
|
|
" configuration.\n", (long long unsigned int)*i);
|
|
rc = -5;
|
|
}
|
|
}
|
|
for (list<const char*>::iterator i = opts->devices.begin();
|
|
i != opts->devices.end(); ++i) {
|
|
if (!(*cfg)->verify_device(*i)) {
|
|
fprintf(stderr, "Error: Could not find device %s in"
|
|
" configuration.\n", *i);
|
|
rc = -6;
|
|
}
|
|
}
|
|
for (list<const char*>::iterator i = opts->mp_devices.begin();
|
|
i != opts->mp_devices.end(); ++i) {
|
|
if (!(*cfg)->verify_mp_device(*i)) {
|
|
fprintf(stderr, "Error: Could not find multipath"
|
|
" device %s in configuration.\n", *i);
|
|
rc = -7;
|
|
}
|
|
}
|
|
if (opts->csv_export && opts->topline > 0) {
|
|
fprintf(stderr, "%s: Warning: Both, topline"
|
|
" repeat and CSV export activated, deactivating"
|
|
" topline repeat.\n", toolname);
|
|
opts->topline = 0;
|
|
}
|
|
|
|
if (!opts->print_summary
|
|
&& adjust_timeframe(opts->filename, &opts->begin, &opts->end,
|
|
&opts->interval))
|
|
rc = -8;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int configure_coarse_devices(struct options *opts, ConfigReader &cfg,
|
|
StagedDeviceFilter &dev_flt)
|
|
{
|
|
list<__u32> mms;
|
|
int rc = 0;
|
|
|
|
for (list<const char *>::const_iterator i = opts->devices.begin();
|
|
i != opts->devices.end(); ++i) {
|
|
__u32 mm = cfg.get_mm_by_device(*i, &rc);
|
|
dev_flt.add_device(mm, cfg.get_ident_by_mm_internal(mm, &rc));
|
|
}
|
|
for (list<__u64>::const_iterator i = opts->luns.begin();
|
|
i != opts->luns.end(); ++i) {
|
|
cfg.get_mms_by_lun(mms, *i);
|
|
for (list<__u32>::const_iterator j = mms.begin();
|
|
j != mms.end(); j++)
|
|
dev_flt.add_device(*j, cfg.get_ident_by_mm_internal(*j, &rc));
|
|
}
|
|
for (list<const char *>::const_iterator i = opts->mp_devices.begin();
|
|
i != opts->mp_devices.end(); ++i)
|
|
dev_flt.stage_mp_mm(cfg.get_mp_mm_by_multipath(*i, &rc));
|
|
if (rc)
|
|
return -1;
|
|
for (list<__u64>::const_iterator i = opts->wwpns.begin();
|
|
i != opts->wwpns.end(); ++i)
|
|
dev_flt.stage_wwpn(*i);
|
|
for (list<__u32>::const_iterator i = opts->devnos.begin();
|
|
i != opts->devnos.end(); ++i)
|
|
dev_flt.stage_devno(*i);
|
|
for (list<__u32>::const_iterator i = opts->chpids.begin();
|
|
i != opts->chpids.end(); ++i)
|
|
dev_flt.stage_chpid(*i);
|
|
|
|
return dev_flt.finish(cfg, false);
|
|
}
|
|
|
|
|
|
static int configure_device_filter(struct options *opts, ConfigReader &cfg,
|
|
DeviceFilter **dev_filt)
|
|
{
|
|
int rc = 0;
|
|
list<__u32> mm_lst;
|
|
list<__u32>::const_iterator i_32, j_32;
|
|
|
|
verbose_msg("Configure device filter\n");
|
|
// verify and sort out devices
|
|
if (opts->devices.size() > 0 || opts->luns.size() > 0
|
|
|| opts->wwpns.size() > 0 || opts->mp_devices.size() > 0
|
|
|| opts->devnos.size() > 0 || opts->chpids.size() > 0) {
|
|
verbose_msg(" devices specified via coarse criteria"
|
|
" - let's see...\n");
|
|
StagedDeviceFilter *stgd_flt = new StagedDeviceFilter;
|
|
*dev_filt = stgd_flt;
|
|
rc = configure_coarse_devices(opts, cfg, *stgd_flt);
|
|
}
|
|
else {
|
|
verbose_msg(" no devices specified - accept all\n");
|
|
*dev_filt = new DeviceFilter;
|
|
add_all_devices(cfg, **dev_filt);
|
|
}
|
|
|
|
verbose_msg("Device filter configured (total: %zd devices)\n",
|
|
(*dev_filt)->get_mm_list().size());
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int print_report(struct options *opts, ConfigReader &cfg)
|
|
{
|
|
int rc = 0;
|
|
FILE *fp;
|
|
MsgTypeFilter msgtype_filter;
|
|
Printer *printer = NULL;
|
|
DeviceFilter *dev_filt;
|
|
Collapser *col = NULL;
|
|
list<MsgTypes> type_flt;
|
|
|
|
type_flt.push_back(zfcpdd);
|
|
type_flt.push_back(blkiomon);
|
|
|
|
if (configure_device_filter(opts, cfg, &dev_filt)) {
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (opts->col_crit == none)
|
|
col = new NoopCollapser();
|
|
else if (opts->col_crit == all)
|
|
col = new TotalCollapser();
|
|
else {
|
|
col = new AggregationCollapser(cfg,
|
|
opts->col_crit,
|
|
*dev_filt,
|
|
&rc);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
|
|
if (opts->details) {
|
|
if (opts->csv_export) {
|
|
fp = open_csv_output_file(opts->filename,
|
|
"_traffic_detailed.csv", &rc);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
else
|
|
fp = stdout;
|
|
printer = new DetailedTrafficPrinter(&cfg, *col,
|
|
opts->csv_export);
|
|
}
|
|
else {
|
|
if (opts->csv_export) {
|
|
fp = open_csv_output_file(opts->filename,
|
|
"_traffic.csv",
|
|
&rc);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
else
|
|
fp = stdout;
|
|
printer = new SummaryTrafficPrinter(&cfg, *col,
|
|
opts->csv_export);
|
|
}
|
|
|
|
if ( (rc = print_report(fp, opts->begin, opts->end,
|
|
opts->interval, opts->filename, opts->topline,
|
|
&type_flt, *dev_filt, *col, *printer)) < 0 )
|
|
rc = -3;
|
|
|
|
if (opts->csv_export)
|
|
fclose(fp);
|
|
out:
|
|
delete dev_filt;
|
|
delete col;
|
|
delete printer;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int rc;
|
|
struct options opts;
|
|
ConfigReader *cfg = NULL;
|
|
|
|
verbose = 0;
|
|
|
|
init_opts(&opts);
|
|
if ( (rc = parse_params(argc, argv, &opts)) ) {
|
|
if (rc == 1)
|
|
rc = 0;
|
|
goto out;
|
|
}
|
|
if ( (rc = check_opts(&opts, &cfg)) )
|
|
goto out;
|
|
|
|
if (opts.print_summary)
|
|
rc = print_summary_report(stdout, opts.filename, *cfg);
|
|
else
|
|
rc = print_report(&opts, *cfg);
|
|
|
|
out:
|
|
delete cfg;
|
|
|
|
return rc;
|
|
}
|
|
|