iowatcher: Initial revision
[blktrace.git] / iowatcher / main.c
1 /*
2  * Copyright (C) 2012 Fusion-io
3  *
4  *  This program is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU General Public
6  *  License v2 as published by the Free Software Foundation.
7  *
8  *  This program is distributed in the hope that it will be useful,
9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *  GNU General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License
14  *  along with this program; if not, write to the Free Software
15  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16  *
17  *  Parts of this file were imported from Jens Axboe's blktrace sources (also GPL)
18  */
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <math.h>
26 #include <inttypes.h>
27 #include <string.h>
28 #include <asm/types.h>
29 #include <errno.h>
30 #include <sys/mman.h>
31 #include <time.h>
32 #include <math.h>
33 #include <getopt.h>
34
35 #include "plot.h"
36 #include "blkparse.h"
37 #include "list.h"
38 #include "tracers.h"
39
40 LIST_HEAD(all_traces);
41
42 static int color_index = 0;
43 char *colors[] = {
44         "blue", "darkgreen",
45         "red", "aqua",
46         "orange", "darkviolet",
47         "brown", "#00FF00",
48         "yellow", "coral",
49         "black", "darkred",
50         "fuchsia", "crimson",
51         NULL };
52
53 char *pick_color(void) {
54         char *ret = colors[color_index];
55         if (!ret) {
56                 color_index = 0;
57                 ret = colors[color_index];
58         }
59         color_index++;
60         return ret;
61 }
62
63 char *pick_cpu_color(void) {
64         char *ret = colors[color_index];
65         if (!ret) {
66                 color_index = 0;
67                 ret = colors[color_index];
68         }
69         color_index++;
70         return ret;
71 }
72
73 enum {
74         IO_GRAPH_INDEX = 0,
75         TPUT_GRAPH_INDEX,
76         LATENCY_GRAPH_INDEX,
77         QUEUE_DEPTH_GRAPH_INDEX,
78         IOPS_GRAPH_INDEX,
79         TOTAL_GRAPHS
80 };
81
82 static char *graphs_by_name[] = {
83         "io",
84         "tput",
85         "latency",
86         "queue-depth",
87         "iops",
88 };
89
90 static int active_graphs[TOTAL_GRAPHS];
91 static int last_active_graph = IOPS_GRAPH_INDEX;
92
93 static int label_index = 0;
94 static int num_traces = 0;
95 static int longest_label = 0;
96
97 struct trace_file {
98         struct list_head list;
99         char *filename;
100         char *label;
101         struct trace *trace;
102         int seconds;
103         int stop_seconds;
104         u64 max_offset;
105
106         char *read_color;
107         char *write_color;
108
109         struct graph_line_data *tput_gld;
110         struct graph_line_data *iop_gld;
111         struct graph_line_data *latency_gld;
112         struct graph_line_data *queue_depth_gld;
113         struct graph_dot_data *gdd_writes;
114         struct graph_dot_data *gdd_reads;
115 };
116
117 static void enable_all_graphs(void)
118 {
119         int i;
120         for (i = 0; i < TOTAL_GRAPHS; i++)
121                 active_graphs[i] = 1;
122 }
123
124 static void disable_all_graphs(void)
125 {
126         int i;
127         for (i = 0; i < TOTAL_GRAPHS; i++)
128                 active_graphs[i] = 0;
129 }
130
131 static int enable_one_graph(char *name)
132 {
133         int i;
134         for (i = 0; i < TOTAL_GRAPHS; i++) {
135                 if (strcmp(name, graphs_by_name[i]) == 0) {
136                         active_graphs[i] = 1;
137                         return 0;
138                 }
139         }
140         return -ENOENT;
141 }
142
143 static int disable_one_graph(char *name)
144 {
145         int i;
146         for (i = 0; i < TOTAL_GRAPHS; i++) {
147                 if (strcmp(name, graphs_by_name[i]) == 0) {
148                         active_graphs[i] = 0;
149                         return 0;
150                 }
151         }
152         return -ENOENT;
153 }
154
155 static int last_graph(void)
156 {
157         int i;
158         for (i = TOTAL_GRAPHS - 1; i >= 0; i--) {
159                 if (active_graphs[i]) {
160                         return i;
161                 }
162         }
163         return -ENOENT;
164 }
165
166 static void add_trace_file(char *filename)
167 {
168         struct trace_file *tf;
169
170         tf = calloc(1, sizeof(*tf));
171         if (!tf) {
172                 fprintf(stderr, "Unable to allocate memory\n");
173                 exit(1);
174         }
175         tf->filename = strdup(filename);
176         list_add_tail(&tf->list, &all_traces);
177         tf->read_color = pick_color();
178         tf->write_color = pick_color();
179         num_traces++;
180 }
181
182 static void setup_trace_file_graphs(void)
183 {
184         struct trace_file *tf;
185
186         list_for_each_entry(tf, &all_traces, list) {
187                 tf->tput_gld = alloc_line_data(tf->seconds, tf->stop_seconds);
188                 tf->latency_gld = alloc_line_data(tf->seconds, tf->stop_seconds);
189                 tf->queue_depth_gld = alloc_line_data(tf->seconds, tf->stop_seconds);
190                 tf->iop_gld = alloc_line_data(tf->seconds, tf->stop_seconds);
191                 tf->gdd_writes = alloc_dot_data(tf->seconds, tf->max_offset, tf->stop_seconds);
192                 tf->gdd_reads = alloc_dot_data(tf->seconds, tf->max_offset, tf->stop_seconds);
193         }
194 }
195
196 static void read_traces(void)
197 {
198         struct trace_file *tf;
199         struct trace *trace;
200         u64 last_time;
201         u64 ymin;
202         u64 ymax;
203
204         list_for_each_entry(tf, &all_traces, list) {
205                 trace = open_trace(tf->filename);
206                 if (!trace)
207                         exit(1);
208
209                 last_time = find_last_time(trace);
210                 tf->trace = trace;
211                 tf->seconds = SECONDS(last_time);
212                 tf->stop_seconds = SECONDS(last_time);
213                 tf->max_offset = find_highest_offset(trace);
214
215                 filter_outliers(trace, tf->max_offset, &ymin, &ymax);
216                 tf->max_offset = ymax;
217         }
218 }
219
220 static void read_trace_events(void)
221 {
222
223         struct trace_file *tf;
224         struct trace *trace;
225         int ret;
226
227         list_for_each_entry(tf, &all_traces, list) {
228                 trace = tf->trace;
229                 first_record(trace);
230                 while (1) {
231                         check_record(trace);
232                         add_tput(trace, tf->tput_gld);
233                         add_iop(trace, tf->iop_gld);
234                         add_io(trace, tf->gdd_writes, tf->gdd_reads);
235                         add_pending_io(trace, tf->queue_depth_gld);
236                         add_completed_io(trace, tf->latency_gld);
237                         ret = next_record(trace);
238                         if (ret)
239                                 break;
240                 }
241         }
242 }
243
244 static void set_trace_label(char *label)
245 {
246         int cur = 0;
247         struct trace_file *tf;
248         int len = strlen(label);
249
250         if (len > longest_label)
251                 longest_label = len;
252
253         list_for_each_entry(tf, &all_traces, list) {
254                 if (cur == label_index) {
255                         tf->label = strdup(label);
256                         label_index++;
257                         break;
258                 }
259                 cur++;
260         }
261 }
262
263 static char *graph_title = "";
264 static char *output_filename = "trace.svg";
265 static char *blktrace_device = NULL;
266 static char *blktrace_outfile = "trace";
267 static char *blktrace_dest_dir = ".";
268 static char *program_to_run = NULL;
269
270 static void set_blktrace_outfile(char *arg)
271 {
272         char *s = strdup(arg);
273         char *last_dot = strrchr(s, '.');
274
275         if (last_dot) {
276                 if (strcmp(last_dot, ".dump") == 0)
277                         *last_dot = '\0';
278         }
279         blktrace_outfile = s;
280 }
281
282
283 char *option_string = "hT:t:o:l:r:O:N:d:p:";
284 static struct option long_options[] = {
285         {"title", required_argument, 0, 'T'},
286         {"trace", required_argument, 0, 't'},
287         {"output", required_argument, 0, 'o'},
288         {"label", required_argument, 0, 'l'},
289         {"rolling", required_argument, 0, 'r'},
290         {"no-graph", required_argument, 0, 'N'},
291         {"only-graph", required_argument, 0, 'O'},
292         {"device", required_argument, 0, 'd'},
293         {"prog", required_argument, 0, 'p'},
294         {"help", required_argument, 0, 'h'},
295         {0, 0, 0, 0}
296 };
297
298 static void print_usage(void)
299 {
300         fprintf(stderr, "iowatcher usage:\n"
301                 "\t-d (--device): device for blktrace to trace\n"
302                 "\t-t (--trace): trace file name (more than one allowed)\n"
303                 "\t-l (--label): trace label in the graph\n"
304                 "\t-o (--output): output file name (SVG only)\n"
305                 "\t-p (--prog): program to run while blktrace is run\n"
306                 "\t-r (--rolling): number of seconds in the rolling averge\n"
307                 "\t-T (--title): graph title\n"
308                 "\t-N (--no-graph): skip a single graph (io, tput, latency, queue_depth, iops)\n"
309                 "\t-O (--only-graph): add a single graph (io, tput, latency, queue_depth, iops)\n"
310                );
311         exit(1);
312 }
313
314 static int parse_options(int ac, char **av)
315 {
316         int c;
317         int disabled = 0;
318
319         while (1) {
320                 // int this_option_optind = optind ? optind : 1;
321                 int option_index = 0;
322
323                 c = getopt_long(ac, av, option_string,
324                                 long_options, &option_index);
325
326                 if (c == -1)
327                         break;
328
329                 switch(c) {
330                 case 'h':
331                         print_usage();
332                         break;
333                 case 'T':
334                         graph_title = strdup(optarg);
335                         break;
336                 case 't':
337                         add_trace_file(optarg);
338                         set_blktrace_outfile(optarg);
339                         break;
340                 case 'o':
341                         output_filename = strdup(optarg);
342                         break;
343                 case 'l':
344                         set_trace_label(optarg);
345                         break;
346                 case 'r':
347                         set_rolling_avg(atoi(optarg));
348                         break;
349                 case 'O':
350                         if (!disabled) {
351                                 disable_all_graphs();
352                                 disabled = 1;
353                         }
354                         enable_one_graph(optarg);
355                         break;
356                 case 'N':
357                         disable_one_graph(optarg);
358                         break;
359                 case 'd':
360                         blktrace_device = strdup(optarg);
361                         break;
362                 case 'p':
363                         program_to_run = strdup(optarg);
364                         break;
365                 case '?':
366                         print_usage();
367                         break;
368                 default:
369                         break;
370                 }
371         }
372         return 0;
373 }
374
375 static void compare_max_tf(struct trace_file *tf, int *seconds, u64 *max_offset)
376 {
377         if (tf->seconds > *seconds)
378                 *seconds = tf->seconds;
379         if (tf->max_offset > *max_offset)
380                 *max_offset = tf->max_offset;
381 }
382
383 static void set_all_max_tf(int seconds, u64 max_offset)
384 {
385         struct trace_file *tf;
386
387         list_for_each_entry(tf, &all_traces, list) {
388                 tf->seconds = seconds;
389                 tf->max_offset = max_offset;
390         }
391 }
392
393 static void plot_io(struct plot *plot, int seconds, u64 max_offset)
394 {
395         struct trace_file *tf;
396
397         if (active_graphs[IO_GRAPH_INDEX] == 0)
398                 return;
399
400         plot->add_xlabel = last_active_graph == IO_GRAPH_INDEX;
401         setup_axis(plot);
402
403         svg_alloc_legend(plot, num_traces * 2);
404
405         set_plot_label(plot, "Device IO");
406         set_ylabel(plot, "Offset (MB)");
407         set_yticks(plot, 4, 0, max_offset / (1024 * 1024), "");
408         set_xticks(plot, 9, 0, seconds);
409
410         list_for_each_entry(tf, &all_traces, list) {
411                 char *label = tf->label;
412
413                 if (!label)
414                         label = "";
415                 svg_io_graph(plot, tf->gdd_reads, tf->read_color);
416                 if (tf->gdd_reads->total_ios)
417                         svg_add_legend(plot, label, " Reads", tf->read_color);
418
419                 svg_io_graph(plot, tf->gdd_writes, tf->write_color);
420                 if (tf->gdd_writes->total_ios) {
421                         svg_add_legend(plot, label, " Writes", tf->write_color);
422                 }
423         }
424         if (plot->add_xlabel)
425                 set_xlabel(plot, "Time (seconds)");
426         svg_write_legend(plot);
427         close_plot(plot);
428 }
429
430 static void plot_tput(struct plot *plot, int seconds)
431 {
432         struct trace_file *tf;
433         char *units;
434         char line[128];
435         u64 max = 0;
436
437         if (active_graphs[TPUT_GRAPH_INDEX] == 0)
438                 return;
439
440         if (num_traces > 1)
441                 svg_alloc_legend(plot, num_traces);
442         list_for_each_entry(tf, &all_traces, list) {
443                 if (tf->tput_gld->max > max)
444                         max = tf->tput_gld->max;
445         }
446         list_for_each_entry(tf, &all_traces, list)
447                 tf->tput_gld->max = max;
448
449         plot->add_xlabel = last_active_graph == TPUT_GRAPH_INDEX;
450         setup_axis(plot);
451         set_plot_label(plot, "Throughput");
452
453         tf = list_entry(all_traces.next, struct trace_file, list);
454
455         scale_line_graph_bytes(&max, &units, 1024);
456         sprintf(line, "%sB/s", units);
457         set_ylabel(plot, line);
458         set_yticks(plot, 4, 0, max, "");
459         set_xticks(plot, 9, 0, seconds);
460
461         list_for_each_entry(tf, &all_traces, list) {
462                 svg_line_graph(plot, tf->tput_gld, tf->read_color);
463                 if (num_traces > 1)
464                         svg_add_legend(plot, tf->label, "", tf->read_color);
465         }
466
467         if (plot->add_xlabel)
468                 set_xlabel(plot, "Time (seconds)");
469         if (num_traces > 1)
470                 svg_write_legend(plot);
471         close_plot(plot);
472 }
473
474 static void plot_latency(struct plot *plot, int seconds)
475 {
476         struct trace_file *tf;
477         char *units;
478         char line[128];
479         u64 max = 0;
480
481         if (active_graphs[LATENCY_GRAPH_INDEX] == 0)
482                 return;
483
484         if (num_traces > 1)
485                 svg_alloc_legend(plot, num_traces);
486         list_for_each_entry(tf, &all_traces, list) {
487                 if (tf->latency_gld->max > max)
488                         max = tf->latency_gld->max;
489         }
490         list_for_each_entry(tf, &all_traces, list)
491                 tf->latency_gld->max = max;
492
493         plot->add_xlabel = last_active_graph == TPUT_GRAPH_INDEX;
494         setup_axis(plot);
495         set_plot_label(plot, "IO Latency");
496
497         tf = list_entry(all_traces.next, struct trace_file, list);
498
499         scale_line_graph_time(&max, &units);
500         sprintf(line, "latency (%ss)", units);
501         set_ylabel(plot, line);
502         set_yticks(plot, 4, 0, max, "");
503         set_xticks(plot, 9, 0, seconds);
504
505         list_for_each_entry(tf, &all_traces, list) {
506                 svg_line_graph(plot, tf->latency_gld, tf->read_color);
507                 if (num_traces > 1)
508                         svg_add_legend(plot, tf->label, "", tf->read_color);
509         }
510
511         if (plot->add_xlabel)
512                 set_xlabel(plot, "Time (seconds)");
513         if (num_traces > 1)
514                 svg_write_legend(plot);
515         close_plot(plot);
516 }
517
518 static void plot_queue_depth(struct plot *plot, int seconds)
519 {
520         struct trace_file *tf;
521
522         if (active_graphs[QUEUE_DEPTH_GRAPH_INDEX] == 0)
523                 return;
524
525         plot->add_xlabel = last_active_graph == QUEUE_DEPTH_GRAPH_INDEX;
526
527         setup_axis(plot);
528         set_plot_label(plot, "Queue Depth");
529         if (num_traces > 1)
530                 svg_alloc_legend(plot, num_traces);
531
532         tf = list_entry(all_traces.next, struct trace_file, list);
533         set_ylabel(plot, "Pending IO");
534         set_yticks(plot, 4, 0, tf->queue_depth_gld->max, "");
535         set_xticks(plot, 9, 0, seconds);
536
537         list_for_each_entry(tf, &all_traces, list) {
538                 svg_line_graph(plot, tf->queue_depth_gld, tf->read_color);
539                 if (num_traces > 1)
540                         svg_add_legend(plot, tf->label, "", tf->read_color);
541         }
542
543         if (plot->add_xlabel)
544                 set_xlabel(plot, "Time (seconds)");
545         if (num_traces > 1)
546                 svg_write_legend(plot);
547         close_plot(plot);
548 }
549
550 static void plot_iops(struct plot *plot, int seconds)
551 {
552         struct trace_file *tf;
553         char *units;
554         u64 max = 0;
555
556         if (active_graphs[IOPS_GRAPH_INDEX] == 0)
557                 return;
558
559         list_for_each_entry(tf, &all_traces, list) {
560                 if (tf->iop_gld->max > max)
561                         max = tf->iop_gld->max;
562         }
563
564         list_for_each_entry(tf, &all_traces, list)
565                 tf->iop_gld->max = max;
566
567
568         plot->add_xlabel = last_active_graph == IOPS_GRAPH_INDEX;
569         setup_axis(plot);
570         set_plot_label(plot, "IOPs");
571         if (num_traces > 1)
572                 svg_alloc_legend(plot, num_traces);
573
574         tf = list_entry(all_traces.next, struct trace_file, list);
575
576         scale_line_graph_bytes(&max, &units, 1000);
577         set_ylabel(plot, "IO/s");
578
579         set_yticks(plot, 4, 0, max, units);
580         set_xticks(plot, 9, 0, seconds);
581
582         list_for_each_entry(tf, &all_traces, list) {
583                 svg_line_graph(plot, tf->iop_gld, tf->read_color);
584                 if (num_traces > 1)
585                         svg_add_legend(plot, tf->label, "", tf->read_color);
586         }
587
588         if (plot->add_xlabel)
589                 set_xlabel(plot, "Time (seconds)");
590         if (num_traces > 1)
591                 svg_write_legend(plot);
592
593         close_plot(plot);
594 }
595
596 int main(int ac, char **av)
597 {
598         struct plot *plot;
599         int seconds = 0;
600         u64 max_offset = 0;
601         int fd;
602         struct trace_file *tf;
603         int ret;
604
605         init_io_hash_table();
606
607         enable_all_graphs();
608
609         parse_options(ac, av);
610
611         last_active_graph = last_graph();
612
613         if (list_empty(&all_traces)) {
614                 fprintf(stderr, "No traces found, exiting\n");
615                 exit(1);
616         }
617
618         if (blktrace_device) {
619                 ret = start_blktrace(blktrace_device, blktrace_outfile,
620                                      blktrace_dest_dir);
621                 if (ret) {
622                         fprintf(stderr, "exiting due to blktrace failure\n");
623                         exit(1);
624                 }
625                 if (program_to_run) {
626                         ret = run_program(program_to_run);
627                         if (ret) {
628                                 fprintf(stderr, "failed to run %s\n",
629                                         program_to_run);
630                                 exit(1);
631                         }
632                         wait_for_tracers();
633                         blktrace_to_dump(blktrace_outfile);
634                 } else {
635                         /* no program specified, just wait for
636                          * blktrace to exit
637                          */
638                         wait_for_tracers();
639                 }
640         }
641
642         /* step one, read all the traces */
643         read_traces();
644
645         /* step two, find the maxes for time and offset */
646         list_for_each_entry(tf, &all_traces, list)
647                 compare_max_tf(tf, &seconds, &max_offset);
648
649         /* push the max we found into all the tfs */
650         set_all_max_tf(seconds, max_offset);
651
652         /* alloc graphing structs for all the traces */
653         setup_trace_file_graphs();
654
655         /* run through all the traces and read their events */
656         read_trace_events();
657
658         fd = open(output_filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
659         if (fd < 0) {
660                 fprintf(stderr, "Unable to open output file %s %s\n",
661                         output_filename, strerror(errno));
662                 exit(1);
663         }
664
665         write_svg_header(fd);
666         plot = alloc_plot(fd);
667
668         if (active_graphs[IO_GRAPH_INDEX])
669                 set_legend_width(longest_label + strlen("writes"));
670         else if (num_traces > 1)
671                 set_legend_width(longest_label);
672         else
673                 set_legend_width(0);
674
675         set_plot_title(plot, graph_title);
676
677         plot_io(plot, seconds, max_offset);
678         plot_tput(plot, seconds);
679         plot_latency(plot, seconds);
680         plot_queue_depth(plot, seconds);
681         plot_iops(plot, seconds);
682
683         /* once for all */
684         close_plot(plot);
685         close(fd);
686         return 0;
687 }