/*
 * blktrace output analysis: generate a timeline & gather statistics
 *
 * Copyright (C) 2006 Alan D. Brunelle <Alan.Brunelle@hp.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "globals.h"

#define SETBUFFER_SIZE	(64 * 1024)

#define S_OPTS	"aAB:d:D:e:hi:I:l:L:m:M:o:p:P:q:Q:rs:S:t:T:u:VvXz:Z"
static struct option l_opts[] = {
	{
		.name = "seek-absolute",
		.has_arg = no_argument,
		.flag = NULL,
		.val = 'a'
	},
	{
		.name = "all-data",
		.has_arg = no_argument,
		.flag = NULL,
		.val = 'A'
	},
	{
		.name = "dump-blocknos",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'B'
	},
	{
		.name = "range-delta",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'd'
	},
	{
		.name = "devices",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'D'
	},
	{
		.name = "exes",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'e'
	},
	{
		.name = "help",
		.has_arg = no_argument,
		.flag = NULL,
		.val = 'h'
	},
	{
		.name = "input-file",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'i'
	},
	{
		.name = "iostat",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'I'
	},
	{
		.name = "d2c-latencies",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'l'
	},
	{
		.name = "periodic-latencies",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'L'
	},
	{
		.name = "seeks-per-second",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'm'
	},
	{
		.name = "dev-maps",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'M'
	},
	{
		.name = "output-file",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'o'
	},
	{
		.name = "per-io-dump",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'p'
	},
	{
		.name = "per-io-trees",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'P'
	},
	{
		.name = "q2c-latencies",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'q'
	},
	{
		.name = "active-queue-depth",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'Q'
	},
	{
		.name = "no-remaps",
		.has_arg = no_argument,
		.flag = NULL,
		.val = 'r'
	},
	{
		.name = "seeks",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 's'
	},
	{
		.name = "iostat-interval",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'S'
	},
	{
		.name = "time-start",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 't'
	},
	{
		.name = "time-end",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'T'
	},
	{
		.name = "unplug-hist",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'u'
	},
	{
		.name = "version",
		.has_arg = no_argument,
		.flag = NULL,
		.val = 'V'
	},
	{
		.name = "verbose",
		.has_arg = no_argument,
		.flag = NULL,
		.val = 'v'
	},
	{
		.name = "do-active",
		.has_arg = no_argument,
		.flag = NULL,
		.val = 'z'
	},
	{
		.name = "easy-parse-avgs",
		.has_arg = no_argument,
		.flag = NULL,
		.val = 'X'
	},
	{
		.name = "q2d-latencies",
		.has_arg = required_argument,
		.flag = NULL,
		.val = 'z'
	},
	{
		.name = NULL,
	}
};

static char usage_str[] = \
	"\n[ -a               | --seek-absolute ]\n" \
	"[ -A               | --all-data ]\n" \
	"[ -B <output name> | --dump-blocknos=<output name> ]\n" \
	"[ -d <seconds>     | --range-delta=<seconds> ]\n" \
	"[ -D <dev;...>     | --devices=<dev;...> ]\n" \
	"[ -e <exe,...>     | --exes=<exe,...>  ]\n" \
	"[ -h               | --help ]\n" \
	"[ -i <input name>  | --input-file=<input name> ]\n" \
	"[ -I <output name> | --iostat=<output name> ]\n" \
	"[ -l <output name> | --d2c-latencies=<output name> ]\n" \
	"[ -L <freq>        | --periodic-latencies=<freq> ]\n" \
	"[ -m <output name> | --seeks-per-second=<output name> ]\n" \
	"[ -M <dev map>     | --dev-maps=<dev map>\n" \
	"[ -o <output name> | --output-file=<output name> ]\n" \
	"[ -p <output name> | --per-io-dump=<output name> ]\n" \
	"[ -P <output name> | --per-io-trees=<output name> ]\n" \
	"[ -q <output name> | --q2c-latencies=<output name> ]\n" \
	"[ -Q <output name> | --active-queue-depth=<output name> ]\n" \
	"[ -r               | --no-remaps ]\n" \
	"[ -s <output name> | --seeks=<output name> ]\n" \
	"[ -S <interval>    | --iostat-interval=<interval> ]\n" \
	"[ -t <sec>         | --time-start=<sec> ]\n" \
	"[ -T <sec>         | --time-end=<sec> ]\n" \
	"[ -u <output name> | --unplug-hist=<output name> ]\n" \
	"[ -V               | --version ]\n" \
	"[ -v               | --verbose ]\n" \
	"[ -X               | --easy-parse-avgs ]\n" \
	"[ -z <output name> | --q2d-latencies=<output name> ]\n" \
	"[ -Z               | --do-active\n" \
	"\n";

static void usage(char *prog)
{
	fprintf(stderr, "Usage: %s %s", prog, usage_str);
}

static FILE *setup_ofile(char *fname)
{
	if (fname) {
		char *buf;
		FILE *ofp = my_fopen(fname, "w");

		if (!ofp) {
			perror(fname);
			exit(1);
		}

		buf = malloc(SETBUFFER_SIZE);
		setbuffer(ofp, buf, SETBUFFER_SIZE);

		add_file(ofp, fname);
		add_buf(buf);

		return ofp;
	}

	return NULL;
}

static FILE *std_open(char *output_name, char *sfx, char *msg)
{
	FILE *fp;
	char fname[strlen(output_name) + 32];

	sprintf(fname, "%s.%s", output_name, sfx);
	fp = my_fopen(fname, "w");
	if (fp == NULL) {
		perror(fname);
		exit(1);
	}
	if (verbose)
		printf("Sending %s to %s\n", msg, fname);

	return fp;
}

void handle_args(int argc, char *argv[])
{
	int c;

	while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
		switch (c) {
		case 'a':
			seek_absolute = 1;
			break;
		case 'A':
			output_all_data = 1;
			break;
		case 'B':
			bno_dump_name = optarg;
			break;
		case 'd':
			sscanf(optarg, "%lf", &range_delta);
			break;
		case 'D':
			devices = optarg;
			break;
		case 'e':
			exes = optarg;
			break;
		case 'h':
			usage(argv[0]);
			exit(0);
		case 'i':
			input_name = optarg;
			break;
		case 'l':
			d2c_name = optarg;
			break;
		case 'L':
			plat_freq = atof(optarg);
			break;
		case 'I':
			iostat_name = strdup(optarg);
			break;
		case 'm':
			sps_name = optarg;
			break;
		case 'M':
			if (dev_map_read(optarg))
				exit(1);
			break;
		case 'o':
			output_name = optarg;
			break;
		case 'p':
			per_io_name = strdup(optarg);
			break;
		case 'P':
			per_io_trees = optarg;
			break;
		case 'q':
			q2c_name = optarg;
			break;
		case 'Q':
			aqd_name = optarg;
			break;
		case 'r':
			ignore_remaps = 1;
			break;
		case 's':
			seek_name = optarg;
			break;
		case 'S': {
			unsigned int interval;

			sscanf(optarg, "%u", &interval);
			iostat_interval = (__u64)interval * 1000000000LL;
			break;
		}
		case 't':
			sscanf(optarg, "%lf", &t_astart);
			time_bounded = 1;
			break;
		case 'T':
			sscanf(optarg, "%lf", &t_aend);
			time_bounded = 1;
			break;
		case 'u':
			unplug_hist_name = optarg;
			break;
		case 'v':
			verbose = 1;
			break;
		case 'V':
			printf("%s version %s\n", argv[0], bt_timeline_version);
			exit(0);
		case 'X':
			easy_parse_avgs++;
			break;
		case 'z':
			q2d_name = optarg;
			break;
		case 'Z':
			do_p_live = 1;
			break;
		default:
			usage(argv[0]);
			exit(1);
		}
	}

	if (input_name == NULL) {
		usage(argv[0]);
		exit(1);
	}

	if (sps_name && !seek_name) {
		fprintf(stderr, "FATAL: -m option requires -s options\n");
		exit(1);
	}

	setup_ifile(input_name);

	if (output_name == NULL) {
		rngs_ofp = avgs_ofp = msgs_ofp = stdout;
		easy_parse_avgs = 0;
	} else {
		rngs_ofp = std_open(output_name, "dat", "range data");
		avgs_ofp = std_open(output_name, "avg", "stats data");
		msgs_ofp = std_open(output_name, "msg", "K messages");
		if (easy_parse_avgs) {
			xavgs_ofp = std_open(output_name, "xvg",
					     "EZ stats data");
		}
	}

	iostat_ofp = setup_ofile(iostat_name);
	per_io_ofp = setup_ofile(per_io_name);
}
