/*
 * FCP report generators
 *
 * Utility classes to print framsets
 *
 * Copyright IBM Corp. 2008, 2021
 *
 * 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 <stdlib.h>
#include <assert.h>
#include <math.h>

extern "C" {
	#include "ziomon_tools.h"
	#include "stats.h"
}
#include "ziorep_printers.hpp"
#include "ziorep_utils.hpp"

extern const char *toolname;
extern int verbose;


#define ZIOREP_PRINTERS_MAX_QUEUE_LEN		128.



Printer::Printer(const ConfigReader *cfg, bool csv_mode)
: m_cfg(cfg), m_csv(csv_mode), m_prev_day(-1)
{
	if (m_csv)
		m_delim = ',';
	else
		m_delim = ' ';

	memset(&m_util, 0, sizeof(struct adapter_utilization));
	m_util.valid = 1;
	memset(&m_util_invalid, 0, sizeof(struct adapter_utilization));
	m_util_invalid.valid = 0;
	memset(&m_ioerr, 0, sizeof(struct ioerr_cnt));
	memset(&m_dstat, 0, sizeof(struct zfcpdd_dstat));
	memset(&m_stat, 0, sizeof(struct blkiomon_stat));
}


bool Printer::print_csv() const
{
	return m_csv;
}


void Printer::print_timestamp(FILE *fp, const Frameset &frameset)
{
	time_t t = frameset.get_end_time();
	struct tm *tm = localtime(&t);

	if (m_csv) {
		fprintf(fp, "%s", print_time_formatted(frameset.get_timestamp()));
		print_delimiter(fp);
		fprintf(fp, "%d", frameset.is_aggregated());
		print_delimiter(fp);
	}
	else {
		if (m_prev_day < 0 || m_prev_day != tm->tm_mday) {
			fprintf(fp, "%s",
				print_time_formatted(frameset.get_timestamp()));
			m_prev_day = tm->tm_mday;
		}
		else
			fprintf(fp, "%s",
				print_time_formatted_short(frameset.get_timestamp()));

		if (frameset.is_aggregated())
			fprintf(fp, " Aggregated Frame\n");
		else
			fputc('\n', fp);
	}
}


void Printer::determine_mag_factor(int leading_places, char *magnitude,
				   double *factor) const
{
	if (leading_places < 7) {
		*magnitude = 'K';
		*factor = 1000;
	}
	else if (leading_places < 10) {
		*magnitude = 'M';
		*factor = 1000000;
	}
	else if (leading_places < 13) {
		*magnitude = 'G';
		*factor = 1000000000;
	}
	else {
		// this is quite expensive, hence we avoid if possible
		*factor = pow(1000, leading_places/3);
		if (leading_places < 16)
			*magnitude = 'T';
		else if (leading_places < 19)
			*magnitude = 'P';
		else {
			assert(false);	// would be surprising if
			*magnitude = '?';
		}
	}
}


void Printer::determine_digs(int leading_places, int max_digs,
			     int *num_full_digs, int *num_places) const
{
	*num_full_digs = leading_places % 3;
	if (*num_full_digs == 0)
		*num_full_digs = 3;
	*num_places = max_digs - *num_full_digs - 2;
	if (*num_places <= 0) {
		*num_places = 0;
		*num_full_digs = max_digs - 1;
	}
}


void Printer::print_abbreviated(FILE *fp, double num, int leading_places,
			       int max_digs) const
{
	int num_full_digs;
	int num_places;
	double factor;
	int rc;
	char magnitude;

	determine_digs(leading_places, max_digs, &num_full_digs, &num_places);

	determine_mag_factor(leading_places, &magnitude, &factor);

	double tmp = num / factor;
	if (tmp + 5 * pow(10, -1 - num_places) >= pow(10, num_full_digs)) {
		if (num_places > 0)
			// just strip down one decimal place,
			// e.g. 9.96... with 1.1 format would result in
			// 10.0 otherwise
			num_places--;
		else {
			// indicate that we need one more leading place
			// e.g. 999.872 with 3.0 format digits would result
			// in 1.000K otherwise
			leading_places++;
			determine_digs(leading_places, max_digs, &num_full_digs, &num_places);
			determine_mag_factor(leading_places, &magnitude, &factor);
			tmp = num / factor;
		}
	}

	rc = fprintf(fp, "%*.*lf%c", max_digs - 1, num_places, num / factor, magnitude);

#ifdef NDEBUG
	(void)(rc);
#else
	assert(rc <= max_digs);
#endif
}


void Printer::print_abbrev_num(FILE *fp, __u64 num, int max_digs) const
{
	int rc = 0;
	char tmp[128];

	if (m_csv) {
		fprintf(fp, "%lld", (long long int)num);
		return;
	}

	/* print into a tmp buffer first - if buf is too small we might
	   write out of bounds otherwise! */
	rc = sprintf(tmp, "%Lu", (long long unsigned int)num);
	if (rc < max_digs)
		fprintf(fp, "%*Lu", max_digs, (long long unsigned int)num);
	else
		print_abbreviated(fp, num, rc, max_digs);
}


