graph.c revision a1e7972d96ce1482aa43c2fcafd81d6c7f3c44d2
1/* 2 * gfio - gui front end for fio - the flexible io tester 3 * 4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com> 5 * 6 * The license below covers all files distributed with fio unless otherwise 7 * noted in the file itself. 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2 as 11 * published by the Free Software Foundation. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 * 22 */ 23#include <string.h> 24#include <malloc.h> 25#include <math.h> 26#include <assert.h> 27#include <stdlib.h> 28 29#include <cairo.h> 30#include <gtk/gtk.h> 31 32#include "tickmarks.h" 33#include "graph.h" 34#include "flist.h" 35#include "lib/prio_tree.h" 36 37/* 38 * Allowable difference to show tooltip 39 */ 40#define TOOLTIP_DELTA 0.08 41 42struct xyvalue { 43 double x, y; 44}; 45 46enum { 47 GV_F_ON_PRIO = 1, 48 GV_F_PRIO_SKIP = 2, 49}; 50 51struct graph_value { 52 struct flist_head list; 53 struct prio_tree_node node; 54 struct flist_head alias; 55 unsigned int flags; 56 char *tooltip; 57 void *value; 58}; 59 60struct graph_label { 61 struct flist_head list; 62 char *label; 63 struct flist_head value_list; 64 struct prio_tree_root prio_tree; 65 double r, g, b; 66 int hide; 67 int value_count; 68 struct graph *parent; 69}; 70 71struct tick_value { 72 unsigned int offset; 73 double value; 74}; 75 76struct graph { 77 char *title; 78 char *xtitle; 79 char *ytitle; 80 unsigned int xdim, ydim; 81 double xoffset, yoffset; 82 struct flist_head label_list; 83 int per_label_limit; 84 const char *font; 85 graph_axis_unit_change_callback x_axis_unit_change_callback; 86 graph_axis_unit_change_callback y_axis_unit_change_callback; 87 unsigned int base_offset; 88 unsigned int dont_graph_all_zeroes; 89 double left_extra; 90 double right_extra; 91 double top_extra; 92 double bottom_extra; 93 94 double xtick_zero; 95 double xtick_delta; 96 double xtick_zero_val; 97 double xtick_one_val; 98 double ytick_zero; 99 double ytick_delta; 100 double ytick_zero_val; 101 double ytick_one_val; 102}; 103 104void graph_set_size(struct graph *g, unsigned int xdim, unsigned int ydim) 105{ 106 g->xdim = xdim; 107 g->ydim = ydim; 108} 109 110void graph_set_position(struct graph *g, double xoffset, double yoffset) 111{ 112 g->xoffset = xoffset; 113 g->yoffset = yoffset; 114} 115 116struct graph *graph_new(unsigned int xdim, unsigned int ydim, const char *font) 117{ 118 struct graph *g; 119 120 g = calloc(1, sizeof(*g)); 121 INIT_FLIST_HEAD(&g->label_list); 122 graph_set_size(g, xdim, ydim); 123 g->per_label_limit = -1; 124 g->font = font; 125 if (!g->font) 126 g->font = GRAPH_DEFAULT_FONT; 127 return g; 128} 129 130void graph_set_font(struct graph *g, const char *font) 131{ 132 g->font = font; 133} 134 135void graph_x_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f) 136{ 137 g->x_axis_unit_change_callback = f; 138} 139 140void graph_y_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f) 141{ 142 g->y_axis_unit_change_callback = f; 143} 144 145static int count_labels(struct graph *g) 146{ 147 struct flist_head *entry; 148 int count = 0; 149 150 flist_for_each(entry, &g->label_list) 151 count++; 152 153 return count; 154} 155 156static int count_values(struct graph_label *l) 157{ 158 struct flist_head *entry; 159 int count = 0; 160 161 flist_for_each(entry, &l->value_list) 162 count++; 163 164 return count; 165} 166 167typedef double (*double_comparator)(double a, double b); 168 169static double mindouble(double a, double b) 170{ 171 return a < b ? a : b; 172} 173 174static double maxdouble(double a, double b) 175{ 176 return a < b ? b : a; 177} 178 179static double find_double_values(struct graph_label *l, double_comparator cmp) 180{ 181 struct flist_head *entry; 182 double answer, tmp; 183 int first = 1; 184 185 if (flist_empty(&l->value_list)) 186 return 0.0; 187 188 flist_for_each(entry, &l->value_list) { 189 struct graph_value *i; 190 191 i = flist_entry(entry, struct graph_value, list); 192 tmp = *(double *) i->value; 193 if (first) { 194 answer = tmp; 195 first = 0; 196 } else { 197 answer = cmp(answer, tmp); 198 } 199 } 200 return answer; 201} 202 203static double find_double_data(struct graph *g, double_comparator cmp) 204{ 205 struct flist_head *entry; 206 struct graph_label *i; 207 int first = 1; 208 double answer, tmp; 209 210 if (flist_empty(&g->label_list)) 211 return 0.0; 212 213 flist_for_each(entry, &g->label_list) { 214 i = flist_entry(entry, struct graph_label, list); 215 tmp = find_double_values(i, cmp); 216 if (first) { 217 answer = tmp; 218 first = 0; 219 } else { 220 answer = cmp(tmp, answer); 221 } 222 } 223 return answer; 224} 225 226static double find_min_data(struct graph *g) 227{ 228 return find_double_data(g, mindouble); 229} 230 231static double find_max_data(struct graph *g) 232{ 233 return find_double_data(g, maxdouble); 234} 235 236static void draw_bars(struct graph *bg, cairo_t *cr, struct graph_label *lb, 237 double label_offset, double bar_width, 238 double mindata, double maxdata) 239{ 240 struct flist_head *entry; 241 double x1, y1, x2, y2; 242 int bar_num = 0; 243 double domain, range, v; 244 245 domain = (maxdata - mindata); 246 range = (double) bg->ydim * 0.80; /* FIXME */ 247 cairo_stroke(cr); 248 flist_for_each(entry, &lb->value_list) { 249 struct graph_value *i; 250 251 i = flist_entry(entry, struct graph_value, list); 252 253 x1 = label_offset + (double) bar_num * bar_width + (bar_width * 0.05); 254 x2 = x1 + bar_width * 0.90; 255 y2 = bg->ydim * 0.90; 256 v = *(double *) i->value; 257 y1 = y2 - (((v - mindata) / domain) * range); 258 cairo_move_to(cr, x1, y1); 259 cairo_line_to(cr, x1, y2); 260 cairo_line_to(cr, x2, y2); 261 cairo_line_to(cr, x2, y1); 262 cairo_close_path(cr); 263 cairo_fill(cr); 264 cairo_stroke(cr); 265 bar_num++; 266 } 267} 268 269static void draw_aligned_text(struct graph *g, cairo_t *cr, double x, double y, 270 double fontsize, const char *text, int alignment) 271{ 272#define CENTERED 0 273#define LEFT_JUSTIFIED 1 274#define RIGHT_JUSTIFIED 2 275 276 double factor, direction; 277 cairo_text_extents_t extents; 278 279 switch(alignment) { 280 case CENTERED: 281 direction = -1.0; 282 factor = 0.5; 283 break; 284 case RIGHT_JUSTIFIED: 285 direction = -1.0; 286 factor = 1.0; 287 break; 288 case LEFT_JUSTIFIED: 289 default: 290 direction = 1.0; 291 factor = 1.0; 292 break; 293 } 294 cairo_select_font_face(cr, g->font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 295 296 cairo_set_font_size(cr, fontsize); 297 cairo_text_extents(cr, text, &extents); 298 x = x + direction * (factor * extents.width + extents.x_bearing); 299 y = y - (extents.height / 2 + extents.y_bearing); 300 301 cairo_move_to(cr, x, y); 302 cairo_show_text(cr, text); 303} 304 305static inline void draw_centered_text(struct graph *g, cairo_t *cr, double x, double y, 306 double fontsize, const char *text) 307{ 308 draw_aligned_text(g, cr, x, y, fontsize, text, CENTERED); 309} 310 311static inline void draw_right_justified_text(struct graph *g, cairo_t *cr, 312 double x, double y, 313 double fontsize, const char *text) 314{ 315 draw_aligned_text(g, cr, x, y, fontsize, text, RIGHT_JUSTIFIED); 316} 317 318static inline void draw_left_justified_text(struct graph *g, cairo_t *cr, 319 double x, double y, 320 double fontsize, const char *text) 321{ 322 draw_aligned_text(g, cr, x, y, fontsize, text, LEFT_JUSTIFIED); 323} 324 325static void draw_vertical_centered_text(struct graph *g, cairo_t *cr, double x, 326 double y, double fontsize, 327 const char *text) 328{ 329 double sx, sy; 330 cairo_text_extents_t extents; 331 332 cairo_select_font_face(cr, g->font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 333 334 cairo_set_font_size(cr, fontsize); 335 cairo_text_extents(cr, text, &extents); 336 sx = x; 337 sy = y; 338 y = y + (extents.width / 2.0 + extents.x_bearing); 339 x = x - (extents.height / 2.0 + extents.y_bearing); 340 341 cairo_move_to(cr, x, y); 342 cairo_save(cr); 343 cairo_translate(cr, -sx, -sy); 344 cairo_rotate(cr, -90.0 * M_PI / 180.0); 345 cairo_translate(cr, sx, sy); 346 cairo_show_text(cr, text); 347 cairo_restore(cr); 348} 349 350static void graph_draw_common(struct graph *g, cairo_t *cr, 351 double *x1, double *y1, double *x2, double *y2) 352{ 353 cairo_set_source_rgb(cr, 0, 0, 0); 354 cairo_set_line_width (cr, 0.8); 355 356 *x1 = 0.10 * g->xdim; 357 *x2 = 0.95 * g->xdim; 358 *y1 = 0.10 * g->ydim; 359 *y2 = 0.90 * g->ydim; 360 361 cairo_move_to(cr, *x1, *y1); 362 cairo_line_to(cr, *x1, *y2); 363 cairo_line_to(cr, *x2, *y2); 364 cairo_line_to(cr, *x2, *y1); 365 cairo_line_to(cr, *x1, *y1); 366 cairo_stroke(cr); 367 368 draw_centered_text(g, cr, g->xdim / 2, g->ydim / 20, 20.0, g->title); 369 draw_centered_text(g, cr, g->xdim / 2, g->ydim * 0.97, 14.0, g->xtitle); 370 draw_vertical_centered_text(g, cr, g->xdim * 0.02, g->ydim / 2, 14.0, g->ytitle); 371 cairo_stroke(cr); 372} 373 374static void graph_draw_x_ticks(struct graph *g, cairo_t *cr, 375 double x1, double y1, double x2, double y2, 376 double minx, double maxx, int nticks, int add_tm_text) 377{ 378 struct tickmark *tm; 379 double tx; 380 int i, power_of_ten; 381 static double dash[] = { 1.0, 2.0 }; 382 383 nticks = calc_tickmarks(minx, maxx, nticks, &tm, &power_of_ten, 384 g->x_axis_unit_change_callback == NULL, g->base_offset); 385 if (g->x_axis_unit_change_callback) 386 g->x_axis_unit_change_callback(g, power_of_ten); 387 388 for (i = 0; i < nticks; i++) { 389 tx = (((tm[i].value) - minx) / (maxx - minx)) * (x2 - x1) + x1; 390 391 /* 392 * Update tick delta 393 */ 394 if (!i) { 395 g->xtick_zero = tx; 396 g->xtick_zero_val = tm[0].value; 397 } else if (i == 1) { 398 g->xtick_delta = (tm[1].value - tm[0].value) / (tx - g->xtick_zero); 399 g->xtick_one_val = tm[1].value; 400 } 401 402 /* really tx < yx || tx > x2, but protect against rounding */ 403 if (x1 - tx > 0.01 || tx - x2 > 0.01) 404 continue; 405 406 /* Draw tick mark */ 407 cairo_set_line_width(cr, 0.8); 408 cairo_move_to(cr, tx, y2); 409 cairo_line_to(cr, tx, y2 + (y2 - y1) * 0.03); 410 cairo_stroke(cr); 411 412 /* draw grid lines */ 413 cairo_save(cr); 414 cairo_set_dash(cr, dash, 2, 2.0); 415 cairo_set_line_width(cr, 0.5); 416 cairo_move_to(cr, tx, y1); 417 cairo_line_to(cr, tx, y2); 418 cairo_stroke(cr); 419 cairo_restore(cr); 420 421 if (!add_tm_text) 422 continue; 423 424 /* draw tickmark label */ 425 draw_centered_text(g, cr, tx, y2 * 1.04, 12.0, tm[i].string); 426 cairo_stroke(cr); 427 } 428} 429 430static double graph_draw_y_ticks(struct graph *g, cairo_t *cr, 431 double x1, double y1, double x2, double y2, 432 double miny, double maxy, int nticks, int add_tm_text) 433{ 434 struct tickmark *tm; 435 double ty; 436 int i, power_of_ten; 437 static double dash[] = { 2.0, 2.0 }; 438 439 nticks = calc_tickmarks(miny, maxy, nticks, &tm, &power_of_ten, 440 g->y_axis_unit_change_callback == NULL, g->base_offset); 441 if (g->y_axis_unit_change_callback) 442 g->y_axis_unit_change_callback(g, power_of_ten); 443 444 /* 445 * Use highest tickmark as top of graph, not highest value. Otherwise 446 * it's impossible to see what the max value is, if the graph is 447 * fairly flat. 448 */ 449 maxy = tm[nticks - 1].value; 450 451 for (i = 0; i < nticks; i++) { 452 ty = y2 - (((tm[i].value) - miny) / (maxy - miny)) * (y2 - y1); 453 454 /* 455 * Update tick delta 456 */ 457 if (!i) { 458 g->ytick_zero = ty; 459 g->ytick_zero_val = tm[0].value; 460 } else if (i == 1) { 461 g->ytick_delta = (tm[1].value - tm[0].value) / (ty - g->ytick_zero); 462 g->ytick_one_val = tm[1].value; 463 } 464 465 /* really ty < y1 || ty > y2, but protect against rounding */ 466 if (y1 - ty > 0.01 || ty - y2 > 0.01) 467 continue; 468 469 /* draw tick mark */ 470 cairo_move_to(cr, x1, ty); 471 cairo_line_to(cr, x1 - (x2 - x1) * 0.02, ty); 472 cairo_stroke(cr); 473 474 /* draw grid lines */ 475 cairo_save(cr); 476 cairo_set_dash(cr, dash, 2, 2.0); 477 cairo_set_line_width(cr, 0.5); 478 cairo_move_to(cr, x1, ty); 479 cairo_line_to(cr, x2, ty); 480 cairo_stroke(cr); 481 cairo_restore(cr); 482 483 if (!add_tm_text) 484 continue; 485 486 /* draw tickmark label */ 487 draw_right_justified_text(g, cr, x1 - (x2 - x1) * 0.025, ty, 12.0, tm[i].string); 488 cairo_stroke(cr); 489 } 490 491 /* 492 * Return new max to use 493 */ 494 return maxy; 495} 496 497void bar_graph_draw(struct graph *bg, cairo_t *cr) 498{ 499 double x1, y1, x2, y2; 500 double space_per_label, bar_width; 501 double label_offset, mindata, maxdata; 502 int i, nlabels; 503 struct graph_label *lb; 504 struct flist_head *entry; 505 506 cairo_save(cr); 507 cairo_translate(cr, bg->xoffset, bg->yoffset); 508 graph_draw_common(bg, cr, &x1, &y1, &x2, &y2); 509 510 nlabels = count_labels(bg); 511 space_per_label = (x2 - x1) / (double) nlabels; 512 513 /* 514 * Start bars at 0 unless we have negative values, otherwise we 515 * present a skewed picture comparing label X and X+1. 516 */ 517 mindata = find_min_data(bg); 518 if (mindata > 0) 519 mindata = 0; 520 521 maxdata = find_max_data(bg); 522 523 if (fabs(maxdata - mindata) < 1e-20) { 524 draw_centered_text(bg, cr, 525 x1 + (x2 - x1) / 2.0, 526 y1 + (y2 - y1) / 2.0, 20.0, "No good data"); 527 return; 528 } 529 530 maxdata = graph_draw_y_ticks(bg, cr, x1, y1, x2, y2, mindata, maxdata, 10, 1); 531 i = 0; 532 flist_for_each(entry, &bg->label_list) { 533 int nvalues; 534 535 lb = flist_entry(entry, struct graph_label, list); 536 nvalues = count_values(lb); 537 bar_width = (space_per_label - space_per_label * 0.2) / (double) nvalues; 538 label_offset = bg->xdim * 0.1 + space_per_label * (double) i + space_per_label * 0.1; 539 draw_bars(bg, cr, lb, label_offset, bar_width, mindata, maxdata); 540 // draw_centered_text(cr, label_offset + (bar_width / 2.0 + bar_width * 0.1), bg->ydim * 0.93, 541 draw_centered_text(bg, cr, x1 + space_per_label * (i + 0.5), bg->ydim * 0.93, 542 12.0, lb->label); 543 i++; 544 } 545 cairo_stroke(cr); 546 cairo_restore(cr); 547} 548 549typedef double (*xy_value_extractor)(struct graph_value *v); 550 551static double getx(struct graph_value *v) 552{ 553 struct xyvalue *xy = v->value; 554 return xy->x; 555} 556 557static double gety(struct graph_value *v) 558{ 559 struct xyvalue *xy = v->value; 560 return xy->y; 561} 562 563static double find_xy_value(struct graph *g, xy_value_extractor getvalue, double_comparator cmp) 564{ 565 double tmp, answer = 0.0; 566 struct graph_label *i; 567 struct graph_value *j; 568 struct flist_head *jentry, *entry; 569 int first = 1; 570 571 flist_for_each(entry, &g->label_list) { 572 i = flist_entry(entry, struct graph_label, list); 573 574 flist_for_each(jentry, &i->value_list) { 575 j = flist_entry(jentry, struct graph_value, list); 576 tmp = getvalue(j); 577 if (first) { 578 first = 0; 579 answer = tmp; 580 } 581 answer = cmp(tmp, answer); 582 } 583 } 584 585 return answer; 586} 587 588void line_graph_draw(struct graph *g, cairo_t *cr) 589{ 590 double x1, y1, x2, y2; 591 double minx, miny, maxx, maxy, gminx, gminy, gmaxx, gmaxy; 592 double tx, ty, top_extra, bottom_extra, left_extra, right_extra; 593 struct graph_label *i; 594 struct graph_value *j; 595 int good_data = 1, first = 1; 596 struct flist_head *entry, *lentry; 597 598 cairo_save(cr); 599 cairo_translate(cr, g->xoffset, g->yoffset); 600 graph_draw_common(g, cr, &x1, &y1, &x2, &y2); 601 602 minx = find_xy_value(g, getx, mindouble); 603 maxx = find_xy_value(g, getx, maxdouble); 604 miny = find_xy_value(g, gety, mindouble); 605 606 /* 607 * Start graphs at zero, unless we have a value below. Otherwise 608 * it's hard to visually compare the read and write graph, since 609 * the lowest valued one will be the floor of the graph view. 610 */ 611 if (miny > 0) 612 miny = 0; 613 614 maxy = find_xy_value(g, gety, maxdouble); 615 616 if (fabs(maxx - minx) < 1e-20 || fabs(maxy - miny) < 1e-20) { 617 good_data = 0; 618 minx = 0.0; 619 miny = 0.0; 620 maxx = 10.0; 621 maxy = 100.0; 622 } 623 624 top_extra = 0.0; 625 bottom_extra = 0.0; 626 left_extra = 0.0; 627 right_extra = 0.0; 628 629 if (g->top_extra > 0.001) 630 top_extra = fabs(maxy - miny) * g->top_extra; 631 if (g->bottom_extra > 0.001) 632 bottom_extra = fabs(maxy - miny) * g->bottom_extra; 633 if (g->left_extra > 0.001) 634 left_extra = fabs(maxx - minx) * g->left_extra; 635 if (g->right_extra > 0.001) 636 right_extra = fabs(maxx - minx) * g->right_extra; 637 638 gminx = minx - left_extra; 639 gmaxx = maxx + right_extra; 640 gminy = miny - bottom_extra; 641 gmaxy = maxy + top_extra; 642 643 graph_draw_x_ticks(g, cr, x1, y1, x2, y2, gminx, gmaxx, 10, good_data); 644 gmaxy = graph_draw_y_ticks(g, cr, x1, y1, x2, y2, gminy, gmaxy, 10, good_data); 645 646 if (!good_data) 647 goto skip_data; 648 649 cairo_set_line_width(cr, 1.5); 650 flist_for_each(lentry, &g->label_list) { 651 i = flist_entry(lentry, struct graph_label, list); 652 first = 1; 653 if (i->hide || i->r < 0) /* invisible data */ 654 continue; 655 656 cairo_set_source_rgb(cr, i->r, i->g, i->b); 657 flist_for_each(entry, &i->value_list) { 658 j = flist_entry(entry, struct graph_value, list); 659 tx = ((getx(j) - gminx) / (gmaxx - gminx)) * (x2 - x1) + x1; 660 ty = y2 - ((gety(j) - gminy) / (gmaxy - gminy)) * (y2 - y1); 661 if (first) { 662 cairo_move_to(cr, tx, ty); 663 first = 0; 664 } else { 665 cairo_line_to(cr, tx, ty); 666 } 667 } 668 cairo_stroke(cr); 669 } 670 671skip_data: 672 cairo_restore(cr); 673} 674 675static void setstring(char **str, const char *value) 676{ 677 free(*str); 678 *str = strdup(value); 679} 680 681void graph_title(struct graph *bg, const char *title) 682{ 683 setstring(&bg->title, title); 684} 685 686void graph_x_title(struct graph *bg, const char *title) 687{ 688 setstring(&bg->xtitle, title); 689} 690 691void graph_y_title(struct graph *bg, const char *title) 692{ 693 setstring(&bg->ytitle, title); 694} 695 696static struct graph_label *graph_find_label(struct graph *bg, 697 const char *label) 698{ 699 struct flist_head *entry; 700 struct graph_label *i; 701 702 flist_for_each(entry, &bg->label_list) { 703 i = flist_entry(entry, struct graph_label, list); 704 705 if (strcmp(label, i->label) == 0) 706 return i; 707 } 708 709 return NULL; 710} 711 712graph_label_t graph_add_label(struct graph *bg, const char *label) 713{ 714 struct graph_label *i; 715 716 i = graph_find_label(bg, label); 717 if (i) 718 return i; /* already present. */ 719 i = calloc(1, sizeof(*i)); 720 INIT_FLIST_HEAD(&i->value_list); 721 i->parent = bg; 722 setstring(&i->label, label); 723 flist_add_tail(&i->list, &bg->label_list); 724 INIT_PRIO_TREE_ROOT(&i->prio_tree); 725 return i; 726} 727 728static void __graph_value_drop(struct graph_label *l, struct graph_value *v) 729{ 730 flist_del_init(&v->list); 731 if (v->tooltip) 732 free(v->tooltip); 733 free(v->value); 734 free(v); 735 l->value_count--; 736} 737 738static void graph_value_drop(struct graph_label *l, struct graph_value *v) 739{ 740 if (v->flags & GV_F_PRIO_SKIP) { 741 __graph_value_drop(l, v); 742 return; 743 } 744 745 /* 746 * Find head, the guy that's on the prio tree 747 */ 748 while (!(v->flags & GV_F_ON_PRIO)) { 749 assert(!flist_empty(&v->alias)); 750 v = flist_entry(v->alias.next, struct graph_value, alias); 751 } 752 753 prio_tree_remove(&l->prio_tree, &v->node); 754 755 /* 756 * Free aliases 757 */ 758 while (!flist_empty(&v->alias)) { 759 struct graph_value *a; 760 761 a = flist_entry(v->alias.next, struct graph_value, alias); 762 flist_del_init(&a->alias); 763 764 __graph_value_drop(l, a); 765 } 766 767 __graph_value_drop(l, v); 768} 769 770static void graph_label_add_value(struct graph_label *i, void *value, 771 const char *tooltip) 772{ 773 struct graph *g = i->parent; 774 struct graph_value *x; 775 776 x = malloc(sizeof(*x)); 777 memset(x, 0, sizeof(*x)); 778 INIT_FLIST_HEAD(&x->alias); 779 INIT_FLIST_HEAD(&x->list); 780 flist_add_tail(&x->list, &i->value_list); 781 i->value_count++; 782 x->value = value; 783 784 if (tooltip) { 785 double xval = getx(x); 786 double minx = xval - (g->xtick_one_val * TOOLTIP_DELTA); 787 double maxx = xval + (g->xtick_one_val * TOOLTIP_DELTA); 788 struct prio_tree_node *ret; 789 790 /* 791 * use msec to avoid dropping too much precision when 792 * storing as an integer. 793 */ 794 minx = minx * 1000.0; 795 maxx = maxx * 1000.0; 796 797 INIT_PRIO_TREE_NODE(&x->node); 798 x->node.start = minx; 799 x->node.last = maxx; 800 x->tooltip = strdup(tooltip); 801 if (x->node.last == x->node.start) { 802 x->node.last += fabs(g->xtick_delta); 803 if (x->node.last == x->node.start) 804 x->node.last++; 805 } 806 807 /* 808 * If ret != &x->node, we have an alias. Since the values 809 * should be identical, we can drop it 810 */ 811 ret = prio_tree_insert(&i->prio_tree, &x->node); 812 if (ret != &x->node) { 813 struct graph_value *alias; 814 815 alias = container_of(ret, struct graph_value, node); 816 flist_add_tail(&x->alias, &alias->alias); 817 } else 818 x->flags = GV_F_ON_PRIO; 819 } else 820 x->flags = GV_F_PRIO_SKIP; 821 822 if (g->per_label_limit != -1 && 823 i->value_count > g->per_label_limit) { 824 int to_drop = 1; 825 826 /* 827 * If the limit was dynamically reduced, making us more 828 * than 1 entry ahead after adding this one, drop two 829 * entries. This will make us (eventually) reach the 830 * specified limit. 831 */ 832 if (i->value_count - g->per_label_limit >= 2) 833 to_drop = 2; 834 835 while (to_drop-- && !flist_empty(&i->value_list)) { 836 x = flist_entry(i->value_list.next, struct graph_value, list); 837 graph_value_drop(i, x); 838 839 /* 840 * If we have aliases, we could drop > 1 above. 841 */ 842 if (i->value_count <= g->per_label_limit) 843 break; 844 } 845 } 846} 847 848int graph_add_data(struct graph *bg, graph_label_t label, const double value) 849{ 850 struct graph_label *i = label; 851 double *d; 852 853 d = malloc(sizeof(*d)); 854 *d = value; 855 856 graph_label_add_value(i, d, NULL); 857 return 0; 858} 859 860static int graph_nonzero_y(struct graph_label *l) 861{ 862 struct flist_head *entry; 863 864 flist_for_each(entry, &l->value_list) { 865 struct graph_value *v; 866 867 v = flist_entry(entry, struct graph_value, list); 868 if (gety(v) != 0.0) 869 return 1; 870 } 871 872 return 0; 873} 874 875int graph_add_xy_data(struct graph *bg, graph_label_t label, 876 const double x, const double y, const char *tooltip) 877{ 878 struct graph_label *i = label; 879 struct xyvalue *xy; 880 881 if (bg->dont_graph_all_zeroes && y == 0.0 && !graph_nonzero_y(i)) 882 i->hide = 1; 883 else 884 i->hide = 0; 885 886 xy = malloc(sizeof(*xy)); 887 xy->x = x; 888 xy->y = y; 889 890 graph_label_add_value(i, xy, tooltip); 891 return 0; 892} 893 894static void graph_free_values(struct graph_label *l) 895{ 896 struct graph_value *i; 897 898 while (!flist_empty(&l->value_list)) { 899 i = flist_entry(l->value_list.next, struct graph_value, list); 900 graph_value_drop(l, i); 901 } 902} 903 904static void graph_free_labels(struct graph *g) 905{ 906 struct graph_label *i; 907 908 while (!flist_empty(&g->label_list)) { 909 i = flist_entry(g->label_list.next, struct graph_label, list); 910 flist_del(&i->list); 911 graph_free_values(i); 912 free(i); 913 } 914} 915 916void graph_set_color(struct graph *gr, graph_label_t label, double red, 917 double green, double blue) 918{ 919 struct graph_label *i = label; 920 double r, g, b; 921 922 if (red < 0.0) { /* invisible color */ 923 r = -1.0; 924 g = -1.0; 925 b = -1.0; 926 } else { 927 r = fabs(red); 928 g = fabs(green); 929 b = fabs(blue); 930 931 if (r > 1.0) 932 r = 1.0; 933 if (g > 1.0) 934 g = 1.0; 935 if (b > 1.0) 936 b = 1.0; 937 } 938 939 i->r = r; 940 i->g = g; 941 i->b = b; 942} 943 944void graph_free(struct graph *bg) 945{ 946 free(bg->title); 947 free(bg->xtitle); 948 free(bg->ytitle); 949 graph_free_labels(bg); 950} 951 952/* For each line in the line graph, up to per_label_limit segments may 953 * be added. After that, adding more data to the end of the line 954 * causes data to drop off of the front of the line. 955 */ 956void line_graph_set_data_count_limit(struct graph *g, int per_label_limit) 957{ 958 g->per_label_limit = per_label_limit; 959} 960 961void graph_add_extra_space(struct graph *g, double left_percent, double right_percent, 962 double top_percent, double bottom_percent) 963{ 964 g->left_extra = left_percent; 965 g->right_extra = right_percent; 966 g->top_extra = top_percent; 967 g->bottom_extra = bottom_percent; 968} 969 970/* 971 * Normally values are logged in a base unit of 0, but for other purposes 972 * it makes more sense to log in higher unit. For instance for bandwidth 973 * purposes, you may want to log in KB/sec (or MB/sec) rather than bytes/sec. 974 */ 975void graph_set_base_offset(struct graph *g, unsigned int base_offset) 976{ 977 g->base_offset = base_offset; 978} 979 980int graph_has_tooltips(struct graph *g) 981{ 982 struct flist_head *entry; 983 struct graph_label *i; 984 985 flist_for_each(entry, &g->label_list) { 986 i = flist_entry(entry, struct graph_label, list); 987 988 if (!prio_tree_empty(&i->prio_tree)) 989 return 1; 990 } 991 992 return 0; 993} 994 995int graph_contains_xy(struct graph *g, int x, int y) 996{ 997 int first_x = g->xoffset; 998 int last_x = g->xoffset + g->xdim; 999 int first_y = g->yoffset; 1000 int last_y = g->yoffset + g->ydim; 1001 1002 return (x >= first_x && x <= last_x) && (y >= first_y && y <= last_y); 1003} 1004 1005const char *graph_find_tooltip(struct graph *g, int ix, int iy) 1006{ 1007 double x = ix, y = iy; 1008 struct prio_tree_iter iter; 1009 struct prio_tree_node *n; 1010 struct graph_value *best = NULL; 1011 struct flist_head *entry; 1012 double best_delta; 1013 double maxy, miny; 1014 1015 x -= g->xoffset; 1016 y -= g->yoffset; 1017 1018 x = g->xtick_zero_val + ((x - g->xtick_zero) * g->xtick_delta); 1019 y = g->ytick_zero_val + ((y - g->ytick_zero) * g->ytick_delta); 1020 1021 x = x * 1000.0; 1022 maxy = y + (g->ytick_one_val * TOOLTIP_DELTA); 1023 miny = y - (g->ytick_one_val * TOOLTIP_DELTA); 1024 best_delta = UINT_MAX; 1025 flist_for_each(entry, &g->label_list) { 1026 struct graph_label *i; 1027 1028 i = flist_entry(entry, struct graph_label, list); 1029 if (i->hide) 1030 continue; 1031 1032 INIT_PRIO_TREE_ITER(&iter); 1033 prio_tree_iter_init(&iter, &i->prio_tree, x, x); 1034 1035 n = prio_tree_next(&iter); 1036 if (!n) 1037 continue; 1038 1039 do { 1040 struct graph_value *v, *rootv; 1041 double yval, ydiff; 1042 1043 v = container_of(n, struct graph_value, node); 1044 rootv = v; 1045 do { 1046 yval = gety(v); 1047 ydiff = fabs(yval - y); 1048 1049 /* 1050 * zero delta, or within or match critera, break 1051 */ 1052 if (ydiff < best_delta) { 1053 best_delta = ydiff; 1054 if (!best_delta || 1055 (yval >= miny && yval <= maxy)) { 1056 best = v; 1057 break; 1058 } 1059 } 1060 if (!flist_empty(&v->alias)) 1061 v = flist_entry(v->alias.next, struct graph_value, alias); 1062 } while (v != rootv); 1063 } while ((n = prio_tree_next(&iter)) != NULL); 1064 1065 /* 1066 * If we got matches in one label, don't check others. 1067 */ 1068 if (best) 1069 break; 1070 } 1071 1072 if (best) 1073 return best->tooltip; 1074 1075 return NULL; 1076} 1077 1078void graph_set_graph_all_zeroes(struct graph *g, unsigned int set) 1079{ 1080 g->dont_graph_all_zeroes = !set; 1081} 1082