/*
 * FCP adapter trace utility
 *
 * Utilization data collector for zfcp adapters
 *
 * 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 <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <time.h>
#include <unistd.h>

#include "lib/zt_common.h"
#include "ziomon_util.h"


#ifdef WITH_MAIN
const char *toolname = "ziomon_util";
#else
extern const char *toolname;
#endif


struct overall_result_wrp {
	long			mtype;	/* must be long due to msg-passing
					   interface */
	struct utilization_data   o_res;
};

struct ioerr_wrp {
	long			mtype;
	struct ioerr_data	data;
};

__u32 get_result_sz(struct utilization_data *res)
{
	return (sizeof(struct utilization_data)
		+ res->num_adapters * sizeof(struct adapter_utilization));
}


__u32 get_ioerr_data_sz(struct ioerr_data *wrp)
{
	return (sizeof(struct ioerr_data)
		+ wrp->num_luns * sizeof(struct ioerr_cnt));
}


void print_utilization_result(struct utilization_data *res)
{
	int i;
	struct adapter_utilization	*a_res;
	struct utilization_stats	*u_res;
	time_t			 t;

	t = res->timestamp;
	printf("timestamp     : %s", ctime(&t));
	printf("num adapters  : %d\n", res->num_adapters);
	for (i = 0; i < res->num_adapters; ++i) {
		a_res = &res->adapt_utils[i];
		u_res = &a_res->stats;
		if (i != 0)
			printf("\n");
		printf("utilization of host adapter %d\n", a_res->adapter_no);
		printf("\tnum samples: %llu\n", (unsigned long long)u_res->count);
		if (u_res->count > 0) {
			printf("\tqueue full incidents: %d\n", u_res->queue_full);
			printf("\taverage queue utilization: %.1lf slots\n",
				u_res->queue_util_integral / (double)u_res->queue_util_interval);
			printf("\tadapter:\n");
			print_abbrev_stat(&u_res->adapter, u_res->count);
			printf("\tbus:\n");
			print_abbrev_stat(&u_res->bus, u_res->count);
			printf("\tprocessor:\n");
			print_abbrev_stat(&u_res->cpu, u_res->count);
		}
	}
}


static void swap_overall_result(struct utilization_data *res)
{
	int i;
	struct adapter_utilization *a_res;
	struct utilization_stats *u_res;

	swap_64(res->timestamp);

	for (i = 0; i < res->num_adapters; ++i) {
		a_res = &res->adapt_utils[i];
		swap_32(a_res->adapter_no);
		swap_16(a_res->valid);
		u_res = &a_res->stats;
		swap_64(u_res->count);
		swap_32(u_res->queue_full);
		swap_64(u_res->queue_util_integral);
		swap_64(u_res->queue_util_interval);
		swap_abbrev_stat(&u_res->adapter);
		swap_abbrev_stat(&u_res->bus);
		swap_abbrev_stat(&u_res->cpu);
	}
}


void conv_overall_result_to_BE(struct utilization_data *res)
{
	swap_overall_result(res);
	swap_16(res->num_adapters);
}


void conv_overall_result_from_BE(struct utilization_data *res)
{
	swap_16(res->num_adapters);
	swap_overall_result(res);
}


int compare_hctl_idents(const struct hctl_ident *src,
			const struct hctl_ident *tgt)
{
	if (src->host == tgt->host) {
		if (src->channel == tgt->channel) {
			if (src->target == tgt->target)
				return (src->lun - tgt->lun);
			else
				return (src->target - tgt->target);
		}
		else
			return (src->channel - tgt->channel);
	}
	else
		return (src->host - tgt->host);
}

void aggregate_utilization_data(const struct utilization_data *src,
				struct utilization_data *tgt)
{
	int i;

	assert(tgt->timestamp = src->timestamp);
	tgt->timestamp = src->timestamp;

	if (src->num_adapters != tgt->num_adapters)
		fprintf(stderr, "%s: Warning: inconsistent"
			" number of adapters: %d %d\n", toolname,
			src->num_adapters, tgt->num_adapters);

	for (i = 0; i < src->num_adapters; ++i)
		aggregate_adapter_result(&src->adapt_utils[i], &tgt->adapt_utils[i]);

	return;
}


void aggregate_adapter_result(const struct adapter_utilization *src,
			      struct adapter_utilization *tgt)
{
	if (src->valid) {
		/* in case we aggregate over different adapters,
		   we clearly indicate that this is not valid anymore */
		if (src->adapter_no != tgt->adapter_no)
			tgt->adapter_no = -1;

		tgt->stats.queue_full += src->stats.queue_full;
		tgt->stats.queue_util_integral +=
					src->stats.queue_util_integral;
		tgt->stats.queue_util_interval +=
					src->stats.queue_util_interval;
		tgt->stats.count += src->stats.count;
		aggregate_abbrev_stat(&src->stats.adapter,
				&tgt->stats.adapter);
		aggregate_abbrev_stat(&src->stats.bus,
				&tgt->stats.bus);
		aggregate_abbrev_stat(&src->stats.cpu,
				&tgt->stats.cpu);
	}
}