void Printer::print_abbrev_num(FILE *fp, __u32 num, int max_digs) const
{
	return print_abbrev_num(fp, (__u64)num, max_digs);
}


void Printer::print_abbrev_num(FILE *fp, double num, int max_digs,
			       bool more_places) const
{
	int  rc = 0;

	assert(max_digs > 2);

	if (m_csv) {
		fprintf(fp, "%lf", num);
		return;
	}

	/* print into a tmp buffer first - if buf is too small we might
	   write out of bounds otherwise! */
	if (num == 0)
		fprintf(fp, "%*.1lf", max_digs, 0.);
	else if (num < 1 && more_places)
		fprintf(fp, "%.*lf", max_digs - 2, num);
	else if (num < pow(10, max_digs - 2))
		fprintf(fp, "%*.1lf", max_digs, num);
	else
		print_abbreviated(fp, num, rc, max_digs);
}


/**
 * Always print doubles using 6 characters: 5 for a 3.1 format and
 * 1 for an optional character giving the magintude. */
void Printer::print_abbrev_num(FILE *fp, double num) const
{
	int  rc = 0;
	char tmp[128];

	if (m_csv) {
		fprintf(fp, "%.1lf", num);
		return;
	}

	if (num == 0) {
		fprintf(fp, "  0.0 ");
		return;
	}

	/* print into a tmp buffer first - if buf is too small we might
	   write out of bounds otherwise! */
	rc = sprintf(tmp, "%lld", (long long int)num);
	if (rc < 4)
		fprintf(fp, "%5.1lf ", num);
	else
		print_abbreviated(fp, num, rc, 6);
}


const struct adapter_utilization* Printer::get_empty_utilization(__u32 host_id)
{
	m_util.adapter_no = host_id;

	return &m_util;
}

const struct adapter_utilization* Printer::get_invalid_utilization(__u32 host_id)
{
	m_util_invalid.adapter_no = host_id;

	return &m_util_invalid;
}

const struct ioerr_cnt* Printer::get_empty_ioerr(struct hctl_ident *identifier)
{
	if (identifier)
		m_ioerr.identifier = *identifier;
	else
		memset(&m_ioerr.identifier, 0, sizeof(struct hctl_ident));

	return &m_ioerr;
}


const struct zfcpdd_dstat* Printer::get_empty_zfcpdd_dstat(__u32 device)
{
	m_dstat.device = device;

	return &m_dstat;
}


const struct blkiomon_stat* Printer::get_empty_blkiomon_stat(__u32 device)
{
	m_stat.device = device;

	return &m_stat;
}


void Printer::print_delimiter(FILE *fp)
{
	fputc(m_delim, fp);
}

void Printer::print_invalid(FILE *fp, int width)
{
	if (m_csv)
		fputc('-', fp);
	else
		fprintf(fp, "%*c", width, '-');
}

void PhysAdapterPrinter::print_phys_adpt(FILE *fp, __u32 host_id, int *rc)
{
	__u32 chpid = m_cfg->get_chpid_by_host_id(host_id, rc);

	if (m_csv)
		fprintf(fp, "%x", chpid);
	else
		fprintf(fp, "%3x", chpid);
}


void PhysAdapterPrinter::print_utilization(FILE *fp,
					  const struct abbrev_stat *stat,
					  __u64 count, bool valid)
{
	__u64 tmp64;

	print_delimiter(fp);
	if (valid) {
		tmp64 = 0;
		if (count)
			tmp64 = stat->min;
		print_abbrev_num(fp, tmp64, 3);
	}
	else
		print_invalid(fp, 3);


	print_delimiter(fp);
	if (valid) {
		tmp64 = 0;
		if (count)
			tmp64 = stat->max;
		print_abbrev_num(fp, tmp64, 3);
	}
	else
		print_invalid(fp, 3);

	print_delimiter(fp);
	if (valid) {
		double tmp = 0;
		if (count)
			tmp = calc_avg(stat->sum, count);
		print_abbrev_num(fp, tmp, 5);
	}
	else
		print_invalid(fp, 5);
}


PhysAdapterPrinter::PhysAdapterPrinter(const ConfigReader *cfg,
				       bool csv_mode)
: Printer(cfg, csv_mode)
{
}


void PhysAdapterPrinter::print_topline(FILE *fp)
{
	if (m_csv)
		fprintf(fp, "timestamp,aggregated,CHPID,adapter min %%,"
			"adapter max %%,adapter avg %%,bus min %%,bus max %%,"
			"bus avg %%,cpu min %%,cpu max %%,cpu avg %%\n");
	else {
		fprintf(fp, "CHP|adapter in %%-|--bus in %%---|--cpu in %%---|\n");
		fprintf(fp, " ID min max   avg min max   avg min max   avg\n");
	}
}


