Add support for fio bandwith logs (-F logfile)

Signed-off-by: Chris Mason <chris.mason@fusionio.com>
diff --git a/Makefile b/Makefile
index 0e96043..7b5101c 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@
 %.o: %.c
 	$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
-iowatcher: blkparse.o plot.o main.o tracers.o mpstat.o
+iowatcher: blkparse.o plot.o main.o tracers.o mpstat.o fio.o
 	$(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) -lm
 
 depend:
diff --git a/blkparse.h b/blkparse.h
index d435a87..68becbf 100644
--- a/blkparse.h
+++ b/blkparse.h
@@ -57,6 +57,12 @@
 	int mpstat_fd;
 	int mpstat_seconds;
 	int mpstat_num_cpus;
+
+	char *fio_start;
+	char *fio_cur;
+	u64 fio_len;
+	int fio_fd;
+	int fio_seconds;
 	int num_devices;
 	struct dev_info devices[MAX_DEVICES_PER_TRACE];
 };
@@ -81,8 +87,13 @@
 	struct graph_line_data *iop_gld;
 	struct graph_line_data *latency_gld;
 	struct graph_line_data *queue_depth_gld;
+
+	int fio_trace;
+	struct graph_line_data *fio_gld;
+
 	/* Number of entries in gdd_writes / gdd_reads */
 	int io_plots;
+
 	/* Allocated array size for gdd_writes / gdd_reads */
 	int io_plots_allocated;
 	struct graph_dot_data **gdd_writes;
