1 /****************************************************************************
2 * RRDtool 1.4.3 Copyright by Tobi Oetiker, 1997-2010
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
20 #include "plbasename.h"
23 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
32 #ifdef HAVE_LANGINFO_H
36 #include "rrd_graph.h"
37 #include "rrd_client.h"
39 /* some constant definitions */
43 #ifndef RRD_DEFAULT_FONT
44 /* there is special code later to pick Cour.ttf when running on windows */
45 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
48 text_prop_t text_prop[] = {
49 {8.0, RRD_DEFAULT_FONT,NULL}
51 {9.0, RRD_DEFAULT_FONT,NULL}
53 {7.0, RRD_DEFAULT_FONT,NULL}
55 {8.0, RRD_DEFAULT_FONT,NULL}
57 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
59 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
63 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
65 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
67 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
69 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
71 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
73 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
75 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
77 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
79 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
81 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
82 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
84 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
86 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
88 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
90 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
92 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
95 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
98 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
101 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
103 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
104 365 * 24 * 3600, "%y"}
106 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
109 /* sensible y label intervals ...*/
133 {20.0, {1, 5, 10, 20}
139 {100.0, {1, 2, 5, 10}
142 {200.0, {1, 5, 10, 20}
145 {500.0, {1, 2, 4, 10}
153 gfx_color_t graph_col[] = /* default colors */
155 {1.00, 1.00, 1.00, 1.00}, /* canvas */
156 {0.95, 0.95, 0.95, 1.00}, /* background */
157 {0.81, 0.81, 0.81, 1.00}, /* shade A */
158 {0.62, 0.62, 0.62, 1.00}, /* shade B */
159 {0.56, 0.56, 0.56, 0.75}, /* grid */
160 {0.87, 0.31, 0.31, 0.60}, /* major grid */
161 {0.00, 0.00, 0.00, 1.00}, /* font */
162 {0.50, 0.12, 0.12, 1.00}, /* arrow */
163 {0.12, 0.12, 0.12, 1.00}, /* axis */
164 {0.00, 0.00, 0.00, 1.00} /* frame */
171 # define DPRINT(x) (void)(printf x, printf("\n"))
177 /* initialize with xtr(im,0); */
185 pixie = (double) im->xsize / (double) (im->end - im->start);
188 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
191 /* translate data values into y coordinates */
200 if (!im->logarithmic)
201 pixie = (double) im->ysize / (im->maxval - im->minval);
204 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
206 } else if (!im->logarithmic) {
207 yval = im->yorigin - pixie * (value - im->minval);
209 if (value < im->minval) {
212 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
220 /* conversion function for symbolic entry names */
223 #define conv_if(VV,VVV) \
224 if (strcmp(#VV, string) == 0) return VVV ;
230 conv_if(PRINT, GF_PRINT);
231 conv_if(GPRINT, GF_GPRINT);
232 conv_if(COMMENT, GF_COMMENT);
233 conv_if(HRULE, GF_HRULE);
234 conv_if(VRULE, GF_VRULE);
235 conv_if(LINE, GF_LINE);
236 conv_if(AREA, GF_AREA);
237 conv_if(STACK, GF_STACK);
238 conv_if(TICK, GF_TICK);
239 conv_if(TEXTALIGN, GF_TEXTALIGN);
240 conv_if(DEF, GF_DEF);
241 conv_if(CDEF, GF_CDEF);
242 conv_if(VDEF, GF_VDEF);
243 conv_if(XPORT, GF_XPORT);
244 conv_if(SHIFT, GF_SHIFT);
246 return (enum gf_en)(-1);
249 enum gfx_if_en if_conv(
253 conv_if(PNG, IF_PNG);
254 conv_if(SVG, IF_SVG);
255 conv_if(EPS, IF_EPS);
256 conv_if(PDF, IF_PDF);
258 return (enum gfx_if_en)(-1);
261 enum tmt_en tmt_conv(
265 conv_if(SECOND, TMT_SECOND);
266 conv_if(MINUTE, TMT_MINUTE);
267 conv_if(HOUR, TMT_HOUR);
268 conv_if(DAY, TMT_DAY);
269 conv_if(WEEK, TMT_WEEK);
270 conv_if(MONTH, TMT_MONTH);
271 conv_if(YEAR, TMT_YEAR);
272 return (enum tmt_en)(-1);
275 enum grc_en grc_conv(
279 conv_if(BACK, GRC_BACK);
280 conv_if(CANVAS, GRC_CANVAS);
281 conv_if(SHADEA, GRC_SHADEA);
282 conv_if(SHADEB, GRC_SHADEB);
283 conv_if(GRID, GRC_GRID);
284 conv_if(MGRID, GRC_MGRID);
285 conv_if(FONT, GRC_FONT);
286 conv_if(ARROW, GRC_ARROW);
287 conv_if(AXIS, GRC_AXIS);
288 conv_if(FRAME, GRC_FRAME);
290 return (enum grc_en)(-1);
293 enum text_prop_en text_prop_conv(
297 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
298 conv_if(TITLE, TEXT_PROP_TITLE);
299 conv_if(AXIS, TEXT_PROP_AXIS);
300 conv_if(UNIT, TEXT_PROP_UNIT);
301 conv_if(LEGEND, TEXT_PROP_LEGEND);
302 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
303 return (enum text_prop_en)(-1);
313 cairo_status_t status = (cairo_status_t) 0;
318 if (im->daemon_addr != NULL)
319 free(im->daemon_addr);
321 for (i = 0; i < (unsigned) im->gdes_c; i++) {
322 if (im->gdes[i].data_first) {
323 /* careful here, because a single pointer can occur several times */
324 free(im->gdes[i].data);
325 if (im->gdes[i].ds_namv) {
326 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
327 free(im->gdes[i].ds_namv[ii]);
328 free(im->gdes[i].ds_namv);
331 /* free allocated memory used for dashed lines */
332 if (im->gdes[i].p_dashes != NULL)
333 free(im->gdes[i].p_dashes);
335 free(im->gdes[i].p_data);
336 free(im->gdes[i].rpnp);
339 if (im->font_options)
340 cairo_font_options_destroy(im->font_options);
343 status = cairo_status(im->cr);
344 cairo_destroy(im->cr);
346 if (im->rendered_image) {
347 free(im->rendered_image);
351 g_object_unref (im->layout);
355 cairo_surface_destroy(im->surface);
358 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
359 cairo_status_to_string(status));
364 /* find SI magnitude symbol for the given number*/
366 image_desc_t *im, /* image description */
372 char *symbol[] = { "a", /* 10e-18 Atto */
373 "f", /* 10e-15 Femto */
374 "p", /* 10e-12 Pico */
375 "n", /* 10e-9 Nano */
376 "u", /* 10e-6 Micro */
377 "m", /* 10e-3 Milli */
382 "T", /* 10e12 Tera */
383 "P", /* 10e15 Peta */
390 if (*value == 0.0 || isnan(*value)) {
394 sindex = floor(log(fabs(*value)) / log((double) im->base));
395 *magfact = pow((double) im->base, (double) sindex);
396 (*value) /= (*magfact);
398 if (sindex <= symbcenter && sindex >= -symbcenter) {
399 (*symb_ptr) = symbol[sindex + symbcenter];
406 static char si_symbol[] = {
407 'a', /* 10e-18 Atto */
408 'f', /* 10e-15 Femto */
409 'p', /* 10e-12 Pico */
410 'n', /* 10e-9 Nano */
411 'u', /* 10e-6 Micro */
412 'm', /* 10e-3 Milli */
417 'T', /* 10e12 Tera */
418 'P', /* 10e15 Peta */
421 static const int si_symbcenter = 6;
423 /* find SI magnitude symbol for the numbers on the y-axis*/
425 image_desc_t *im /* image description */
429 double digits, viewdigits = 0;
432 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
433 log((double) im->base));
435 if (im->unitsexponent != 9999) {
436 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
437 viewdigits = floor((double)(im->unitsexponent / 3));
442 im->magfact = pow((double) im->base, digits);
445 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
448 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
450 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
451 ((viewdigits + si_symbcenter) >= 0))
452 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
457 /* move min and max values around to become sensible */
462 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
463 600.0, 500.0, 400.0, 300.0, 250.0,
464 200.0, 125.0, 100.0, 90.0, 80.0,
465 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
466 25.0, 20.0, 10.0, 9.0, 8.0,
467 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
468 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
469 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
472 double scaled_min, scaled_max;
479 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
480 im->minval, im->maxval, im->magfact);
483 if (isnan(im->ygridstep)) {
484 if (im->extra_flags & ALTAUTOSCALE) {
485 /* measure the amplitude of the function. Make sure that
486 graph boundaries are slightly higher then max/min vals
487 so we can see amplitude on the graph */
490 delt = im->maxval - im->minval;
492 fact = 2.0 * pow(10.0,
494 (max(fabs(im->minval), fabs(im->maxval)) /
497 adj = (fact - delt) * 0.55;
500 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
501 im->minval, im->maxval, delt, fact, adj);
506 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
507 /* measure the amplitude of the function. Make sure that
508 graph boundaries are slightly lower than min vals
509 so we can see amplitude on the graph */
510 adj = (im->maxval - im->minval) * 0.1;
512 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
513 /* measure the amplitude of the function. Make sure that
514 graph boundaries are slightly higher than max vals
515 so we can see amplitude on the graph */
516 adj = (im->maxval - im->minval) * 0.1;
519 scaled_min = im->minval / im->magfact;
520 scaled_max = im->maxval / im->magfact;
522 for (i = 1; sensiblevalues[i] > 0; i++) {
523 if (sensiblevalues[i - 1] >= scaled_min &&
524 sensiblevalues[i] <= scaled_min)
525 im->minval = sensiblevalues[i] * (im->magfact);
527 if (-sensiblevalues[i - 1] <= scaled_min &&
528 -sensiblevalues[i] >= scaled_min)
529 im->minval = -sensiblevalues[i - 1] * (im->magfact);
531 if (sensiblevalues[i - 1] >= scaled_max &&
532 sensiblevalues[i] <= scaled_max)
533 im->maxval = sensiblevalues[i - 1] * (im->magfact);
535 if (-sensiblevalues[i - 1] <= scaled_max &&
536 -sensiblevalues[i] >= scaled_max)
537 im->maxval = -sensiblevalues[i] * (im->magfact);
541 /* adjust min and max to the grid definition if there is one */
542 im->minval = (double) im->ylabfact * im->ygridstep *
543 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
544 im->maxval = (double) im->ylabfact * im->ygridstep *
545 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
549 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
550 im->minval, im->maxval, im->magfact);
558 if (isnan(im->minval) || isnan(im->maxval))
561 if (im->logarithmic) {
562 double ya, yb, ypix, ypixfrac;
563 double log10_range = log10(im->maxval) - log10(im->minval);
565 ya = pow((double) 10, floor(log10(im->minval)));
566 while (ya < im->minval)
569 return; /* don't have y=10^x gridline */
571 if (yb <= im->maxval) {
572 /* we have at least 2 y=10^x gridlines.
573 Make sure distance between them in pixels
574 are an integer by expanding im->maxval */
575 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
576 double factor = y_pixel_delta / floor(y_pixel_delta);
577 double new_log10_range = factor * log10_range;
578 double new_ymax_log10 = log10(im->minval) + new_log10_range;
580 im->maxval = pow(10, new_ymax_log10);
581 ytr(im, DNAN); /* reset precalc */
582 log10_range = log10(im->maxval) - log10(im->minval);
584 /* make sure first y=10^x gridline is located on
585 integer pixel position by moving scale slightly
586 downwards (sub-pixel movement) */
587 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
588 ypixfrac = ypix - floor(ypix);
589 if (ypixfrac > 0 && ypixfrac < 1) {
590 double yfrac = ypixfrac / im->ysize;
592 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
593 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
594 ytr(im, DNAN); /* reset precalc */
597 /* Make sure we have an integer pixel distance between
598 each minor gridline */
599 double ypos1 = ytr(im, im->minval);
600 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
601 double y_pixel_delta = ypos1 - ypos2;
602 double factor = y_pixel_delta / floor(y_pixel_delta);
603 double new_range = factor * (im->maxval - im->minval);
604 double gridstep = im->ygrid_scale.gridstep;
605 double minor_y, minor_y_px, minor_y_px_frac;
607 if (im->maxval > 0.0)
608 im->maxval = im->minval + new_range;
610 im->minval = im->maxval - new_range;
611 ytr(im, DNAN); /* reset precalc */
612 /* make sure first minor gridline is on integer pixel y coord */
613 minor_y = gridstep * floor(im->minval / gridstep);
614 while (minor_y < im->minval)
616 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
617 minor_y_px_frac = minor_y_px - floor(minor_y_px);
618 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
619 double yfrac = minor_y_px_frac / im->ysize;
620 double range = im->maxval - im->minval;
622 im->minval = im->minval - yfrac * range;
623 im->maxval = im->maxval - yfrac * range;
624 ytr(im, DNAN); /* reset precalc */
626 calc_horizontal_grid(im); /* recalc with changed im->maxval */
630 /* reduce data reimplementation by Alex */
633 enum cf_en cf, /* which consolidation function ? */
634 unsigned long cur_step, /* step the data currently is in */
635 time_t *start, /* start, end and step as requested ... */
636 time_t *end, /* ... by the application will be ... */
637 unsigned long *step, /* ... adjusted to represent reality */
638 unsigned long *ds_cnt, /* number of data sources in file */
640 { /* two dimensional array containing the data */
641 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
642 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
644 rrd_value_t *srcptr, *dstptr;
646 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
649 row_cnt = ((*end) - (*start)) / cur_step;
655 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
656 row_cnt, reduce_factor, *start, *end, cur_step);
657 for (col = 0; col < row_cnt; col++) {
658 printf("time %10lu: ", *start + (col + 1) * cur_step);
659 for (i = 0; i < *ds_cnt; i++)
660 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
665 /* We have to combine [reduce_factor] rows of the source
666 ** into one row for the destination. Doing this we also
667 ** need to take care to combine the correct rows. First
668 ** alter the start and end time so that they are multiples
669 ** of the new step time. We cannot reduce the amount of
670 ** time so we have to move the end towards the future and
671 ** the start towards the past.
673 end_offset = (*end) % (*step);
674 start_offset = (*start) % (*step);
676 /* If there is a start offset (which cannot be more than
677 ** one destination row), skip the appropriate number of
678 ** source rows and one destination row. The appropriate
679 ** number is what we do know (start_offset/cur_step) of
680 ** the new interval (*step/cur_step aka reduce_factor).
683 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
684 printf("row_cnt before: %lu\n", row_cnt);
687 (*start) = (*start) - start_offset;
688 skiprows = reduce_factor - start_offset / cur_step;
689 srcptr += skiprows * *ds_cnt;
690 for (col = 0; col < (*ds_cnt); col++)
695 printf("row_cnt between: %lu\n", row_cnt);
698 /* At the end we have some rows that are not going to be
699 ** used, the amount is end_offset/cur_step
702 (*end) = (*end) - end_offset + (*step);
703 skiprows = end_offset / cur_step;
707 printf("row_cnt after: %lu\n", row_cnt);
710 /* Sanity check: row_cnt should be multiple of reduce_factor */
711 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
713 if (row_cnt % reduce_factor) {
714 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
715 row_cnt, reduce_factor);
716 printf("BUG in reduce_data()\n");
720 /* Now combine reduce_factor intervals at a time
721 ** into one interval for the destination.
724 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
725 for (col = 0; col < (*ds_cnt); col++) {
726 rrd_value_t newval = DNAN;
727 unsigned long validval = 0;
729 for (i = 0; i < reduce_factor; i++) {
730 if (isnan(srcptr[i * (*ds_cnt) + col])) {
735 newval = srcptr[i * (*ds_cnt) + col];
744 newval += srcptr[i * (*ds_cnt) + col];
747 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
750 /* an interval contains a failure if any subintervals contained a failure */
752 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
755 newval = srcptr[i * (*ds_cnt) + col];
781 srcptr += (*ds_cnt) * reduce_factor;
782 row_cnt -= reduce_factor;
784 /* If we had to alter the endtime, we didn't have enough
785 ** source rows to fill the last row. Fill it with NaN.
788 for (col = 0; col < (*ds_cnt); col++)
791 row_cnt = ((*end) - (*start)) / *step;
793 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
794 row_cnt, *start, *end, *step);
795 for (col = 0; col < row_cnt; col++) {
796 printf("time %10lu: ", *start + (col + 1) * (*step));
797 for (i = 0; i < *ds_cnt; i++)
798 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
805 /* get the data required for the graphs from the
814 /* pull the data from the rrd files ... */
815 for (i = 0; i < (int) im->gdes_c; i++) {
816 /* only GF_DEF elements fetch data */
817 if (im->gdes[i].gf != GF_DEF)
821 /* do we have it already ? */
822 for (ii = 0; ii < i; ii++) {
823 if (im->gdes[ii].gf != GF_DEF)
825 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
826 && (im->gdes[i].cf == im->gdes[ii].cf)
827 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
828 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
829 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
830 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
831 /* OK, the data is already there.
832 ** Just copy the header portion
834 im->gdes[i].start = im->gdes[ii].start;
835 im->gdes[i].end = im->gdes[ii].end;
836 im->gdes[i].step = im->gdes[ii].step;
837 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
838 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
839 im->gdes[i].data = im->gdes[ii].data;
840 im->gdes[i].data_first = 0;
847 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
850 * - a connection to the daemon has been established
851 * - this is the first occurrence of that RRD file
853 if (rrdc_is_connected(im->daemon_addr))
858 for (ii = 0; ii < i; ii++)
860 if (strcmp (im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
869 status = rrdc_flush (im->gdes[i].rrd);
872 rrd_set_error ("rrdc_flush (%s) failed with status %i.",
873 im->gdes[i].rrd, status);
877 } /* if (rrdc_is_connected()) */
879 if ((rrd_fetch_fn(im->gdes[i].rrd,
885 &im->gdes[i].ds_namv,
886 &im->gdes[i].data)) == -1) {
889 im->gdes[i].data_first = 1;
891 if (ft_step < im->gdes[i].step) {
892 reduce_data(im->gdes[i].cf_reduce,
897 &im->gdes[i].ds_cnt, &im->gdes[i].data);
899 im->gdes[i].step = ft_step;
903 /* lets see if the required data source is really there */
904 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
905 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
909 if (im->gdes[i].ds == -1) {
910 rrd_set_error("No DS called '%s' in '%s'",
911 im->gdes[i].ds_nam, im->gdes[i].rrd);
919 /* evaluate the expressions in the CDEF functions */
921 /*************************************************************
923 *************************************************************/
925 long find_var_wrapper(
929 return find_var((image_desc_t *) arg1, key);
932 /* find gdes containing var*/
939 for (ii = 0; ii < im->gdes_c - 1; ii++) {
940 if ((im->gdes[ii].gf == GF_DEF
941 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
942 && (strcmp(im->gdes[ii].vname, key) == 0)) {
949 /* find the greatest common divisor for all the numbers
950 in the 0 terminated num array */
957 for (i = 0; num[i + 1] != 0; i++) {
959 rest = num[i] % num[i + 1];
965 /* return i==0?num[i]:num[i-1]; */
969 /* run the rpn calculator on all the VDEF and CDEF arguments */
976 long *steparray, rpi;
981 rpnstack_init(&rpnstack);
983 for (gdi = 0; gdi < im->gdes_c; gdi++) {
984 /* Look for GF_VDEF and GF_CDEF in the same loop,
985 * so CDEFs can use VDEFs and vice versa
987 switch (im->gdes[gdi].gf) {
991 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
993 /* remove current shift */
994 vdp->start -= vdp->shift;
995 vdp->end -= vdp->shift;
998 if (im->gdes[gdi].shidx >= 0)
999 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1002 vdp->shift = im->gdes[gdi].shval;
1004 /* normalize shift to multiple of consolidated step */
1005 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1008 vdp->start += vdp->shift;
1009 vdp->end += vdp->shift;
1013 /* A VDEF has no DS. This also signals other parts
1014 * of rrdtool that this is a VDEF value, not a CDEF.
1016 im->gdes[gdi].ds_cnt = 0;
1017 if (vdef_calc(im, gdi)) {
1018 rrd_set_error("Error processing VDEF '%s'",
1019 im->gdes[gdi].vname);
1020 rpnstack_free(&rpnstack);
1025 im->gdes[gdi].ds_cnt = 1;
1026 im->gdes[gdi].ds = 0;
1027 im->gdes[gdi].data_first = 1;
1028 im->gdes[gdi].start = 0;
1029 im->gdes[gdi].end = 0;
1034 /* Find the variables in the expression.
1035 * - VDEF variables are substituted by their values
1036 * and the opcode is changed into OP_NUMBER.
1037 * - CDEF variables are analized for their step size,
1038 * the lowest common denominator of all the step
1039 * sizes of the data sources involved is calculated
1040 * and the resulting number is the step size for the
1041 * resulting data source.
1043 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1044 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1045 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1046 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1048 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1051 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1052 im->gdes[gdi].vname, im->gdes[ptr].vname);
1053 printf("DEBUG: value from vdef is %f\n",
1054 im->gdes[ptr].vf.val);
1056 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1057 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1058 } else { /* normal variables and PREF(variables) */
1060 /* add one entry to the array that keeps track of the step sizes of the
1061 * data sources going into the CDEF. */
1063 (long*)rrd_realloc(steparray,
1065 1) * sizeof(*steparray))) == NULL) {
1066 rrd_set_error("realloc steparray");
1067 rpnstack_free(&rpnstack);
1071 steparray[stepcnt - 1] = im->gdes[ptr].step;
1073 /* adjust start and end of cdef (gdi) so
1074 * that it runs from the latest start point
1075 * to the earliest endpoint of any of the
1076 * rras involved (ptr)
1079 if (im->gdes[gdi].start < im->gdes[ptr].start)
1080 im->gdes[gdi].start = im->gdes[ptr].start;
1082 if (im->gdes[gdi].end == 0 ||
1083 im->gdes[gdi].end > im->gdes[ptr].end)
1084 im->gdes[gdi].end = im->gdes[ptr].end;
1086 /* store pointer to the first element of
1087 * the rra providing data for variable,
1088 * further save step size and data source
1091 im->gdes[gdi].rpnp[rpi].data =
1092 im->gdes[ptr].data + im->gdes[ptr].ds;
1093 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1094 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1096 /* backoff the *.data ptr; this is done so
1097 * rpncalc() function doesn't have to treat
1098 * the first case differently
1100 } /* if ds_cnt != 0 */
1101 } /* if OP_VARIABLE */
1102 } /* loop through all rpi */
1104 /* move the data pointers to the correct period */
1105 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1106 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1107 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1108 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1110 im->gdes[gdi].start - im->gdes[ptr].start;
1113 im->gdes[gdi].rpnp[rpi].data +=
1114 (diff / im->gdes[ptr].step) *
1115 im->gdes[ptr].ds_cnt;
1119 if (steparray == NULL) {
1120 rrd_set_error("rpn expressions without DEF"
1121 " or CDEF variables are not supported");
1122 rpnstack_free(&rpnstack);
1125 steparray[stepcnt] = 0;
1126 /* Now find the resulting step. All steps in all
1127 * used RRAs have to be visited
1129 im->gdes[gdi].step = lcd(steparray);
1131 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1132 im->gdes[gdi].start)
1133 / im->gdes[gdi].step)
1134 * sizeof(double))) == NULL) {
1135 rrd_set_error("malloc im->gdes[gdi].data");
1136 rpnstack_free(&rpnstack);
1140 /* Step through the new cdef results array and
1141 * calculate the values
1143 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1144 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1145 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1147 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1148 * in this case we are advancing by timesteps;
1149 * we use the fact that time_t is a synonym for long
1151 if (rpn_calc(rpnp, &rpnstack, (long) now,
1152 im->gdes[gdi].data, ++dataidx) == -1) {
1153 /* rpn_calc sets the error string */
1154 rpnstack_free(&rpnstack);
1157 } /* enumerate over time steps within a CDEF */
1162 } /* enumerate over CDEFs */
1163 rpnstack_free(&rpnstack);
1167 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1168 /* yes we are loosing precision by doing tos with floats instead of doubles
1169 but it seems more stable this way. */
1171 static int AlmostEqual2sComplement(
1177 int aInt = *(int *) &A;
1178 int bInt = *(int *) &B;
1181 /* Make sure maxUlps is non-negative and small enough that the
1182 default NAN won't compare as equal to anything. */
1184 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1186 /* Make aInt lexicographically ordered as a twos-complement int */
1189 aInt = 0x80000000l - aInt;
1191 /* Make bInt lexicographically ordered as a twos-complement int */
1194 bInt = 0x80000000l - bInt;
1196 intDiff = abs(aInt - bInt);
1198 if (intDiff <= maxUlps)
1204 /* massage data so, that we get one value for each x coordinate in the graph */
1209 double pixstep = (double) (im->end - im->start)
1210 / (double) im->xsize; /* how much time
1211 passes in one pixel */
1213 double minval = DNAN, maxval = DNAN;
1215 unsigned long gr_time;
1217 /* memory for the processed data */
1218 for (i = 0; i < im->gdes_c; i++) {
1219 if ((im->gdes[i].gf == GF_LINE) ||
1220 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1221 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1222 * sizeof(rrd_value_t))) == NULL) {
1223 rrd_set_error("malloc data_proc");
1229 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1232 gr_time = im->start + pixstep * i; /* time of the current step */
1235 for (ii = 0; ii < im->gdes_c; ii++) {
1238 switch (im->gdes[ii].gf) {
1242 if (!im->gdes[ii].stack)
1244 value = im->gdes[ii].yrule;
1245 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1246 /* The time of the data doesn't necessarily match
1247 ** the time of the graph. Beware.
1249 vidx = im->gdes[ii].vidx;
1250 if (im->gdes[vidx].gf == GF_VDEF) {
1251 value = im->gdes[vidx].vf.val;
1253 if (((long int) gr_time >=
1254 (long int) im->gdes[vidx].start)
1255 && ((long int) gr_time <
1256 (long int) im->gdes[vidx].end)) {
1257 value = im->gdes[vidx].data[(unsigned long)
1263 im->gdes[vidx].step)
1264 * im->gdes[vidx].ds_cnt +
1271 if (!isnan(value)) {
1273 im->gdes[ii].p_data[i] = paintval;
1274 /* GF_TICK: the data values are not
1275 ** relevant for min and max
1277 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1278 if ((isnan(minval) || paintval < minval) &&
1279 !(im->logarithmic && paintval <= 0.0))
1281 if (isnan(maxval) || paintval > maxval)
1285 im->gdes[ii].p_data[i] = DNAN;
1290 ("STACK should already be turned into LINE or AREA here");
1299 /* if min or max have not been asigned a value this is because
1300 there was no data in the graph ... this is not good ...
1301 lets set these to dummy values then ... */
1303 if (im->logarithmic) {
1304 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1305 minval = 0.0; /* catching this right away below */
1308 /* in logarithm mode, where minval is smaller or equal
1309 to 0 make the beast just way smaller than maxval */
1311 minval = maxval / 10e8;
1314 if (isnan(minval) || isnan(maxval)) {
1320 /* adjust min and max values given by the user */
1321 /* for logscale we add something on top */
1322 if (isnan(im->minval)
1323 || ((!im->rigid) && im->minval > minval)
1325 if (im->logarithmic)
1326 im->minval = minval / 2.0;
1328 im->minval = minval;
1330 if (isnan(im->maxval)
1331 || (!im->rigid && im->maxval < maxval)
1333 if (im->logarithmic)
1334 im->maxval = maxval * 2.0;
1336 im->maxval = maxval;
1339 /* make sure min is smaller than max */
1340 if (im->minval > im->maxval) {
1342 im->minval = 0.99 * im->maxval;
1344 im->minval = 1.01 * im->maxval;
1347 /* make sure min and max are not equal */
1348 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1354 /* make sure min and max are not both zero */
1355 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1362 static int find_first_weekday(void){
1363 static int first_weekday = -1;
1364 if (first_weekday == -1){
1365 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1366 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1367 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1368 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1369 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1370 else first_weekday = 1; /* we go for a monday default */
1375 return first_weekday;
1378 /* identify the point where the first gridline, label ... gets placed */
1380 time_t find_first_time(
1381 time_t start, /* what is the initial time */
1382 enum tmt_en baseint, /* what is the basic interval */
1383 long basestep /* how many if these do we jump a time */
1388 localtime_r(&start, &tm);
1392 tm. tm_sec -= tm.tm_sec % basestep;
1397 tm. tm_min -= tm.tm_min % basestep;
1403 tm. tm_hour -= tm.tm_hour % basestep;
1407 /* we do NOT look at the basestep for this ... */
1414 /* we do NOT look at the basestep for this ... */
1418 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1420 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1421 tm. tm_mday -= 7; /* we want the *previous* week */
1429 tm. tm_mon -= tm.tm_mon % basestep;
1440 tm.tm_year + 1900) %basestep;
1446 /* identify the point where the next gridline, label ... gets placed */
1447 time_t find_next_time(
1448 time_t current, /* what is the initial time */
1449 enum tmt_en baseint, /* what is the basic interval */
1450 long basestep /* how many if these do we jump a time */
1456 localtime_r(¤t, &tm);
1461 tm. tm_sec += basestep;
1465 tm. tm_min += basestep;
1469 tm. tm_hour += basestep;
1473 tm. tm_mday += basestep;
1477 tm. tm_mday += 7 * basestep;
1481 tm. tm_mon += basestep;
1485 tm. tm_year += basestep;
1487 madetime = mktime(&tm);
1488 } while (madetime == -1); /* this is necessary to skip impssible times
1489 like the daylight saving time skips */
1495 /* calculate values required for PRINT and GPRINT functions */
1500 long i, ii, validsteps;
1503 int graphelement = 0;
1506 double magfact = -1;
1511 /* wow initializing tmvdef is quite a task :-) */
1512 time_t now = time(NULL);
1514 localtime_r(&now, &tmvdef);
1515 for (i = 0; i < im->gdes_c; i++) {
1516 vidx = im->gdes[i].vidx;
1517 switch (im->gdes[i].gf) {
1520 /* PRINT and GPRINT can now print VDEF generated values.
1521 * There's no need to do any calculations on them as these
1522 * calculations were already made.
1524 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1525 printval = im->gdes[vidx].vf.val;
1526 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1527 } else { /* need to calculate max,min,avg etcetera */
1528 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1529 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1532 for (ii = im->gdes[vidx].ds;
1533 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1534 if (!finite(im->gdes[vidx].data[ii]))
1536 if (isnan(printval)) {
1537 printval = im->gdes[vidx].data[ii];
1542 switch (im->gdes[i].cf) {
1546 case CF_DEVSEASONAL:
1550 printval += im->gdes[vidx].data[ii];
1553 printval = min(printval, im->gdes[vidx].data[ii]);
1557 printval = max(printval, im->gdes[vidx].data[ii]);
1560 printval = im->gdes[vidx].data[ii];
1563 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1564 if (validsteps > 1) {
1565 printval = (printval / validsteps);
1568 } /* prepare printval */
1570 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1571 /* Magfact is set to -1 upon entry to print_calc. If it
1572 * is still less than 0, then we need to run auto_scale.
1573 * Otherwise, put the value into the correct units. If
1574 * the value is 0, then do not set the symbol or magnification
1575 * so next the calculation will be performed again. */
1576 if (magfact < 0.0) {
1577 auto_scale(im, &printval, &si_symb, &magfact);
1578 if (printval == 0.0)
1581 printval /= magfact;
1583 *(++percent_s) = 's';
1584 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1585 auto_scale(im, &printval, &si_symb, &magfact);
1588 if (im->gdes[i].gf == GF_PRINT) {
1589 rrd_infoval_t prline;
1591 if (im->gdes[i].strftm) {
1592 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1593 strftime(prline.u_str,
1594 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1595 } else if (bad_format(im->gdes[i].format)) {
1597 ("bad format for PRINT in '%s'", im->gdes[i].format);
1601 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1605 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1610 if (im->gdes[i].strftm) {
1611 strftime(im->gdes[i].legend,
1612 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1614 if (bad_format(im->gdes[i].format)) {
1616 ("bad format for GPRINT in '%s'",
1617 im->gdes[i].format);
1620 #ifdef HAVE_SNPRINTF
1621 snprintf(im->gdes[i].legend,
1623 im->gdes[i].format, printval, si_symb);
1625 sprintf(im->gdes[i].legend,
1626 im->gdes[i].format, printval, si_symb);
1638 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1639 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1644 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1645 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1654 #ifdef WITH_PIECHART
1662 ("STACK should already be turned into LINE or AREA here");
1667 return graphelement;
1672 /* place legends with color spots */
1678 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1679 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1680 int fill = 0, fill_last;
1681 double legendwidth; // = im->ximg - 2 * border;
1683 double leg_x = border;
1684 int leg_y = 0; //im->yimg;
1685 int leg_y_prev = 0; // im->yimg;
1688 int i, ii, mark = 0;
1689 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1692 char saved_legend[FMT_LEG_LEN + 5];
1698 legendwidth = im->legendwidth - 2 * border;
1702 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1703 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1704 rrd_set_error("malloc for legspace");
1708 for (i = 0; i < im->gdes_c; i++) {
1709 char prt_fctn; /*special printfunctions */
1711 strcpy(saved_legend, im->gdes[i].legend);
1715 /* hide legends for rules which are not displayed */
1716 if (im->gdes[i].gf == GF_TEXTALIGN) {
1717 default_txtalign = im->gdes[i].txtalign;
1720 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1721 if (im->gdes[i].gf == GF_HRULE
1722 && (im->gdes[i].yrule <
1723 im->minval || im->gdes[i].yrule > im->maxval))
1724 im->gdes[i].legend[0] = '\0';
1725 if (im->gdes[i].gf == GF_VRULE
1726 && (im->gdes[i].xrule <
1727 im->start || im->gdes[i].xrule > im->end))
1728 im->gdes[i].legend[0] = '\0';
1731 /* turn \\t into tab */
1732 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1733 memmove(tab, tab + 1, strlen(tab));
1737 leg_cc = strlen(im->gdes[i].legend);
1738 /* is there a controle code at the end of the legend string ? */
1739 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1740 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1742 im->gdes[i].legend[leg_cc] = '\0';
1746 /* only valid control codes */
1747 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1752 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1755 ("Unknown control code at the end of '%s\\%c'",
1756 im->gdes[i].legend, prt_fctn);
1760 if (prt_fctn == 'n') {
1764 /* remove exess space from the end of the legend for \g */
1765 while (prt_fctn == 'g' &&
1766 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1768 im->gdes[i].legend[leg_cc] = '\0';
1773 /* no interleg space if string ends in \g */
1774 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1776 fill += legspace[i];
1779 gfx_get_text_width(im,
1785 im->tabwidth, im->gdes[i].legend);
1790 /* who said there was a special tag ... ? */
1791 if (prt_fctn == 'g') {
1795 if (prt_fctn == '\0') {
1796 if(calc_width && (fill > legendwidth)){
1799 if (i == im->gdes_c - 1 || fill > legendwidth) {
1800 /* just one legend item is left right or center */
1801 switch (default_txtalign) {
1816 /* is it time to place the legends ? */
1817 if (fill > legendwidth) {
1825 if (leg_c == 1 && prt_fctn == 'j') {
1830 if (prt_fctn != '\0') {
1832 if (leg_c >= 2 && prt_fctn == 'j') {
1833 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1837 if (prt_fctn == 'c')
1838 leg_x = (double)(legendwidth - fill) / 2.0;
1839 if (prt_fctn == 'r')
1840 leg_x = legendwidth - fill + border;
1841 for (ii = mark; ii <= i; ii++) {
1842 if (im->gdes[ii].legend[0] == '\0')
1843 continue; /* skip empty legends */
1844 im->gdes[ii].leg_x = leg_x;
1845 im->gdes[ii].leg_y = leg_y + border;
1847 (double)gfx_get_text_width(im, leg_x,
1852 im->tabwidth, im->gdes[ii].legend)
1853 +(double)legspace[ii]
1857 if (leg_x > border || prt_fctn == 's')
1858 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1859 if (prt_fctn == 's')
1860 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1861 if (prt_fctn == 'u')
1862 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1864 if(calc_width && (fill > legendwidth)){
1873 strcpy(im->gdes[i].legend, saved_legend);
1878 im->legendwidth = legendwidth + 2 * border;
1881 im->legendheight = leg_y + border * 0.6;
1888 /* create a grid on the graph. it determines what to do
1889 from the values of xsize, start and end */
1891 /* the xaxis labels are determined from the number of seconds per pixel
1892 in the requested graph */
1894 int calc_horizontal_grid(
1902 int decimals, fractionals;
1904 im->ygrid_scale.labfact = 2;
1905 range = im->maxval - im->minval;
1906 scaledrange = range / im->magfact;
1907 /* does the scale of this graph make it impossible to put lines
1908 on it? If so, give up. */
1909 if (isnan(scaledrange)) {
1913 /* find grid spaceing */
1915 if (isnan(im->ygridstep)) {
1916 if (im->extra_flags & ALTYGRID) {
1917 /* find the value with max number of digits. Get number of digits */
1920 (max(fabs(im->maxval), fabs(im->minval)) *
1921 im->viewfactor / im->magfact));
1922 if (decimals <= 0) /* everything is small. make place for zero */
1924 im->ygrid_scale.gridstep =
1926 floor(log10(range * im->viewfactor / im->magfact))) /
1927 im->viewfactor * im->magfact;
1928 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1929 im->ygrid_scale.gridstep = 0.1;
1930 /* should have at least 5 lines but no more then 15 */
1931 if (range / im->ygrid_scale.gridstep < 5
1932 && im->ygrid_scale.gridstep >= 30)
1933 im->ygrid_scale.gridstep /= 10;
1934 if (range / im->ygrid_scale.gridstep > 15)
1935 im->ygrid_scale.gridstep *= 10;
1936 if (range / im->ygrid_scale.gridstep > 5) {
1937 im->ygrid_scale.labfact = 1;
1938 if (range / im->ygrid_scale.gridstep > 8
1939 || im->ygrid_scale.gridstep <
1940 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1941 im->ygrid_scale.labfact = 2;
1943 im->ygrid_scale.gridstep /= 5;
1944 im->ygrid_scale.labfact = 5;
1948 (im->ygrid_scale.gridstep *
1949 (double) im->ygrid_scale.labfact * im->viewfactor /
1951 if (fractionals < 0) { /* small amplitude. */
1952 int len = decimals - fractionals + 1;
1954 if (im->unitslength < len + 2)
1955 im->unitslength = len + 2;
1956 sprintf(im->ygrid_scale.labfmt,
1958 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1960 int len = decimals + 1;
1962 if (im->unitslength < len + 2)
1963 im->unitslength = len + 2;
1964 sprintf(im->ygrid_scale.labfmt,
1965 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1967 } else { /* classic rrd grid */
1968 for (i = 0; ylab[i].grid > 0; i++) {
1969 pixel = im->ysize / (scaledrange / ylab[i].grid);
1975 for (i = 0; i < 4; i++) {
1976 if (pixel * ylab[gridind].lfac[i] >=
1977 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1978 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1983 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1986 im->ygrid_scale.gridstep = im->ygridstep;
1987 im->ygrid_scale.labfact = im->ylabfact;
1992 int draw_horizontal_grid(
1998 char graph_label[100];
2000 double X0 = im->xorigin;
2001 double X1 = im->xorigin + im->xsize;
2002 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2003 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2005 double second_axis_magfact = 0;
2006 char *second_axis_symb = "";
2009 im->ygrid_scale.gridstep /
2010 (double) im->magfact * (double) im->viewfactor;
2011 MaxY = scaledstep * (double) egrid;
2012 for (i = sgrid; i <= egrid; i++) {
2014 im->ygrid_scale.gridstep * i);
2016 im->ygrid_scale.gridstep * (i + 1));
2018 if (floor(Y0 + 0.5) >=
2019 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2020 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2021 with the chosen settings. Add a label if required by settings, or if
2022 there is only one label so far and the next grid line is out of bounds. */
2023 if (i % im->ygrid_scale.labfact == 0
2025 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2026 if (im->symbol == ' ') {
2027 if (im->extra_flags & ALTYGRID) {
2028 sprintf(graph_label,
2029 im->ygrid_scale.labfmt,
2030 scaledstep * (double) i);
2033 sprintf(graph_label, "%4.1f",
2034 scaledstep * (double) i);
2036 sprintf(graph_label, "%4.0f",
2037 scaledstep * (double) i);
2041 char sisym = (i == 0 ? ' ' : im->symbol);
2043 if (im->extra_flags & ALTYGRID) {
2044 sprintf(graph_label,
2045 im->ygrid_scale.labfmt,
2046 scaledstep * (double) i, sisym);
2049 sprintf(graph_label, "%4.1f %c",
2050 scaledstep * (double) i, sisym);
2052 sprintf(graph_label, "%4.0f %c",
2053 scaledstep * (double) i, sisym);
2058 if (im->second_axis_scale != 0){
2059 char graph_label_right[100];
2060 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2061 if (im->second_axis_format[0] == '\0'){
2062 if (!second_axis_magfact){
2063 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2064 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2066 sval /= second_axis_magfact;
2069 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2071 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2075 sprintf(graph_label_right,im->second_axis_format,sval);
2079 im->graph_col[GRC_FONT],
2080 im->text_prop[TEXT_PROP_AXIS].font_desc,
2081 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2082 graph_label_right );
2088 text_prop[TEXT_PROP_AXIS].
2090 im->graph_col[GRC_FONT],
2092 text_prop[TEXT_PROP_AXIS].
2095 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2096 gfx_line(im, X0 - 2, Y0, X0, Y0,
2097 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2098 gfx_line(im, X1, Y0, X1 + 2, Y0,
2099 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2100 gfx_dashed_line(im, X0 - 2, Y0,
2106 im->grid_dash_on, im->grid_dash_off);
2107 } else if (!(im->extra_flags & NOMINOR)) {
2110 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2111 gfx_line(im, X1, Y0, X1 + 2, Y0,
2112 GRIDWIDTH, im->graph_col[GRC_GRID]);
2113 gfx_dashed_line(im, X0 - 1, Y0,
2117 graph_col[GRC_GRID],
2118 im->grid_dash_on, im->grid_dash_off);
2125 /* this is frexp for base 10 */
2136 iexp = floor(log((double)fabs(x)) / log((double)10));
2137 mnt = x / pow(10.0, iexp);
2140 mnt = x / pow(10.0, iexp);
2147 /* logaritmic horizontal grid */
2148 int horizontal_log_grid(
2152 double yloglab[][10] = {
2154 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2156 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2158 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2175 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2177 int i, j, val_exp, min_exp;
2178 double nex; /* number of decades in data */
2179 double logscale; /* scale in logarithmic space */
2180 int exfrac = 1; /* decade spacing */
2181 int mid = -1; /* row in yloglab for major grid */
2182 double mspac; /* smallest major grid spacing (pixels) */
2183 int flab; /* first value in yloglab to use */
2184 double value, tmp, pre_value;
2186 char graph_label[100];
2188 nex = log10(im->maxval / im->minval);
2189 logscale = im->ysize / nex;
2190 /* major spacing for data with high dynamic range */
2191 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2198 /* major spacing for less dynamic data */
2200 /* search best row in yloglab */
2202 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2203 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2206 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2209 /* find first value in yloglab */
2211 yloglab[mid][flab] < 10
2212 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2213 if (yloglab[mid][flab] == 10.0) {
2218 if (val_exp % exfrac)
2219 val_exp += abs(-val_exp % exfrac);
2221 X1 = im->xorigin + im->xsize;
2226 value = yloglab[mid][flab] * pow(10.0, val_exp);
2227 if (AlmostEqual2sComplement(value, pre_value, 4))
2228 break; /* it seems we are not converging */
2230 Y0 = ytr(im, value);
2231 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2233 /* major grid line */
2235 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2236 gfx_line(im, X1, Y0, X1 + 2, Y0,
2237 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2238 gfx_dashed_line(im, X0 - 2, Y0,
2243 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2245 if (im->extra_flags & FORCE_UNITS_SI) {
2250 scale = floor(val_exp / 3.0);
2252 pvalue = pow(10.0, val_exp % 3);
2254 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2255 pvalue *= yloglab[mid][flab];
2256 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2257 && ((scale + si_symbcenter) >= 0))
2258 symbol = si_symbol[scale + si_symbcenter];
2261 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2263 sprintf(graph_label, "%3.0e", value);
2265 if (im->second_axis_scale != 0){
2266 char graph_label_right[100];
2267 double sval = value*im->second_axis_scale+im->second_axis_shift;
2268 if (im->second_axis_format[0] == '\0'){
2269 if (im->extra_flags & FORCE_UNITS_SI) {
2272 auto_scale(im,&sval,&symb,&mfac);
2273 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2276 sprintf(graph_label_right,"%3.0e", sval);
2280 sprintf(graph_label_right,im->second_axis_format,sval);
2285 im->graph_col[GRC_FONT],
2286 im->text_prop[TEXT_PROP_AXIS].font_desc,
2287 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2288 graph_label_right );
2294 text_prop[TEXT_PROP_AXIS].
2296 im->graph_col[GRC_FONT],
2298 text_prop[TEXT_PROP_AXIS].
2301 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2303 if (mid < 4 && exfrac == 1) {
2304 /* find first and last minor line behind current major line
2305 * i is the first line and j tha last */
2307 min_exp = val_exp - 1;
2308 for (i = 1; yloglab[mid][i] < 10.0; i++);
2309 i = yloglab[mid][i - 1] + 1;
2313 i = yloglab[mid][flab - 1] + 1;
2314 j = yloglab[mid][flab];
2317 /* draw minor lines below current major line */
2318 for (; i < j; i++) {
2320 value = i * pow(10.0, min_exp);
2321 if (value < im->minval)
2323 Y0 = ytr(im, value);
2324 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2329 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2330 gfx_line(im, X1, Y0, X1 + 2, Y0,
2331 GRIDWIDTH, im->graph_col[GRC_GRID]);
2332 gfx_dashed_line(im, X0 - 1, Y0,
2336 graph_col[GRC_GRID],
2337 im->grid_dash_on, im->grid_dash_off);
2339 } else if (exfrac > 1) {
2340 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2341 value = pow(10.0, i);
2342 if (value < im->minval)
2344 Y0 = ytr(im, value);
2345 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2350 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2351 gfx_line(im, X1, Y0, X1 + 2, Y0,
2352 GRIDWIDTH, im->graph_col[GRC_GRID]);
2353 gfx_dashed_line(im, X0 - 1, Y0,
2357 graph_col[GRC_GRID],
2358 im->grid_dash_on, im->grid_dash_off);
2363 if (yloglab[mid][++flab] == 10.0) {
2369 /* draw minor lines after highest major line */
2370 if (mid < 4 && exfrac == 1) {
2371 /* find first and last minor line below current major line
2372 * i is the first line and j tha last */
2374 min_exp = val_exp - 1;
2375 for (i = 1; yloglab[mid][i] < 10.0; i++);
2376 i = yloglab[mid][i - 1] + 1;
2380 i = yloglab[mid][flab - 1] + 1;
2381 j = yloglab[mid][flab];
2384 /* draw minor lines below current major line */
2385 for (; i < j; i++) {
2387 value = i * pow(10.0, min_exp);
2388 if (value < im->minval)
2390 Y0 = ytr(im, value);
2391 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2395 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2396 gfx_line(im, X1, Y0, X1 + 2, Y0,
2397 GRIDWIDTH, im->graph_col[GRC_GRID]);
2398 gfx_dashed_line(im, X0 - 1, Y0,
2402 graph_col[GRC_GRID],
2403 im->grid_dash_on, im->grid_dash_off);
2406 /* fancy minor gridlines */
2407 else if (exfrac > 1) {
2408 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2409 value = pow(10.0, i);
2410 if (value < im->minval)
2412 Y0 = ytr(im, value);
2413 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2417 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2418 gfx_line(im, X1, Y0, X1 + 2, Y0,
2419 GRIDWIDTH, im->graph_col[GRC_GRID]);
2420 gfx_dashed_line(im, X0 - 1, Y0,
2424 graph_col[GRC_GRID],
2425 im->grid_dash_on, im->grid_dash_off);
2436 int xlab_sel; /* which sort of label and grid ? */
2437 time_t ti, tilab, timajor;
2439 char graph_label[100];
2440 double X0, Y0, Y1; /* points for filled graph and more */
2443 /* the type of time grid is determined by finding
2444 the number of seconds per pixel in the graph */
2445 if (im->xlab_user.minsec == -1) {
2446 factor = (im->end - im->start) / im->xsize;
2448 while (xlab[xlab_sel + 1].minsec !=
2449 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2451 } /* pick the last one */
2452 while (xlab[xlab_sel - 1].minsec ==
2453 xlab[xlab_sel].minsec
2454 && xlab[xlab_sel].length > (im->end - im->start)) {
2456 } /* go back to the smallest size */
2457 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2458 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2459 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2460 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2461 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2462 im->xlab_user.labst = xlab[xlab_sel].labst;
2463 im->xlab_user.precis = xlab[xlab_sel].precis;
2464 im->xlab_user.stst = xlab[xlab_sel].stst;
2467 /* y coords are the same for every line ... */
2469 Y1 = im->yorigin - im->ysize;
2470 /* paint the minor grid */
2471 if (!(im->extra_flags & NOMINOR)) {
2472 for (ti = find_first_time(im->start,
2480 find_first_time(im->start,
2487 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2489 /* are we inside the graph ? */
2490 if (ti < im->start || ti > im->end)
2492 while (timajor < ti) {
2493 timajor = find_next_time(timajor,
2496 mgridtm, im->xlab_user.mgridst);
2499 continue; /* skip as falls on major grid line */
2501 gfx_line(im, X0, Y1 - 2, X0, Y1,
2502 GRIDWIDTH, im->graph_col[GRC_GRID]);
2503 gfx_line(im, X0, Y0, X0, Y0 + 2,
2504 GRIDWIDTH, im->graph_col[GRC_GRID]);
2505 gfx_dashed_line(im, X0, Y0 + 1, X0,
2508 graph_col[GRC_GRID],
2509 im->grid_dash_on, im->grid_dash_off);
2513 /* paint the major grid */
2514 for (ti = find_first_time(im->start,
2522 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2524 /* are we inside the graph ? */
2525 if (ti < im->start || ti > im->end)
2528 gfx_line(im, X0, Y1 - 2, X0, Y1,
2529 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2530 gfx_line(im, X0, Y0, X0, Y0 + 3,
2531 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2532 gfx_dashed_line(im, X0, Y0 + 3, X0,
2536 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2538 /* paint the labels below the graph */
2540 find_first_time(im->start -
2549 im->xlab_user.precis / 2;
2550 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2552 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2553 /* are we inside the graph ? */
2554 if (tilab < im->start || tilab > im->end)
2557 localtime_r(&tilab, &tm);
2558 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2560 # error "your libc has no strftime I guess we'll abort the exercise here."
2565 im->graph_col[GRC_FONT],
2567 text_prop[TEXT_PROP_AXIS].
2570 GFX_H_CENTER, GFX_V_TOP, graph_label);
2579 /* draw x and y axis */
2580 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2581 im->xorigin+im->xsize,im->yorigin-im->ysize,
2582 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2584 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2585 im->xorigin+im->xsize,im->yorigin-im->ysize,
2586 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2588 gfx_line(im, im->xorigin - 4,
2590 im->xorigin + im->xsize +
2591 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2592 gfx_line(im, im->xorigin,
2595 im->yorigin - im->ysize -
2596 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2597 /* arrow for X and Y axis direction */
2598 gfx_new_area(im, im->xorigin + im->xsize + 2, im->yorigin - 3, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin, /* horyzontal */
2599 im->graph_col[GRC_ARROW]);
2601 gfx_new_area(im, im->xorigin - 3, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin, im->yorigin - im->ysize - 7, /* vertical */
2602 im->graph_col[GRC_ARROW]);
2604 if (im->second_axis_scale != 0){
2605 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2606 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2607 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2609 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2610 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2611 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2612 im->graph_col[GRC_ARROW]);
2623 double X0, Y0; /* points for filled graph and more */
2624 struct gfx_color_t water_color;
2626 if (im->draw_3d_border > 0) {
2627 /* draw 3d border */
2628 i = im->draw_3d_border;
2629 gfx_new_area(im, 0, im->yimg,
2630 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2631 gfx_add_point(im, im->ximg - i, i);
2632 gfx_add_point(im, im->ximg, 0);
2633 gfx_add_point(im, 0, 0);
2635 gfx_new_area(im, i, im->yimg - i,
2637 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2638 gfx_add_point(im, im->ximg, 0);
2639 gfx_add_point(im, im->ximg, im->yimg);
2640 gfx_add_point(im, 0, im->yimg);
2643 if (im->draw_x_grid == 1)
2645 if (im->draw_y_grid == 1) {
2646 if (im->logarithmic) {
2647 res = horizontal_log_grid(im);
2649 res = draw_horizontal_grid(im);
2652 /* dont draw horizontal grid if there is no min and max val */
2654 char *nodata = "No Data found";
2656 gfx_text(im, im->ximg / 2,
2659 im->graph_col[GRC_FONT],
2661 text_prop[TEXT_PROP_AXIS].
2664 GFX_H_CENTER, GFX_V_CENTER, nodata);
2668 /* yaxis unit description */
2669 if (im->ylegend[0] != '\0'){
2671 im->xOriginLegendY+10,
2673 im->graph_col[GRC_FONT],
2675 text_prop[TEXT_PROP_UNIT].
2678 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2681 if (im->second_axis_legend[0] != '\0'){
2683 im->xOriginLegendY2+10,
2684 im->yOriginLegendY2,
2685 im->graph_col[GRC_FONT],
2686 im->text_prop[TEXT_PROP_UNIT].font_desc,
2688 RRDGRAPH_YLEGEND_ANGLE,
2689 GFX_H_CENTER, GFX_V_CENTER,
2690 im->second_axis_legend);
2695 im->xOriginTitle, im->yOriginTitle+6,
2696 im->graph_col[GRC_FONT],
2698 text_prop[TEXT_PROP_TITLE].
2700 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2701 /* rrdtool 'logo' */
2702 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2703 water_color = im->graph_col[GRC_FONT];
2704 water_color.alpha = 0.3;
2705 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2706 gfx_text(im, xpos, 5,
2709 text_prop[TEXT_PROP_WATERMARK].
2710 font_desc, im->tabwidth,
2711 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2713 /* graph watermark */
2714 if (im->watermark[0] != '\0') {
2715 water_color = im->graph_col[GRC_FONT];
2716 water_color.alpha = 0.3;
2718 im->ximg / 2, im->yimg - 6,
2721 text_prop[TEXT_PROP_WATERMARK].
2722 font_desc, im->tabwidth, 0,
2723 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2727 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2728 for (i = 0; i < im->gdes_c; i++) {
2729 if (im->gdes[i].legend[0] == '\0')
2731 /* im->gdes[i].leg_y is the bottom of the legend */
2732 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2733 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2734 gfx_text(im, X0, Y0,
2735 im->graph_col[GRC_FONT],
2738 [TEXT_PROP_LEGEND].font_desc,
2740 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2741 /* The legend for GRAPH items starts with "M " to have
2742 enough space for the box */
2743 if (im->gdes[i].gf != GF_PRINT &&
2744 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2748 boxH = gfx_get_text_width(im, 0,
2753 im->tabwidth, "o") * 1.2;
2755 /* shift the box up a bit */
2758 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2760 cairo_new_path(im->cr);
2761 cairo_set_line_width(im->cr, 1.0);
2764 X0 + boxH, Y0 - boxV / 2,
2765 1.0, im->gdes[i].col);
2767 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2769 cairo_new_path(im->cr);
2770 cairo_set_line_width(im->cr, 1.0);
2773 X0 + boxH / 2, Y0 - boxV,
2774 1.0, im->gdes[i].col);
2776 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2778 cairo_new_path(im->cr);
2779 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2782 X0 + boxH, Y0 - boxV,
2783 im->gdes[i].linewidth, im->gdes[i].col);
2786 /* make sure transparent colors show up the same way as in the graph */
2789 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2790 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2792 gfx_new_area(im, X0, Y0 - boxV, X0,
2793 Y0, X0 + boxH, Y0, im->gdes[i].col);
2794 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2797 cairo_new_path(im->cr);
2798 cairo_set_line_width(im->cr, 1.0);
2801 gfx_line_fit(im, &X0, &Y0);
2802 gfx_line_fit(im, &X1, &Y1);
2803 cairo_move_to(im->cr, X0, Y0);
2804 cairo_line_to(im->cr, X1, Y0);
2805 cairo_line_to(im->cr, X1, Y1);
2806 cairo_line_to(im->cr, X0, Y1);
2807 cairo_close_path(im->cr);
2808 cairo_set_source_rgba(im->cr,
2809 im->graph_col[GRC_FRAME].red,
2810 im->graph_col[GRC_FRAME].green,
2811 im->graph_col[GRC_FRAME].blue,
2812 im->graph_col[GRC_FRAME].alpha);
2814 if (im->gdes[i].dash) {
2815 /* make box borders in legend dashed if the graph is dashed */
2819 cairo_set_dash(im->cr, dashes, 1, 0.0);
2821 cairo_stroke(im->cr);
2822 cairo_restore(im->cr);
2829 /*****************************************************
2830 * lazy check make sure we rely need to create this graph
2831 *****************************************************/
2838 struct stat imgstat;
2841 return 0; /* no lazy option */
2842 if (strlen(im->graphfile) == 0)
2843 return 0; /* inmemory option */
2844 if (stat(im->graphfile, &imgstat) != 0)
2845 return 0; /* can't stat */
2846 /* one pixel in the existing graph is more then what we would
2848 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2850 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2851 return 0; /* the file does not exist */
2852 switch (im->imgformat) {
2854 size = PngSize(fd, &(im->ximg), &(im->yimg));
2864 int graph_size_location(
2869 /* The actual size of the image to draw is determined from
2870 ** several sources. The size given on the command line is
2871 ** the graph area but we need more as we have to draw labels
2872 ** and other things outside the graph area. If the option
2873 ** --full-size-mode is selected the size defines the total
2874 ** image size and the size available for the graph is
2878 /** +---+-----------------------------------+
2879 ** | y |...............graph title.........|
2880 ** | +---+-------------------------------+
2884 ** | s | x | main graph area |
2889 ** | l | b +-------------------------------+
2890 ** | e | l | x axis labels |
2891 ** +---+---+-------------------------------+
2892 ** |....................legends............|
2893 ** +---------------------------------------+
2895 ** +---------------------------------------+
2898 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2899 0, Xylabel = 0, Xmain = 0, Ymain =
2900 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2902 // no legends and no the shall be plotted it's easy
2903 if (im->extra_flags & ONLY_GRAPH) {
2905 im->ximg = im->xsize;
2906 im->yimg = im->ysize;
2907 im->yorigin = im->ysize;
2912 if(im->watermark[0] != '\0') {
2913 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2916 // calculate the width of the left vertical legend
2917 if (im->ylegend[0] != '\0') {
2918 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2921 // calculate the width of the right vertical legend
2922 if (im->second_axis_legend[0] != '\0') {
2923 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2926 Xvertical2 = Xspacing;
2929 if (im->title[0] != '\0') {
2930 /* The title is placed "inbetween" two text lines so it
2931 ** automatically has some vertical spacing. The horizontal
2932 ** spacing is added here, on each side.
2934 /* if necessary, reduce the font size of the title until it fits the image width */
2935 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2938 // we have no title; get a little clearing from the top
2939 Ytitle = 1.5 * Yspacing;
2943 if (im->draw_x_grid) {
2944 // calculate the height of the horizontal labelling
2945 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2947 if (im->draw_y_grid || im->forceleftspace) {
2948 // calculate the width of the vertical labelling
2950 gfx_get_text_width(im, 0,
2951 im->text_prop[TEXT_PROP_AXIS].font_desc,
2952 im->tabwidth, "0") * im->unitslength;
2956 // add some space to the labelling
2957 Xylabel += Xspacing;
2959 /* If the legend is printed besides the graph the width has to be
2960 ** calculated first. Placing the legend north or south of the
2961 ** graph requires the width calculation first, so the legend is
2962 ** skipped for the moment.
2964 im->legendheight = 0;
2965 im->legendwidth = 0;
2966 if (!(im->extra_flags & NOLEGEND)) {
2967 if(im->legendposition == WEST || im->legendposition == EAST){
2968 if (leg_place(im, 1) == -1){
2974 if (im->extra_flags & FULL_SIZE_MODE) {
2976 /* The actual size of the image to draw has been determined by the user.
2977 ** The graph area is the space remaining after accounting for the legend,
2978 ** the watermark, the axis labels, and the title.
2980 im->ximg = im->xsize;
2981 im->yimg = im->ysize;
2985 /* Now calculate the total size. Insert some spacing where
2986 desired. im->xorigin and im->yorigin need to correspond
2987 with the lower left corner of the main graph area or, if
2988 this one is not set, the imaginary box surrounding the
2990 /* Initial size calculation for the main graph area */
2992 Xmain -= Xylabel;// + Xspacing;
2993 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2994 Xmain -= im->legendwidth;// + Xspacing;
2996 if (im->second_axis_scale != 0){
2999 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3003 Xmain -= Xvertical + Xvertical2;
3005 /* limit the remaining space to 0 */
3011 /* Putting the legend north or south, the height can now be calculated */
3012 if (!(im->extra_flags & NOLEGEND)) {
3013 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3014 im->legendwidth = im->ximg;
3015 if (leg_place(im, 0) == -1){
3021 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3022 Ymain -= Yxlabel + im->legendheight;
3028 /* reserve space for the title *or* some padding above the graph */
3031 /* reserve space for padding below the graph */
3032 if (im->extra_flags & NOLEGEND) {
3036 if (im->watermark[0] != '\0') {
3037 Ymain -= Ywatermark;
3039 /* limit the remaining height to 0 */
3044 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3046 /* The actual size of the image to draw is determined from
3047 ** several sources. The size given on the command line is
3048 ** the graph area but we need more as we have to draw labels
3049 ** and other things outside the graph area.
3053 Xmain = im->xsize; // + Xspacing;
3057 im->ximg = Xmain + Xylabel;
3058 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3059 im->ximg += Xspacing;
3062 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3063 im->ximg += im->legendwidth;// + Xspacing;
3065 if (im->second_axis_scale != 0){
3066 im->ximg += Xylabel;
3069 im->ximg += Xvertical + Xvertical2;
3071 if (!(im->extra_flags & NOLEGEND)) {
3072 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3073 im->legendwidth = im->ximg;
3074 if (leg_place(im, 0) == -1){
3080 im->yimg = Ymain + Yxlabel;
3081 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3082 im->yimg += im->legendheight;
3085 /* reserve space for the title *or* some padding above the graph */
3089 im->yimg += 1.5 * Yspacing;
3091 /* reserve space for padding below the graph */
3092 if (im->extra_flags & NOLEGEND) {
3093 im->yimg += Yspacing;
3096 if (im->watermark[0] != '\0') {
3097 im->yimg += Ywatermark;
3102 /* In case of putting the legend in west or east position the first
3103 ** legend calculation might lead to wrong positions if some items
3104 ** are not aligned on the left hand side (e.g. centered) as the
3105 ** legendwidth wight have been increased after the item was placed.
3106 ** In this case the positions have to be recalculated.
3108 if (!(im->extra_flags & NOLEGEND)) {
3109 if(im->legendposition == WEST || im->legendposition == EAST){
3110 if (leg_place(im, 0) == -1){
3116 /* After calculating all dimensions
3117 ** it is now possible to calculate
3120 switch(im->legendposition){
3122 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3123 im->yOriginTitle = 0;
3125 im->xOriginLegend = 0;
3126 im->yOriginLegend = Ytitle;
3128 im->xOriginLegendY = 0;
3129 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3131 im->xorigin = Xvertical + Xylabel;
3132 im->yorigin = Ytitle + im->legendheight + Ymain;
3134 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3135 if (im->second_axis_scale != 0){
3136 im->xOriginLegendY2 += Xylabel;
3138 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3143 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3144 im->yOriginTitle = 0;
3146 im->xOriginLegend = 0;
3147 im->yOriginLegend = Ytitle;
3149 im->xOriginLegendY = im->legendwidth;
3150 im->yOriginLegendY = Ytitle + (Ymain / 2);
3152 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3153 im->yorigin = Ytitle + Ymain;
3155 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3156 if (im->second_axis_scale != 0){
3157 im->xOriginLegendY2 += Xylabel;
3159 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3164 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3165 im->yOriginTitle = 0;
3167 im->xOriginLegend = 0;
3168 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3170 im->xOriginLegendY = 0;
3171 im->yOriginLegendY = Ytitle + (Ymain / 2);
3173 im->xorigin = Xvertical + Xylabel;
3174 im->yorigin = Ytitle + Ymain;
3176 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3177 if (im->second_axis_scale != 0){
3178 im->xOriginLegendY2 += Xylabel;
3180 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3185 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3186 im->yOriginTitle = 0;
3188 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3189 if (im->second_axis_scale != 0){
3190 im->xOriginLegend += Xylabel;
3192 im->yOriginLegend = Ytitle;
3194 im->xOriginLegendY = 0;
3195 im->yOriginLegendY = Ytitle + (Ymain / 2);
3197 im->xorigin = Xvertical + Xylabel;
3198 im->yorigin = Ytitle + Ymain;
3200 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3201 if (im->second_axis_scale != 0){
3202 im->xOriginLegendY2 += Xylabel;
3204 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3206 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3207 im->xOriginTitle += Xspacing;
3208 im->xOriginLegend += Xspacing;
3209 im->xOriginLegendY += Xspacing;
3210 im->xorigin += Xspacing;
3211 im->xOriginLegendY2 += Xspacing;
3221 static cairo_status_t cairo_output(
3225 unsigned int length)
3227 image_desc_t *im = (image_desc_t*)closure;
3229 im->rendered_image =
3230 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3231 if (im->rendered_image == NULL)
3232 return CAIRO_STATUS_WRITE_ERROR;
3233 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3234 im->rendered_image_size += length;
3235 return CAIRO_STATUS_SUCCESS;
3238 /* draw that picture thing ... */
3243 int lazy = lazy_check(im);
3244 double areazero = 0.0;
3245 graph_desc_t *lastgdes = NULL;
3248 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3250 /* pull the data from the rrd files ... */
3251 if (data_fetch(im) == -1)
3253 /* evaluate VDEF and CDEF operations ... */
3254 if (data_calc(im) == -1)
3256 /* calculate and PRINT and GPRINT definitions. We have to do it at
3257 * this point because it will affect the length of the legends
3258 * if there are no graph elements (i==0) we stop here ...
3259 * if we are lazy, try to quit ...
3265 /* if we want and can be lazy ... quit now */
3269 /**************************************************************
3270 *** Calculating sizes and locations became a bit confusing ***
3271 *** so I moved this into a separate function. ***
3272 **************************************************************/
3273 if (graph_size_location(im, i) == -1)
3276 info.u_cnt = im->xorigin;
3277 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3278 info.u_cnt = im->yorigin - im->ysize;
3279 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3280 info.u_cnt = im->xsize;
3281 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3282 info.u_cnt = im->ysize;
3283 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3284 info.u_cnt = im->ximg;
3285 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3286 info.u_cnt = im->yimg;
3287 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3288 info.u_cnt = im->start;
3289 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3290 info.u_cnt = im->end;
3291 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3293 /* if we want and can be lazy ... quit now */
3297 /* get actual drawing data and find min and max values */
3298 if (data_proc(im) == -1)
3300 if (!im->logarithmic) {
3304 /* identify si magnitude Kilo, Mega Giga ? */
3305 if (!im->rigid && !im->logarithmic)
3306 expand_range(im); /* make sure the upper and lower limit are
3309 info.u_val = im->minval;
3310 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3311 info.u_val = im->maxval;
3312 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3315 if (!calc_horizontal_grid(im))
3320 apply_gridfit(im); */
3321 /* the actual graph is created by going through the individual
3322 graph elements and then drawing them */
3323 cairo_surface_destroy(im->surface);
3324 switch (im->imgformat) {
3327 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3328 im->ximg * im->zoom,
3329 im->yimg * im->zoom);
3333 im->surface = strlen(im->graphfile)
3334 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3335 im->yimg * im->zoom)
3336 : cairo_pdf_surface_create_for_stream
3337 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3341 im->surface = strlen(im->graphfile)
3343 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3344 im->yimg * im->zoom)
3345 : cairo_ps_surface_create_for_stream
3346 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3350 im->surface = strlen(im->graphfile)
3352 cairo_svg_surface_create(im->
3354 im->ximg * im->zoom, im->yimg * im->zoom)
3355 : cairo_svg_surface_create_for_stream
3356 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3357 cairo_svg_surface_restrict_to_version
3358 (im->surface, CAIRO_SVG_VERSION_1_1);
3361 cairo_destroy(im->cr);
3362 im->cr = cairo_create(im->surface);
3363 cairo_set_antialias(im->cr, im->graph_antialias);
3364 cairo_scale(im->cr, im->zoom, im->zoom);
3365 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3366 gfx_new_area(im, 0, 0, 0, im->yimg,
3367 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3368 gfx_add_point(im, im->ximg, 0);
3370 gfx_new_area(im, im->xorigin,
3373 im->xsize, im->yorigin,
3376 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3377 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3379 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3380 im->xsize, im->ysize + 2.0);
3382 if (im->minval > 0.0)
3383 areazero = im->minval;
3384 if (im->maxval < 0.0)
3385 areazero = im->maxval;
3386 for (i = 0; i < im->gdes_c; i++) {
3387 switch (im->gdes[i].gf) {
3401 for (ii = 0; ii < im->xsize; ii++) {
3402 if (!isnan(im->gdes[i].p_data[ii])
3403 && im->gdes[i].p_data[ii] != 0.0) {
3404 if (im->gdes[i].yrule > 0) {
3411 im->ysize, 1.0, im->gdes[i].col);
3412 } else if (im->gdes[i].yrule < 0) {
3415 im->yorigin - im->ysize - 1.0,
3417 im->yorigin - im->ysize -
3420 im->ysize, 1.0, im->gdes[i].col);
3427 /* fix data points at oo and -oo */
3428 for (ii = 0; ii < im->xsize; ii++) {
3429 if (isinf(im->gdes[i].p_data[ii])) {
3430 if (im->gdes[i].p_data[ii] > 0) {
3431 im->gdes[i].p_data[ii] = im->maxval;
3433 im->gdes[i].p_data[ii] = im->minval;
3439 /* *******************************************************
3444 -------|--t-1--t--------------------------------
3446 if we know the value at time t was a then
3447 we draw a square from t-1 to t with the value a.
3449 ********************************************************* */
3450 if (im->gdes[i].col.alpha != 0.0) {
3451 /* GF_LINE and friend */
3452 if (im->gdes[i].gf == GF_LINE) {
3453 double last_y = 0.0;
3457 cairo_new_path(im->cr);
3458 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3459 if (im->gdes[i].dash) {
3460 cairo_set_dash(im->cr,
3461 im->gdes[i].p_dashes,
3462 im->gdes[i].ndash, im->gdes[i].offset);
3465 for (ii = 1; ii < im->xsize; ii++) {
3466 if (isnan(im->gdes[i].p_data[ii])
3467 || (im->slopemode == 1
3468 && isnan(im->gdes[i].p_data[ii - 1]))) {
3473 last_y = ytr(im, im->gdes[i].p_data[ii]);
3474 if (im->slopemode == 0) {
3475 double x = ii - 1 + im->xorigin;
3478 gfx_line_fit(im, &x, &y);
3479 cairo_move_to(im->cr, x, y);
3480 x = ii + im->xorigin;
3482 gfx_line_fit(im, &x, &y);
3483 cairo_line_to(im->cr, x, y);
3485 double x = ii - 1 + im->xorigin;
3487 ytr(im, im->gdes[i].p_data[ii - 1]);
3488 gfx_line_fit(im, &x, &y);
3489 cairo_move_to(im->cr, x, y);
3490 x = ii + im->xorigin;
3492 gfx_line_fit(im, &x, &y);
3493 cairo_line_to(im->cr, x, y);
3497 double x1 = ii + im->xorigin;
3498 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3500 if (im->slopemode == 0
3501 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3502 double x = ii - 1 + im->xorigin;
3505 gfx_line_fit(im, &x, &y);
3506 cairo_line_to(im->cr, x, y);
3509 gfx_line_fit(im, &x1, &y1);
3510 cairo_line_to(im->cr, x1, y1);
3513 cairo_set_source_rgba(im->cr,
3519 col.blue, im->gdes[i].col.alpha);
3520 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3521 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3522 cairo_stroke(im->cr);
3523 cairo_restore(im->cr);
3527 (double *) malloc(sizeof(double) * im->xsize * 2);
3529 (double *) malloc(sizeof(double) * im->xsize * 2);
3531 (double *) malloc(sizeof(double) * im->xsize * 2);
3533 (double *) malloc(sizeof(double) * im->xsize * 2);
3536 for (ii = 0; ii <= im->xsize; ii++) {
3539 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3545 AlmostEqual2sComplement(foreY
3549 AlmostEqual2sComplement(foreY
3559 foreY[cntI], im->gdes[i].col);
3560 while (cntI < idxI) {
3565 AlmostEqual2sComplement(foreY
3569 AlmostEqual2sComplement(foreY
3576 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3578 gfx_add_point(im, backX[idxI], backY[idxI]);
3584 AlmostEqual2sComplement(backY
3588 AlmostEqual2sComplement(backY
3595 gfx_add_point(im, backX[idxI], backY[idxI]);
3605 if (ii == im->xsize)
3607 if (im->slopemode == 0 && ii == 0) {
3610 if (isnan(im->gdes[i].p_data[ii])) {
3614 ytop = ytr(im, im->gdes[i].p_data[ii]);
3615 if (lastgdes && im->gdes[i].stack) {
3616 ybase = ytr(im, lastgdes->p_data[ii]);
3618 ybase = ytr(im, areazero);
3620 if (ybase == ytop) {
3626 double extra = ytop;
3631 if (im->slopemode == 0) {
3632 backY[++idxI] = ybase - 0.2;
3633 backX[idxI] = ii + im->xorigin - 1;
3634 foreY[idxI] = ytop + 0.2;
3635 foreX[idxI] = ii + im->xorigin - 1;
3637 backY[++idxI] = ybase - 0.2;
3638 backX[idxI] = ii + im->xorigin;
3639 foreY[idxI] = ytop + 0.2;
3640 foreX[idxI] = ii + im->xorigin;
3642 /* close up any remaining area */
3647 } /* else GF_LINE */
3649 /* if color != 0x0 */
3650 /* make sure we do not run into trouble when stacking on NaN */
3651 for (ii = 0; ii < im->xsize; ii++) {
3652 if (isnan(im->gdes[i].p_data[ii])) {
3653 if (lastgdes && (im->gdes[i].stack)) {
3654 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3656 im->gdes[i].p_data[ii] = areazero;
3660 lastgdes = &(im->gdes[i]);
3664 ("STACK should already be turned into LINE or AREA here");
3669 cairo_reset_clip(im->cr);
3671 /* grid_paint also does the text */
3672 if (!(im->extra_flags & ONLY_GRAPH))
3674 if (!(im->extra_flags & ONLY_GRAPH))
3676 /* the RULES are the last thing to paint ... */
3677 for (i = 0; i < im->gdes_c; i++) {
3679 switch (im->gdes[i].gf) {
3681 if (im->gdes[i].yrule >= im->minval
3682 && im->gdes[i].yrule <= im->maxval) {
3684 if (im->gdes[i].dash) {
3685 cairo_set_dash(im->cr,
3686 im->gdes[i].p_dashes,
3687 im->gdes[i].ndash, im->gdes[i].offset);
3689 gfx_line(im, im->xorigin,
3690 ytr(im, im->gdes[i].yrule),
3691 im->xorigin + im->xsize,
3692 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3693 cairo_stroke(im->cr);
3694 cairo_restore(im->cr);
3698 if (im->gdes[i].xrule >= im->start
3699 && im->gdes[i].xrule <= im->end) {
3701 if (im->gdes[i].dash) {
3702 cairo_set_dash(im->cr,
3703 im->gdes[i].p_dashes,
3704 im->gdes[i].ndash, im->gdes[i].offset);
3707 xtr(im, im->gdes[i].xrule),
3708 im->yorigin, xtr(im,
3712 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3713 cairo_stroke(im->cr);
3714 cairo_restore(im->cr);
3723 switch (im->imgformat) {
3726 cairo_status_t status;
3728 status = strlen(im->graphfile) ?
3729 cairo_surface_write_to_png(im->surface, im->graphfile)
3730 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3733 if (status != CAIRO_STATUS_SUCCESS) {
3734 rrd_set_error("Could not save png to '%s'", im->graphfile);
3740 if (strlen(im->graphfile)) {
3741 cairo_show_page(im->cr);
3743 cairo_surface_finish(im->surface);
3752 /*****************************************************
3754 *****************************************************/
3761 if ((im->gdes = (graph_desc_t *)
3762 rrd_realloc(im->gdes, (im->gdes_c)
3763 * sizeof(graph_desc_t))) == NULL) {
3764 rrd_set_error("realloc graph_descs");
3769 im->gdes[im->gdes_c - 1].step = im->step;
3770 im->gdes[im->gdes_c - 1].step_orig = im->step;
3771 im->gdes[im->gdes_c - 1].stack = 0;
3772 im->gdes[im->gdes_c - 1].linewidth = 0;
3773 im->gdes[im->gdes_c - 1].debug = 0;
3774 im->gdes[im->gdes_c - 1].start = im->start;
3775 im->gdes[im->gdes_c - 1].start_orig = im->start;
3776 im->gdes[im->gdes_c - 1].end = im->end;
3777 im->gdes[im->gdes_c - 1].end_orig = im->end;
3778 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3779 im->gdes[im->gdes_c - 1].data = NULL;
3780 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3781 im->gdes[im->gdes_c - 1].data_first = 0;
3782 im->gdes[im->gdes_c - 1].p_data = NULL;
3783 im->gdes[im->gdes_c - 1].rpnp = NULL;
3784 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3785 im->gdes[im->gdes_c - 1].shift = 0.0;
3786 im->gdes[im->gdes_c - 1].dash = 0;
3787 im->gdes[im->gdes_c - 1].ndash = 0;
3788 im->gdes[im->gdes_c - 1].offset = 0;
3789 im->gdes[im->gdes_c - 1].col.red = 0.0;
3790 im->gdes[im->gdes_c - 1].col.green = 0.0;
3791 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3792 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3793 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3794 im->gdes[im->gdes_c - 1].format[0] = '\0';
3795 im->gdes[im->gdes_c - 1].strftm = 0;
3796 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3797 im->gdes[im->gdes_c - 1].ds = -1;
3798 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3799 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3800 im->gdes[im->gdes_c - 1].yrule = DNAN;
3801 im->gdes[im->gdes_c - 1].xrule = 0;
3805 /* copies input untill the first unescaped colon is found
3806 or until input ends. backslashes have to be escaped as well */
3808 const char *const input,
3814 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3815 if (input[inp] == '\\'
3816 && input[inp + 1] != '\0'
3817 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3818 output[outp++] = input[++inp];
3820 output[outp++] = input[inp];
3823 output[outp] = '\0';
3827 /* Now just a wrapper around rrd_graph_v */
3839 rrd_info_t *grinfo = NULL;
3842 grinfo = rrd_graph_v(argc, argv);
3848 if (strcmp(walker->key, "image_info") == 0) {
3851 (char**)rrd_realloc((*prdata),
3852 (prlines + 1) * sizeof(char *))) == NULL) {
3853 rrd_set_error("realloc prdata");
3856 /* imginfo goes to position 0 in the prdata array */
3857 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3858 + 2) * sizeof(char));
3859 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3860 (*prdata)[prlines] = NULL;
3862 /* skip anything else */
3863 walker = walker->next;
3871 if (strcmp(walker->key, "image_width") == 0) {
3872 *xsize = walker->value.u_cnt;
3873 } else if (strcmp(walker->key, "image_height") == 0) {
3874 *ysize = walker->value.u_cnt;
3875 } else if (strcmp(walker->key, "value_min") == 0) {
3876 *ymin = walker->value.u_val;
3877 } else if (strcmp(walker->key, "value_max") == 0) {
3878 *ymax = walker->value.u_val;
3879 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3882 (char**)rrd_realloc((*prdata),
3883 (prlines + 1) * sizeof(char *))) == NULL) {
3884 rrd_set_error("realloc prdata");
3887 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3888 + 2) * sizeof(char));
3889 (*prdata)[prlines] = NULL;
3890 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3891 } else if (strcmp(walker->key, "image") == 0) {
3892 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3893 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3894 rrd_set_error("writing image");
3898 /* skip anything else */
3899 walker = walker->next;
3901 rrd_info_free(grinfo);
3906 /* Some surgery done on this function, it became ridiculously big.
3908 ** - initializing now in rrd_graph_init()
3909 ** - options parsing now in rrd_graph_options()
3910 ** - script parsing now in rrd_graph_script()
3912 rrd_info_t *rrd_graph_v(
3919 rrd_graph_init(&im);
3920 /* a dummy surface so that we can measure text sizes for placements */
3921 old_locale = setlocale(LC_NUMERIC, "C");
3922 rrd_graph_options(argc, argv, &im);
3923 if (rrd_test_error()) {
3924 rrd_info_free(im.grinfo);
3929 if (optind >= argc) {
3930 rrd_info_free(im.grinfo);
3932 rrd_set_error("missing filename");
3936 if (strlen(argv[optind]) >= MAXPATH) {
3937 rrd_set_error("filename (including path) too long");
3938 rrd_info_free(im.grinfo);
3943 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3944 im.graphfile[MAXPATH - 1] = '\0';
3946 if (strcmp(im.graphfile, "-") == 0) {
3947 im.graphfile[0] = '\0';
3950 rrd_graph_script(argc, argv, &im, 1);
3951 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3953 if (rrd_test_error()) {
3954 rrd_info_free(im.grinfo);
3959 /* Everything is now read and the actual work can start */
3961 if (graph_paint(&im) == -1) {
3962 rrd_info_free(im.grinfo);
3968 /* The image is generated and needs to be output.
3969 ** Also, if needed, print a line with information about the image.
3977 path = strdup(im.graphfile);
3978 filename = basename(path);
3980 sprintf_alloc(im.imginfo,
3983 im.ximg), (long) (im.zoom * im.yimg));
3984 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3988 if (im.rendered_image) {
3991 img.u_blo.size = im.rendered_image_size;
3992 img.u_blo.ptr = im.rendered_image;
3993 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4002 image_desc_t *im,int prop,char *font, double size ){
4004 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4005 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4006 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4009 im->text_prop[prop].size = size;
4011 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4012 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4016 void rrd_graph_init(
4021 char *deffont = getenv("RRD_DEFAULT_FONT");
4022 static PangoFontMap *fontmap = NULL;
4023 PangoContext *context;
4030 im->daemon_addr = NULL;
4031 im->draw_x_grid = 1;
4032 im->draw_y_grid = 1;
4033 im->draw_3d_border = 2;
4034 im->dynamic_labels = 0;
4035 im->extra_flags = 0;
4036 im->font_options = cairo_font_options_create();
4037 im->forceleftspace = 0;
4040 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4041 im->grid_dash_off = 1;
4042 im->grid_dash_on = 1;
4044 im->grinfo = (rrd_info_t *) NULL;
4045 im->grinfo_current = (rrd_info_t *) NULL;
4046 im->imgformat = IF_PNG;
4049 im->legenddirection = TOP_DOWN;
4050 im->legendheight = 0;
4051 im->legendposition = SOUTH;
4052 im->legendwidth = 0;
4053 im->logarithmic = 0;
4059 im->rendered_image_size = 0;
4060 im->rendered_image = NULL;
4064 im->tabwidth = 40.0;
4065 im->title[0] = '\0';
4066 im->unitsexponent = 9999;
4067 im->unitslength = 6;
4068 im->viewfactor = 1.0;
4069 im->watermark[0] = '\0';
4070 im->with_markup = 0;
4072 im->xlab_user.minsec = -1;
4074 im->xOriginLegend = 0;
4075 im->xOriginLegendY = 0;
4076 im->xOriginLegendY2 = 0;
4077 im->xOriginTitle = 0;
4079 im->ygridstep = DNAN;
4081 im->ylegend[0] = '\0';
4082 im->second_axis_scale = 0; /* 0 disables it */
4083 im->second_axis_shift = 0; /* no shift by default */
4084 im->second_axis_legend[0] = '\0';
4085 im->second_axis_format[0] = '\0';
4087 im->yOriginLegend = 0;
4088 im->yOriginLegendY = 0;
4089 im->yOriginLegendY2 = 0;
4090 im->yOriginTitle = 0;
4094 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4095 im->cr = cairo_create(im->surface);
4097 for (i = 0; i < DIM(text_prop); i++) {
4098 im->text_prop[i].size = -1;
4099 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4102 if (fontmap == NULL){
4103 fontmap = pango_cairo_font_map_get_default();
4106 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4108 pango_cairo_context_set_resolution(context, 100);
4110 pango_cairo_update_context(im->cr,context);
4112 im->layout = pango_layout_new(context);
4114 // im->layout = pango_cairo_create_layout(im->cr);
4117 cairo_font_options_set_hint_style
4118 (im->font_options, CAIRO_HINT_STYLE_FULL);
4119 cairo_font_options_set_hint_metrics
4120 (im->font_options, CAIRO_HINT_METRICS_ON);
4121 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4125 for (i = 0; i < DIM(graph_col); i++)
4126 im->graph_col[i] = graph_col[i];
4132 void rrd_graph_options(
4139 char *parsetime_error = NULL;
4140 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4141 time_t start_tmp = 0, end_tmp = 0;
4143 rrd_time_value_t start_tv, end_tv;
4144 long unsigned int color;
4146 /* defines for long options without a short equivalent. should be bytes,
4147 and may not collide with (the ASCII value of) short options */
4148 #define LONGOPT_UNITS_SI 255
4151 struct option long_options[] = {
4152 { "alt-autoscale", no_argument, 0, 'A'},
4153 { "imgformat", required_argument, 0, 'a'},
4154 { "font-smoothing-threshold", required_argument, 0, 'B'},
4155 { "base", required_argument, 0, 'b'},
4156 { "color", required_argument, 0, 'c'},
4157 { "full-size-mode", no_argument, 0, 'D'},
4158 { "daemon", required_argument, 0, 'd'},
4159 { "slope-mode", no_argument, 0, 'E'},
4160 { "end", required_argument, 0, 'e'},
4161 { "force-rules-legend", no_argument, 0, 'F'},
4162 { "imginfo", required_argument, 0, 'f'},
4163 { "graph-render-mode", required_argument, 0, 'G'},
4164 { "no-legend", no_argument, 0, 'g'},
4165 { "height", required_argument, 0, 'h'},
4166 { "no-minor", no_argument, 0, 'I'},
4167 { "interlaced", no_argument, 0, 'i'},
4168 { "alt-autoscale-min", no_argument, 0, 'J'},
4169 { "only-graph", no_argument, 0, 'j'},
4170 { "units-length", required_argument, 0, 'L'},
4171 { "lower-limit", required_argument, 0, 'l'},
4172 { "alt-autoscale-max", no_argument, 0, 'M'},
4173 { "zoom", required_argument, 0, 'm'},
4174 { "no-gridfit", no_argument, 0, 'N'},
4175 { "font", required_argument, 0, 'n'},
4176 { "logarithmic", no_argument, 0, 'o'},
4177 { "pango-markup", no_argument, 0, 'P'},
4178 { "font-render-mode", required_argument, 0, 'R'},
4179 { "rigid", no_argument, 0, 'r'},
4180 { "step", required_argument, 0, 'S'},
4181 { "start", required_argument, 0, 's'},
4182 { "tabwidth", required_argument, 0, 'T'},
4183 { "title", required_argument, 0, 't'},
4184 { "upper-limit", required_argument, 0, 'u'},
4185 { "vertical-label", required_argument, 0, 'v'},
4186 { "watermark", required_argument, 0, 'W'},
4187 { "width", required_argument, 0, 'w'},
4188 { "units-exponent", required_argument, 0, 'X'},
4189 { "x-grid", required_argument, 0, 'x'},
4190 { "alt-y-grid", no_argument, 0, 'Y'},
4191 { "y-grid", required_argument, 0, 'y'},
4192 { "lazy", no_argument, 0, 'z'},
4193 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4194 { "alt-y-mrtg", no_argument, 0, 1000}, /* this has no effect it is just here to save old apps from crashing when they use it */
4195 { "disable-rrdtool-tag",no_argument, 0, 1001},
4196 { "right-axis", required_argument, 0, 1002},
4197 { "right-axis-label", required_argument, 0, 1003},
4198 { "right-axis-format", required_argument, 0, 1004},
4199 { "legend-position", required_argument, 0, 1005},
4200 { "legend-direction", required_argument, 0, 1006},
4201 { "border", required_argument, 0, 1007},
4202 { "grid-dash", required_argument, 0, 1008},
4203 { "dynamic-labels", no_argument, 0, 1009},
4209 opterr = 0; /* initialize getopt */
4210 rrd_parsetime("end-24h", &start_tv);
4211 rrd_parsetime("now", &end_tv);
4213 int option_index = 0;
4215 int col_start, col_end;
4217 opt = getopt_long(argc, argv,
4218 "Aa:B:b:c:Dd:Ee:Ff:G:gh:IiJjL:l:Mm:Nn:oPR:rS:s:T:t:u:v:W:w:X:x:Yy:z",
4219 long_options, &option_index);
4224 im->extra_flags |= NOMINOR;
4227 im->extra_flags |= ALTYGRID;
4230 im->extra_flags |= ALTAUTOSCALE;
4233 im->extra_flags |= ALTAUTOSCALE_MIN;
4236 im->extra_flags |= ALTAUTOSCALE_MAX;
4239 im->extra_flags |= ONLY_GRAPH;
4242 im->extra_flags |= NOLEGEND;
4245 if (strcmp(optarg, "north") == 0) {
4246 im->legendposition = NORTH;
4247 } else if (strcmp(optarg, "west") == 0) {
4248 im->legendposition = WEST;
4249 } else if (strcmp(optarg, "south") == 0) {
4250 im->legendposition = SOUTH;
4251 } else if (strcmp(optarg, "east") == 0) {
4252 im->legendposition = EAST;
4254 rrd_set_error("unknown legend-position '%s'", optarg);
4259 if (strcmp(optarg, "topdown") == 0) {
4260 im->legenddirection = TOP_DOWN;
4261 } else if (strcmp(optarg, "bottomup") == 0) {
4262 im->legenddirection = BOTTOM_UP;
4264 rrd_set_error("unknown legend-position '%s'", optarg);
4269 im->extra_flags |= FORCE_RULES_LEGEND;
4272 im->extra_flags |= NO_RRDTOOL_TAG;
4274 case LONGOPT_UNITS_SI:
4275 if (im->extra_flags & FORCE_UNITS) {
4276 rrd_set_error("--units can only be used once!");
4279 if (strcmp(optarg, "si") == 0)
4280 im->extra_flags |= FORCE_UNITS_SI;
4282 rrd_set_error("invalid argument for --units: %s", optarg);
4287 im->unitsexponent = atoi(optarg);
4290 im->unitslength = atoi(optarg);
4291 im->forceleftspace = 1;
4294 im->tabwidth = atof(optarg);
4297 im->step = atoi(optarg);
4303 im->with_markup = 1;
4306 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4307 rrd_set_error("start time: %s", parsetime_error);
4312 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4313 rrd_set_error("end time: %s", parsetime_error);
4318 if (strcmp(optarg, "none") == 0) {
4319 im->draw_x_grid = 0;
4323 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4325 &im->xlab_user.gridst,
4327 &im->xlab_user.mgridst,
4329 &im->xlab_user.labst,
4330 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4331 strncpy(im->xlab_form, optarg + stroff,
4332 sizeof(im->xlab_form) - 1);
4333 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4335 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4336 rrd_set_error("unknown keyword %s", scan_gtm);
4339 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4341 rrd_set_error("unknown keyword %s", scan_mtm);
4344 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4345 rrd_set_error("unknown keyword %s", scan_ltm);
4348 im->xlab_user.minsec = 1;
4349 im->xlab_user.stst = im->xlab_form;
4351 rrd_set_error("invalid x-grid format");
4357 if (strcmp(optarg, "none") == 0) {
4358 im->draw_y_grid = 0;
4361 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4362 if (im->ygridstep <= 0) {
4363 rrd_set_error("grid step must be > 0");
4365 } else if (im->ylabfact < 1) {
4366 rrd_set_error("label factor must be > 0");
4370 rrd_set_error("invalid y-grid format");
4375 im->draw_3d_border = atoi(optarg);
4377 case 1008: /* grid-dash */
4381 &im->grid_dash_off) != 2) {
4382 rrd_set_error("expected grid-dash format float:float");
4386 case 1009: /* enable dynamic labels */
4387 im->dynamic_labels = 1;
4389 case 1002: /* right y axis */
4393 &im->second_axis_scale,
4394 &im->second_axis_shift) == 2) {
4395 if(im->second_axis_scale==0){
4396 rrd_set_error("the second_axis_scale must not be 0");
4400 rrd_set_error("invalid right-axis format expected scale:shift");
4405 strncpy(im->second_axis_legend,optarg,150);
4406 im->second_axis_legend[150]='\0';
4409 if (bad_format(optarg)){
4410 rrd_set_error("use either %le or %lf formats");
4413 strncpy(im->second_axis_format,optarg,150);
4414 im->second_axis_format[150]='\0';
4417 strncpy(im->ylegend, optarg, 150);
4418 im->ylegend[150] = '\0';
4421 im->maxval = atof(optarg);
4424 im->minval = atof(optarg);
4427 im->base = atol(optarg);
4428 if (im->base != 1024 && im->base != 1000) {
4430 ("the only sensible value for base apart from 1000 is 1024");
4435 long_tmp = atol(optarg);
4436 if (long_tmp < 10) {
4437 rrd_set_error("width below 10 pixels");
4440 im->xsize = long_tmp;
4443 long_tmp = atol(optarg);
4444 if (long_tmp < 10) {
4445 rrd_set_error("height below 10 pixels");
4448 im->ysize = long_tmp;
4451 im->extra_flags |= FULL_SIZE_MODE;
4454 /* interlaced png not supported at the moment */
4460 im->imginfo = optarg;
4464 (im->imgformat = if_conv(optarg)) == -1) {
4465 rrd_set_error("unsupported graphics format '%s'", optarg);
4476 im->logarithmic = 1;
4480 "%10[A-Z]#%n%8lx%n",
4481 col_nam, &col_start, &color, &col_end) == 2) {
4483 int col_len = col_end - col_start;
4488 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4496 (((color & 0xF000) *
4497 0x11000) | ((color & 0x0F00) *
4498 0x01100) | ((color &
4501 ((color & 0x000F) * 0x00011)
4505 color = (color << 8) + 0xff /* shift left by 8 */ ;
4510 rrd_set_error("the color format is #RRGGBB[AA]");
4513 if ((ci = grc_conv(col_nam)) != -1) {
4514 im->graph_col[ci] = gfx_hex_to_col(color);
4516 rrd_set_error("invalid color name '%s'", col_nam);
4520 rrd_set_error("invalid color def format");
4529 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4530 int sindex, propidx;
4532 if ((sindex = text_prop_conv(prop)) != -1) {
4533 for (propidx = sindex;
4534 propidx < TEXT_PROP_LAST; propidx++) {
4536 rrd_set_font_desc(im,propidx,NULL,size);
4538 if ((int) strlen(optarg) > end+2) {
4539 if (optarg[end] == ':') {
4540 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4543 ("expected : after font size in '%s'",
4548 /* only run the for loop for DEFAULT (0) for
4549 all others, we break here. woodo programming */
4550 if (propidx == sindex && sindex != 0)
4554 rrd_set_error("invalid fonttag '%s'", prop);
4558 rrd_set_error("invalid text property format");
4564 im->zoom = atof(optarg);
4565 if (im->zoom <= 0.0) {
4566 rrd_set_error("zoom factor must be > 0");
4571 strncpy(im->title, optarg, 150);
4572 im->title[150] = '\0';
4575 if (strcmp(optarg, "normal") == 0) {
4576 cairo_font_options_set_antialias
4577 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4578 cairo_font_options_set_hint_style
4579 (im->font_options, CAIRO_HINT_STYLE_FULL);
4580 } else if (strcmp(optarg, "light") == 0) {
4581 cairo_font_options_set_antialias
4582 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4583 cairo_font_options_set_hint_style
4584 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4585 } else if (strcmp(optarg, "mono") == 0) {
4586 cairo_font_options_set_antialias
4587 (im->font_options, CAIRO_ANTIALIAS_NONE);
4588 cairo_font_options_set_hint_style
4589 (im->font_options, CAIRO_HINT_STYLE_FULL);
4591 rrd_set_error("unknown font-render-mode '%s'", optarg);
4596 if (strcmp(optarg, "normal") == 0)
4597 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4598 else if (strcmp(optarg, "mono") == 0)
4599 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4601 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4606 /* not supported curently */
4609 strncpy(im->watermark, optarg, 100);
4610 im->watermark[99] = '\0';
4614 if (im->daemon_addr != NULL)
4616 rrd_set_error ("You cannot specify --daemon "
4621 im->daemon_addr = strdup(optarg);
4622 if (im->daemon_addr == NULL)
4624 rrd_set_error("strdup failed");
4632 rrd_set_error("unknown option '%c'", optopt);
4634 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4639 { /* try to connect to rrdcached */
4640 int status = rrdc_connect(im->daemon_addr);
4641 if (status != 0) return;
4644 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4645 pango_layout_context_changed(im->layout);
4649 if (im->logarithmic && im->minval <= 0) {
4651 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4655 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4656 /* error string is set in rrd_parsetime.c */
4660 if (start_tmp < 3600 * 24 * 365 * 10) {
4662 ("the first entry to fetch should be after 1980 (%ld)",
4667 if (end_tmp < start_tmp) {
4669 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4673 im->start = start_tmp;
4675 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4678 int rrd_graph_color(
4686 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4688 color = strstr(var, "#");
4689 if (color == NULL) {
4690 if (optional == 0) {
4691 rrd_set_error("Found no color in %s", err);
4698 long unsigned int col;
4700 rest = strstr(color, ":");
4707 sscanf(color, "#%6lx%n", &col, &n);
4708 col = (col << 8) + 0xff /* shift left by 8 */ ;
4710 rrd_set_error("Color problem in %s", err);
4713 sscanf(color, "#%8lx%n", &col, &n);
4717 rrd_set_error("Color problem in %s", err);
4719 if (rrd_test_error())
4721 gdp->col = gfx_hex_to_col(col);
4734 while (*ptr != '\0')
4735 if (*ptr++ == '%') {
4737 /* line cannot end with percent char */
4740 /* '%s', '%S' and '%%' are allowed */
4741 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4743 /* %c is allowed (but use only with vdef!) */
4744 else if (*ptr == 'c') {
4749 /* or else '% 6.2lf' and such are allowed */
4751 /* optional padding character */
4752 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4754 /* This should take care of 'm.n' with all three optional */
4755 while (*ptr >= '0' && *ptr <= '9')
4759 while (*ptr >= '0' && *ptr <= '9')
4761 /* Either 'le', 'lf' or 'lg' must follow here */
4764 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4779 const char *const str)
4781 /* A VDEF currently is either "func" or "param,func"
4782 * so the parsing is rather simple. Change if needed.
4789 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4790 if (n == (int) strlen(str)) { /* matched */
4794 sscanf(str, "%29[A-Z]%n", func, &n);
4795 if (n == (int) strlen(str)) { /* matched */
4799 ("Unknown function string '%s' in VDEF '%s'",
4804 if (!strcmp("PERCENT", func))
4805 gdes->vf.op = VDEF_PERCENT;
4806 else if (!strcmp("PERCENTNAN", func))
4807 gdes->vf.op = VDEF_PERCENTNAN;
4808 else if (!strcmp("MAXIMUM", func))
4809 gdes->vf.op = VDEF_MAXIMUM;
4810 else if (!strcmp("AVERAGE", func))
4811 gdes->vf.op = VDEF_AVERAGE;
4812 else if (!strcmp("STDEV", func))
4813 gdes->vf.op = VDEF_STDEV;
4814 else if (!strcmp("MINIMUM", func))
4815 gdes->vf.op = VDEF_MINIMUM;
4816 else if (!strcmp("TOTAL", func))
4817 gdes->vf.op = VDEF_TOTAL;
4818 else if (!strcmp("FIRST", func))
4819 gdes->vf.op = VDEF_FIRST;
4820 else if (!strcmp("LAST", func))
4821 gdes->vf.op = VDEF_LAST;
4822 else if (!strcmp("LSLSLOPE", func))
4823 gdes->vf.op = VDEF_LSLSLOPE;
4824 else if (!strcmp("LSLINT", func))
4825 gdes->vf.op = VDEF_LSLINT;
4826 else if (!strcmp("LSLCORREL", func))
4827 gdes->vf.op = VDEF_LSLCORREL;
4830 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4833 switch (gdes->vf.op) {
4835 case VDEF_PERCENTNAN:
4836 if (isnan(param)) { /* no parameter given */
4838 ("Function '%s' needs parameter in VDEF '%s'\n",
4842 if (param >= 0.0 && param <= 100.0) {
4843 gdes->vf.param = param;
4844 gdes->vf.val = DNAN; /* undefined */
4845 gdes->vf.when = 0; /* undefined */
4848 ("Parameter '%f' out of range in VDEF '%s'\n",
4849 param, gdes->vname);
4862 case VDEF_LSLCORREL:
4864 gdes->vf.param = DNAN;
4865 gdes->vf.val = DNAN;
4869 ("Function '%s' needs no parameter in VDEF '%s'\n",
4883 graph_desc_t *src, *dst;
4887 dst = &im->gdes[gdi];
4888 src = &im->gdes[dst->vidx];
4889 data = src->data + src->ds;
4891 steps = (src->end - src->start) / src->step;
4894 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4895 src->start, src->end, steps);
4897 switch (dst->vf.op) {
4901 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4902 rrd_set_error("malloc VDEV_PERCENT");
4905 for (step = 0; step < steps; step++) {
4906 array[step] = data[step * src->ds_cnt];
4908 qsort(array, step, sizeof(double), vdef_percent_compar);
4909 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4910 dst->vf.val = array[field];
4911 dst->vf.when = 0; /* no time component */
4914 for (step = 0; step < steps; step++)
4915 printf("DEBUG: %3li:%10.2f %c\n",
4916 step, array[step], step == field ? '*' : ' ');
4920 case VDEF_PERCENTNAN:{
4923 /* count number of "valid" values */
4925 for (step = 0; step < steps; step++) {
4926 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4928 /* and allocate it */
4929 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4930 rrd_set_error("malloc VDEV_PERCENT");
4933 /* and fill it in */
4935 for (step = 0; step < steps; step++) {
4936 if (!isnan(data[step * src->ds_cnt])) {
4937 array[field] = data[step * src->ds_cnt];
4941 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4942 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4943 dst->vf.val = array[field];
4944 dst->vf.when = 0; /* no time component */
4950 while (step != steps && isnan(data[step * src->ds_cnt]))
4952 if (step == steps) {
4956 dst->vf.val = data[step * src->ds_cnt];
4957 dst->vf.when = src->start + (step + 1) * src->step;
4959 while (step != steps) {
4960 if (finite(data[step * src->ds_cnt])) {
4961 if (data[step * src->ds_cnt] > dst->vf.val) {
4962 dst->vf.val = data[step * src->ds_cnt];
4963 dst->vf.when = src->start + (step + 1) * src->step;
4974 double average = 0.0;
4976 for (step = 0; step < steps; step++) {
4977 if (finite(data[step * src->ds_cnt])) {
4978 sum += data[step * src->ds_cnt];
4983 if (dst->vf.op == VDEF_TOTAL) {
4984 dst->vf.val = sum * src->step;
4985 dst->vf.when = 0; /* no time component */
4986 } else if (dst->vf.op == VDEF_AVERAGE) {
4987 dst->vf.val = sum / cnt;
4988 dst->vf.when = 0; /* no time component */
4990 average = sum / cnt;
4992 for (step = 0; step < steps; step++) {
4993 if (finite(data[step * src->ds_cnt])) {
4994 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4997 dst->vf.val = pow(sum / cnt, 0.5);
4998 dst->vf.when = 0; /* no time component */
5008 while (step != steps && isnan(data[step * src->ds_cnt]))
5010 if (step == steps) {
5014 dst->vf.val = data[step * src->ds_cnt];
5015 dst->vf.when = src->start + (step + 1) * src->step;
5017 while (step != steps) {
5018 if (finite(data[step * src->ds_cnt])) {
5019 if (data[step * src->ds_cnt] < dst->vf.val) {
5020 dst->vf.val = data[step * src->ds_cnt];
5021 dst->vf.when = src->start + (step + 1) * src->step;
5028 /* The time value returned here is one step before the
5029 * actual time value. This is the start of the first
5033 while (step != steps && isnan(data[step * src->ds_cnt]))
5035 if (step == steps) { /* all entries were NaN */
5039 dst->vf.val = data[step * src->ds_cnt];
5040 dst->vf.when = src->start + step * src->step;
5044 /* The time value returned here is the
5045 * actual time value. This is the end of the last
5049 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5051 if (step < 0) { /* all entries were NaN */
5055 dst->vf.val = data[step * src->ds_cnt];
5056 dst->vf.when = src->start + (step + 1) * src->step;
5061 case VDEF_LSLCORREL:{
5062 /* Bestfit line by linear least squares method */
5065 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5072 for (step = 0; step < steps; step++) {
5073 if (finite(data[step * src->ds_cnt])) {
5076 SUMxx += step * step;
5077 SUMxy += step * data[step * src->ds_cnt];
5078 SUMy += data[step * src->ds_cnt];
5079 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5083 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5084 y_intercept = (SUMy - slope * SUMx) / cnt;
5087 (SUMx * SUMy) / cnt) /
5089 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5091 if (dst->vf.op == VDEF_LSLSLOPE) {
5092 dst->vf.val = slope;
5094 } else if (dst->vf.op == VDEF_LSLINT) {
5095 dst->vf.val = y_intercept;
5097 } else if (dst->vf.op == VDEF_LSLCORREL) {
5098 dst->vf.val = correl;
5111 /* NaN < -INF < finite_values < INF */
5112 int vdef_percent_compar(
5118 /* Equality is not returned; this doesn't hurt except
5119 * (maybe) for a little performance.
5122 /* First catch NaN values. They are smallest */
5123 if (isnan(*(double *) a))
5125 if (isnan(*(double *) b))
5127 /* NaN doesn't reach this part so INF and -INF are extremes.
5128 * The sign from isinf() is compatible with the sign we return
5130 if (isinf(*(double *) a))
5131 return isinf(*(double *) a);
5132 if (isinf(*(double *) b))
5133 return isinf(*(double *) b);
5134 /* If we reach this, both values must be finite */
5135 if (*(double *) a < *(double *) b)
5144 rrd_info_type_t type,
5145 rrd_infoval_t value)
5147 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5148 if (im->grinfo == NULL) {
5149 im->grinfo = im->grinfo_current;