int PhysAdapterPrinter::print_frame(FILE *fp,
				    const Frameset &frameset,
				    const DeviceFilter &dev_filt)
{
	int lrc = 0;
	bool timestamp_printed = false;
	const struct adapter_utilization *util;
	list<__u32> host_ids = dev_filt.get_host_id_list();

	assert(frameset.get_collapser()->get_criterion() == none);
	for (list<__u32>::iterator i = host_ids.begin();
	      i != host_ids.end(); ++i) {
		if (!timestamp_printed) {
			print_timestamp(fp, frameset);
			if (!m_csv)
				// print timestamp for every line in CSV mode
				timestamp_printed = true;
		}
		print_phys_adpt(fp, *i, &lrc);
		if (lrc)
			return -1;
		util = frameset.get_utilization_stat_by_host_id(*i);
		if (!util)
			util = get_empty_utilization(*i);
		print_utilization(fp, &util->stats.adapter,
				  util->stats.count,
				  util->valid);
		print_utilization(fp, &util->stats.bus,
				  util->stats.count,
				  util->valid);
		print_utilization(fp, &util->stats.cpu,
				  util->stats.count,
				  util->valid);
		fputc('\n', fp);
	}

	return 0;
}


VirtAdapterPrinter::VirtAdapterPrinter(const ConfigReader *cfg,
				       bool csv_mode)
: Printer(cfg, csv_mode)
{
}


void VirtAdapterPrinter::print_virt_adpt(FILE *fp, __u32 devno,
					int *rc)
{
	if (m_csv)
		fprintf(fp, "%x,%x.%x.%04x",
			       m_cfg->get_chpid_by_devno(devno, rc),
			       ZIOREP_BUSID_UNPACKED(devno));
	else
		fprintf(fp, "%3x/%x.%x.%04x",
			       m_cfg->get_chpid_by_devno(devno, rc),
			       ZIOREP_BUSID_UNPACKED(devno));
}

void VirtAdapterPrinter::print_queue_fill(FILE *fp,
					  const struct zfcpdd_dstat *stat,
					  const struct adapter_utilization *res)
{
	double tmp;

	tmp = 0;
	if (stat && stat->count)
		tmp = (stat->outb_max * 100)/ZIOREP_PRINTERS_MAX_QUEUE_LEN;
	print_delimiter(fp);
	print_abbrev_num(fp, tmp, 5);

	tmp = 0;
	if (res && res->valid)
		tmp = res->stats.queue_util_integral
			/ (double)res->stats.queue_util_interval;
	print_delimiter(fp);
	print_abbrev_num(fp, tmp, 5);
}


void VirtAdapterPrinter::print_queue_full(FILE *fp,
					 const struct adapter_utilization *res)
{
	__u32 val = 0;

	if (res) {
		if (res->valid)
			val = res->stats.queue_full;
		else {
			print_invalid(fp, 4);
			return;
		}
	}
	print_delimiter(fp);
	print_abbrev_num(fp, val, 4);
}


void VirtAdapterPrinter::print_failures(FILE *fp, const struct ioerr_cnt *cnt)
{
	print_delimiter(fp);
	if (cnt)
		print_abbrev_num(fp, cnt->num_ioerr, 4);
	else
		print_abbrev_num(fp, (__u32)0, 4);
}

void VirtAdapterPrinter::print_throughput(FILE *fp,
					 const struct blkiomon_stat *stat,
					 const __u64 interval)
{
	double tmp;

	if (!stat || interval == 0 || stat->size_r.num <= 0)
		tmp = 0;
	else
		tmp = calc_avg(stat->size_r.sum, interval);
	print_delimiter(fp);
	print_abbrev_num(fp, tmp);

	if (!stat || interval == 0 || stat->size_w.num <= 0)
		tmp = 0;
	else
		tmp = calc_avg(stat->size_w.sum, interval);
	print_delimiter(fp);
	print_abbrev_num(fp, tmp);
}


void VirtAdapterPrinter::print_num_requests(FILE *fp, const struct blkiomon_stat *stat)
{
	print_delimiter(fp);
	if (stat)
		print_abbrev_num(fp, stat->size_r.num, 4);
	else
		print_abbrev_num(fp, (__u64)0, 4);

	print_delimiter(fp);
	if (stat)
		print_abbrev_num(fp, stat->size_w.num, 4);
	else
		print_abbrev_num(fp, (__u64)0, 4);
}


void VirtAdapterPrinter::print_topline(FILE *fp)
{
	if (m_csv)
		fprintf(fp, "timestamp,aggregated,CHPID,Bus-ID,qdio utilization max %%,qdio utilization avg %%,queue full,fail erc,throughput read / MS/s,throughput write / MS/s,I/O requests read,I/O requests write\n");
	else {
		fprintf(fp, "CHP Bus-ID  |qdio util.%%|queu|fail|-thp in MB/s-|I/O reqs-|\n");
		fprintf(fp, " ID            max   avg full  erc     rd    wrt   rd  wrt\n");
	}
}