diff --git a/fio.c b/fio.c
new file mode 100644
index 0000000..110f4a1
--- /dev/null
+++ b/fio.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2013 Fusion-io
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU General Public
+ *  License v2 as published by the Free Software Foundation.
+ *
+ *  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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <inttypes.h>
+#include <string.h>
+#include <asm/types.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <math.h>
+
+#include "plot.h"
+#include "blkparse.h"
+#include "list.h"
+#include "tracers.h"
+#include "fio.h"
+
+static int past_eof(struct trace *trace, char *cur)
+{
+	if (cur >= trace->fio_start + trace->fio_len)
+		return 1;
+	return 0;
+}
+
+static int parse_fio_line(struct trace *trace, int *time, int *rate, int *dir, int *bs)
+{
+	char *cur = trace->fio_cur;
+	char *p;
+	int *res[] = { time, rate, dir, bs, NULL };
+	int val;
+	int i = 0;
+	int *t;
+	char *end = index(cur, '\n');
+	char *tmp;
+
+	if (!end)
+		return 1;
+
+	tmp = strndup(cur, end - cur);
+	if (!tmp)
+		return 1;
+	p = strtok(tmp, ",");
+	while (p && *res) {
+		val = atoi(p);
+		t = res[i++];
+		*t = val;
+		p = strtok(NULL, ",");
+	}
+
+	free(tmp);
+
+	if (i < 3)
+		return 1;
+	return 0;
+}
+
+int next_fio_line(struct trace *trace)
+{
+	char *next;
+	char *cur = trace->fio_cur;
+
+	next = strchr(cur, '\n');
+	if (!next)
+		return 1;
+	next++;
+	if (past_eof(trace, next))
+		return 1;
+	trace->fio_cur = next;
+	return 0;
+}
+
+char *first_fio(struct trace *trace)
+{
+	trace->fio_cur = trace->fio_start;
+	return trace->fio_cur;
+}
+
+static void find_last_fio_time(struct trace *trace)
+{
+	double d;
+	int time, rate, dir, bs;
+	int ret;
+	int last_time = 0;
+
+	if (trace->fio_len == 0)
+		return;
+
+	first_fio(trace);
+	while (1) {
+		ret = parse_fio_line(trace, &time, &rate, &dir, &bs);
+		if (ret)
+			break;
+		if (dir <= 1 && time > last_time)
+			last_time = time;
+		ret = next_fio_line(trace);
+		if (ret)
+			break;
+	}
+	d = (double)time / 1000;
+	trace->fio_seconds = ceil(d);
+	return;
+}
+
+int read_fio(struct trace *trace, char *trace_name)
+{
+	int fd;
+	struct stat st;
+	int ret;
+	char *p;
+
+	fd = open(trace_name, O_RDONLY);
+	if (fd < 0)
+		return 1;
+
+	ret = fstat(fd, &st);
+	if (ret < 0) {
+		fprintf(stderr, "stat failed on %s err %s\n",
+			trace_name, strerror(errno));
+		goto fail_fd;
+	}
+	p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+	if (p == MAP_FAILED) {
+		fprintf(stderr, "Unable to mmap trace file %s, err %s\n",
+			trace_name, strerror(errno));
+		goto fail_fd;
+	}
+	trace->fio_start = p;
+	trace->fio_len = st.st_size;
+	trace->fio_cur = p;
+	trace->fio_fd = fd;
+	find_last_fio_time(trace);
+	first_fio(trace);
+	return 0;
+
+fail_fd:
+	close(fd);
+	return 1;
+}
+
+struct trace *open_fio_trace(char *path)
+{
+	int ret;
+	struct trace *trace;
+
+	trace = calloc(1, sizeof(*trace));
+	if (!trace) {
+		fprintf(stderr, "unable to allocate memory for trace\n");
+		exit(1);
+	}
+
+	ret = read_fio(trace, path);
+	if (ret) {
+		free(trace);
+		return NULL;
+	}
+
+	return trace;
+}
+
+int read_fio_event(struct trace *trace, int *time_ret, u64 *bw_ret, int *dir_ret)
+{
+	char *cur = trace->fio_cur;
+	int time, rate, dir, bs;
+	int ret;
+
+	if (past_eof(trace, cur))
+		return 1;
+
+	ret = parse_fio_line(trace, &time, &rate, &dir, &bs);
+	if (ret)
+		return 1;
+
+	time = floor((double)time / 1000);
+	*time_ret = time;
+	*bw_ret = (u64)rate * 1024;
+
+	*dir_ret = dir;
+	return 0;
+}
+
+int add_fio_gld(int time, u64 bw, struct graph_line_data *gld)
+{
+	double val;
+
+	if (time > gld->max_seconds)
+		return 0;
+
+	gld->data[time].sum += bw;
+	gld->data[time].count++;
+
+	val = ((double)gld->data[time].sum) / gld->data[time].count;
+
+	if (val > gld->max)
+		gld->max = ceil(val);
+	return 0;
+
+}
diff --git a/fio.h b/fio.h
new file mode 100644
index 0000000..7f49ddd
--- /dev/null
+++ b/fio.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 Fusion-io
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU General Public
+ *  License v2 as published by the Free Software Foundation.
+ *
+ *  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
+ */
+#ifndef __FIO__
+#define __FIO__
+
+int read_fio_event(struct trace *trace, int *time, u64 *bw, int *dir);
+int add_fio_gld(int time, u64 bw, struct graph_line_data *gld);
+int next_fio_line(struct trace *trace);
+struct trace *open_fio_trace(char *path);
+char *first_fio(struct trace *trace);
+
+#endif
diff --git a/main.c b/main.c
index fbd8a88..827f57d 100644
--- a/main.c
+++ b/main.c
@@ -39,8 +39,10 @@
 #include "list.h"
 #include "tracers.h"
 #include "mpstat.h"
+#include "fio.h"
 
 LIST_HEAD(all_traces);
+LIST_HEAD(fio_traces);
 
 static char line[1024];
 static int line_len = 1024;
