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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 34 #include "plot.h" 35 36 static int io_graph_scale = 8; 37 static int graph_width = 700; 38 static int graph_height = 250; 39 static int graph_circle_extra = 30; 40 static int graph_inner_x_margin = 2; 41 static int graph_inner_y_margin = 2; 42 static int graph_tick_len = 5; 43 static int graph_left_pad = 120; 44 static int tick_label_pad = 16; 45 static int tick_font_size = 15; 46 static char *font_family = "sans-serif"; 47 48 /* this is the title for the whole page */ 49 static int plot_title_height = 50; 50 static int plot_title_font_size = 25; 51 52 /* this is the label at the top of each plot */ 53 static int plot_label_height = 60; 54 static int plot_label_font_size = 20; 55 56 /* label for each axis is slightly smaller */ 57 static int axis_label_font_size = 16; 58 59 int legend_x_off = 45; 60 int legend_y_off = -10; 61 int legend_font_size = 15; 62 int legend_width = 80; 63 64 static int rolling_avg_secs = 0; 65 66 static int line_len = 1024; 67 static char line[1024]; 68 69 static int final_height = 0; 70 static int final_width = 0; 71 72 static char *colors[] = { 73 "blue", "darkgreen", 74 "red", 75 "darkviolet", 76 "orange", 77 "aqua", 78 "brown", "#00FF00", 79 "yellow", "coral", 80 "black", "darkred", 81 "fuchsia", "crimson", 82 NULL }; 83 84 extern unsigned int longest_proc_name; 85 86 char *pick_color(void) 87 { 88 static int color_index; 89 char *ret = colors[color_index]; 90 91 if (!ret) { 92 color_index = 0; 93 ret = colors[color_index]; 94 } 95 color_index++; 96 return ret; 97 } 98 99 char *pick_fio_color(void) 100 { 101 static int fio_color_index; 102 char *ret = colors[fio_color_index]; 103 104 if (!ret) { 105 fio_color_index = 0; 106 ret = colors[fio_color_index]; 107 } 108 fio_color_index += 2; 109 return ret; 110 } 111 112 static int cpu_color_index; 113 114 char *pick_cpu_color(void) 115 { 116 char *ret = colors[cpu_color_index]; 117 if (!ret) { 118 cpu_color_index = 0; 119 ret = colors[cpu_color_index]; 120 } 121 cpu_color_index++; 122 return ret; 123 } 124 125 void reset_cpu_color(void) 126 { 127 cpu_color_index = 0; 128 } 129 130 struct graph_line_data *alloc_line_data(unsigned int min_seconds, 131 unsigned int max_seconds, 132 unsigned int stop_seconds) 133 { 134 int size = sizeof(struct graph_line_data) + (stop_seconds + 1) * sizeof(struct graph_line_pair); 135 struct graph_line_data *gld; 136 137 gld = calloc(1, size); 138 if (!gld) { 139 fprintf(stderr, "Unable to allocate memory for graph data\n"); 140 exit(1); 141 } 142 gld->min_seconds = min_seconds; 143 gld->max_seconds = max_seconds; 144 gld->stop_seconds = stop_seconds; 145 return gld; 146 } 147 148 struct graph_dot_data *alloc_dot_data(unsigned int min_seconds, 149 unsigned int max_seconds, 150 u64 min_offset, u64 max_offset, 151 unsigned int stop_seconds, 152 char *color, char *label) 153 { 154 int size; 155 int arr_size; 156 int rows = graph_height * io_graph_scale; 157 int cols = graph_width; 158 struct graph_dot_data *gdd; 159 160 size = sizeof(struct graph_dot_data); 161 162 /* the number of bits */ 163 arr_size = (rows + 1) * cols; 164 165 /* the number of bytes */ 166 arr_size = (arr_size + 7) / 8; 167 168 gdd = calloc(1, size + arr_size); 169 if (!gdd) { 170 fprintf(stderr, "Unable to allocate memory for graph data\n"); 171 exit(1); 172 } 173 gdd->min_seconds = min_seconds; 174 gdd->max_seconds = max_seconds; 175 gdd->stop_seconds = stop_seconds; 176 gdd->rows = rows; 177 gdd->cols = cols; 178 gdd->min_offset = min_offset; 179 gdd->max_offset = max_offset; 180 gdd->color = color; 181 gdd->label = label; 182 183 if (strlen(label) > longest_proc_name) 184 longest_proc_name = strlen(label); 185 186 return gdd; 187 } 188 189 void set_gdd_bit(struct graph_dot_data *gdd, u64 offset, double bytes, double time) 190 { 191 double bytes_per_row = (double)(gdd->max_offset - gdd->min_offset + 1) / gdd->rows; 192 double secs_per_col = (double)(gdd->max_seconds - gdd->min_seconds) / gdd->cols; 193 double col; 194 double row; 195 int col_int; 196 int row_int; 197 int bit_index; 198 int arr_index; 199 int bit_mod; 200 double mod = bytes_per_row; 201 202 if (offset > gdd->max_offset || offset < gdd->min_offset) 203 return; 204 time = time / 1000000000.0; 205 if (time < gdd->min_seconds || time > gdd->max_seconds) 206 return; 207 gdd->total_ios++; 208 while (bytes > 0 && offset <= gdd->max_offset) { 209 row = (double)(offset - gdd->min_offset) / bytes_per_row; 210 col = (time - gdd->min_seconds) / secs_per_col; 211 212 col_int = floor(col); 213 row_int = floor(row); 214 bit_index = row_int * gdd->cols + col_int; 215 arr_index = bit_index / 8; 216 bit_mod = bit_index % 8; 217 218 gdd->data[arr_index] |= 1 << bit_mod; 219 offset += mod; 220 bytes -= mod; 221 } 222 } 223 224 static double rolling_avg(struct graph_line_pair *data, int index, int distance) 225 { 226 double sum = 0; 227 int start; 228 229 if (distance < 0) 230 distance = 1; 231 if (distance > index) { 232 start = 0; 233 } else { 234 start = index - distance; 235 } 236 distance = 0; 237 while (start <= index) { 238 double avg; 239 240 if (data[start].count) 241 avg = ((double)data[start].sum) / data[start].count; 242 else 243 avg= 0; 244 245 sum += avg; 246 distance++; 247 start++; 248 } 249 return sum / distance; 250 } 251 252 static void write_check(int fd, char *buf, size_t size) 253 { 254 ssize_t ret; 255 256 ret = write(fd, buf, size); 257 if (ret != (ssize_t)size) { 258 if (ret < 0) 259 perror("write failed"); 260 else 261 fprintf(stderr, "error: short write\n"); 262 exit(1); 263 } 264 } 265 266 void write_svg_header(int fd) 267 { 268 char *spaces = " \n"; 269 char *header = "<svg xmlns=\"http://www.w3.org/2000/svg\">\n"; 270 char *filter1 ="<filter id=\"shadow\">\n " 271 "<feOffset result=\"offOut\" in=\"SourceAlpha\" dx=\"4\" dy=\"4\" />\n " 272 "<feGaussianBlur result=\"blurOut\" in=\"offOut\" stdDeviation=\"2\" />\n " 273 "<feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n " 274 "</filter>\n"; 275 char *filter2 ="<filter id=\"textshadow\" x=\"0\" y=\"0\" width=\"200%\" height=\"200%\">\n " 276 "<feOffset result=\"offOut\" in=\"SourceAlpha\" dx=\"1\" dy=\"1\" />\n " 277 "<feGaussianBlur result=\"blurOut\" in=\"offOut\" stdDeviation=\"1.5\" />\n " 278 "<feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n " 279 "</filter>\n"; 280 char *filter3 ="<filter id=\"labelshadow\" x=\"0\" y=\"0\" width=\"200%\" height=\"200%\">\n " 281 "<feOffset result=\"offOut\" in=\"SourceGraphic\" dx=\"3\" dy=\"3\" />\n " 282 "<feColorMatrix result=\"matrixOut\" in=\"offOut\" type=\"matrix\" " 283 "values=\"0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.2 0 0 0 0 0 1 0\" /> " 284 "<feGaussianBlur result=\"blurOut\" in=\"offOut\" stdDeviation=\"2\" />\n " 285 "<feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n " 286 "</filter>\n"; 287 char *defs_start = "<defs>\n"; 288 char *defs_close = "</defs>\n"; 289 final_width = 0; 290 final_height = 0; 291 292 write_check(fd, header, strlen(header)); 293 /* write a bunch of spaces so we can stuff in the width and height later */ 294 write_check(fd, spaces, strlen(spaces)); 295 write_check(fd, spaces, strlen(spaces)); 296 write_check(fd, spaces, strlen(spaces)); 297 298 write_check(fd, defs_start, strlen(defs_start)); 299 write_check(fd, filter1, strlen(filter1)); 300 write_check(fd, filter2, strlen(filter2)); 301 write_check(fd, filter3, strlen(filter3)); 302 write_check(fd, defs_close, strlen(defs_close)); 303 } 304 305 /* svg y offset for the traditional 0,0 (bottom left corner) of the plot */ 306 static int axis_y(void) 307 { 308 return plot_label_height + graph_height + graph_inner_y_margin; 309 } 310 311 /* this gives you the correct pixel for a given offset from the bottom left y axis */ 312 static double axis_y_off_double(double y) 313 { 314 return plot_label_height + graph_height - y; 315 } 316 317 static int axis_y_off(int y) 318 { 319 return axis_y_off_double(y); 320 } 321 322 /* svg x axis offset from 0 */ 323 static int axis_x(void) 324 { 325 return graph_left_pad; 326 } 327 328 /* the correct pixel for a given X offset */ 329 static double axis_x_off_double(double x) 330 { 331 return graph_left_pad + graph_inner_x_margin + x; 332 } 333 334 static int axis_x_off(int x) 335 { 336 return (int)axis_x_off_double(x); 337 } 338 339 /* 340 * this draws a backing rectangle for the plot and it 341 * also creates a new svg element so our offsets can 342 * be relative to this one plot. 343 */ 344 void setup_axis(struct plot *plot) 345 { 346 int len; 347 int fd = plot->fd; 348 int bump_height = tick_font_size * 3 + axis_label_font_size; 349 int local_legend_width = legend_width; 350 351 if (plot->no_legend) 352 local_legend_width = 0; 353 354 plot->total_width = axis_x_off(graph_width) + graph_left_pad / 2 + local_legend_width; 355 plot->total_height = axis_y() + tick_label_pad + tick_font_size; 356 357 if (plot->add_xlabel) 358 plot->total_height += bump_height; 359 360 /* backing rect */ 361 snprintf(line, line_len, "<rect x=\"%d\" y=\"%d\" width=\"%d\" " 362 "height=\"%d\" fill=\"white\" stroke=\"none\"/>", 363 plot->start_x_offset, 364 plot->start_y_offset, plot->total_width + 40, 365 plot->total_height + 20); 366 len = strlen(line); 367 write_check(fd, line, len); 368 369 snprintf(line, line_len, "<rect x=\"%d\" y=\"%d\" width=\"%d\" " 370 "filter=\"url(#shadow)\" " 371 "height=\"%d\" fill=\"white\" stroke=\"none\"/>", 372 plot->start_x_offset + 15, 373 plot->start_y_offset, plot->total_width, plot->total_height); 374 len = strlen(line); 375 write_check(fd, line, len); 376 plot->total_height += 20; 377 plot->total_width += 20; 378 379 if (plot->total_height + plot->start_y_offset > final_height) 380 final_height = plot->total_height + plot->start_y_offset; 381 if (plot->start_x_offset + plot->total_width + 40 > final_width) 382 final_width = plot->start_x_offset + plot->total_width + 40; 383 384 /* create an svg object for all our coords to be relative against */ 385 snprintf(line, line_len, "<svg x=\"%d\" y=\"%d\">\n", plot->start_x_offset, plot->start_y_offset); 386 write_check(fd, line, strlen(line)); 387 388 snprintf(line, 1024, "<path d=\"M%d %d h %d V %d H %d Z\" stroke=\"black\" stroke-width=\"2\" fill=\"none\"/>\n", 389 axis_x(), axis_y(), 390 graph_width + graph_inner_x_margin * 2, axis_y_off(graph_height) - graph_inner_y_margin, 391 axis_x()); 392 len = strlen(line); 393 write_check(fd, line, len); 394 } 395 396 /* 397 * this draws a backing rectangle for the plot and it 398 * also creates a new svg element so our offsets can 399 * be relative to this one plot. 400 */ 401 void setup_axis_spindle(struct plot *plot) 402 { 403 int len; 404 int fd = plot->fd; 405 int bump_height = tick_font_size * 3 + axis_label_font_size; 406 407 legend_x_off = -60; 408 409 plot->total_width = axis_x_off(graph_width) + legend_width; 410 plot->total_height = axis_y() + tick_label_pad + tick_font_size; 411 412 if (plot->add_xlabel) 413 plot->total_height += bump_height; 414 415 /* backing rect */ 416 snprintf(line, line_len, "<rect x=\"%d\" y=\"%d\" width=\"%d\" " 417 "height=\"%d\" fill=\"white\" stroke=\"none\"/>", 418 plot->start_x_offset, 419 plot->start_y_offset, plot->total_width + 10, 420 plot->total_height + 20); 421 len = strlen(line); 422 write_check(fd, line, len); 423 424 snprintf(line, line_len, "<rect x=\"%d\" y=\"%d\" width=\"%d\" " 425 "filter=\"url(#shadow)\" " 426 "height=\"%d\" fill=\"white\" stroke=\"none\"/>", 427 plot->start_x_offset + 15, 428 plot->start_y_offset, plot->total_width - 30, 429 plot->total_height); 430 len = strlen(line); 431 write_check(fd, line, len); 432 plot->total_height += 20; 433 434 if (plot->total_height + plot->start_y_offset > final_height) 435 final_height = plot->total_height + plot->start_y_offset; 436 if (plot->start_x_offset + plot->total_width + 40 > final_width) 437 final_width = plot->start_x_offset + plot->total_width + 40; 438 439 /* create an svg object for all our coords to be relative against */ 440 snprintf(line, line_len, "<svg x=\"%d\" y=\"%d\">\n", plot->start_x_offset, plot->start_y_offset); 441 write_check(fd, line, strlen(line)); 442 443 } 444 445 /* draw a plot title. This should be done only once, 446 * and it bumps the plot width/height numbers by 447 * what it draws. 448 * 449 * Call this before setting up the first axis 450 */ 451 void set_plot_title(struct plot *plot, char *title) 452 { 453 int len; 454 int fd = plot->fd; 455 456 plot->total_height = plot_title_height; 457 plot->total_width = axis_x_off(graph_width) + graph_left_pad / 2 + legend_width; 458 459 /* backing rect */ 460 snprintf(line, line_len, "<rect x=\"0\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"white\" stroke=\"none\"/>", 461 plot->start_y_offset, plot->total_width + 40, plot_title_height + 20); 462 len = strlen(line); 463 write_check(fd, line, len); 464 465 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" " 466 "font-weight=\"bold\" fill=\"black\" style=\"text-anchor: %s\">%s</text>\n", 467 axis_x_off(graph_width / 2), 468 plot->start_y_offset + plot_title_height / 2, 469 font_family, plot_title_font_size, "middle", title); 470 plot->start_y_offset += plot_title_height; 471 len = strlen(line); 472 write_check(fd, line, len); 473 } 474 475 #define TICK_MINI_STEPS 3 476 477 static double find_step(double first, double last, int num_ticks) 478 { 479 int mini_step[TICK_MINI_STEPS] = { 1, 2, 5 }; 480 int cur_mini_step = 0; 481 double step = (last - first) / num_ticks; 482 double log10 = log(10); 483 484 /* Round to power of 10 */ 485 step = exp(floor(log(step) / log10) * log10); 486 /* Scale down step to provide enough ticks */ 487 while (cur_mini_step < TICK_MINI_STEPS 488 && (last - first) / (step * mini_step[cur_mini_step]) > num_ticks) 489 cur_mini_step++; 490 491 if (cur_mini_step > 0) 492 step *= mini_step[cur_mini_step - 1]; 493 494 return step; 495 } 496 497 /* 498 * create evenly spread out ticks along the xaxis. if tick only is set 499 * this just makes the ticks, otherwise it labels each tick as it goes 500 */ 501 void set_xticks(struct plot *plot, int num_ticks, int first, int last) 502 { 503 int pixels_per_tick; 504 double step; 505 int i; 506 int tick_y = axis_y_off(graph_tick_len) + graph_inner_y_margin; 507 int tick_x = axis_x(); 508 int tick_only = plot->add_xlabel == 0; 509 510 int text_y = axis_y() + tick_label_pad; 511 512 char *middle = "middle"; 513 char *start = "start"; 514 515 step = find_step(first, last, num_ticks); 516 /* 517 * We don't want last two ticks to be too close together so subtract 518 * 20% of the step from the interval 519 */ 520 num_ticks = (double)(last - first - step) / step + 1; 521 pixels_per_tick = graph_width * step / (double)(last - first); 522 523 for (i = 0; i < num_ticks; i++) { 524 char *anchor; 525 if (i != 0) { 526 snprintf(line, line_len, "<rect x=\"%d\" y=\"%d\" width=\"2\" height=\"%d\" style=\"stroke:none;fill:black;\"/>\n", 527 tick_x, tick_y, graph_tick_len); 528 write_check(plot->fd, line, strlen(line)); 529 anchor = middle; 530 } else { 531 anchor = start; 532 } 533 534 if (!tick_only) { 535 if (step >= 1) 536 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" " 537 "fill=\"black\" style=\"text-anchor: %s\">%d</text>\n", 538 tick_x, text_y, font_family, tick_font_size, anchor, 539 (int)(first + step * i)); 540 else 541 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" " 542 "fill=\"black\" style=\"text-anchor: %s\">%.2f</text>\n", 543 tick_x, text_y, font_family, tick_font_size, anchor, 544 first + step * i); 545 write_check(plot->fd, line, strlen(line)); 546 } 547 tick_x += pixels_per_tick; 548 } 549 550 if (!tick_only) { 551 if (step >= 1) 552 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" " 553 "fill=\"black\" style=\"text-anchor: middle\">%d</text>\n", 554 axis_x_off(graph_width - 2), 555 text_y, font_family, tick_font_size, last); 556 else 557 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" " 558 "fill=\"black\" style=\"text-anchor: middle\">%.2f</text>\n", 559 axis_x_off(graph_width - 2), 560 text_y, font_family, tick_font_size, (double)last); 561 write_check(plot->fd, line, strlen(line)); 562 } 563 } 564 565 void set_ylabel(struct plot *plot, char *label) 566 { 567 int len; 568 int fd = plot->fd; 569 570 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" " 571 "transform=\"rotate(-90 %d %d)\" font-weight=\"bold\" " 572 "font-size=\"%d\" fill=\"black\" style=\"text-anchor: %s\">%s</text>\n", 573 graph_left_pad / 2 - axis_label_font_size, 574 axis_y_off(graph_height / 2), 575 font_family, 576 graph_left_pad / 2 - axis_label_font_size, 577 (int)axis_y_off(graph_height / 2), 578 axis_label_font_size, "middle", label); 579 len = strlen(line); 580 write_check(fd, line, len); 581 } 582 583 void set_xlabel(struct plot *plot, char *label) 584 { 585 int len; 586 int fd = plot->fd; 587 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" " 588 "font-weight=\"bold\" " 589 "font-size=\"%d\" fill=\"black\" style=\"text-anchor: %s\">%s</text>\n", 590 axis_x_off(graph_width / 2), 591 axis_y() + tick_font_size * 3 + axis_label_font_size / 2, 592 font_family, 593 axis_label_font_size, "middle", label); 594 len = strlen(line); 595 write_check(fd, line, len); 596 597 } 598 599 /* 600 * create evenly spread out ticks along the y axis. 601 * The ticks are labeled as it goes 602 */ 603 void set_yticks(struct plot *plot, int num_ticks, int first, int last, char *units) 604 { 605 int pixels_per_tick = graph_height / num_ticks; 606 int step = (last - first) / num_ticks; 607 int i; 608 int tick_y = 0; 609 int text_x = axis_x() - 6; 610 int tick_x = axis_x(); 611 char *anchor = "end"; 612 613 for (i = 0; i < num_ticks; i++) { 614 if (i != 0) { 615 snprintf(line, line_len, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" " 616 "style=\"stroke:lightgray;stroke-width:2;stroke-dasharray:9,12;\"/>\n", 617 tick_x, axis_y_off(tick_y), 618 axis_x_off(graph_width), axis_y_off(tick_y)); 619 write_check(plot->fd, line, strlen(line)); 620 } 621 622 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" " 623 "fill=\"black\" style=\"text-anchor: %s\">%d%s</text>\n", 624 text_x, 625 axis_y_off(tick_y - tick_font_size / 2), 626 font_family, tick_font_size, anchor, first + step * i, units); 627 write_check(plot->fd, line, strlen(line)); 628 tick_y += pixels_per_tick; 629 } 630 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" " 631 "fill=\"black\" style=\"text-anchor: %s\">%d%s</text>\n", 632 text_x, axis_y_off(graph_height), font_family, tick_font_size, anchor, last, units); 633 write_check(plot->fd, line, strlen(line)); 634 } 635 636 void set_plot_label(struct plot *plot, char *label) 637 { 638 int len; 639 int fd = plot->fd; 640 641 snprintf(line, line_len, "<text x=\"%d\" y=\"%d\" font-family=\"%s\" " 642 "font-size=\"%d\" fill=\"black\" style=\"text-anchor: %s\">%s</text>\n", 643 axis_x() + graph_width / 2, 644 plot_label_height / 2, 645 font_family, plot_label_font_size, "middle", label); 646 len = strlen(line); 647 write_check(fd, line, len); 648 } 649 650 static void close_svg(int fd) 651 { 652 char *close_line = "</svg>\n"; 653 654 write_check(fd, close_line, strlen(close_line)); 655 } 656 657 int close_plot(struct plot *plot) 658 { 659 close_svg(plot->fd); 660 if (plot->direction == PLOT_DOWN) 661 plot->start_y_offset += plot->total_height; 662 else if (plot->direction == PLOT_ACROSS) 663 plot->start_x_offset += plot->total_width; 664 return 0; 665 } 666 667 struct plot *alloc_plot(void) 668 { 669 struct plot *plot; 670 plot = calloc(1, sizeof(*plot)); 671 if (!plot) { 672 fprintf(stderr, "Unable to allocate memory %s\n", strerror(errno)); 673 exit(1); 674 } 675 plot->fd = 0; 676 return plot; 677 } 678 679 int close_plot_file(struct plot *plot) 680 { 681 int ret; 682 ret = lseek(plot->fd, 0, SEEK_SET); 683 if (ret == (off_t)-1) { 684 perror("seek"); 685 exit(1); 686 } 687 final_width = ((final_width + 1) / 2) * 2; 688 final_height = ((final_height + 1) / 2) * 2; 689 snprintf(line, line_len, "<svg xmlns=\"http://www.w3.org/2000/svg\" " 690 "width=\"%d\" height=\"%d\">\n", 691 final_width, final_height); 692 write_check(plot->fd, line, strlen(line)); 693 snprintf(line, line_len, "<rect x=\"0\" y=\"0\" width=\"%d\" " 694 "height=\"%d\" fill=\"white\"/>\n", final_width, final_height); 695 write_check(plot->fd, line, strlen(line)); 696 close(plot->fd); 697 plot->fd = 0; 698 return 0; 699 } 700 701 void set_plot_output(struct plot *plot, char *filename) 702 { 703 int fd; 704 705 if (plot->fd) 706 close_plot_file(plot); 707 fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); 708 if (fd < 0) { 709 fprintf(stderr, "Unable to open output file %s err %s\n", filename, strerror(errno)); 710 exit(1); 711 } 712 plot->fd = fd; 713 plot->start_y_offset = plot->start_x_offset = 0; 714 write_svg_header(fd); 715 } 716 717 char *byte_unit_names[] = { "", "K", "M", "G", "T", "P", "E", "Z", "Y", "unobtainium" }; 718 int MAX_BYTE_UNIT_SCALE = 9; 719 720 char *time_unit_names[] = { "n", "u", "m", "s" }; 721 int MAX_TIME_UNIT_SCALE = 3; 722 723 void scale_line_graph_bytes(u64 *max, char **units, u64 factor) 724 { 725 int scale = 0; 726 u64 val = *max; 727 u64 div = 1; 728 while (val > factor * 64) { 729 val /= factor; 730 scale++; 731 div *= factor; 732 } 733 *units = byte_unit_names[scale]; 734 if (scale == 0) 735 return; 736 737 if (scale > MAX_BYTE_UNIT_SCALE) 738 scale = MAX_BYTE_UNIT_SCALE; 739 740 *max /= div; 741 } 742 743 void scale_line_graph_time(u64 *max, char **units) 744 { 745 int scale = 0; 746 u64 val = *max; 747 u64 div = 1; 748 while (val > 1000 * 10) { 749 val /= 1000; 750 scale++; 751 div *= 1000; 752 if (scale == MAX_TIME_UNIT_SCALE) 753 break; 754 } 755 *units = time_unit_names[scale]; 756 if (scale == 0) 757 return; 758 759 *max /= div; 760 } 761 762 static int rolling_span(struct graph_line_data *gld) 763 { 764 if (rolling_avg_secs) 765 return rolling_avg_secs; 766 return (gld->stop_seconds - gld->min_seconds) / 25; 767 } 768 769 770 double line_graph_roll_avg_max(struct graph_line_data *gld) 771 { 772 unsigned int i; 773 int rolling; 774 double avg, max = 0; 775 776 rolling = rolling_span(gld); 777 for (i = gld->min_seconds; i < gld->stop_seconds; i++) { 778 avg = rolling_avg(gld->data, i, rolling); 779 if (avg > max) 780 max = avg; 781 } 782 return max; 783 } 784 785 int svg_line_graph(struct plot *plot, struct graph_line_data *gld, char *color, int thresh1, int thresh2) 786 { 787 unsigned int i; 788 double val; 789 double avg; 790 int rolling; 791 int fd = plot->fd; 792 char *start = "<path d=\""; 793 double yscale = ((double)gld->max) / graph_height; 794 double xscale = (double)(gld->max_seconds - gld->min_seconds - 1) / graph_width; 795 char c = 'M'; 796 double x; 797 int printed_header = 0; 798 int printed_lines = 0; 799 800 if (thresh1 && thresh2) 801 rolling = 0; 802 else 803 rolling = rolling_span(gld); 804 805 for (i = gld->min_seconds; i < gld->stop_seconds; i++) { 806 avg = rolling_avg(gld->data, i, rolling); 807 if (yscale == 0) 808 val = 0; 809 else 810 val = avg / yscale; 811 812 if (val > graph_height) 813 val = graph_height; 814 if (val < 0) 815 val = 0; 816 817 x = (double)(i - gld->min_seconds) / xscale; 818 if (!thresh1 && !thresh2) { 819 if (!printed_header) { 820 write_check(fd, start, strlen(start)); 821 printed_header = 1; 822 } 823 824 /* in full line mode, everything in the graph is connected */ 825 snprintf(line, line_len, "%c %d %d ", c, axis_x_off(x), axis_y_off(val)); 826 c = 'L'; 827 write_check(fd, line, strlen(line)); 828 printed_lines = 1; 829 } else if (avg > thresh1 || avg > thresh2) { 830 int len = 10; 831 if (!printed_header) { 832 write_check(fd, start, strlen(start)); 833 printed_header = 1; 834 } 835 836 /* otherwise, we just print a bar up there to show this one data point */ 837 if (i >= gld->stop_seconds - 2) 838 len = -10; 839 840 /* 841 * we don't use the rolling averages here to show high 842 * points in the data 843 */ 844 snprintf(line, line_len, "M %d %d h %d ", axis_x_off(x), 845 axis_y_off(val), len); 846 write_check(fd, line, strlen(line)); 847 printed_lines = 1; 848 } 849 850 } 851 if (printed_lines) { 852 snprintf(line, line_len, "\" fill=\"none\" stroke=\"%s\" stroke-width=\"2\"/>\n", color); 853 write_check(fd, line, strlen(line)); 854 } 855 if (plot->timeline) 856 svg_write_time_line(plot, plot->timeline); 857 858 return 0; 859 } 860 861 void svg_write_time_line(struct plot *plot, int col) 862 { 863 snprintf(line, line_len, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" " 864 "style=\"stroke:black;stroke-width:2;\"/>\n", 865 axis_x_off(col), axis_y_off(0), 866 axis_x_off(col), axis_y_off(graph_height)); 867 write_check(plot->fd, line, strlen(line)); 868 } 869 870 static void svg_add_io(int fd, double row, double col, double width, double height, char *color) 871 { 872 float rx = 0; 873 874 snprintf(line, line_len, "<rect x=\"%.2f\" y=\"%.2f\" width=\"%.1f\" height=\"%.1f\" " 875 "rx=\"%.2f\" style=\"stroke:none;fill:%s;stroke-width:0\"/>\n", 876 axis_x_off_double(col), axis_y_off_double(row), width, height, rx, color); 877 write_check(fd, line, strlen(line)); 878 } 879 880 int svg_io_graph_movie_array(struct plot *plot, struct pid_plot_history *pph) 881 { 882 double cell_index; 883 double movie_row; 884 double movie_col; 885 int i; 886 887 for (i = 0; i < pph->num_used; i++) { 888 cell_index = pph->history[i]; 889 movie_row = floor(cell_index / graph_width); 890 movie_col = cell_index - movie_row * graph_width; 891 svg_add_io(plot->fd, movie_row, movie_col, 4, 4, pph->color); 892 } 893 return 0; 894 } 895 896 static float spindle_steps = 0; 897 898 void rewind_spindle_steps(int num) 899 { 900 spindle_steps -= num * 0.01; 901 } 902 903 int svg_io_graph_movie_array_spindle(struct plot *plot, struct pid_plot_history *pph) 904 { 905 double cell_index; 906 int i; 907 int num_circles = 0; 908 double cells_per_circle; 909 double circle_num; 910 double degrees_per_cell; 911 double rot; 912 double center_x; 913 double center_y; 914 double graph_width_extra = graph_width + graph_circle_extra; 915 double graph_height_extra = graph_height + graph_circle_extra; 916 double radius;; 917 918 if (graph_width_extra > graph_height_extra) 919 graph_width_extra = graph_height_extra; 920 921 if (graph_width_extra < graph_height_extra) 922 graph_height_extra = graph_width_extra; 923 924 radius = graph_width_extra; 925 926 center_x = axis_x_off_double(graph_width_extra / 2); 927 center_y = axis_y_off_double(graph_height_extra / 2); 928 929 snprintf(line, line_len, "<g transform=\"rotate(%.4f, %.2f, %.2f)\"> " 930 "<circle cx=\"%.2f\" cy=\"%.2f\" " 931 "stroke=\"black\" stroke-width=\"6\" " 932 "r=\"%.2f\" fill=\"none\"/>\n", 933 spindle_steps * 1.2, center_x, center_y, center_x, center_y, graph_width_extra / 2); 934 write_check(plot->fd, line, strlen(line)); 935 snprintf(line, line_len, "<circle cx=\"%.2f\" cy=\"%.2f\" " 936 "stroke=\"none\" fill=\"red\" r=\"%.2f\"/>\n</g>\n", 937 axis_x_off_double(graph_width_extra), center_y, 4.5); 938 write_check(plot->fd, line, strlen(line)); 939 spindle_steps += 0.01; 940 941 radius = floor(radius / 2); 942 num_circles = radius / 4 - 3; 943 cells_per_circle = pph->history_max / num_circles; 944 degrees_per_cell = 360 / cells_per_circle; 945 946 for (i = 0; i < pph->num_used; i++) { 947 cell_index = pph->history[i]; 948 circle_num = floor(cell_index / cells_per_circle); 949 rot = cell_index - circle_num * cells_per_circle; 950 circle_num = num_circles - circle_num; 951 radius = circle_num * 4; 952 953 rot = rot * degrees_per_cell; 954 rot -= spindle_steps; 955 snprintf(line, line_len, "<path transform=\"rotate(%.4f, %.2f, %.2f)\" " 956 "d=\"M %.2f %.2f a %.2f %.2f 0 0 1 0 5\" " 957 "stroke=\"%s\" stroke-width=\"4\"/>\n", 958 -rot, center_x, center_y, 959 axis_x_off_double(graph_width_extra / 2 + radius) + 8, center_y, 960 radius, radius, pph->color); 961 962 write_check(plot->fd, line, strlen(line)); 963 } 964 return 0; 965 } 966 967 static int add_plot_history(struct pid_plot_history *pph, double val) 968 { 969 if (pph->num_used == pph->history_len) { 970 pph->history_len += 4096; 971 pph->history = realloc(pph->history, 972 pph->history_len * sizeof(double)); 973 if (!pph->history) { 974 perror("Unable to allocate memory"); 975 exit(1); 976 } 977 } 978 pph->history[pph->num_used++] = val; 979 return 0; 980 } 981 982 int svg_io_graph_movie(struct graph_dot_data *gdd, struct pid_plot_history *pph, int col) 983 { 984 int row = 0; 985 int arr_index; 986 unsigned char val; 987 int bit_index; 988 int bit_mod; 989 double blocks_per_row = (gdd->max_offset - gdd->min_offset + 1) / gdd->rows; 990 double movie_blocks_per_cell = (gdd->max_offset - gdd->min_offset + 1) / (graph_width * graph_height); 991 double cell_index; 992 int margin_orig = graph_inner_y_margin; 993 994 graph_inner_y_margin += 5; 995 pph->history_max = (gdd->max_offset - gdd->min_offset + 1) / movie_blocks_per_cell; 996 997 for (row = gdd->rows - 1; row >= 0; row--) { 998 bit_index = row * gdd->cols + col; 999 arr_index = bit_index / 8; 1000 bit_mod = bit_index % 8; 1001 1002 if (arr_index < 0) 1003 continue; 1004 val = gdd->data[arr_index]; 1005 if (val & (1 << bit_mod)) { 1006 /* in bytes, linear offset from the start of the drive */ 1007 cell_index = (double)row * blocks_per_row; 1008 1009 /* a cell number in the graph */ 1010 cell_index /= movie_blocks_per_cell; 1011 1012 add_plot_history(pph, cell_index); 1013 } 1014 } 1015 graph_inner_y_margin = margin_orig; 1016 return 0; 1017 } 1018 1019 int svg_io_graph(struct plot *plot, struct graph_dot_data *gdd) 1020 { 1021 int fd = plot->fd;; 1022 int col = 0; 1023 int row = 0; 1024 int arr_index; 1025 unsigned char val; 1026 int bit_index; 1027 int bit_mod; 1028 1029 for (row = gdd->rows - 1; row >= 0; row--) { 1030 for (col = 0; col < gdd->cols; col++) { 1031 bit_index = row * gdd->cols + col; 1032 arr_index = bit_index / 8; 1033 bit_mod = bit_index % 8; 1034 1035 if (arr_index < 0) 1036 continue; 1037 val = gdd->data[arr_index]; 1038 if (val & (1 << bit_mod)) 1039 svg_add_io(fd, floor(row / io_graph_scale), col, 1.5, 1.5, gdd->color); 1040 } 1041 } 1042 return 0; 1043 } 1044 1045 void svg_alloc_legend(struct plot *plot, int num_lines) 1046 { 1047 char **lines = calloc(num_lines, sizeof(char *)); 1048 plot->legend_index = 0; 1049 plot->legend_lines = lines; 1050 plot->num_legend_lines = num_lines; 1051 } 1052 1053 void svg_free_legend(struct plot *plot) 1054 { 1055 int i; 1056 for (i = 0; i < plot->legend_index; i++) 1057 free(plot->legend_lines[i]); 1058 free(plot->legend_lines); 1059 plot->legend_lines = NULL; 1060 plot->legend_index = 0; 1061 } 1062 1063 void svg_write_legend(struct plot *plot) 1064 { 1065 int legend_line_x = axis_x_off(graph_width) + legend_x_off; 1066 int legend_line_y = axis_y_off(graph_height) + legend_y_off; 1067 int i; 1068 1069 if (plot->legend_index == 0) 1070 return; 1071 1072 snprintf(line, line_len, "<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" " 1073 "fill=\"white\" filter=\"url(#shadow)\"/>\n", 1074 legend_line_x - 15, 1075 legend_line_y - 12, 1076 legend_width, 1077 plot->legend_index * legend_font_size + legend_font_size / 2 + 12); 1078 1079 write_check(plot->fd, line, strlen(line)); 1080 for (i = 0; i < plot->legend_index; i++) { 1081 write_check(plot->fd, plot->legend_lines[i], 1082 strlen(plot->legend_lines[i])); 1083 free(plot->legend_lines[i]); 1084 } 1085 free(plot->legend_lines); 1086 plot->legend_lines = NULL; 1087 plot->legend_index = 0; 1088 } 1089 1090 void svg_add_legend(struct plot *plot, char *text, char *extra, char *color) 1091 { 1092 int legend_line_x = axis_x_off(graph_width) + legend_x_off; 1093 int legend_line_y = axis_y_off(graph_height) + legend_y_off; 1094 1095 if (!text && (!extra || strlen(extra) == 0)) 1096 return; 1097 1098 legend_line_y += plot->legend_index * legend_font_size + legend_font_size / 2; 1099 snprintf(line, line_len, "<path d=\"M %d %d h 8\" stroke=\"%s\" stroke-width=\"8\" " 1100 "filter=\"url(#labelshadow)\"/> " 1101 "<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" " 1102 "fill=\"black\" style=\"text-anchor: left\">%s%s</text>\n", 1103 legend_line_x, legend_line_y, 1104 color, legend_line_x + 13, 1105 legend_line_y + 4, font_family, legend_font_size, 1106 text, extra); 1107 1108 plot->legend_lines[plot->legend_index++] = strdup(line); 1109 } 1110 1111 void set_legend_width(int longest_str) 1112 { 1113 if (longest_str) 1114 legend_width = longest_str * (legend_font_size * 3 / 4) + 25; 1115 else 1116 legend_width = 0; 1117 } 1118 1119 void set_rolling_avg(int rolling) 1120 { 1121 rolling_avg_secs = rolling; 1122 } 1123 1124 void set_io_graph_scale(int scale) 1125 { 1126 io_graph_scale = scale; 1127 } 1128 1129 void set_graph_size(int width, int height) 1130 { 1131 graph_width = width; 1132 graph_height = height; 1133 } 1134 1135 void get_graph_size(int *width, int *height) 1136 { 1137 *width = graph_width; 1138 *height = graph_height; 1139 } 1140 1141 void set_graph_height(int h) 1142 { 1143 graph_height = h; 1144 } 1145 void set_graph_width(int w) 1146 { 1147 graph_width = w; 1148 } 1149