int VirtAdapterPrinter::print_frame(FILE *fp,
				    const Frameset &frameset,
				    const DeviceFilter &dev_filt)
{
	int lrc = 0;
	bool timestamp_printed = false;
	const struct adapter_utilization 	*util;
	const struct ioerr_cnt			*ioerr;
	const struct blkiomon_stat		*blk_stat;
	const struct zfcpdd_dstat		*zfcp_stat;

	/**
	 * Receive Frameset interval and convert it
	 * from seconds to microseconds
	 */
	__u64 interval = frameset.get_duration() * 1000000;

	list<__u32> devnos;
	devnos = ((StagedDeviceFilter*)&dev_filt)->get_filter_devnos();

	assert(frameset.get_collapser()->get_criterion() == devno);

	for (list<__u32>::iterator i = devnos.begin(); i != devnos.end(); ++i) {
		if (!timestamp_printed) {
			print_timestamp(fp, frameset);
			if (!m_csv)
				// print timestamp for every line in CSV mode
				timestamp_printed = true;
		}

		util = frameset.get_utilization_stat_by_devno(*i);
		ioerr = frameset.get_ioerr_stat_by_devno(*i);
		blk_stat = frameset.get_blkiomon_stat_by_devno(*i);
		zfcp_stat = frameset.get_zfcpdd_stat_by_devno(*i);
		print_virt_adpt(fp, *i, &lrc);
		print_queue_fill(fp, zfcp_stat, util);
		print_queue_full(fp, util);
		print_failures(fp, ioerr);
		print_throughput(fp, blk_stat, interval);
		print_num_requests(fp, blk_stat);
		if (lrc) {
			fprintf(stderr, "%s: Did not find matching data in"
				" .cfg file. Please check if it matches your"
				" data and try again.\n", toolname);
			return -1;
		}
		fputc('\n', fp);
	}

	return 0;
}



TrafficPrinter::TrafficPrinter(const ConfigReader *cfg, Collapser &col,
			       bool csv_mode)
: Printer(cfg, csv_mode), m_mp_whitespace(NULL), m_mp_topline_pref1(NULL),
	m_mp_topline_pref2(NULL)
{
	m_agg_crit = col.get_criterion();

	if (m_agg_crit == multipath_device) {
		if (m_csv) {
			m_mp_topline_pref1 = (char*)malloc(25);
			sprintf(m_mp_topline_pref1, "Multipath Device");
		}
		else {
			int rc = 0;
			int max_len = 32;
			int len;
			int corrector = 0;
			const list<__u32> lst =
				((AggregationCollapser*)&col)->get_reference_mp_mms();
			for (list<__u32>::const_iterator i = lst.begin();
			      i != lst.end(); ++i) {
				len = strlen(cfg->get_multipath_by_mp_mm(*i, &rc));
				assert(rc == 0);
				if (len > max_len)
					max_len = len;
			}
			m_mp_whitespace = (char*)malloc(max_len + 1);
			m_mp_topline_pref1 = (char*)malloc(max_len + 1);
			m_mp_topline_pref2 = (char*)malloc(max_len + 1);
			sprintf(m_mp_whitespace, "%*c", max_len - 1, ' ');
			corrector = (max_len - 9) % 2;
			len = (max_len - 9) / 2;
			sprintf(m_mp_topline_pref1, "%*cMultipath%*c",
				len, ' ', len + corrector, ' ');
			corrector = 1 - corrector; // invert
			len = (max_len - 6) / 2;
			sprintf(m_mp_topline_pref2, "%*cdevice%*c",
				len, ' ', len + corrector, ' ');
		}
	}
}


TrafficPrinter::~TrafficPrinter()
{
	free(m_mp_whitespace);
	free(m_mp_topline_pref1);
	free(m_mp_topline_pref2);
}


void TrafficPrinter::print_topline_prefix1(FILE *fp)
{
	const char *str = NULL;

	switch (m_agg_crit) {
	case none:
		if (m_csv)
			str = "WWPN,LUN";
		else
			str = "       WWPN                LUN       ";
		break;
	case chpid:
		if (m_csv)
			str = "CHPID";
		else
			str = "CHP";
		break;
	case devno:
		if (m_csv)
			str = "Bus-ID";
		else
			str = " Bus-ID ";
		break;
	case wwpn:
		if (m_csv)
			str = "WWPN";
		else
			str = "       WWPN       ";
		break;
	case multipath_device:
		str = m_mp_topline_pref1;
		break;
	case all:
		str = " * ";
		break;
	}

	fprintf(fp, "%s", str);
}

void TrafficPrinter::print_topline_prefix2(FILE *fp)
{
	const char *str = NULL;

	switch (m_agg_crit) {
	case none:
		str = "                                     ";
		break;
	case chpid:
		str = " ID";
		break;
	case devno:
		str = "        ";
		break;
	case wwpn:
		str = "                  ";
		break;
	case multipath_device:
		str = m_mp_topline_pref2;
		break;
	case all:
		str = "   ";
		break;
	}

	fprintf(fp, "%s", str);
}

void TrafficPrinter::print_topline_whitespace(FILE *fp)
{
	const char *str = NULL;

	switch (m_agg_crit) {
	case none:
		str = "                                     ";
		break;
	case chpid:
		str = "   ";
		break;
	case devno:
		str = "        ";
		break;
	case wwpn:
		str = "                  ";
		break;
	case multipath_device:
		str = m_mp_whitespace;
		break;
	case all:
		str = "   ";
		break;
	}

	fprintf(fp, "%s", str);
}