@@ -72,6 +74,7 @@
 enum {
 	IO_GRAPH_INDEX = 0,
 	TPUT_GRAPH_INDEX,
+	FIO_GRAPH_INDEX,
 	CPU_SYS_GRAPH_INDEX,
 	CPU_IO_GRAPH_INDEX,
 	CPU_IRQ_GRAPH_INDEX,
@@ -95,6 +98,7 @@
 static char *graphs_by_name[] = {
 	"io",
 	"tput",
+	"fio",
 	"cpu-sys",
 	"cpu-io",
 	"cpu-irq",
@@ -135,6 +139,7 @@
 
 static int label_index = 0;
 static int num_traces = 0;
+static int num_fio_traces = 0;
 static int longest_label = 0;
 
 static char *graph_title = "";
@@ -247,6 +252,23 @@
 	num_traces++;
 }
 
+static void add_fio_trace_file(char *filename)
+{
+	struct trace_file *tf;
+
+	tf = calloc(1, sizeof(*tf));
+	if (!tf) {
+		fprintf(stderr, "Unable to allocate memory\n");
+		exit(1);
+	}
+	tf->label = "";
+	tf->filename = strdup(filename);
+	list_add_tail(&tf->list, &fio_traces);
+	tf->line_color = pick_fio_color();
+	tf->fio_trace = 1;
+	num_fio_traces++;
+}
+
 static void setup_trace_file_graphs(void)
 {
 	struct trace_file *tf;
@@ -257,11 +279,13 @@
 		alloc_ptrs = 16;
 	else
 		alloc_ptrs = 1;
+
 	list_for_each_entry(tf, &all_traces, list) {
 		tf->tput_reads_gld = alloc_line_data(tf->min_seconds, tf->max_seconds, tf->stop_seconds);
 		tf->tput_writes_gld = alloc_line_data(tf->min_seconds, tf->max_seconds, tf->stop_seconds);
 		tf->latency_gld = alloc_line_data(tf->min_seconds, tf->max_seconds, tf->stop_seconds);
 		tf->queue_depth_gld = alloc_line_data(tf->min_seconds, tf->max_seconds, tf->stop_seconds);
+
 		tf->iop_gld = alloc_line_data(tf->min_seconds, tf->max_seconds, tf->stop_seconds);
 		tf->gdd_writes = calloc(alloc_ptrs, sizeof(struct graph_dot_data));
 		tf->gdd_reads = calloc(alloc_ptrs, sizeof(struct graph_dot_data));
@@ -279,6 +303,15 @@
 			tf->mpstat_gld[i]->max = 100;
 		}
 	}
+
+	list_for_each_entry(tf, &fio_traces, list) {
+		if (tf->trace->fio_seconds > 0) {
+			tf->fio_gld = alloc_line_data(tf->min_seconds,
+						      tf->max_seconds,
+						      tf->stop_seconds);
+		}
+	}
+
 }
 
 static void read_traces(void)
@@ -294,6 +327,7 @@
 
 	list_for_each_entry(tf, &all_traces, list) {
 		path = join_path(blktrace_dest_dir, tf->filename);
+
 		trace = open_trace(path);
 		if (!trace)
 			exit(1);
@@ -314,8 +348,18 @@
 		tf->mpstat_max_seconds = trace->mpstat_seconds;
 		if (tf->mpstat_max_seconds)
 			found_mpstat = 1;
+
 		free(path);
 	}
+
+	list_for_each_entry(tf, &fio_traces, list) {
+		trace = open_fio_trace(tf->filename);
+		if (!trace)
+			exit(1);
+		tf->trace = trace;
+		tf->max_seconds = tf->trace->fio_seconds;
+		tf->stop_seconds = tf->trace->fio_seconds;
+	}
 }
 
 static void pick_line_graph_color(void)
@@ -343,6 +387,26 @@
 	}
 }
 