void print_ioerr_data(struct ioerr_data *data)
{
	__u64 i;
	struct ioerr_cnt *cnt;
	time_t		  t;

	t = data->timestamp;
	fprintf(stdout, "timestamp : %s", ctime(&t));
	fprintf(stdout, "num luns  : %lld\n", (long long)data->num_luns);
	for (i = 0; i < data->num_luns; ++i) {
		cnt = &data->ioerrors[i];
		fprintf(stdout, "\t%d:%d:%d:%d: %lld I/O errors\n",
			cnt->identifier.host, cnt->identifier.channel,
			cnt->identifier.target, cnt->identifier.lun,
			(long long)cnt->num_ioerr);
	}
}

static void swap_ioerr_data(struct ioerr_data *data)
{
	__u64 i;
	struct ioerr_cnt *cnt;

	swap_64(data->timestamp);
	for (i = 0; i < data->num_luns; ++i) {
		cnt = &data->ioerrors[i];
		swap_32(cnt->identifier.host);
		swap_32(cnt->identifier.channel);
		swap_32(cnt->identifier.target);
		swap_32(cnt->identifier.lun);
		swap_64(cnt->num_ioerr);
	}
}

void conv_ioerr_data_to_BE(struct ioerr_data *data)
{
	swap_ioerr_data(data);
	swap_64(data->num_luns);
}


void conv_ioerr_data_from_BE(struct ioerr_data *data)
{
	swap_64(data->num_luns);
	swap_ioerr_data(data);
}


void aggregate_ioerr_cnt(const struct ioerr_cnt *src, struct ioerr_cnt *tgt)
{
	/* in case we aggregate over different devices,
	   we clearly indicate that this is not valid anymore */
	if (compare_hctl_idents(&src->identifier, &tgt->identifier) != 0)
		memset(&tgt->identifier, 0xff, sizeof(struct hctl_ident));

	tgt->num_ioerr += src->num_ioerr;
}


void aggregate_ioerr_data(const struct ioerr_data *src, struct ioerr_data *tgt)
{
	__u64 i;

	assert(tgt->timestamp <= src->timestamp);
	tgt->timestamp = src->timestamp;
	for (i=0; i<src->num_luns; ++i)
		aggregate_ioerr_cnt(&src->ioerrors[i], &tgt->ioerrors[i]);
}


#ifdef WITH_MAIN

#define	SAMPLE_INTERVAL_DFT	2
#define	SAMPLE_INTERVAL_DFT_STR	"2"


static int keep_running;
int verbose=0;


struct util_data {
	int 			queue_full; /* # queue full instances in frame */
	int			queue_full_prev; /* previous absolut value
						    of queue_full */
	__u64			queue_util_integral;
	__u64			queue_util_interval;
	__u64			queue_util_prev; /* previous absolut value
						    of queue_util_integral */
	struct timeval		queue_util_timestamp;
	struct abbrev_stat 	adapter;
	struct abbrev_stat 	bus;
	struct abbrev_stat 	cpu;
	long count;
};

struct adapter_data {
	int			host_nr;
	char		       *path;
	char		       *q_full_path;
	int			status;	/* 0 if good != 0 in case of failure */
	struct util_data	data;
};

struct adapters {
	int			num_adapters;
	struct adapter_data	adapters[0];
};

#define MAX_HOST_PATH_LEN	42
#define MAX_LUN_PATH_LEN	strlen("/sys/bus/scsi/devices/4000000000:4000000000:4000000000:4000000000/ioerr_cnt")

struct options {
	int   	num_hosts;	/* total number of hosts */
	int   	num_hosts_a;	/* number of allocated host & lun structures */
	long   *host_nr;	/* array if host numbers. to trace */
	char  **host_path;	/* array of paths to utilization files */
	int     num_luns;	/* number of luns */
	char  **luns;		/* array of luns to monitor */
	__u32  *luns_prev;	/* array of previous values of luns */
	long  	duration;	/* overall duration in seconds */
	long  	s_duration;	/* ssample duration in seconds */
	long  	i_duration;	/* interval duration in seconds */
	char   *msg_q_path;
	int	msg_q_id;
	int	msg_q;		/* msg q handle */
	long	msg_id;		/* msg id to use in msg q */
	long	msg_id_ioerr;	/* msg id to use in msg q for ioerr messages*/
};


static void init_opts(struct options *opts)
{
	opts->i_duration   = -1;
	opts->duration     = -1;
	opts->s_duration   = SAMPLE_INTERVAL_DFT;
	opts->num_hosts	   = 0;
	opts->num_hosts_a  = 0;
	opts->host_nr	   = NULL;
	opts->host_path    = NULL;
	opts->num_luns	   = 0;
	opts->luns	   = NULL;
	opts->luns_prev	   = NULL;
	opts->msg_q_path   = NULL;
	opts->msg_q_id	   = -1;
	opts->msg_q	   = -1;
	opts->msg_id	   = LONG_MIN;
	opts->msg_id_ioerr = LONG_MIN;
}


static void deinit_opts(struct options *opts)
{
	int i;

	free(opts->host_nr);
	opts->host_nr = NULL;
	if (opts->host_path) {
		for (i = 0; i < opts->num_hosts_a; ++i)
			free(opts->host_path[i]);
		free(opts->host_path);
		opts->host_path = NULL;
	}
	for (i=0; i<opts->num_hosts_a; ++i)
		free(opts->luns[i]);
	opts->num_hosts_a = 0;
	opts->msg_q = -1;
	free(opts->luns);
	free(opts->luns_prev);
}