void TrafficPrinter::get_device_list(list<__u32> &lst,
				     const DeviceFilter &dev_filt)
{
	switch (m_agg_crit) {
	case none:
	case all:
		lst = dev_filt.get_mm_list();
		break;
	case devno:
		lst = ((StagedDeviceFilter*)&dev_filt)->get_filter_devnos();
		break;
	case multipath_device:
		lst = ((StagedDeviceFilter*)&dev_filt)->get_filter_mp_mms();
		break;
	case chpid:
		lst = ((StagedDeviceFilter*)&dev_filt)->get_filter_chpids();
		break;
	default:
		assert(false);
	}
}


void TrafficPrinter::get_device_list(list<__u64> &lst,
				     const DeviceFilter &dev_filt)
{
	assert(m_agg_crit == wwpn);
	lst = ((StagedDeviceFilter*)&dev_filt)->get_filter_wwpns();
}


void TrafficPrinter::print_device_wwpn(FILE *fp, __u64 wwpn)
{
	fprintf(fp, "0x%016Lx", (long long unsigned int)wwpn);
}

void TrafficPrinter::print_device_chpid(FILE *fp, __u32 chpid)
{
	if (m_csv)
		fprintf(fp, "%x", chpid);
	else
		fprintf(fp, "%3x", chpid);
}

void TrafficPrinter::print_device_devno(FILE *fp, __u32 devno)
{
	fprintf(fp, "%x.%x.%04x", ZIOREP_BUSID_UNPACKED(devno));
}

void TrafficPrinter::print_device_mp_mm(FILE *fp, __u32 mp_mm,
				       const ConfigReader &cfg, int *rc)
{
	if (m_csv)
		fprintf(fp, "%s", cfg.get_multipath_by_mp_mm(mp_mm, rc));
	else
		fprintf(fp, "%16s", cfg.get_multipath_by_mp_mm(mp_mm, rc));
}

void TrafficPrinter::print_device(FILE *fp, __u32 dev,
				 const ConfigReader &cfg, int *rc)
{
	if (m_csv)
		fprintf(fp, "0x%016Lx,0x%016Lx",
			       (long long unsigned int)cfg.get_wwpn_by_mm_internal(dev, rc),
			       (long long unsigned int)cfg.get_lun_by_mm_internal(dev, rc));
	else
		fprintf(fp, "0x%016Lx:0x%016Lx",
			       (long long unsigned int)cfg.get_wwpn_by_mm_internal(dev, rc),
			       (long long unsigned int)cfg.get_lun_by_mm_internal(dev, rc));
}

void TrafficPrinter::print_device_all(FILE *fp)
{
	if (m_csv)
		fprintf(fp, "*");
	else
		fprintf(fp, " * ");
}

int TrafficPrinter::print_frame(FILE *fp, const Frameset &frameset,
				       const DeviceFilter &dev_filt)
{
	int rc = 0;
	bool timestamp_printed = false;
	list<__u32> lst_32;
	list<__u64> lst_64;
	const struct blkiomon_stat	*blk_stat = NULL;
	const struct zfcpdd_dstat	*zfcp_stat = NULL;
	const AggregationCollapser *agg_col;

	/**
	 * Receive Frameset interval and convert it
	 * from seconds to microseconds
	 */
	__u64 interval = frameset.get_duration() * 1000000;

	switch (m_agg_crit) {
	case none:
		get_device_list(lst_32, dev_filt);
		break;
	case chpid:
		agg_col = (AggregationCollapser*)frameset.get_collapser();
		lst_32 = agg_col->get_reference_chpids();
		break;
	case devno:
		agg_col = (AggregationCollapser*)frameset.get_collapser();
		lst_32 = agg_col->get_reference_devnos();
		break;
	case wwpn:
		agg_col = (AggregationCollapser*)frameset.get_collapser();
		lst_64 = agg_col->get_reference_wwpns();
		break;
	case multipath_device:
		agg_col = (AggregationCollapser*)frameset.get_collapser();
		lst_32 = agg_col->get_reference_mp_mms();
		break;
	case all:
		break;
	}

	if (m_agg_crit == wwpn) {
		for (list<__u64>::const_iterator i = lst_64.begin();
		      i != lst_64.end(); ++i) {
			if (!timestamp_printed) {
				print_timestamp(fp, frameset);
				if (!m_csv)
					// print timestamp for every line in CSV mode
					timestamp_printed = true;
			}

			blk_stat = frameset.get_blkiomon_stat_by_wwpn(*i);
			zfcp_stat = frameset.get_zfcpdd_stat_by_wwpn(*i);
			print_device_wwpn(fp, *i);
			print_data_row(fp, blk_stat, zfcp_stat, interval);
		}
	}
	else if (m_agg_crit == all) {
		print_timestamp(fp, frameset);

		blk_stat = frameset.get_first_blkiomon_stat();
		zfcp_stat = frameset.get_first_zfcpdd_stat();
		print_device_all(fp);
		print_data_row(fp, blk_stat, zfcp_stat, interval);
	}
	else {
		for (list<__u32>::const_iterator i = lst_32.begin();
		      i != lst_32.end(); ++i) {
			if (!timestamp_printed) {
				print_timestamp(fp, frameset);
				if (!m_csv)
					// print timestamp for every line in CSV mode
					timestamp_printed = true;
			}

			switch (m_agg_crit) {
			case none:
				blk_stat = frameset.get_blkiomon_stat_by_mm(*i);
				zfcp_stat = frameset.get_zfcpdd_stat_by_mm(*i);
				print_device(fp, *i, *m_cfg, &rc);
				break;
			case devno:
				blk_stat = frameset.get_blkiomon_stat_by_devno(*i);
				zfcp_stat = frameset.get_zfcpdd_stat_by_devno(*i);
				print_device_devno(fp, *i);
				break;
			case multipath_device:
				blk_stat = frameset.get_blkiomon_stat_by_mp_mm(*i);
				zfcp_stat = frameset.get_zfcpdd_stat_by_mp_mm(*i);
				print_device_mp_mm(fp, *i, *m_cfg, &rc);
				break;
			case chpid:
				blk_stat = frameset.get_blkiomon_stat_by_chpid(*i);
				zfcp_stat = frameset.get_zfcpdd_stat_by_chpid(*i);
				print_device_chpid(fp, *i);
				break;
			default:
				assert(false);
			}
			if (rc )
				return -1;
			print_data_row(fp, blk_stat, zfcp_stat, interval);
		}
	}

	return 0;
}


