iowatcher: Initial revision
[blktrace.git] / iowatcher / main.c
CommitLineData
9e066e23
CM
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
40LIST_HEAD(all_traces);
41
42static int color_index = 0;
43char *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
53char *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
63char *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
73enum {
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
82static char *graphs_by_name[] = {
83 "io",
84 "tput",
85 "latency",
86 "queue-depth",
87 "iops",
88};
89
90static int active_graphs[TOTAL_GRAPHS];
91static int last_active_graph = IOPS_GRAPH_INDEX;
92
93static int label_index = 0;
94static int num_traces = 0;
95static int longest_label = 0;
96
97struct 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
117static void enable_all_graphs(void)
118{
119 int i;
120 for (i = 0; i < TOTAL_GRAPHS; i++)
121 active_graphs[i] = 1;
122}
123
124static void disable_all_graphs(void)
125{
126 int i;
127 for (i = 0; i < TOTAL_GRAPHS; i++)
128 active_graphs[i] = 0;
129}
130
131static 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
143static 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
155static 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
166static 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
182static 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
196static 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
220static 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
244static 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
263static char *graph_title = "";
264static char *output_filename = "trace.svg";
265static char *blktrace_device = NULL;
266static char *blktrace_outfile = "trace";
267static char *blktrace_dest_dir = ".";
268static char *program_to_run = NULL;
269
270static 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
283char *option_string = "hT:t:o:l:r:O:N:d:p:";
284static 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
298static 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
314static 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
375static 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
383static 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
393static 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
430static 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
474static 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
518static 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
550static 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
596int 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}