static void init_util_data(struct util_data *data)
{
	init_abbrev_stat(&data->adapter);
	init_abbrev_stat(&data->bus);
	init_abbrev_stat(&data->cpu);
	data->count = 0;
}


static int init_ioerr_cnt(struct ioerr_cnt *cnt, char *str)
{
	cnt->num_ioerr = 0;
	return (sscanf(str, "%d:%d:%d:%d", &cnt->identifier.host,
		       &cnt->identifier.channel, &cnt->identifier.target,
		       &cnt->identifier.lun) == 5);
}


static int init_host_opts(struct options *opts, int num_hosts)
{
	int i;

	free(opts->host_nr);
	if (opts->host_path) {
		for (i = 0; i < opts->num_hosts_a; ++i)
			free(opts->host_path[i]);
		free(opts->host_path);
	}

	opts->host_nr = calloc(num_hosts, sizeof(long));
	if (!opts->host_nr) {
	     fprintf(stderr, "%s: calloc opts->host_nr: %s\n",
			toolname, strerror(errno));
	     return -1;
	}
	opts->host_path = malloc(num_hosts * sizeof(char *));
	if (!opts->host_path) {
	     fprintf(stderr, "%s: malloc opts->host_path: %s\n",
			toolname, strerror(errno));
	     return -1;
	}
	for (i = 0; i < num_hosts; ++i) {
		opts->host_path[i] = malloc(sizeof(char) * MAX_HOST_PATH_LEN + 1);
		if (!opts->host_path[i]) {
			fprintf(stderr, "%s: malloc opts->host_path[]: %s\n",
				toolname, strerror(errno));
			return -1;
		}
	}

	free(opts->luns);
	opts->luns = calloc(num_hosts, sizeof(char *));
	free(opts->luns_prev);
	opts->luns_prev = calloc(num_hosts, sizeof(__u32));
	if (!opts->luns || !opts->luns_prev) {
	     fprintf(stderr, "%s: malloc opts->luns: %s\n",
			toolname, strerror(errno));
	     return -1;
	}
	for (i = 0; i < num_hosts; ++i) {
		opts->luns[i] = malloc(sizeof(char) * MAX_LUN_PATH_LEN + 1);
		if (!opts->host_path[i]) {
			fprintf(stderr, "%s: malloc opts->luns[]: %s\n",
				toolname, strerror(errno));
			return -1;
		}
	}
	opts->num_hosts_a = num_hosts;

	return 0;
}


#define LINE_LEN	255

static int read_attribute(char *path, char *line, int *status)
{
	int fd;
	int rc = 0;

	fd = open(path, O_RDONLY);
	if (fd < 0) {
		rc = -1;		/* adapter gone */
		goto out;
	}
	rc = read(fd, line, LINE_LEN);
	if (rc < 0) {
		rc = -2;		/* I/O error */
		goto out;
	}
	rc = 0;
	close(fd);
out:
	if (status)
		*status = rc;

	return rc;
}


static int poll_utilization(struct adapters *all_adapters)
{
	char line[LINE_LEN];
	int cpu, bus, adapter;
	int rc = 0, grc = 0;
	struct adapter_data *adpt;
	struct util_data    *u_data;
	int i;

	for (i = 0; i < all_adapters->num_adapters; ++i) {
		adpt = &all_adapters->adapters[i];
		u_data = &adpt->data;
		/* read utilization attribute */
		if (read_attribute(adpt->path, line, &adpt->status)) {
			grc++;
			continue;
		}
		rc = sscanf(line, "%d %d %d", &cpu, &bus, &adapter);
		if (rc != 3) {
			fprintf(stderr, "%s: Warning:"
				" Could not parse %s: %s\n", toolname,
				line, strerror(errno));
			adpt->status = 3;
			continue;
		}
		update_abbrev_stat(&u_data->adapter, adapter);
		update_abbrev_stat(&u_data->bus, bus);
		update_abbrev_stat(&u_data->cpu, cpu);

		verbose_msg("data read for adapter %d: adapter=%d, bus=%d,"
			    " cpu=%d\n",
			    adpt->host_nr, adapter, bus, cpu);

		u_data->count++;
	}

	return grc;
}


static int calc_overflow(__u64 old_val, __u64 new_val)
{
	double ofl_val = 0;
	int i = 0;

	assert(old_val > new_val);

	while (ofl_val < old_val) {
		ofl_val = pow(2, i);
		++i;
	}

	return (new_val + (int)(ofl_val - old_val));
}