SummaryTrafficPrinter::SummaryTrafficPrinter(const ConfigReader *cfg,
					     Collapser &col,
					     bool csv_mode)
: TrafficPrinter(cfg, col, csv_mode)
{
}


void SummaryTrafficPrinter::print_topline(FILE *fp)
{
	if (m_csv) {
		fprintf(fp, "timestamp,aggregated,");
		print_topline_prefix1(fp);
		fprintf(fp, ",I/O rate in MB/s min,I/O rate in MB/s max,throughput in MB/s avg,throughput var,#I/O requests total,#I/O requests rd,"
			"#I/O requests wrt,#I/O requests bidi,#I/O subsystem latency in us min,#I/O subsystem latency in us max,"
			"#I/O subsystem latency in us avg,#I/O subsystem latency var,channel latency in us min,channel latency in us max,"
			"channel latency in us avg,channel latency var,fabric latency in us min,fabric latency in us max,fabric latency in us avg,"
			"fabric latency var\n");
	}
	else {
		print_topline_prefix1(fp);
		fprintf(fp, "|I/O rt MB/s|thrp in MB/s-|----I/O requests----|-I/O subs. lat. in us--|--channel lat. in us---|---fabric lat. in us---|\n");
		print_topline_prefix2(fp);
		fprintf(fp, "   min   max    avg  stdev #reqs   rd  wrt bidi  min  max    avg  stdev  min  max    avg  stdev  min  max    avg  stdev\n");
	}
}


void SummaryTrafficPrinter::print_throughput(FILE *fp, const struct blkiomon_stat *stat,
					     const __u64 interval)
{
	struct minmax thrp_data, total_size, total_latency;
	double tmp;

	minmax_init(&thrp_data);
	minmax_init(&total_size);
	minmax_init(&total_latency);

	if (stat) {
		minmax_merge(&thrp_data, &stat->thrput_r);
		minmax_merge(&thrp_data, &stat->thrput_w);
		minmax_merge(&total_size, &stat->size_r);
		minmax_merge(&total_size, &stat->size_w);
		minmax_merge(&total_latency, &stat->d2c_r);
		minmax_merge(&total_latency, &stat->d2c_w);
	}

	tmp = 0;
	if (stat)
		tmp = thrp_data.min / 1000.;
	print_delimiter(fp);
	if (tmp < 1)
		print_abbrev_num(fp, tmp, 5, true);
	else
		print_abbrev_num(fp, tmp, 5);

	tmp = 0;
	if (stat)
		tmp = ((double)(thrp_data.max)) / 1000.;
	print_delimiter(fp);
	if (tmp < 1)
		print_abbrev_num(fp, tmp, 5, false);
	else
		print_abbrev_num(fp, tmp, 5);

	tmp = 0;
	if (stat && total_size.sum > 0)
		tmp = calc_avg(total_size.sum, interval);
	print_delimiter(fp);
	print_abbrev_num(fp, tmp);

	tmp = 0;
	if (stat && total_size.sum > 0)
		tmp = calc_std_dev(total_size.sum, total_size.sos,
					total_latency.sum);
	print_delimiter(fp);
	print_abbrev_num(fp, tmp);
}

void SummaryTrafficPrinter::print_request_stats(FILE *fp,
					const struct blkiomon_stat *stat)
{
	__u64 val;

	val = 0;
	if (stat)
		val = stat->bidir + stat->thrput_r.num + stat->thrput_w.num;
	print_delimiter(fp);
	print_abbrev_num(fp, val, 5);

	val = 0;
	if (stat)
		val = stat->size_r.num;
	print_delimiter(fp);
	print_abbrev_num(fp, val, 4);

	val = 0;
	if (stat)
		val = stat->size_w.num;
	print_delimiter(fp);
	print_abbrev_num(fp, val, 4);

	val = 0;
	if (stat)
		val = stat->bidir;
	print_delimiter(fp);
	print_abbrev_num(fp, val, 4);
}


