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>
558 lines
14 KiB
C++
558 lines
14 KiB
C++
/*
|
|
* FCP report generators
|
|
*
|
|
* Utility functions
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#define __STDC_LIMIT_MACROS
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
|
|
#include "ziorep_utils.hpp"
|
|
#include "ziorep_cfgreader.hpp"
|
|
|
|
extern "C" {
|
|
#include "ziomon_msg_tools.h"
|
|
}
|
|
|
|
extern const char *toolname;
|
|
extern int verbose;
|
|
|
|
|
|
|
|
/**
|
|
* Read all essential data from the files,
|
|
* including the headers, timestamp of first message
|
|
* and a DeviceFilter for all available devices.
|
|
* Note that 'agg' is NULL if not available and must
|
|
* be free()'d otherwise. */
|
|
static int get_initial_data(const char *filename, struct file_header *f_hdr,
|
|
struct aggr_data **agg,
|
|
DeviceFilter &dev_filt, ConfigReader &cfg)
|
|
{
|
|
FILE *fp;
|
|
int rc = 0;
|
|
__u64 begin;
|
|
|
|
if (open_data_files(&fp, filename, f_hdr, agg))
|
|
return -1;
|
|
close_data_files(fp);
|
|
|
|
/*
|
|
* Retrieve first real frame
|
|
*/
|
|
MsgTypeFilter msgtype_filter;
|
|
NoopCollapser nop_col;
|
|
Frameset frameset(&nop_col);
|
|
list<MsgTypes> type_flt;
|
|
type_flt.push_back(ioerr);
|
|
|
|
// we retrieve the first interval only, and eventually the .agg data
|
|
// as well
|
|
if (*agg)
|
|
begin = (*agg)->end_time;
|
|
else
|
|
begin = f_hdr->begin_time;
|
|
|
|
Framer framer(begin,
|
|
begin + f_hdr->interval_length,
|
|
f_hdr->interval_length, &type_flt,
|
|
(DeviceFilter*)NULL, filename, &rc);
|
|
vector <struct ioerr_cnt*> ioerrs;
|
|
do {
|
|
if ( framer.get_next_frameset(frameset) != 0 ) {
|
|
fprintf(stderr, "%s: Could not read"
|
|
" any frames in %s%s\n", toolname, filename,
|
|
DACC_FILE_EXT_LOG);
|
|
return -2;
|
|
}
|
|
|
|
/*
|
|
* Construct a DeviceFilter with all devices.
|
|
* NOTE: The very first ioerr msg might already have been moved to the .agg
|
|
* file - hence we have to consider the .agg data as well!
|
|
*/
|
|
ioerrs = frameset.get_ioerr_stats();
|
|
rc = 0;
|
|
for (vector<struct ioerr_cnt*>::const_iterator i = ioerrs.begin();
|
|
i != ioerrs.end(); ++i) {
|
|
vverbose_msg(" add device: hctl=[%d:%d:%d:%d], mm=%d\n",
|
|
(*i)->identifier.host, (*i)->identifier.channel,
|
|
(*i)->identifier.target, (*i)->identifier.lun,
|
|
cfg.get_mm_by_ident(&(*i)->identifier, &rc));
|
|
dev_filt.add_device(cfg.get_mm_by_ident(&(*i)->identifier, &rc), &(*i)->identifier);
|
|
if (rc)
|
|
return -1;
|
|
}
|
|
} while ( frameset.is_aggregated() && !ioerrs.size());
|
|
|
|
if (dev_filt.get_host_id_list().size() == 0 || dev_filt.get_mm_list().size() == 0) {
|
|
fprintf(stderr, "%s: Could not retrieve initial data"
|
|
" - data files corrupted or broken, or the .agg file is missing.\n",
|
|
toolname);
|
|
return -1;
|
|
}
|
|
|
|
verbose_msg("retrieve initial data FINISHED\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int add_all_devices(ConfigReader &cfg, DeviceFilter &dev_filt)
|
|
{
|
|
int rc = 0;
|
|
list<__u32> lst;
|
|
|
|
cfg.get_unique_mms(lst);
|
|
for (list<__u32>::const_iterator i = lst.begin();
|
|
i != lst.end(); ++i) {
|
|
dev_filt.add_device(*i, cfg.get_ident_by_mm_internal(*i, &rc));
|
|
assert(rc == 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int get_all_devices(const char *filename, DeviceFilter &dev_filt,
|
|
ConfigReader &cfg)
|
|
{
|
|
struct file_header f_hdr;
|
|
struct aggr_data *agg;
|
|
int rc = 0;
|
|
|
|
rc = get_initial_data(filename, &f_hdr, &agg, dev_filt, cfg);
|
|
discard_aggr_data_struct(agg);
|
|
free(agg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
#define BUF_SZ 256
|
|
|
|
const char* print_time_formatted(__u64 timestamp)
|
|
{
|
|
time_t t = timestamp;
|
|
static char buf[BUF_SZ];
|
|
|
|
strftime(buf, BUF_SZ - 1, "%Y-%m-%d %H:%M:%S", localtime(&t));
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
const char* print_time_formatted_short(__u64 timestamp)
|
|
{
|
|
time_t t = timestamp;
|
|
static char buf[BUF_SZ];
|
|
|
|
strftime(buf, BUF_SZ - 1, "%H:%M:%S", localtime(&t));
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
// adjust lower timestamp to last frame boundary that was touched
|
|
static __u64 round_lower_boundary(__u64 time, __u64 begin,
|
|
__u32 interval_length)
|
|
{
|
|
__u64 tmp = (time - begin)%interval_length;
|
|
|
|
if (tmp)
|
|
time += (interval_length - tmp);
|
|
|
|
return time;
|
|
}
|
|
|
|
|
|
// adjust upper timestamp to last frame boundary that was touched
|
|
static __u64 round_upper_boundary(__u64 time, __u64 begin,
|
|
__u32 interval_length)
|
|
{
|
|
__u64 tmp = (time - begin)%interval_length;
|
|
|
|
if (tmp)
|
|
time -= tmp;
|
|
|
|
return time;
|
|
}
|
|
|
|
|
|
/**
|
|
* The times will be adjusted to respective exakt frame boundaries.
|
|
* The end time is only ever used to determine whether we are done - we stop
|
|
* in case the begin of the timeframe is later than the end date.
|
|
* Note that in case we scratch into the .agg data, we
|
|
* (a) take all of it
|
|
* (b) synonymously associate the .agg data with the timestamp of its
|
|
* final frame. This is _utterly_ important, especially in context
|
|
* with the interval: If it is user adjusted, and the date range touches
|
|
* the .agg data, we adjust the begin to the .agg data's final frame's
|
|
* timestamp, and make sure that when we process the agg data, the next
|
|
* frame is 1 unit of the original interval length away. Only after we
|
|
* have shifted into the 'regular' .log data can we apply any user-set
|
|
* interval length.
|
|
* */
|
|
int adjust_timeframe(const char *filename, __u64 *begin, __u64 *end,
|
|
__u32 *interval)
|
|
{
|
|
struct file_header f_hdr;
|
|
struct aggr_data *agg = NULL;
|
|
FILE *fp;
|
|
time_t t;
|
|
int rc = 0;
|
|
|
|
verbose_msg("adjust timeframe:\n");
|
|
|
|
// check begin and end time
|
|
if (*begin > *end) {
|
|
fprintf(stderr, "%s: Start time must be"
|
|
" prior to end time.\n", toolname);
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (open_data_files(&fp, filename, &f_hdr, &agg)) {
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
close_data_files(fp);
|
|
|
|
// check if begin scratches into .agg data
|
|
// if so, we use the _end_ time of the .agg frame
|
|
if (*begin == 0 && agg) {
|
|
verbose_msg(" begin time: take from .agg data\n");
|
|
*begin = agg->end_time;
|
|
}
|
|
else if (agg && *begin < agg->begin_time) {
|
|
fprintf(stderr, "%s: Warning: Begin of timeframe is before"
|
|
" earliest available data, which is %s.\n", toolname,
|
|
print_time_formatted(agg->begin_time));
|
|
*begin = agg->end_time;
|
|
verbose_msg(" begin time: adjust to begin from .agg data\n");
|
|
}
|
|
else if (agg && *begin <= agg->end_time) {
|
|
*begin = agg->end_time;
|
|
verbose_msg(" begin time: set to end of .agg data\n");
|
|
}
|
|
else if (*begin < f_hdr.begin_time) {
|
|
*begin = f_hdr.begin_time;
|
|
verbose_msg(" begin time: adjust to begin of .log data\n");
|
|
}
|
|
else if (*begin > f_hdr.end_time) {
|
|
fprintf(stderr, "%s: Begin of timeframe is past the"
|
|
" end of available data, which is %s.\n",
|
|
toolname, print_time_formatted(f_hdr.end_time));
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
else {
|
|
verbose_msg(" begin time: round to nearest boundary\n");
|
|
*begin = round_lower_boundary(*begin, f_hdr.begin_time, f_hdr.interval_length);
|
|
}
|
|
t = *begin;
|
|
verbose_msg(" begin time set to: %s", ctime(&t));
|
|
|
|
if (*end == UINT64_MAX) {
|
|
*end = f_hdr.end_time;
|
|
verbose_msg(" end time : take from .log data\n");
|
|
}
|
|
else if (agg && *end < agg->begin_time) {
|
|
fprintf(stderr, "%s: End of timeframe is prior to earliest"
|
|
" available data, which is %s.\n", toolname,
|
|
print_time_formatted(agg->begin_time));
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
else if (agg && *end < agg->end_time) {
|
|
*end = agg->end_time;
|
|
verbose_msg(" end time : take from .agg data\n");
|
|
}
|
|
else if (!agg && *end < f_hdr.begin_time) {
|
|
fprintf(stderr, "%s: End of timeframe is prior to earliest"
|
|
" available data, which is %s.\n", toolname,
|
|
print_time_formatted(f_hdr.begin_time));
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
else if (*end > f_hdr.end_time) {
|
|
fprintf(stderr, "%s: Warning: End of timeframe is after"
|
|
" latest available data, which is %s.\n", toolname,
|
|
print_time_formatted(f_hdr.end_time));
|
|
*end = f_hdr.end_time;
|
|
verbose_msg(" end time : adjust to end of .log data\n");
|
|
}
|
|
else {
|
|
*end = round_upper_boundary(*end, f_hdr.begin_time,
|
|
f_hdr.interval_length);
|
|
verbose_msg(" end time : round to nearest boundary\n");
|
|
}
|
|
t = *end;
|
|
verbose_msg(" end time set to : %s", ctime(&t));
|
|
assert((*end - *begin) % f_hdr.interval_length == 0);
|
|
|
|
if (*interval == UINT32_MAX) {
|
|
*interval = f_hdr.interval_length;
|
|
verbose_msg("using original interval length: %lus\n",
|
|
(long unsigned int)*interval);
|
|
}
|
|
/* the exact frame boundaries don't include the length of the very
|
|
first interval, so we have to add one more to our calculations */
|
|
if (*interval && (*end - *begin + f_hdr.interval_length) % *interval != 0) {
|
|
// cut off rest in case of user-set interval
|
|
*end -= (*end - *begin) % *interval + f_hdr.interval_length;
|
|
t = *end;
|
|
verbose_msg(" cut off at : %s", ctime(&t));
|
|
}
|
|
|
|
// check if the interval is correct
|
|
if (*interval % f_hdr.interval_length) {
|
|
fprintf(stderr, "%s: Data aggregation interval %lu"
|
|
" is incompatible with source data. Please use"
|
|
" a multiple of %lu and try again.\n", toolname,
|
|
(long unsigned int)*interval,
|
|
(long unsigned int)(f_hdr.interval_length));
|
|
rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (agg) {
|
|
discard_aggr_data_struct(agg);
|
|
free(agg);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int print_report(FILE *fp, __u64 begin, __u64 end, __u32 interval,
|
|
char *filename, __u64 topline,
|
|
list<MsgTypes> *filter_types,
|
|
DeviceFilter &dev_filter, Collapser &col,
|
|
Printer &printer)
|
|
{
|
|
int frames_printed = 0;
|
|
bool first_time = true;
|
|
time_t t;
|
|
int rc = 0;
|
|
Frameset frameset(&col);
|
|
Framer framer(begin, end, interval,
|
|
filter_types, &dev_filter,
|
|
filename, &rc);
|
|
|
|
if (rc)
|
|
return -1;
|
|
|
|
if (topline && printer.print_csv()) {
|
|
fprintf(stderr, "%s: Warning: Cannot use '-t' with CSV mode,"
|
|
" ignoring\n", toolname);
|
|
topline = 0;
|
|
}
|
|
|
|
verbose_msg("print report for:\n");
|
|
t = (time_t)begin;
|
|
verbose_msg(" begin : %s", ctime(&t));
|
|
t = (time_t)end;
|
|
verbose_msg(" end : %s", (end == UINT64_MAX ? "-\n" : ctime(&t)));
|
|
verbose_msg(" interval : %lu\n", (long unsigned int)interval);
|
|
verbose_msg(" topline : %llu\n", (long long unsigned int)topline);
|
|
verbose_msg(" csv mode : %d\n", printer.print_csv());
|
|
|
|
while ( (rc = framer.get_next_frameset(frameset, true)) == 0 ) {
|
|
vverbose_msg("printing frameset %d\n", frames_printed);
|
|
if (first_time || (topline && frames_printed % topline == 0)) {
|
|
first_time = false;
|
|
printer.print_topline(fp);
|
|
}
|
|
if (printer.print_frame(fp, frameset, dev_filter) < 0)
|
|
return -1;
|
|
++frames_printed;
|
|
}
|
|
|
|
if (rc > 0)
|
|
return frames_printed;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int print_summary_report(FILE *fp, char *filename, ConfigReader &cfg)
|
|
{
|
|
int rc = 0;
|
|
int lrc=0;
|
|
struct file_header f_hdr;
|
|
struct aggr_data *a_hdr;
|
|
DeviceFilter dev_filt;
|
|
|
|
if (get_initial_data(filename, &f_hdr, &a_hdr, dev_filt, cfg))
|
|
return -1;
|
|
|
|
rc += fprintf(fp, "Data Summary\n");
|
|
rc += fprintf(fp, "------------\n");
|
|
|
|
rc += fprintf(fp, "Aggregated range: ");
|
|
if (a_hdr) {
|
|
rc += fprintf(fp, "%s to ",
|
|
print_time_formatted(a_hdr->begin_time - f_hdr.interval_length));
|
|
rc += fprintf(fp, "%s\n",
|
|
print_time_formatted(a_hdr->end_time));
|
|
}
|
|
else
|
|
rc += fprintf(fp, "none\n");
|
|
|
|
discard_aggr_data_struct(a_hdr);
|
|
free(a_hdr);
|
|
a_hdr = NULL;
|
|
|
|
rc += fprintf(fp, "Detailed range: %s to ",
|
|
print_time_formatted(f_hdr.begin_time - f_hdr.interval_length));
|
|
rc += fprintf(fp, "%s\n", print_time_formatted(f_hdr.end_time));
|
|
rc += fprintf(fp, "Interval length: %d seconds\n",
|
|
f_hdr.interval_length);
|
|
|
|
list<__u32> disks = dev_filt.get_mm_list();
|
|
list<__u32> host_ids = dev_filt.get_host_id_list();
|
|
int first = 1;
|
|
const char *frmt;
|
|
for (list<__u32>::const_iterator i = host_ids.begin();
|
|
i != host_ids.end(); ++i) {
|
|
if (first) {
|
|
frmt = "HBA/CHPID: %x.%x.%04x/%x\n";
|
|
first = 0;
|
|
}
|
|
else
|
|
frmt = " %x.%x.%04x/%x\n";
|
|
rc += fprintf(fp, frmt, ZIOREP_BUSID_UNPACKED(cfg.get_devno_by_host_id(*i, &lrc)),
|
|
cfg.get_chpid_by_host_id(*i, &lrc));
|
|
if (lrc)
|
|
return -1;
|
|
}
|
|
|
|
first = 1;
|
|
for (list<__u32>::const_iterator i = disks.begin();
|
|
i != disks.end(); ++i) {
|
|
if (first) {
|
|
frmt = "WWPN/LUN (dev): 0x%016Lx/0x%016Lx (%s)\n";
|
|
first = 0;
|
|
}
|
|
else
|
|
frmt = " 0x%016Lx/0x%016Lx (%s)\n";
|
|
rc += fprintf(fp, frmt, cfg.get_wwpn_by_mm_internal(*i, &lrc),
|
|
cfg.get_lun_by_mm_internal(*i, &lrc),
|
|
cfg.get_dev_by_mm_internal(*i, &lrc));
|
|
if (lrc)
|
|
return -1;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Calculates seconds since 1970 _without_ caring for daylight
|
|
savings time (comtrary to mktime() et al).
|
|
It does not care for leap years and the like, which is OK,
|
|
since we use it in a very narrow scenario: To calculate any
|
|
daylight savings time related shifts.
|
|
Hence: Dont't use if you're not sure what you are doing... */
|
|
static __u64 secs_since_1970(const struct tm *t) {
|
|
__u64 res = 0;
|
|
res += t->tm_sec;
|
|
res += 60 * t->tm_min;
|
|
res += 3600 * t->tm_hour;
|
|
res += 86400 * t->tm_yday;
|
|
res += 86400 * 365 * t->tm_year;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
int get_datetime_val(const char *str, __u64 *tgt)
|
|
{
|
|
struct tm t, t_old;
|
|
char *ret;
|
|
|
|
// strptime only sets
|
|
memset(&t, 0, sizeof(struct tm));
|
|
ret = strptime(str, "%Y-%m-%d %H:%M", &t);
|
|
if (ret == NULL || *ret != '\0') {
|
|
ret = strptime(str, "%Y-%m-%d %H:%M:%S", &t);
|
|
if (ret == NULL || *ret != '\0') {
|
|
fprintf(stderr, "%s: Could not parse date %s."
|
|
" Please use format as specified in"
|
|
" man-page\n", toolname, str);
|
|
return -1;
|
|
}
|
|
}
|
|
t_old = t;
|
|
*tgt = mktime(&t);
|
|
// if daylight savings time applies, 't' has been adjusted,
|
|
// so we have to correct
|
|
if (t_old.tm_hour != t.tm_hour)
|
|
*tgt -= secs_since_1970(&t) - secs_since_1970(&t_old);
|
|
verbose_msg("datetime value from user after translation: %s", ctime((const time_t *)tgt));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int parse_topline_arg(char *str, __u64 *arg)
|
|
{
|
|
char *p;
|
|
|
|
*arg = strtoull(str, &p, 0);
|
|
if (*arg == ULLONG_MAX) {
|
|
fprintf(stderr, "%s: Cannot convert"
|
|
" %s, over/underflow occurred. Try a smaller/larger"
|
|
" value.\n", toolname, str);
|
|
return -1;
|
|
}
|
|
if (*p != '\0') {
|
|
fprintf(stderr, "%s: Non-numeric"
|
|
" characters in argument '%s' to option '-t'. Make"
|
|
" sure to use only numeric characters.\n", toolname,
|
|
str);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
FILE* open_csv_output_file(const char *filename, const char *extension,
|
|
int *rc)
|
|
{
|
|
char *tmp;
|
|
FILE *fp = NULL;
|
|
|
|
*rc = 0;
|
|
|
|
tmp = (char*)malloc(strlen(filename) + strlen(extension) + 1);
|
|
|
|
sprintf(tmp, "%s%s", filename, extension);
|
|
fp = fopen(tmp, "w");
|
|
|
|
if (!fp) {
|
|
fprintf(stdout, "%s: Could not open file %s. Make sure that you"
|
|
" have sufficient permissions and try again.\n", toolname, tmp);
|
|
*rc = -1;
|
|
}
|
|
else
|
|
fprintf(stdout, "Exporting data in CSV format to %s\n", tmp);
|
|
|
|
free(tmp);
|
|
|
|
return fp;
|
|
}
|
|
|
|
|
|
|
|
|