static int poll_queue_full(int init, struct adapters *all_adapters)
{
	char line[LINE_LEN];
	int rc = 0;
	struct adapter_data *adpt;
	struct util_data    *u_data;
	int queue_full_tmp;
	long long unsigned int queue_util_tmp;
	int i;
	struct timeval tmp, cur_time;

	for (i = 0; i < all_adapters->num_adapters; ++i) {
		adpt = &all_adapters->adapters[i];
		u_data = &adpt->data;

		/* read queue_full attribute */
		if (read_attribute(adpt->q_full_path, line, &adpt->status))
			continue;
		rc = sscanf(line, "%d %Lu", &queue_full_tmp, &queue_util_tmp);
		if (rc == 1) {
			fprintf(stderr, "%s: Only one value in"
				" %s, your kernel level is probably too old.\n",
				toolname, adpt->q_full_path);
			return -1;
		}
		if (rc != 2) {
			fprintf(stderr, "%s: Warning:"
				" Could not parse %s: %s\n",
				toolname, line,	strerror(errno));
			adpt->status = 6;
			continue;
		}
		gettimeofday(&cur_time, NULL);
		if (!init) {
			if (queue_full_tmp < u_data->queue_full_prev)
				u_data->queue_full =
					calc_overflow(u_data->queue_full_prev,
						      queue_full_tmp);
			else
				u_data->queue_full = queue_full_tmp
						- u_data->queue_full_prev;
			if (queue_util_tmp < u_data->queue_util_prev)
				u_data->queue_util_integral =
					calc_overflow(u_data->queue_util_prev,
							queue_util_tmp);
			else
				u_data->queue_util_integral = queue_util_tmp
						- u_data->queue_util_prev;
			timersub(&cur_time, &u_data->queue_util_timestamp, &tmp);
			u_data->queue_util_interval = tmp.tv_sec * 1000000
							+ tmp.tv_usec;
		}
		u_data->queue_full_prev = queue_full_tmp;
		u_data->queue_util_prev = queue_util_tmp;
		u_data->queue_util_timestamp = cur_time;
	}

	return 0;
}


static int poll_ioerr_cnt(int init, struct ioerr_data *data,
			  struct options *opts)
{
	char line[LINE_LEN];
	int rc = 0, grc = 0;
	int i;
	__u32 tmp;

	if (!init)
		data->timestamp = time(NULL);
	for (i=0; i<opts->num_luns; ++i) {
		/* read ioerr_cnt attribute */
		if (read_attribute(opts->luns[i], line, NULL)) {
			fprintf(stderr, "%s: Warning: Could not read %s\n",
				toolname, opts->luns[i]);
			grc++;
			continue;
		}
		rc = sscanf(line, "%i", &tmp);
		if (rc != 1) {
			fprintf(stderr, "%s: Warning:"
				" Could not parse ioerr line %s: %s\n",
				toolname, line,	strerror(errno));
			grc++;
			continue;
		}
		if (!init) {
			if (tmp < opts->luns_prev[i])
				data->ioerrors[i].num_ioerr = calc_overflow(
						opts->luns_prev[i], tmp);
			else
				data->ioerrors[i].num_ioerr = tmp
							- opts->luns_prev[i];
			verbose_msg("data read for i/o err %s: ioerr_cnt=%d\n",
			    opts->luns[i], data->ioerrors[i].num_ioerr);
		}
		opts->luns_prev[i] = tmp;
	}

	return grc;
}


static void reinit_adapters(struct adapters *adptrs)
{
	int i;

	for (i = 0; i < adptrs->num_adapters; ++i)
		init_util_data(&adptrs->adapters[i].data);
}


static int init_adapters(struct adapters *all_adapters, struct options *opts)
{
	struct adapter_data *adapter;
	struct stat buf;
	int i, rc = 0;

	if (opts)
		all_adapters->num_adapters = opts->num_hosts;

	for (i = 0; i < all_adapters->num_adapters; ++i) {
		adapter = &all_adapters->adapters[i];
		if (opts) {
			adapter->host_nr = opts->host_nr[i];
			adapter->path = malloc(sizeof(char)*MAX_HOST_PATH_LEN);
			sprintf(adapter->path, "%s/utilization",
				opts->host_path[i]);
			if (stat(adapter->path, &buf)) {
				fprintf(stderr, "%s: Path does not exist: %s - correct kernel version?\n",
					toolname, adapter->path);
				rc++;
			}
			adapter->q_full_path =
				malloc(sizeof(char)*MAX_HOST_PATH_LEN);
			sprintf(adapter->q_full_path, "%s/queue_full",
				opts->host_path[i]);
			if (stat(adapter->q_full_path, &buf)) {
				fprintf(stderr, "%s: Path does not exist: %s - correct kernel version?\n",
					toolname, adapter->q_full_path);
				rc++;
			}
			adapter->status = 0;
		}
		init_util_data(&all_adapters->adapters[i].data);
	}
	if (poll_queue_full(1, all_adapters))
		return -1;

	return rc;
}


static void deinit_adapters(struct adapters *all_adapters)
{
	int i;

	for (i = 0; i < all_adapters->num_adapters; ++i) {
		free(all_adapters->adapters[i].path);
		free(all_adapters->adapters[i].q_full_path);
	}
}


/**
 * set up structures for the lun result message and replace lun identifiers
 * with complete path to respective ioerr_cnts.
 */
static int init_ioerr_wrp(struct ioerr_wrp **wrp, struct options *opts)
{
	int i;
	struct stat buf;
	char tmp[255];

	*wrp = malloc(sizeof(struct ioerr_wrp)
		     + opts->num_luns*sizeof(struct ioerr_cnt));
	if (!*wrp) {
		fprintf(stderr, "%s: Memory allocation failed\n", toolname);
		return -1;
	}
	(*wrp)->mtype = opts->msg_id_ioerr;
	(*wrp)->data.num_luns = opts->num_luns;
	for (i=0; i<opts->num_luns; ++i) {
		if (init_ioerr_cnt(&(*wrp)->data.ioerrors[i], opts->luns[i])) {
			fprintf(stderr, "%s: Could not parse %s\n",
				toolname, opts->luns[i]);
			return -2;
		}
		strcpy(tmp, opts->luns[i]);
		sprintf(opts->luns[i], "/sys/bus/scsi/devices/%s/ioerr_cnt", tmp);
		if (stat(opts->luns[i], &buf)) {
			fprintf(stderr, "%s: Could not open %s: %s\n",
				toolname, opts->luns[i], strerror(errno));
			return -3;
		}
	}
	if (poll_ioerr_cnt(1, NULL, opts)) {
		fprintf(stderr, "%s: Could not read initial values of ioerr"
			" attributes.\n", toolname);
		return -1;
	}

	return 0;
}