void SummaryTrafficPrinter::print_latency(FILE *fp,
					 const struct minmax *data)
{
	__u64 tmp64;
	double tmplf;

	tmp64 = 0;
	if (data && data->num > 0)
		tmp64 = data->min;
	print_delimiter(fp);
	print_abbrev_num(fp, tmp64, 4);

	tmp64 = 0;
	if (data && data->num > 0)
		tmp64 = data->max;
	print_delimiter(fp);
	print_abbrev_num(fp, tmp64, 4);

	tmplf = 0;
	if (data && data->num > 0)
		tmplf = minmax_avg(data);
	print_delimiter(fp);
	print_abbrev_num(fp, tmplf);

	tmplf = 0;
	if (data && data->num > 0)
		tmplf = minmax_std_dev(data);
	print_delimiter(fp);
	print_abbrev_num(fp, tmplf);
}

void SummaryTrafficPrinter::print_latency(FILE *fp,
					 const struct abbrev_stat *data,
					 __u64 count)
{
	__u64 tmp64;
	double tmplf;

	tmp64 = 0;
	if (count > 0)
		tmp64 = data->min;
	print_delimiter(fp);
	print_abbrev_num(fp, tmp64, 4);

	tmp64 = 0;
	if (count > 0)
		tmp64 = data->max;
	print_delimiter(fp);
	print_abbrev_num(fp, tmp64, 4);

	tmplf = 0;
	if (count > 0)
		tmplf = calc_avg(data->sum, count);
	print_delimiter(fp);
	print_abbrev_num(fp, tmplf);

	tmplf = 0;
	if (count > 0)
		tmplf = calc_std_dev(data->sum, data->sos, count);
	print_delimiter(fp);
	print_abbrev_num(fp, tmplf);
}

void SummaryTrafficPrinter::print_io_subsystem_latency(FILE *fp,
					const struct blkiomon_stat *stat)
{
	struct minmax data;

	minmax_init(&data);
	if (stat) {
		minmax_merge(&data, &stat->d2c_r);
		minmax_merge(&data, &stat->d2c_w);
	}

	print_latency(fp, &data);
}

void SummaryTrafficPrinter::print_channel_latency(FILE *fp,
					const struct zfcpdd_dstat *stat)
{
	print_latency(fp, &stat->chan_lat, stat->count);
}

void SummaryTrafficPrinter::print_fabric_latency(FILE *fp, const struct zfcpdd_dstat *stat)
{
	print_latency(fp, &stat->fabr_lat, stat->count);
}

void SummaryTrafficPrinter::print_data_row(FILE *fp,
					const struct blkiomon_stat *blk_stat,
					const struct zfcpdd_dstat *zfcp_stat,
					const __u64 interval)
{
	if (!blk_stat)
		blk_stat = get_empty_blkiomon_stat();
	if (!zfcp_stat)
		zfcp_stat = get_empty_zfcpdd_dstat();

	print_throughput(fp, blk_stat, interval);
	print_request_stats(fp, blk_stat);
	print_io_subsystem_latency(fp, blk_stat);
	print_channel_latency(fp, zfcp_stat);
	print_fabric_latency(fp, zfcp_stat);
	fputc('\n', fp);
}


DetailedTrafficPrinter::DetailedTrafficPrinter(const ConfigReader *cfg,
					       Collapser &col,
					       bool csv_mode)
: TrafficPrinter(cfg, col, csv_mode)
{
}