+static void read_fio_events(struct trace_file *tf)
+{
+	u64 bw = 0;
+	int time = 0;
+	int dir = 0;
+	int ret;
+
+	first_fio(tf->trace);
+	while (1) {
+		ret = read_fio_event(tf->trace, &time, &bw, &dir);
+		if (ret)
+			break;
+
+		if (dir <= 1)
+			add_fio_gld(time, bw, tf->fio_gld);
+		if (next_fio_line(tf->trace))
+			break;
+	}
+}
+
 static void read_trace_events(void)
 {
 
@@ -355,8 +419,12 @@
 	double max_user = 0, max_sys = 0, max_iowait = 0,
 	       max_irq = 0, max_soft = 0;
 
+	list_for_each_entry(tf, &fio_traces, list)
+		read_fio_events(tf);
+
 	list_for_each_entry(tf, &all_traces, list) {
 		trace = tf->trace;
+
 		first_record(trace);
 		while (1) {
 			check_record(trace);
@@ -436,6 +504,15 @@
 		if (cur == label_index) {
 			tf->label = strdup(label);
 			label_index++;
+			return;
+			break;
+		}
+		cur++;
+	}
+	list_for_each_entry(tf, &fio_traces, list) {
+		if (cur == label_index) {
+			tf->label = strdup(label);
+			label_index++;
 			break;
 		}
 		cur++;
@@ -468,8 +545,9 @@
 static void set_all_minmax_tf(int min_seconds, int max_seconds, u64 min_offset, u64 max_offset)
 {
 	struct trace_file *tf;
-
-	list_for_each_entry(tf, &all_traces, list) {
+	struct list_head *traces = &all_traces;
+again:
+	list_for_each_entry(tf, traces, list) {
 		tf->min_seconds = min_seconds;
 		tf->max_seconds = max_seconds;
 		if (tf->stop_seconds > max_seconds)
@@ -483,6 +561,10 @@
 		tf->min_offset = min_offset;
 		tf->max_offset = max_offset;
 	}
+	if (traces == &all_traces) {
+		traces = &fio_traces;
+		goto again;
+	}
 }
 
 static char *create_movie_temp_dir(void)
@@ -748,6 +830,57 @@
 	total_graphs_written++;
 }
 
+static void plot_fio_tput(struct plot *plot, int min_seconds, int max_seconds)
+{
+	struct trace_file *tf;
+	char *units;
+	char line[128];
+	u64 max = 0;
+
+	if (num_fio_traces == 0 || active_graphs[FIO_GRAPH_INDEX] == 0)
+		return;
+
+	if (num_fio_traces > 1)
+		svg_alloc_legend(plot, num_fio_traces);
+
+	list_for_each_entry(tf, &fio_traces, list) {
+		if (tf->fio_gld->max > max)
+			max = tf->fio_gld->max;
+	}
+
+	list_for_each_entry(tf, &fio_traces, list) {
+		if (tf->fio_gld->max > 0)
+			tf->fio_gld->max = max;
+	}
+
+	setup_axis(plot);
+	set_plot_label(plot, "Fio Throughput");
+
+	tf = list_entry(all_traces.next, struct trace_file, list);
+
+	scale_line_graph_bytes(&max, &units, 1024);
+	sprintf(line, "%sB/s", units);
+	set_ylabel(plot, line);
+	set_yticks(plot, num_yticks, 0, max, "");
+
+	set_xticks(plot, num_xticks, min_seconds, max_seconds);
+	list_for_each_entry(tf, &fio_traces, list) {
+		if (tf->fio_gld->max > 0) {
+			svg_line_graph(plot, tf->fio_gld, tf->line_color, 0, 0);
+			if (num_fio_traces > 1)
+				svg_add_legend(plot, tf->label, "", tf->line_color);
+		}
+	}
+
+	if (plot->add_xlabel)
+		set_xlabel(plot, "Time (seconds)");
+
+	if (num_fio_traces > 1)
+		svg_write_legend(plot);
+	close_plot(plot);
+	total_graphs_written++;
+}
+
 static void plot_cpu(struct plot *plot, int max_seconds, char *label,
 		     int active_index, int gld_index)
 {
@@ -1170,9 +1303,10 @@
 	HELP_LONG_OPT = 1,
 };
 
-char *option_string = "T:t:o:l:r:O:N:d:D:p:m::h:w:c:x:y:a:C:PK";
+char *option_string = "F:T:t:o:l:r:O:N:d:D:p:m::h:w:c:x:y:a:C:PK";
 static struct option long_options[] = {
 	{"columns", required_argument, 0, 'c'},
+	{"fio-trace", required_argument, 0, 'F'},
 	{"title", required_argument, 0, 'T'},
 	{"trace", required_argument, 0, 't'},
 	{"output", required_argument, 0, 'o'},
@@ -1202,6 +1336,7 @@
 		"\t-d (--device): device for blktrace to trace\n"
 		"\t-D (--blktrace-destination): destination for blktrace\n"
 		"\t-t (--trace): trace file name (more than one allowed)\n"
+		"\t-F (--fio-trace): fio bandwidth trace (more than one allowed)\n"
 		"\t-l (--label): trace label in the graph\n"
 		"\t-o (--output): output file name (SVG only)\n"
 		"\t-p (--prog): program to run while blktrace is run\n"
@@ -1300,6 +1435,9 @@
 			add_trace_file(optarg);
 			set_blktrace_outfile(optarg);
 			break;
+		case 'F':
+			add_fio_trace_file(optarg);
+			break;
 		case 'o':
 			output_filename = strdup(optarg);
 			break;
@@ -1459,7 +1597,7 @@
 	if (opt_graph_width)
 		set_graph_width(opt_graph_width);
 
-	if (list_empty(&all_traces)) {
+	if (list_empty(&all_traces) && list_empty(&fio_traces)) {
 		fprintf(stderr, "No traces found, exiting\n");
 		exit(1);
 	}
@@ -1510,6 +1648,8 @@
 	/* step two, find the maxes for time and offset */
 	list_for_each_entry(tf, &all_traces, list)
 		compare_minmax_tf(tf, &max_seconds, &min_offset, &max_offset);
+	list_for_each_entry(tf, &fio_traces, list)
+		compare_minmax_tf(tf, &max_seconds, &min_offset, &max_offset);
 	min_seconds = min_time;
 	if (max_seconds > max_time)
 		max_seconds = ceil(max_time);
@@ -1541,7 +1681,7 @@
 
 	if (active_graphs[IO_GRAPH_INDEX] || found_mpstat)
 		set_legend_width(longest_label + longest_proc_name + 1 + strlen("writes"));
-	else if (num_traces > 1)
+	else if (num_traces >= 1 || num_fio_traces >= 1)
 		set_legend_width(longest_label);
 	else
 		set_legend_width(0);
@@ -1567,6 +1707,9 @@
 	check_plot_columns(plot, TPUT_GRAPH_INDEX);
 	plot_tput(plot, min_seconds, max_seconds);
 
+	check_plot_columns(plot, FIO_GRAPH_INDEX);
+	plot_fio_tput(plot, min_seconds, max_seconds);
+
 	check_plot_columns(plot, CPU_IO_GRAPH_INDEX);
 	plot_cpu(plot, max_seconds, "CPU IO Wait Time",
 		 CPU_IO_GRAPH_INDEX, MPSTAT_IO);
diff --git a/plot.c b/plot.c
index e5af3ad..187789a 100644
--- a/plot.c
+++ b/plot.c
@@ -96,6 +96,19 @@
 	return ret;
 }
 
+char *pick_fio_color(void)
+{
+	static int fio_color_index;
+	char *ret = colors[fio_color_index];
+
+	if (!ret) {
+		fio_color_index = 0;
+		ret = colors[fio_color_index];
+	}
+	fio_color_index += 2;
+	return ret;
+}
+
 static int cpu_color_index;
 
 char *pick_cpu_color(void)
diff --git a/plot.h b/plot.h
index bc85f45..42cbea0 100644
--- a/plot.h
+++ b/plot.h
@@ -133,6 +133,7 @@
 };
 
 char *pick_color(void);
+char *pick_fio_color(void);
 char *pick_cpu_color(void);
 void reset_cpu_color(void);
 int svg_io_graph(struct plot *plot, struct graph_dot_data *gdd);
@@ -178,4 +179,5 @@
 void rewind_spindle_steps(int num);
 void setup_axis_spindle(struct plot *plot);
 int close_plot_col(struct plot *plot);
+
 #endif