static void init_result_wrp(struct overall_result_wrp **res, int num_adapters,
			    struct options *opts)
{
	*res = malloc(sizeof(struct overall_result_wrp) + (num_adapters * sizeof(struct adapter_utilization)));
	(*res)->o_res.num_adapters = num_adapters;
	(*res)->mtype = opts->msg_id;
}


static void deinit_result_wrp(struct overall_result_wrp **res)
{
	free(*res);
	res = NULL;
}


static void print_version(void)
{
        fprintf(stdout, "%s: ziomon utilization monitor, version %s\n"
               "Copyright IBM Corp. 2008, 2017\n",
               toolname, RELEASE_STRING);
}


static int get_argument_long(long *param, char opt)
{
	char *p;

	if (!optarg) {
		fprintf(stderr, "%s: Argument missing"
			" to option %c\n", toolname, opt);
		return -1;
	}
	*param = strtol(optarg, &p, 0);
	if (errno) {
		fprintf(stderr, "%s: Unrecognized"
			" parameter to option %c: %s\n", toolname, opt,
			strerror(errno));
		return -2;
	}
	if (*p != '\0') {
		fprintf(stderr, "%s: Unrecognized"
			" parameter to option %c\n", toolname, opt);
		return -3;
	}

	return 0;
}


static int hostdir_filter(const struct dirent *dir)
{
	return !strncmp(dir->d_name, "host", 4);
}


static int check_host_param(long *host_nr, char *host_path)
{
	struct stat 	buf;
	char *tmp;

	if (*host_nr > 9999) {
		fprintf(stderr, "%s: Host number out"
			" of range\n", toolname);
		return -1;
	}

	sprintf(host_path, "/sys/class/scsi_host/host%ld", *host_nr);
	if (stat(host_path, &buf)) {
		fprintf(stderr, "%s: Cannot access %s:"
			" %s\n", toolname, host_path, strerror(errno));
		return -1;
	}
	tmp = malloc(strlen(host_path) + strlen("/queue_full") + 1);
	sprintf(tmp, "%s/queue_full", host_path);
	if (stat(tmp, &buf)) {
		fprintf(stderr, "%s: Cannot access %s."
			" Your installed kernel is probably too old. Please"
			" check that your kernel matches the level in the"
			" documentation.\n", toolname, tmp);
		free(tmp);
		return -1;
	}
	free(tmp);
	verbose_msg("host path           : %s\n", host_path);

	return 0;
}


static int find_all_hosts(struct options *opts)
{
	char *h_path = "/sys/class/scsi_host";
	struct dirent **namelist;
	int num_dirents;
	int i, k;
	int rc = 0;

	verbose_msg("no host adapter(s) specified, scanning...\n");
	num_dirents = scandir(h_path, &namelist, hostdir_filter, alphasort);
	if (num_dirents <= 0) {
		fprintf(stderr, "%s: No host adapter(s) found\n", toolname);
		rc = -3;
		goto out;
	}

	if (init_host_opts(opts, num_dirents))
		return -1;
	opts->num_hosts = num_dirents;

	for (i = 0; i < num_dirents; ++i) {
		k = sscanf(namelist[i]->d_name, "host%ld", &opts->host_nr[i]);
		if (k != 1) {
			fprintf(stderr, "%s: Internal error while scanning"
				" host adapter no\n", toolname);
			rc = -4;
		} else
			check_host_param(&opts->host_nr[i],
					 opts->host_path[i]);
	}

out:
	for (i = 0; i < num_dirents; i++)
		free(namelist[i]);
	free(namelist);

	return rc;
}


static int host_adapter_compare(const void *a, const void *b)
{
	return (*(long *)a > *(long *)b);
}


static int setup_msg_q(struct options *opts)
{
	key_t util_q;
	int wait=0;

	if (opts->msg_id <= 0) {
		fprintf(stderr, "%s: Invalid or missing msg"
			" id for utilization messages\n", toolname);
		return -1;
	}
	if (opts->msg_id_ioerr <= 0) {
		fprintf(stderr, "%s: Invalid or missing msg"
			" id for I/O error messages\n", toolname);
		return -1;
	}
	if (opts->msg_id == opts->msg_id_ioerr) {
		fprintf(stderr, "%s: Message IDs for"
			" I/O error count and utilization messages must be"
			" different\n", toolname);
		return -1;
	}

	util_q = ftok(opts->msg_q_path, opts->msg_q_id);

	verbose_msg("message queue key is %d\n", util_q);

	while (keep_running) {
		opts->msg_q = msgget(util_q, S_IRWXU);
		if (opts->msg_q >= 0) {
			if (wait)
				fprintf(stderr, "%s: Message queue is up!\n",
					toolname);
			break;
		}
		if (!wait) {
			wait = 1;
			fprintf(stderr, "%s: Warning: Message queue not"
				" up yet, waiting...\n", toolname);
		}
		usleep(200000);
	}
	verbose_msg("message queue id is %d\n", opts->msg_q);

	if (opts->msg_q_path) {
		verbose_msg("message queue path	: %s\n", opts->msg_q_path);
		verbose_msg("message queue id	: %d\n", opts->msg_q_id);
		verbose_msg("message id		: %ld\n", opts->msg_id);
		verbose_msg("message id		: %ld\n", opts->msg_id);
	}

	return (opts->msg_q >= 0 ? 0 : -1);
}