void DetailedTrafficPrinter::print_topline(FILE *fp)
{
	if (m_csv) {
		fprintf(fp, "timestamp,aggregated,");
		print_topline_prefix1(fp);
		fprintf(fp, ",I/O requests 0KB,I/O requests <1KB,I/O requests <2KB,I/O requests <4KB,I/O requests <8KB,"
			"I/O requests <16KB,I/O requests <32KB,I/O requests <64KB,I/O requests <128KB,I/O requests <256KB,"
			"I/O requests <512KB,I/O requests <1MB,I/O requests <2MB,I/O requests <4MB,I/O requests <8MB,"
			"I/O requests >=8MB,I/O subsystem latency 0us,I/O subsystem latency <8us,I/O subsystem latency <16us,"
			"I/O subsystem latency <32us,I/O subsystem latency <64us,I/O subsystem latency <128us,"
			"I/O subsystem latency <256us,I/O subsystem latency <512us,I/O subsystem latency <1ms,"
			"I/O subsystem latency <2ms,I/O subsystem latency <4ms,I/O subsystem latency <8ms,"
			"I/O subsystem latency <16ms,I/O subsystem latency <32ms,I/O subsystem latency <64ms,"
			"I/O subsystem latency <128ms,I/O subsystem latency <256ms,I/O subsystem latency <512ms,"
			"I/O subsystem latency <1s,I/O subsystem latency <2s,I/O subsystem latency <4s,"
			"I/O subsystem latency <8s,I/O subsystem latency <16s,I/O subsystem latency <32s,"
			"I/O subsystem latency >=32s,channel latency 0us,"
			"channel latency <1us,channel latency <2us,channel latency <4us,channel latency <8us,"
			"channel latency <16us,channel latency <32us,channel latency <64us,channel latency <128us,"
			"channel latency <256us,channel latency <512us,channel latency <1ms,channel latency <2ms,"
			"channel latency <4us,channel latency <8ms,channel latency <16ms,channel latency <32ms,"
			"channel latency <64ms,channel latency <128ms,channel latency >=128ms,fabric latency 0us,"
			"fabric latency <8us,fabric latency <16us,fabric latency <32us,fabric latency <64us,"
			"fabric latency <128us,fabric latency <256us,fabric latency <512us,fabric latency <1ms,"
			"fabric latency <2ms,fabric latency <4ms,fabric latency <8ms,fabric latency <16ms,"
			"fabric latency <32ms,fabric latency <64ms,fabric latency <128ms,fabric latency <256ms,"
			"fabric latency <512ms,fabric latency <1s,fabric latency <2s,fabric latency <4s,"
			"fabric latency <8s,fabric latency <16s,fabric latency <32s,fabric latency >=32s\n");
	}
	else {
		print_topline_whitespace(fp);
		fprintf(fp, "|------------------------I/O request sizes in KBytes----------------------------|\n");
		print_topline_whitespace(fp);
		fprintf(fp, "    0    1    2    4    8   16   32   64  128  256  512   1K   2K   4K   8K  >8K\n");
		print_topline_whitespace(fp);
		fprintf(fp, "|------------------------I/O subsystem latency in us-------------------------------------------------------------------------|\n");
		print_topline_whitespace(fp);
		fprintf(fp, "    0    8   16   32   64  128  256  512   1K   2K   4K   8K  16K  32K  64K 128K 256K 512K   1M   2M   4M   8M  16M  32M >32M\n");
		print_topline_whitespace(fp);
		fprintf(fp, "|------------------------channel latency in us------------------------------------------------------|\n");
		print_topline_whitespace(fp);
		fprintf(fp, "    0    1    2    4    8   16   32   64  128  256  512   1K   2K   4K   8K  16K  32K  64K 128K>128K\n");
		print_topline_prefix1(fp);
		fprintf(fp, "|------------------------fabric latency in us--------------------------------------------------------------------------------|\n");
		print_topline_prefix2(fp);
		fprintf(fp, "    0    8   16   32   64  128  256  512   1K   2K   4K   8K  16K  32K  64K 128K 256K 512K   1M   2M   4M   8M  16M  32M >32M\n");
	}
}


void DetailedTrafficPrinter::print_data_row(FILE *fp,
			   const struct blkiomon_stat *blk_stat,
			   const struct zfcpdd_dstat *zfcp_stat,
			   const __u64 interval __attribute__ ((unused)))
{
	if (!blk_stat)
		blk_stat = get_empty_blkiomon_stat();
	if (!zfcp_stat)
		zfcp_stat = get_empty_zfcpdd_dstat();

	print_histogram_io_reqs(fp, blk_stat);
	if (!m_csv) {
		fputc('\n', fp);
		print_topline_whitespace(fp);
	}
	print_histogram_io_subs_lat(fp, blk_stat);
	if (!m_csv) {
		fputc('\n', fp);
		print_topline_whitespace(fp);
	}
	print_histogram_channel_lat(fp, zfcp_stat);
	if (!m_csv) {
		fputc('\n', fp);
		print_topline_whitespace(fp);
	}
	print_histogram_fabric_lat(fp, zfcp_stat);
	fputc('\n', fp);
}


void DetailedTrafficPrinter::print_histogram_io_reqs(FILE *fp,
			    const struct blkiomon_stat *stat)
{
	for (unsigned int i = 0; i < BLKIOMON_SIZE_BUCKETS; ++i) {
		print_delimiter(fp);
		if (stat)
			print_abbrev_num(fp, stat->size_hist[i], 4);
		else
			print_abbrev_num(fp, (__u32)0, 4);
	}
}


void DetailedTrafficPrinter::print_histogram_io_subs_lat(FILE *fp,
				const struct blkiomon_stat *stat)
{
	for (unsigned int i = 0; i < BLKIOMON_D2C_BUCKETS; ++i) {
		print_delimiter(fp);
		if (stat)
			print_abbrev_num(fp, stat->d2c_hist[i], 4);
		else
			print_abbrev_num(fp, (__u32)0, 4);
	}
}


void DetailedTrafficPrinter::print_histogram_channel_lat(FILE *fp,
				const struct zfcpdd_dstat *stat)
{
	for (unsigned int i = 0; i < BLKIOMON_CHAN_LAT_BUCKETS; ++i) {
		print_delimiter(fp);
		if (stat)
			print_abbrev_num(fp, stat->chan_lat_hist[i], 4);
		else
			print_abbrev_num(fp, (__u32)0, 4);
	}
}


void DetailedTrafficPrinter::print_histogram_fabric_lat(FILE *fp,
			       const struct zfcpdd_dstat *stat)
{
	for (unsigned int i = 0; i < BLKIOMON_FABR_LAT_BUCKETS; ++i) {
		print_delimiter(fp);
		if (stat)
			print_abbrev_num(fp, stat->fabr_lat_hist[i], 4);
		else
			print_abbrev_num(fp, (__u32)0, 4);
	}
}