static void generate_result(struct utilization_data *ures,
			    struct adapters *all_adapters)
{
	struct adapter_data	*a_data;
	struct util_data	*u_data;
	struct adapter_utilization	*a_res;
	struct utilization_stats	*u_res;
	int i;

	ures->num_adapters = all_adapters->num_adapters;
	ures->timestamp = time(NULL);

	for (i = 0; i < all_adapters->num_adapters; ++i) {
		a_data = &all_adapters->adapters[i];
		a_res = &ures->adapt_utils[i];
		a_res->adapter_no = a_data->host_nr;
		u_data = &a_data->data;
		a_res->valid = u_data->count;
		if (a_res->valid) {
			u_res = &a_res->stats;
			u_res->queue_full = u_data->queue_full;
			u_res->queue_util_integral = u_data->queue_util_integral;
			u_res->queue_util_interval = u_data->queue_util_interval;
			u_res->count = u_data->count;
			copy_abbrev_stat(&u_res->adapter, &u_data->adapter);
			copy_abbrev_stat(&u_res->bus, &u_data->bus);
			copy_abbrev_stat(&u_res->cpu, &u_data->cpu);
		}
	}
}



static const char help_text[] =
    "Usage: ziomon_util [-h] [-v] [-V] [-i n] [-s n] "
            "[-Q <msgq_path> -q <msgq_id>\n"
    "                   -m <msg_id>] -d n -a <n> -l <lun>\n"
    "\n"
    "Start the monitor for the host adapter utilization.\n"
    "Example: ziomon_util -d 60 -i 4 -a 0\n"
    "\n"
    "-h, --help            Print usage information and exit.\n"
    "-v, --version         Print version information and exit.\n"
    "-V, --verbose         Be verbose.\n"
    "-s, --sample-length   Duration between each sample in seconds.\n"
    "                      Defaults to "SAMPLE_INTERVAL_DFT_STR" seconds.\n"
    "-i, --interval-length Aggregate samples over this duration (in seconds).\n"
    "                      Defaults to 'duration'.\n"
    "-d, --duration        Overall duration in seconds.\n"
    "-a, --adapter         Host adapter no. to watch. Specify each host"
			   " adapter\n"
    "                      separately.\n"
    "-l, --lun             watch I/O error count of LUN. Specify each LUN\n"
    "                      separately in h:b:t:l format.\n"
    "-Q, --msg-queue-name  Specify the message queue path name.\n"
    "-q, --msg-queue-id    Specify the message queue id.\n"
    "-m, --msg-id          Specify the message id to use.\n"
    "-L, --msg-id-ioerr    Specify the message id for I/O error count"
			" messages.\n";

static void print_help(void)
{
        fprintf(stdout, "%s", help_text);
}


static int parse_params(int argc, char **argv, struct options *opts)
{
	int c;
	int index, i;
	static struct option long_options[] = {
		{ "version",        no_argument,       NULL, 'v'},
		{ "help",           no_argument,       NULL, 'h'},
		{ "verbose",        no_argument,       NULL, 'V'},
		{ "msg-queue-name", required_argument, NULL, 'Q'},
		{ "msg-queue-id",   required_argument, NULL, 'q'},
		{ "msg-id",         required_argument, NULL, 'm'},
		{ "msg-id-ioerr",   required_argument, NULL, 'L'},
		{ "sample-length",  required_argument, NULL, 's'},
		{ "interval-length",required_argument, NULL, 'i'},
		{ "duration",       required_argument, NULL, 'd'},
		{ "adapter",        required_argument, NULL, 'a'},
		{ "lun",            required_argument, NULL, 'l'},
		{ NULL,             0,                 NULL,  0 }
	};

	if (argc <= 1) {
		print_help();
		return -1;
	}

	/* this is too much, but argc/2 is a reliable upper boundary
	   and saves us the trouble of figuring out how many host
	   adapters were specified up front */
	init_host_opts(opts, argc/2);

	while ((c = getopt_long(argc, argv, "L:l:m:Q:q:a:s:d:i:vhV", long_options,
				&index)) != EOF) {
		switch (c) {
		case 'V':
			verbose = 1;
			break;
		case 'a':
			if (get_argument_long(&opts->host_nr[opts->num_hosts], c))
				return -1;
			(opts->num_hosts)++;
			break;
		case 'l':
			if (!optarg) {
				fprintf(stderr, "%s: Argument missing to"
					" option '-l'\n", toolname);
				return -1;
			}
			strcpy(opts->luns[opts->num_luns], optarg);
			(opts->num_luns)++;
			break;
		case 'L':
			if (get_argument_long(&opts->msg_id_ioerr, c))
				return -1;
			break;
		case 'i':
			if (get_argument_long(&opts->i_duration, c))
				return -1;
			break;
		case 's':
			if (get_argument_long(&opts->s_duration, c))
				return -1;
			break;
		case 'd':
			if (get_argument_long(&opts->duration, c))
				return -1;
			break;
		case 'm':
			if (get_argument_long(&opts->msg_id, c))
				return -1;
			break;
		case 'Q':
			if (!optarg) {
				fprintf(stderr, "%s: Argument missing to"
					" option '-Q'\n", toolname);
				return -1;
			}
			opts->msg_q_path = optarg;
			break;
		case 'q':
			if (!optarg) {
				fprintf(stderr, "%s: Argument missing to"
					" option '-Q'\n", toolname);
				return -1;
			}
			opts->msg_q_id = atoi(optarg);
			if (opts->msg_q_id < 0) {
				fprintf(stderr, "%s: Parameter to option '-q'"
					" must be greater than 0\n", toolname);
				return -1;
			}
			break;
		case 'v':
			print_version();
			return 1;
		case 'h':
			print_help();
			return 1;
		default:
			fprintf(stderr, "%s: Try '%s --help' for more"
				" information.\n", toolname, toolname);
			return -1;
		}
	}

	if (opts->duration < 0) {
		fprintf(stderr, "%s: No duration specified\n", toolname);
		return -1;
	}
	if (opts->i_duration < 0)
		opts->i_duration = opts->duration;
	if (opts->i_duration < opts->s_duration) {
		fprintf(stderr, "%s: Sample duration"
			" must be at least the interval duration\n", toolname);
		return -1;
	}
	if (opts->duration < opts->i_duration) {
		fprintf(stderr, "%s: Overall duration"
			" must be at least the interval duration\n", toolname);
		return -1;
	}
	if (opts->s_duration && opts->i_duration % opts->s_duration) {
		fprintf(stderr, "%s: Sample duration"
			" must be a multiple of sample duration\n", toolname);
		return -1;
	}
	if (opts->i_duration && opts->duration % opts->i_duration) {
		fprintf(stderr, "%s: overall duration"
			" must be a multiple of section duration\n", toolname);
		return -1;
	}
	if (opts->num_hosts > 0) {
		qsort(opts->host_nr, opts->num_hosts, sizeof(long),
		      host_adapter_compare);
		for (c = 0; c < opts->num_hosts; ++c) {
			if (check_host_param(&opts->host_nr[c],
					     opts->host_path[c]))
				return -1;
		}
	} else {
		if (find_all_hosts(opts))
			return -1;
	}
	if (opts->msg_q_path || opts->msg_q_id >= 0
		|| opts->msg_id != LONG_MIN) {
		if (!opts->msg_q_path || opts->msg_q_id < 0
			|| opts->msg_id == LONG_MIN) {
			fprintf(stderr, "%s: Make sure to"
				" specify all required arguments for message"
				" queue.\n", toolname);
			return -1;
		}
	}

	verbose_msg("num adapters        : %d\n", opts->num_hosts);
	verbose_msg("num luns            : %d\n", opts->num_luns);
	for (i=0; i<opts->num_luns; ++i)
		verbose_msg("lun                 : %s\n", opts->luns[i]);
	verbose_msg("overall duration	: %lds\n", opts->duration);
	verbose_msg("interval duration	: %lds\n", opts->i_duration);
	verbose_msg("sample duration	: %lds\n", opts->s_duration);

	return 0;
}


static void send_message(int msg_q, void *data, size_t data_sz)
{
	if (msgsnd(msg_q, data, data_sz, 0) < 0) {
		/* somehow we don't get this signal if queue is shut down
		   though we should... */
		if (errno == EIDRM) {
			keep_running = 0;
			verbose_msg("msgqueue removed, shutting down...\n");
		} else {
			fprintf(stderr, "%s: Failed to send"
				" message: %s\n", toolname, strerror(errno));
			verbose_msg("msgsnd() returned error code %d\n",
				    errno);
		}
	}
}


static int has_ioerrs(struct ioerr_data *ioerr)
{
	__u64 i;

	for (i = 0; i < ioerr->num_luns; ++i) {
		if (ioerr->ioerrors[i].num_ioerr != 0)
			return 1;
	}

	return 0;
}


static int has_non_null_stats(struct abbrev_stat *var)
{
	return !(var->max == 0 && var->min == 0);
}


static int has_adapter_traffic(struct utilization_data *res)
{
	int i;
	struct adapter_utilization *a_res;

	for (i=0; i<res->num_adapters; ++i) {
		a_res = &res->adapt_utils[i];
		if (!a_res->valid)
			return 1;
		if (a_res->stats.queue_full != 0)
			return 1;
		/* makes sure that we don't send messages when essentially no
		   data is processed. Reading the 'utilization' sysfs attribute
		   causes traffic, hence we would always send a message without
		   this threshold! */
		if (a_res->stats.queue_util_integral
			/ (double)a_res->stats.queue_util_interval >= 0.05)
			return 1;
		if (has_non_null_stats(&a_res->stats.adapter)
		    || has_non_null_stats(&a_res->stats.bus)
		    || has_non_null_stats(&a_res->stats.cpu))
			return 1;
	}

	return 0;
}


static void print_to_msg_q(struct overall_result_wrp *res_wrp,
			   struct ioerr_wrp *ioerr,
			   struct options *opts,
			   int force)
{
	size_t msg_size;

	if (has_adapter_traffic(&res_wrp->o_res) || force) {
		msg_size = get_result_sz(&res_wrp->o_res);
		if (verbose)
			print_utilization_result(&res_wrp->o_res);
		verbose_msg("write utilization result to msg q %d (msg-type: %ld, msg-size: %d)\n",
				opts->msg_q, res_wrp->mtype, (unsigned int)msg_size);

		conv_overall_result_to_BE(&res_wrp->o_res);

		send_message(opts->msg_q, res_wrp, msg_size);
	}

	if (has_ioerrs(&ioerr->data) || force) {
		msg_size = get_ioerr_data_sz(&ioerr->data);
		if (verbose)
			print_ioerr_data(&ioerr->data);
		verbose_msg("write ioerr result to msg q %d (msg-type: %ld, msg-size: %d)\n",
				opts->msg_q, ioerr->mtype, (unsigned int)msg_size);
		conv_ioerr_data_to_BE(&ioerr->data);
		send_message(opts->msg_q, ioerr, msg_size);
	}
}


static void void_handler(int sig)
{
	verbose_msg("interrupted by signal %u\n", sig);
	keep_running = 0;
}


static void setup_signals(void)
{
	signal(SIGALRM, void_handler);
	signal(SIGINT,  void_handler);
	signal(SIGTERM, void_handler);
	signal(SIGQUIT, void_handler);
}


static void sleep_until(struct timeval *end)
{
	struct timeval tmp;

	do {
		gettimeofday(&tmp, NULL);
		if (timercmp(&tmp, end, >=))
			break;
		timersub(end, &tmp, &tmp);
		verbose_msg("sleep for %ld msec\n", tmp.tv_sec * 1000000
			    + tmp.tv_usec);
		usleep(tmp.tv_sec * 1000000 + tmp.tv_usec);
	} while (keep_running);
}


/*
 * Params:
 * -h : host nr.
 * -t : duration
 */
int main(int argc, char **argv)
{
	struct options 			opts;
	struct adapters        	       *all_adapters = NULL;
	struct overall_result_wrp      *result_wrp = NULL;
	struct timeval			sample_end;
	struct timeval	       		interval_end;
	struct timeval	       		duration_end;
	struct timeval	       		first_interval;
	struct ioerr_wrp	       *ioerr = NULL;
	int				rc = 0;
	int				parse_params_rc;

	verbose = 0;
	keep_running = 1;

	setup_signals();

	init_opts(&opts);

	parse_params_rc = parse_params(argc, argv, &opts);
	if (parse_params_rc < 0) {
		rc = -1;
		goto out2;
	} else if (parse_params_rc > 0) {
		rc = 0;
		goto out2;
	}

	if (opts.msg_q_path && setup_msg_q(&opts)) {
		rc = -2;
		goto out2;
	}

	all_adapters = malloc(sizeof(struct adapters)
			      + opts.num_hosts * sizeof(struct adapter_data));
	if (init_adapters(all_adapters, &opts)) {
		rc = -8;
		goto out2;
	}
	init_result_wrp(&result_wrp, all_adapters->num_adapters, &opts);
	if (init_ioerr_wrp(&ioerr, &opts)) {
		rc = -3;
		goto out;
	}

	gettimeofday(&sample_end, NULL);
	timerclear(&interval_end);
	timerclear(&duration_end);
	timerclear(&first_interval);
	timeradd(&interval_end, &sample_end, &interval_end);
	timeradd(&first_interval, &sample_end, &first_interval);
	first_interval.tv_sec += opts.i_duration;
	timeradd(&duration_end, &sample_end, &duration_end);

	duration_end.tv_sec += opts.duration;
	do {
		interval_end.tv_sec += opts.i_duration;
		reinit_adapters(all_adapters);
		do {
			sample_end.tv_sec += opts.s_duration;
			sleep_until(&sample_end);
			poll_utilization(all_adapters);
			if (timercmp(&sample_end, &interval_end, >=)) {
				/* final sample in interval */
				if (poll_queue_full(0, all_adapters)) {
					rc = -3;
					goto out;
				}
				if (poll_ioerr_cnt(0, &ioerr->data, &opts)) {
					rc = -7;
					goto out;
				}
			}
		} while (keep_running
			 && timercmp(&sample_end, &interval_end, <));

		if (!keep_running)
			break;	/* only publish results after a full cycle */

		generate_result(&result_wrp->o_res, all_adapters);

		if (opts.msg_q >= 0)
			/* Always print the first and the last message */
			print_to_msg_q(result_wrp, ioerr, &opts,
				       (timercmp(&interval_end, &first_interval, ==)
					|| timercmp(&interval_end, &duration_end, >=)));
		else {
			print_utilization_result(&result_wrp->o_res);
			print_ioerr_data(&ioerr->data);
		}
		/* we only have to sleep in case d_interval is not
		   a multiple of i_interval */
		sleep_until(&interval_end);
	} while (keep_running && timercmp(&interval_end, &duration_end, <));

	if (!keep_running)
		verbose_msg("signal received, ending...\n");

out:
	deinit_adapters(all_adapters);
	deinit_result_wrp(&result_wrp);
out2:
	deinit_opts(&opts);
	free(all_adapters);
	free(ioerr);

	return rc;
}

#endif

