1 /****************************************************************************
2 * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
28 #include "rrd_graph.h"
29 #include "rrd_client.h"
31 /* some constant definitions */
35 #ifndef RRD_DEFAULT_FONT
36 /* there is special code later to pick Cour.ttf when running on windows */
37 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
40 text_prop_t text_prop[] = {
41 {8.0, RRD_DEFAULT_FONT,NULL}
43 {9.0, RRD_DEFAULT_FONT,NULL}
45 {7.0, RRD_DEFAULT_FONT,NULL}
47 {8.0, RRD_DEFAULT_FONT,NULL}
49 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
51 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
55 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
57 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
59 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
61 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
63 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
65 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
67 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
69 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
71 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
73 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
74 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
76 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
78 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
80 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
82 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
84 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
87 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
90 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
93 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
95 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
96 365 * 24 * 3600, "%y"}
98 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
101 /* sensible y label intervals ...*/
125 {20.0, {1, 5, 10, 20}
131 {100.0, {1, 2, 5, 10}
134 {200.0, {1, 5, 10, 20}
137 {500.0, {1, 2, 4, 10}
145 gfx_color_t graph_col[] = /* default colors */
147 {1.00, 1.00, 1.00, 1.00}, /* canvas */
148 {0.95, 0.95, 0.95, 1.00}, /* background */
149 {0.81, 0.81, 0.81, 1.00}, /* shade A */
150 {0.62, 0.62, 0.62, 1.00}, /* shade B */
151 {0.56, 0.56, 0.56, 0.75}, /* grid */
152 {0.87, 0.31, 0.31, 0.60}, /* major grid */
153 {0.00, 0.00, 0.00, 1.00}, /* font */
154 {0.50, 0.12, 0.12, 1.00}, /* arrow */
155 {0.12, 0.12, 0.12, 1.00}, /* axis */
156 {0.00, 0.00, 0.00, 1.00} /* frame */
163 # define DPRINT(x) (void)(printf x, printf("\n"))
169 /* initialize with xtr(im,0); */
177 pixie = (double) im->xsize / (double) (im->end - im->start);
180 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
183 /* translate data values into y coordinates */
192 if (!im->logarithmic)
193 pixie = (double) im->ysize / (im->maxval - im->minval);
196 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
198 } else if (!im->logarithmic) {
199 yval = im->yorigin - pixie * (value - im->minval);
201 if (value < im->minval) {
204 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
212 /* conversion function for symbolic entry names */
215 #define conv_if(VV,VVV) \
216 if (strcmp(#VV, string) == 0) return VVV ;
222 conv_if(PRINT, GF_PRINT);
223 conv_if(GPRINT, GF_GPRINT);
224 conv_if(COMMENT, GF_COMMENT);
225 conv_if(HRULE, GF_HRULE);
226 conv_if(VRULE, GF_VRULE);
227 conv_if(LINE, GF_LINE);
228 conv_if(AREA, GF_AREA);
229 conv_if(STACK, GF_STACK);
230 conv_if(TICK, GF_TICK);
231 conv_if(TEXTALIGN, GF_TEXTALIGN);
232 conv_if(DEF, GF_DEF);
233 conv_if(CDEF, GF_CDEF);
234 conv_if(VDEF, GF_VDEF);
235 conv_if(XPORT, GF_XPORT);
236 conv_if(SHIFT, GF_SHIFT);
241 enum gfx_if_en if_conv(
245 conv_if(PNG, IF_PNG);
246 conv_if(SVG, IF_SVG);
247 conv_if(EPS, IF_EPS);
248 conv_if(PDF, IF_PDF);
253 enum tmt_en tmt_conv(
257 conv_if(SECOND, TMT_SECOND);
258 conv_if(MINUTE, TMT_MINUTE);
259 conv_if(HOUR, TMT_HOUR);
260 conv_if(DAY, TMT_DAY);
261 conv_if(WEEK, TMT_WEEK);
262 conv_if(MONTH, TMT_MONTH);
263 conv_if(YEAR, TMT_YEAR);
267 enum grc_en grc_conv(
271 conv_if(BACK, GRC_BACK);
272 conv_if(CANVAS, GRC_CANVAS);
273 conv_if(SHADEA, GRC_SHADEA);
274 conv_if(SHADEB, GRC_SHADEB);
275 conv_if(GRID, GRC_GRID);
276 conv_if(MGRID, GRC_MGRID);
277 conv_if(FONT, GRC_FONT);
278 conv_if(ARROW, GRC_ARROW);
279 conv_if(AXIS, GRC_AXIS);
280 conv_if(FRAME, GRC_FRAME);
285 enum text_prop_en text_prop_conv(
289 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
290 conv_if(TITLE, TEXT_PROP_TITLE);
291 conv_if(AXIS, TEXT_PROP_AXIS);
292 conv_if(UNIT, TEXT_PROP_UNIT);
293 conv_if(LEGEND, TEXT_PROP_LEGEND);
294 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
305 cairo_status_t status = 0;
310 if (im->daemon_addr != NULL)
311 free(im->daemon_addr);
313 for (i = 0; i < (unsigned) im->gdes_c; i++) {
314 if (im->gdes[i].data_first) {
315 /* careful here, because a single pointer can occur several times */
316 free(im->gdes[i].data);
317 if (im->gdes[i].ds_namv) {
318 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
319 free(im->gdes[i].ds_namv[ii]);
320 free(im->gdes[i].ds_namv);
323 /* free allocated memory used for dashed lines */
324 if (im->gdes[i].p_dashes != NULL)
325 free(im->gdes[i].p_dashes);
327 free(im->gdes[i].p_data);
328 free(im->gdes[i].rpnp);
331 if (im->font_options)
332 cairo_font_options_destroy(im->font_options);
335 status = cairo_status(im->cr);
336 cairo_destroy(im->cr);
338 if (im->rendered_image) {
339 free(im->rendered_image);
343 g_object_unref (im->layout);
347 cairo_surface_destroy(im->surface);
350 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
351 cairo_status_to_string(status));
356 /* find SI magnitude symbol for the given number*/
358 image_desc_t *im, /* image description */
364 char *symbol[] = { "a", /* 10e-18 Atto */
365 "f", /* 10e-15 Femto */
366 "p", /* 10e-12 Pico */
367 "n", /* 10e-9 Nano */
368 "u", /* 10e-6 Micro */
369 "m", /* 10e-3 Milli */
374 "T", /* 10e12 Tera */
375 "P", /* 10e15 Peta */
382 if (*value == 0.0 || isnan(*value)) {
386 sindex = floor(log(fabs(*value)) / log((double) im->base));
387 *magfact = pow((double) im->base, (double) sindex);
388 (*value) /= (*magfact);
390 if (sindex <= symbcenter && sindex >= -symbcenter) {
391 (*symb_ptr) = symbol[sindex + symbcenter];
398 static char si_symbol[] = {
399 'a', /* 10e-18 Atto */
400 'f', /* 10e-15 Femto */
401 'p', /* 10e-12 Pico */
402 'n', /* 10e-9 Nano */
403 'u', /* 10e-6 Micro */
404 'm', /* 10e-3 Milli */
409 'T', /* 10e12 Tera */
410 'P', /* 10e15 Peta */
413 static const int si_symbcenter = 6;
415 /* find SI magnitude symbol for the numbers on the y-axis*/
417 image_desc_t *im /* image description */
421 double digits, viewdigits = 0;
424 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
425 log((double) im->base));
427 if (im->unitsexponent != 9999) {
428 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
429 viewdigits = floor(im->unitsexponent / 3);
434 im->magfact = pow((double) im->base, digits);
437 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
440 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
442 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
443 ((viewdigits + si_symbcenter) >= 0))
444 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
449 /* move min and max values around to become sensible */
454 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
455 600.0, 500.0, 400.0, 300.0, 250.0,
456 200.0, 125.0, 100.0, 90.0, 80.0,
457 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
458 25.0, 20.0, 10.0, 9.0, 8.0,
459 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
460 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
461 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
464 double scaled_min, scaled_max;
471 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
472 im->minval, im->maxval, im->magfact);
475 if (isnan(im->ygridstep)) {
476 if (im->extra_flags & ALTAUTOSCALE) {
477 /* measure the amplitude of the function. Make sure that
478 graph boundaries are slightly higher then max/min vals
479 so we can see amplitude on the graph */
482 delt = im->maxval - im->minval;
484 fact = 2.0 * pow(10.0,
486 (max(fabs(im->minval), fabs(im->maxval)) /
489 adj = (fact - delt) * 0.55;
492 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
493 im->minval, im->maxval, delt, fact, adj);
498 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
499 /* measure the amplitude of the function. Make sure that
500 graph boundaries are slightly lower than min vals
501 so we can see amplitude on the graph */
502 adj = (im->maxval - im->minval) * 0.1;
504 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
505 /* measure the amplitude of the function. Make sure that
506 graph boundaries are slightly higher than max vals
507 so we can see amplitude on the graph */
508 adj = (im->maxval - im->minval) * 0.1;
511 scaled_min = im->minval / im->magfact;
512 scaled_max = im->maxval / im->magfact;
514 for (i = 1; sensiblevalues[i] > 0; i++) {
515 if (sensiblevalues[i - 1] >= scaled_min &&
516 sensiblevalues[i] <= scaled_min)
517 im->minval = sensiblevalues[i] * (im->magfact);
519 if (-sensiblevalues[i - 1] <= scaled_min &&
520 -sensiblevalues[i] >= scaled_min)
521 im->minval = -sensiblevalues[i - 1] * (im->magfact);
523 if (sensiblevalues[i - 1] >= scaled_max &&
524 sensiblevalues[i] <= scaled_max)
525 im->maxval = sensiblevalues[i - 1] * (im->magfact);
527 if (-sensiblevalues[i - 1] <= scaled_max &&
528 -sensiblevalues[i] >= scaled_max)
529 im->maxval = -sensiblevalues[i] * (im->magfact);
533 /* adjust min and max to the grid definition if there is one */
534 im->minval = (double) im->ylabfact * im->ygridstep *
535 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
536 im->maxval = (double) im->ylabfact * im->ygridstep *
537 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
541 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
542 im->minval, im->maxval, im->magfact);
550 if (isnan(im->minval) || isnan(im->maxval))
553 if (im->logarithmic) {
554 double ya, yb, ypix, ypixfrac;
555 double log10_range = log10(im->maxval) - log10(im->minval);
557 ya = pow((double) 10, floor(log10(im->minval)));
558 while (ya < im->minval)
561 return; /* don't have y=10^x gridline */
563 if (yb <= im->maxval) {
564 /* we have at least 2 y=10^x gridlines.
565 Make sure distance between them in pixels
566 are an integer by expanding im->maxval */
567 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
568 double factor = y_pixel_delta / floor(y_pixel_delta);
569 double new_log10_range = factor * log10_range;
570 double new_ymax_log10 = log10(im->minval) + new_log10_range;
572 im->maxval = pow(10, new_ymax_log10);
573 ytr(im, DNAN); /* reset precalc */
574 log10_range = log10(im->maxval) - log10(im->minval);
576 /* make sure first y=10^x gridline is located on
577 integer pixel position by moving scale slightly
578 downwards (sub-pixel movement) */
579 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
580 ypixfrac = ypix - floor(ypix);
581 if (ypixfrac > 0 && ypixfrac < 1) {
582 double yfrac = ypixfrac / im->ysize;
584 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
585 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
586 ytr(im, DNAN); /* reset precalc */
589 /* Make sure we have an integer pixel distance between
590 each minor gridline */
591 double ypos1 = ytr(im, im->minval);
592 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
593 double y_pixel_delta = ypos1 - ypos2;
594 double factor = y_pixel_delta / floor(y_pixel_delta);
595 double new_range = factor * (im->maxval - im->minval);
596 double gridstep = im->ygrid_scale.gridstep;
597 double minor_y, minor_y_px, minor_y_px_frac;
599 if (im->maxval > 0.0)
600 im->maxval = im->minval + new_range;
602 im->minval = im->maxval - new_range;
603 ytr(im, DNAN); /* reset precalc */
604 /* make sure first minor gridline is on integer pixel y coord */
605 minor_y = gridstep * floor(im->minval / gridstep);
606 while (minor_y < im->minval)
608 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
609 minor_y_px_frac = minor_y_px - floor(minor_y_px);
610 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
611 double yfrac = minor_y_px_frac / im->ysize;
612 double range = im->maxval - im->minval;
614 im->minval = im->minval - yfrac * range;
615 im->maxval = im->maxval - yfrac * range;
616 ytr(im, DNAN); /* reset precalc */
618 calc_horizontal_grid(im); /* recalc with changed im->maxval */
622 /* reduce data reimplementation by Alex */
625 enum cf_en cf, /* which consolidation function ? */
626 unsigned long cur_step, /* step the data currently is in */
627 time_t *start, /* start, end and step as requested ... */
628 time_t *end, /* ... by the application will be ... */
629 unsigned long *step, /* ... adjusted to represent reality */
630 unsigned long *ds_cnt, /* number of data sources in file */
632 { /* two dimensional array containing the data */
633 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
634 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
636 rrd_value_t *srcptr, *dstptr;
638 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
641 row_cnt = ((*end) - (*start)) / cur_step;
647 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
648 row_cnt, reduce_factor, *start, *end, cur_step);
649 for (col = 0; col < row_cnt; col++) {
650 printf("time %10lu: ", *start + (col + 1) * cur_step);
651 for (i = 0; i < *ds_cnt; i++)
652 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
657 /* We have to combine [reduce_factor] rows of the source
658 ** into one row for the destination. Doing this we also
659 ** need to take care to combine the correct rows. First
660 ** alter the start and end time so that they are multiples
661 ** of the new step time. We cannot reduce the amount of
662 ** time so we have to move the end towards the future and
663 ** the start towards the past.
665 end_offset = (*end) % (*step);
666 start_offset = (*start) % (*step);
668 /* If there is a start offset (which cannot be more than
669 ** one destination row), skip the appropriate number of
670 ** source rows and one destination row. The appropriate
671 ** number is what we do know (start_offset/cur_step) of
672 ** the new interval (*step/cur_step aka reduce_factor).
675 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
676 printf("row_cnt before: %lu\n", row_cnt);
679 (*start) = (*start) - start_offset;
680 skiprows = reduce_factor - start_offset / cur_step;
681 srcptr += skiprows * *ds_cnt;
682 for (col = 0; col < (*ds_cnt); col++)
687 printf("row_cnt between: %lu\n", row_cnt);
690 /* At the end we have some rows that are not going to be
691 ** used, the amount is end_offset/cur_step
694 (*end) = (*end) - end_offset + (*step);
695 skiprows = end_offset / cur_step;
699 printf("row_cnt after: %lu\n", row_cnt);
702 /* Sanity check: row_cnt should be multiple of reduce_factor */
703 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
705 if (row_cnt % reduce_factor) {
706 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
707 row_cnt, reduce_factor);
708 printf("BUG in reduce_data()\n");
712 /* Now combine reduce_factor intervals at a time
713 ** into one interval for the destination.
716 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
717 for (col = 0; col < (*ds_cnt); col++) {
718 rrd_value_t newval = DNAN;
719 unsigned long validval = 0;
721 for (i = 0; i < reduce_factor; i++) {
722 if (isnan(srcptr[i * (*ds_cnt) + col])) {
727 newval = srcptr[i * (*ds_cnt) + col];
736 newval += srcptr[i * (*ds_cnt) + col];
739 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
742 /* an interval contains a failure if any subintervals contained a failure */
744 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
747 newval = srcptr[i * (*ds_cnt) + col];
773 srcptr += (*ds_cnt) * reduce_factor;
774 row_cnt -= reduce_factor;
776 /* If we had to alter the endtime, we didn't have enough
777 ** source rows to fill the last row. Fill it with NaN.
780 for (col = 0; col < (*ds_cnt); col++)
783 row_cnt = ((*end) - (*start)) / *step;
785 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
786 row_cnt, *start, *end, *step);
787 for (col = 0; col < row_cnt; col++) {
788 printf("time %10lu: ", *start + (col + 1) * (*step));
789 for (i = 0; i < *ds_cnt; i++)
790 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
797 /* get the data required for the graphs from the
806 /* pull the data from the rrd files ... */
807 for (i = 0; i < (int) im->gdes_c; i++) {
808 /* only GF_DEF elements fetch data */
809 if (im->gdes[i].gf != GF_DEF)
813 /* do we have it already ? */
814 for (ii = 0; ii < i; ii++) {
815 if (im->gdes[ii].gf != GF_DEF)
817 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
818 && (im->gdes[i].cf == im->gdes[ii].cf)
819 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
820 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
821 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
822 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
823 /* OK, the data is already there.
824 ** Just copy the header portion
826 im->gdes[i].start = im->gdes[ii].start;
827 im->gdes[i].end = im->gdes[ii].end;
828 im->gdes[i].step = im->gdes[ii].step;
829 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
830 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
831 im->gdes[i].data = im->gdes[ii].data;
832 im->gdes[i].data_first = 0;
839 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
842 * - a connection to the daemon has been established
843 * - this is the first occurrence of that RRD file
845 if (rrdc_is_connected(im->daemon_addr))
850 for (ii = 0; ii < i; ii++)
852 if (strcmp (im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
861 status = rrdc_flush (im->gdes[i].rrd);
864 rrd_set_error ("rrdc_flush (%s) failed with status %i.",
865 im->gdes[i].rrd, status);
869 } /* if (rrdc_is_connected()) */
871 if ((rrd_fetch_fn(im->gdes[i].rrd,
877 &im->gdes[i].ds_namv,
878 &im->gdes[i].data)) == -1) {
881 im->gdes[i].data_first = 1;
883 if (ft_step < im->gdes[i].step) {
884 reduce_data(im->gdes[i].cf_reduce,
889 &im->gdes[i].ds_cnt, &im->gdes[i].data);
891 im->gdes[i].step = ft_step;
895 /* lets see if the required data source is really there */
896 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
897 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
901 if (im->gdes[i].ds == -1) {
902 rrd_set_error("No DS called '%s' in '%s'",
903 im->gdes[i].ds_nam, im->gdes[i].rrd);
911 /* evaluate the expressions in the CDEF functions */
913 /*************************************************************
915 *************************************************************/
917 long find_var_wrapper(
921 return find_var((image_desc_t *) arg1, key);
924 /* find gdes containing var*/
931 for (ii = 0; ii < im->gdes_c - 1; ii++) {
932 if ((im->gdes[ii].gf == GF_DEF
933 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
934 && (strcmp(im->gdes[ii].vname, key) == 0)) {
941 /* find the largest common denominator for all the numbers
942 in the 0 terminated num array */
949 for (i = 0; num[i + 1] != 0; i++) {
951 rest = num[i] % num[i + 1];
957 /* return i==0?num[i]:num[i-1]; */
961 /* run the rpn calculator on all the VDEF and CDEF arguments */
968 long *steparray, rpi;
973 rpnstack_init(&rpnstack);
975 for (gdi = 0; gdi < im->gdes_c; gdi++) {
976 /* Look for GF_VDEF and GF_CDEF in the same loop,
977 * so CDEFs can use VDEFs and vice versa
979 switch (im->gdes[gdi].gf) {
983 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
985 /* remove current shift */
986 vdp->start -= vdp->shift;
987 vdp->end -= vdp->shift;
990 if (im->gdes[gdi].shidx >= 0)
991 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
994 vdp->shift = im->gdes[gdi].shval;
996 /* normalize shift to multiple of consolidated step */
997 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1000 vdp->start += vdp->shift;
1001 vdp->end += vdp->shift;
1005 /* A VDEF has no DS. This also signals other parts
1006 * of rrdtool that this is a VDEF value, not a CDEF.
1008 im->gdes[gdi].ds_cnt = 0;
1009 if (vdef_calc(im, gdi)) {
1010 rrd_set_error("Error processing VDEF '%s'",
1011 im->gdes[gdi].vname);
1012 rpnstack_free(&rpnstack);
1017 im->gdes[gdi].ds_cnt = 1;
1018 im->gdes[gdi].ds = 0;
1019 im->gdes[gdi].data_first = 1;
1020 im->gdes[gdi].start = 0;
1021 im->gdes[gdi].end = 0;
1026 /* Find the variables in the expression.
1027 * - VDEF variables are substituted by their values
1028 * and the opcode is changed into OP_NUMBER.
1029 * - CDEF variables are analized for their step size,
1030 * the lowest common denominator of all the step
1031 * sizes of the data sources involved is calculated
1032 * and the resulting number is the step size for the
1033 * resulting data source.
1035 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1036 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1037 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1038 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1040 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1043 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1044 im->gdes[gdi].vname, im->gdes[ptr].vname);
1045 printf("DEBUG: value from vdef is %f\n",
1046 im->gdes[ptr].vf.val);
1048 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1049 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1050 } else { /* normal variables and PREF(variables) */
1052 /* add one entry to the array that keeps track of the step sizes of the
1053 * data sources going into the CDEF. */
1055 rrd_realloc(steparray,
1057 1) * sizeof(*steparray))) == NULL) {
1058 rrd_set_error("realloc steparray");
1059 rpnstack_free(&rpnstack);
1063 steparray[stepcnt - 1] = im->gdes[ptr].step;
1065 /* adjust start and end of cdef (gdi) so
1066 * that it runs from the latest start point
1067 * to the earliest endpoint of any of the
1068 * rras involved (ptr)
1071 if (im->gdes[gdi].start < im->gdes[ptr].start)
1072 im->gdes[gdi].start = im->gdes[ptr].start;
1074 if (im->gdes[gdi].end == 0 ||
1075 im->gdes[gdi].end > im->gdes[ptr].end)
1076 im->gdes[gdi].end = im->gdes[ptr].end;
1078 /* store pointer to the first element of
1079 * the rra providing data for variable,
1080 * further save step size and data source
1083 im->gdes[gdi].rpnp[rpi].data =
1084 im->gdes[ptr].data + im->gdes[ptr].ds;
1085 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1086 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1088 /* backoff the *.data ptr; this is done so
1089 * rpncalc() function doesn't have to treat
1090 * the first case differently
1092 } /* if ds_cnt != 0 */
1093 } /* if OP_VARIABLE */
1094 } /* loop through all rpi */
1096 /* move the data pointers to the correct period */
1097 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1098 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1099 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1100 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1102 im->gdes[gdi].start - im->gdes[ptr].start;
1105 im->gdes[gdi].rpnp[rpi].data +=
1106 (diff / im->gdes[ptr].step) *
1107 im->gdes[ptr].ds_cnt;
1111 if (steparray == NULL) {
1112 rrd_set_error("rpn expressions without DEF"
1113 " or CDEF variables are not supported");
1114 rpnstack_free(&rpnstack);
1117 steparray[stepcnt] = 0;
1118 /* Now find the resulting step. All steps in all
1119 * used RRAs have to be visited
1121 im->gdes[gdi].step = lcd(steparray);
1123 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1124 im->gdes[gdi].start)
1125 / im->gdes[gdi].step)
1126 * sizeof(double))) == NULL) {
1127 rrd_set_error("malloc im->gdes[gdi].data");
1128 rpnstack_free(&rpnstack);
1132 /* Step through the new cdef results array and
1133 * calculate the values
1135 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1136 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1137 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1139 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1140 * in this case we are advancing by timesteps;
1141 * we use the fact that time_t is a synonym for long
1143 if (rpn_calc(rpnp, &rpnstack, (long) now,
1144 im->gdes[gdi].data, ++dataidx) == -1) {
1145 /* rpn_calc sets the error string */
1146 rpnstack_free(&rpnstack);
1149 } /* enumerate over time steps within a CDEF */
1154 } /* enumerate over CDEFs */
1155 rpnstack_free(&rpnstack);
1159 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1160 /* yes we are loosing precision by doing tos with floats instead of doubles
1161 but it seems more stable this way. */
1163 static int AlmostEqual2sComplement(
1169 int aInt = *(int *) &A;
1170 int bInt = *(int *) &B;
1173 /* Make sure maxUlps is non-negative and small enough that the
1174 default NAN won't compare as equal to anything. */
1176 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1178 /* Make aInt lexicographically ordered as a twos-complement int */
1181 aInt = 0x80000000l - aInt;
1183 /* Make bInt lexicographically ordered as a twos-complement int */
1186 bInt = 0x80000000l - bInt;
1188 intDiff = abs(aInt - bInt);
1190 if (intDiff <= maxUlps)
1196 /* massage data so, that we get one value for each x coordinate in the graph */
1201 double pixstep = (double) (im->end - im->start)
1202 / (double) im->xsize; /* how much time
1203 passes in one pixel */
1205 double minval = DNAN, maxval = DNAN;
1207 unsigned long gr_time;
1209 /* memory for the processed data */
1210 for (i = 0; i < im->gdes_c; i++) {
1211 if ((im->gdes[i].gf == GF_LINE) ||
1212 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1213 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1214 * sizeof(rrd_value_t))) == NULL) {
1215 rrd_set_error("malloc data_proc");
1221 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1224 gr_time = im->start + pixstep * i; /* time of the current step */
1227 for (ii = 0; ii < im->gdes_c; ii++) {
1230 switch (im->gdes[ii].gf) {
1234 if (!im->gdes[ii].stack)
1236 value = im->gdes[ii].yrule;
1237 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1238 /* The time of the data doesn't necessarily match
1239 ** the time of the graph. Beware.
1241 vidx = im->gdes[ii].vidx;
1242 if (im->gdes[vidx].gf == GF_VDEF) {
1243 value = im->gdes[vidx].vf.val;
1245 if (((long int) gr_time >=
1246 (long int) im->gdes[vidx].start)
1247 && ((long int) gr_time <=
1248 (long int) im->gdes[vidx].end)) {
1249 value = im->gdes[vidx].data[(unsigned long)
1255 im->gdes[vidx].step)
1256 * im->gdes[vidx].ds_cnt +
1263 if (!isnan(value)) {
1265 im->gdes[ii].p_data[i] = paintval;
1266 /* GF_TICK: the data values are not
1267 ** relevant for min and max
1269 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1270 if ((isnan(minval) || paintval < minval) &&
1271 !(im->logarithmic && paintval <= 0.0))
1273 if (isnan(maxval) || paintval > maxval)
1277 im->gdes[ii].p_data[i] = DNAN;
1282 ("STACK should already be turned into LINE or AREA here");
1291 /* if min or max have not been asigned a value this is because
1292 there was no data in the graph ... this is not good ...
1293 lets set these to dummy values then ... */
1295 if (im->logarithmic) {
1296 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1297 minval = 0.0; /* catching this right away below */
1300 /* in logarithm mode, where minval is smaller or equal
1301 to 0 make the beast just way smaller than maxval */
1303 minval = maxval / 10e8;
1306 if (isnan(minval) || isnan(maxval)) {
1312 /* adjust min and max values given by the user */
1313 /* for logscale we add something on top */
1314 if (isnan(im->minval)
1315 || ((!im->rigid) && im->minval > minval)
1317 if (im->logarithmic)
1318 im->minval = minval / 2.0;
1320 im->minval = minval;
1322 if (isnan(im->maxval)
1323 || (!im->rigid && im->maxval < maxval)
1325 if (im->logarithmic)
1326 im->maxval = maxval * 2.0;
1328 im->maxval = maxval;
1331 /* make sure min is smaller than max */
1332 if (im->minval > im->maxval) {
1334 im->minval = 0.99 * im->maxval;
1336 im->minval = 1.01 * im->maxval;
1339 /* make sure min and max are not equal */
1340 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1346 /* make sure min and max are not both zero */
1347 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1356 /* identify the point where the first gridline, label ... gets placed */
1358 time_t find_first_time(
1359 time_t start, /* what is the initial time */
1360 enum tmt_en baseint, /* what is the basic interval */
1361 long basestep /* how many if these do we jump a time */
1366 localtime_r(&start, &tm);
1370 tm. tm_sec -= tm.tm_sec % basestep;
1375 tm. tm_min -= tm.tm_min % basestep;
1381 tm. tm_hour -= tm.tm_hour % basestep;
1385 /* we do NOT look at the basestep for this ... */
1392 /* we do NOT look at the basestep for this ... */
1396 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1398 if (tm.tm_wday == 0)
1399 tm. tm_mday -= 7; /* we want the *previous* monday */
1407 tm. tm_mon -= tm.tm_mon % basestep;
1418 tm.tm_year + 1900) %basestep;
1424 /* identify the point where the next gridline, label ... gets placed */
1425 time_t find_next_time(
1426 time_t current, /* what is the initial time */
1427 enum tmt_en baseint, /* what is the basic interval */
1428 long basestep /* how many if these do we jump a time */
1434 localtime_r(¤t, &tm);
1439 tm. tm_sec += basestep;
1443 tm. tm_min += basestep;
1447 tm. tm_hour += basestep;
1451 tm. tm_mday += basestep;
1455 tm. tm_mday += 7 * basestep;
1459 tm. tm_mon += basestep;
1463 tm. tm_year += basestep;
1465 madetime = mktime(&tm);
1466 } while (madetime == -1); /* this is necessary to skip impssible times
1467 like the daylight saving time skips */
1473 /* calculate values required for PRINT and GPRINT functions */
1478 long i, ii, validsteps;
1481 int graphelement = 0;
1484 double magfact = -1;
1489 /* wow initializing tmvdef is quite a task :-) */
1490 time_t now = time(NULL);
1492 localtime_r(&now, &tmvdef);
1493 for (i = 0; i < im->gdes_c; i++) {
1494 vidx = im->gdes[i].vidx;
1495 switch (im->gdes[i].gf) {
1498 /* PRINT and GPRINT can now print VDEF generated values.
1499 * There's no need to do any calculations on them as these
1500 * calculations were already made.
1502 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1503 printval = im->gdes[vidx].vf.val;
1504 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1505 } else { /* need to calculate max,min,avg etcetera */
1506 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1507 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1510 for (ii = im->gdes[vidx].ds;
1511 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1512 if (!finite(im->gdes[vidx].data[ii]))
1514 if (isnan(printval)) {
1515 printval = im->gdes[vidx].data[ii];
1520 switch (im->gdes[i].cf) {
1524 case CF_DEVSEASONAL:
1528 printval += im->gdes[vidx].data[ii];
1531 printval = min(printval, im->gdes[vidx].data[ii]);
1535 printval = max(printval, im->gdes[vidx].data[ii]);
1538 printval = im->gdes[vidx].data[ii];
1541 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1542 if (validsteps > 1) {
1543 printval = (printval / validsteps);
1546 } /* prepare printval */
1548 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1549 /* Magfact is set to -1 upon entry to print_calc. If it
1550 * is still less than 0, then we need to run auto_scale.
1551 * Otherwise, put the value into the correct units. If
1552 * the value is 0, then do not set the symbol or magnification
1553 * so next the calculation will be performed again. */
1554 if (magfact < 0.0) {
1555 auto_scale(im, &printval, &si_symb, &magfact);
1556 if (printval == 0.0)
1559 printval /= magfact;
1561 *(++percent_s) = 's';
1562 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1563 auto_scale(im, &printval, &si_symb, &magfact);
1566 if (im->gdes[i].gf == GF_PRINT) {
1567 rrd_infoval_t prline;
1569 if (im->gdes[i].strftm) {
1570 prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
1571 strftime(prline.u_str,
1572 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1573 } else if (bad_format(im->gdes[i].format)) {
1575 ("bad format for PRINT in '%s'", im->gdes[i].format);
1579 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1583 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1588 if (im->gdes[i].strftm) {
1589 strftime(im->gdes[i].legend,
1590 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1592 if (bad_format(im->gdes[i].format)) {
1594 ("bad format for GPRINT in '%s'",
1595 im->gdes[i].format);
1598 #ifdef HAVE_SNPRINTF
1599 snprintf(im->gdes[i].legend,
1601 im->gdes[i].format, printval, si_symb);
1603 sprintf(im->gdes[i].legend,
1604 im->gdes[i].format, printval, si_symb);
1616 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1617 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1622 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1623 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1632 #ifdef WITH_PIECHART
1640 ("STACK should already be turned into LINE or AREA here");
1645 return graphelement;
1649 /* place legends with color spots */
1655 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1656 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1657 int fill = 0, fill_last;
1660 int leg_y = im->yimg;
1661 int leg_y_prev = im->yimg;
1664 int i, ii, mark = 0;
1665 char prt_fctn; /*special printfunctions */
1666 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1670 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1671 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1672 rrd_set_error("malloc for legspace");
1676 if (im->extra_flags & FULL_SIZE_MODE)
1677 leg_y = leg_y_prev =
1678 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1679 for (i = 0; i < im->gdes_c; i++) {
1681 /* hide legends for rules which are not displayed */
1682 if (im->gdes[i].gf == GF_TEXTALIGN) {
1683 default_txtalign = im->gdes[i].txtalign;
1686 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1687 if (im->gdes[i].gf == GF_HRULE
1688 && (im->gdes[i].yrule <
1689 im->minval || im->gdes[i].yrule > im->maxval))
1690 im->gdes[i].legend[0] = '\0';
1691 if (im->gdes[i].gf == GF_VRULE
1692 && (im->gdes[i].xrule <
1693 im->start || im->gdes[i].xrule > im->end))
1694 im->gdes[i].legend[0] = '\0';
1697 /* turn \\t into tab */
1698 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1699 memmove(tab, tab + 1, strlen(tab));
1702 leg_cc = strlen(im->gdes[i].legend);
1703 /* is there a controle code ant the end of the legend string ? */
1704 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1705 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1707 im->gdes[i].legend[leg_cc] = '\0';
1711 /* only valid control codes */
1712 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1716 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1719 ("Unknown control code at the end of '%s\\%c'",
1720 im->gdes[i].legend, prt_fctn);
1724 if (prt_fctn == 'n') {
1728 /* remove exess space from the end of the legend for \g */
1729 while (prt_fctn == 'g' &&
1730 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1732 im->gdes[i].legend[leg_cc] = '\0';
1737 /* no interleg space if string ends in \g */
1738 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1740 fill += legspace[i];
1743 gfx_get_text_width(im,
1749 im->tabwidth, im->gdes[i].legend);
1754 /* who said there was a special tag ... ? */
1755 if (prt_fctn == 'g') {
1759 if (prt_fctn == '\0') {
1760 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1761 /* just one legend item is left right or center */
1762 switch (default_txtalign) {
1777 /* is it time to place the legends ? */
1778 if (fill > im->ximg - 2 * border) {
1786 if (leg_c == 1 && prt_fctn == 'j') {
1792 if (prt_fctn != '\0') {
1794 if (leg_c >= 2 && prt_fctn == 'j') {
1795 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1799 if (prt_fctn == 'c')
1800 leg_x = (im->ximg - fill) / 2.0;
1801 if (prt_fctn == 'r')
1802 leg_x = im->ximg - fill - border;
1803 for (ii = mark; ii <= i; ii++) {
1804 if (im->gdes[ii].legend[0] == '\0')
1805 continue; /* skip empty legends */
1806 im->gdes[ii].leg_x = leg_x;
1807 im->gdes[ii].leg_y = leg_y;
1809 gfx_get_text_width(im, leg_x,
1814 im->tabwidth, im->gdes[ii].legend)
1819 if (im->extra_flags & FULL_SIZE_MODE) {
1820 /* only add y space if there was text on the line */
1821 if (leg_x > border || prt_fctn == 's')
1822 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1823 if (prt_fctn == 's')
1824 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1826 if (leg_x > border || prt_fctn == 's')
1827 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1828 if (prt_fctn == 's')
1829 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1837 if (im->extra_flags & FULL_SIZE_MODE) {
1838 if (leg_y != leg_y_prev) {
1839 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1841 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1845 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1853 /* create a grid on the graph. it determines what to do
1854 from the values of xsize, start and end */
1856 /* the xaxis labels are determined from the number of seconds per pixel
1857 in the requested graph */
1859 int calc_horizontal_grid(
1867 int decimals, fractionals;
1869 im->ygrid_scale.labfact = 2;
1870 range = im->maxval - im->minval;
1871 scaledrange = range / im->magfact;
1872 /* does the scale of this graph make it impossible to put lines
1873 on it? If so, give up. */
1874 if (isnan(scaledrange)) {
1878 /* find grid spaceing */
1880 if (isnan(im->ygridstep)) {
1881 if (im->extra_flags & ALTYGRID) {
1882 /* find the value with max number of digits. Get number of digits */
1885 (max(fabs(im->maxval), fabs(im->minval)) *
1886 im->viewfactor / im->magfact));
1887 if (decimals <= 0) /* everything is small. make place for zero */
1889 im->ygrid_scale.gridstep =
1891 floor(log10(range * im->viewfactor / im->magfact))) /
1892 im->viewfactor * im->magfact;
1893 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1894 im->ygrid_scale.gridstep = 0.1;
1895 /* should have at least 5 lines but no more then 15 */
1896 if (range / im->ygrid_scale.gridstep < 5
1897 && im->ygrid_scale.gridstep >= 30)
1898 im->ygrid_scale.gridstep /= 10;
1899 if (range / im->ygrid_scale.gridstep > 15)
1900 im->ygrid_scale.gridstep *= 10;
1901 if (range / im->ygrid_scale.gridstep > 5) {
1902 im->ygrid_scale.labfact = 1;
1903 if (range / im->ygrid_scale.gridstep > 8
1904 || im->ygrid_scale.gridstep <
1905 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1906 im->ygrid_scale.labfact = 2;
1908 im->ygrid_scale.gridstep /= 5;
1909 im->ygrid_scale.labfact = 5;
1913 (im->ygrid_scale.gridstep *
1914 (double) im->ygrid_scale.labfact * im->viewfactor /
1916 if (fractionals < 0) { /* small amplitude. */
1917 int len = decimals - fractionals + 1;
1919 if (im->unitslength < len + 2)
1920 im->unitslength = len + 2;
1921 sprintf(im->ygrid_scale.labfmt,
1923 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1925 int len = decimals + 1;
1927 if (im->unitslength < len + 2)
1928 im->unitslength = len + 2;
1929 sprintf(im->ygrid_scale.labfmt,
1930 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1932 } else { /* classic rrd grid */
1933 for (i = 0; ylab[i].grid > 0; i++) {
1934 pixel = im->ysize / (scaledrange / ylab[i].grid);
1940 for (i = 0; i < 4; i++) {
1941 if (pixel * ylab[gridind].lfac[i] >=
1942 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1943 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1948 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1951 im->ygrid_scale.gridstep = im->ygridstep;
1952 im->ygrid_scale.labfact = im->ylabfact;
1957 int draw_horizontal_grid(
1963 char graph_label[100];
1965 double X0 = im->xorigin;
1966 double X1 = im->xorigin + im->xsize;
1967 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1968 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1972 im->ygrid_scale.gridstep /
1973 (double) im->magfact * (double) im->viewfactor;
1974 MaxY = scaledstep * (double) egrid;
1975 for (i = sgrid; i <= egrid; i++) {
1977 im->ygrid_scale.gridstep * i);
1979 im->ygrid_scale.gridstep * (i + 1));
1981 if (floor(Y0 + 0.5) >=
1982 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1983 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1984 with the chosen settings. Add a label if required by settings, or if
1985 there is only one label so far and the next grid line is out of bounds. */
1986 if (i % im->ygrid_scale.labfact == 0
1988 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1989 if (im->symbol == ' ') {
1990 if (im->extra_flags & ALTYGRID) {
1991 sprintf(graph_label,
1992 im->ygrid_scale.labfmt,
1993 scaledstep * (double) i);
1996 sprintf(graph_label, "%4.1f",
1997 scaledstep * (double) i);
1999 sprintf(graph_label, "%4.0f",
2000 scaledstep * (double) i);
2004 char sisym = (i == 0 ? ' ' : im->symbol);
2006 if (im->extra_flags & ALTYGRID) {
2007 sprintf(graph_label,
2008 im->ygrid_scale.labfmt,
2009 scaledstep * (double) i, sisym);
2012 sprintf(graph_label, "%4.1f %c",
2013 scaledstep * (double) i, sisym);
2015 sprintf(graph_label, "%4.0f %c",
2016 scaledstep * (double) i, sisym);
2024 text_prop[TEXT_PROP_AXIS].
2026 im->graph_col[GRC_FONT],
2028 text_prop[TEXT_PROP_AXIS].
2031 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2032 gfx_line(im, X0 - 2, Y0, X0, Y0,
2033 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2034 gfx_line(im, X1, Y0, X1 + 2, Y0,
2035 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2036 gfx_dashed_line(im, X0 - 2, Y0,
2042 im->grid_dash_on, im->grid_dash_off);
2043 } else if (!(im->extra_flags & NOMINOR)) {
2046 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2047 gfx_line(im, X1, Y0, X1 + 2, Y0,
2048 GRIDWIDTH, im->graph_col[GRC_GRID]);
2049 gfx_dashed_line(im, X0 - 1, Y0,
2053 graph_col[GRC_GRID],
2054 im->grid_dash_on, im->grid_dash_off);
2061 /* this is frexp for base 10 */
2072 iexp = floor(log(fabs(x)) / log(10));
2073 mnt = x / pow(10.0, iexp);
2076 mnt = x / pow(10.0, iexp);
2083 /* logaritmic horizontal grid */
2084 int horizontal_log_grid(
2088 double yloglab[][10] = {
2090 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2092 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2094 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2111 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2113 int i, j, val_exp, min_exp;
2114 double nex; /* number of decades in data */
2115 double logscale; /* scale in logarithmic space */
2116 int exfrac = 1; /* decade spacing */
2117 int mid = -1; /* row in yloglab for major grid */
2118 double mspac; /* smallest major grid spacing (pixels) */
2119 int flab; /* first value in yloglab to use */
2120 double value, tmp, pre_value;
2122 char graph_label[100];
2124 nex = log10(im->maxval / im->minval);
2125 logscale = im->ysize / nex;
2126 /* major spacing for data with high dynamic range */
2127 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2134 /* major spacing for less dynamic data */
2136 /* search best row in yloglab */
2138 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2139 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2142 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2145 /* find first value in yloglab */
2147 yloglab[mid][flab] < 10
2148 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2149 if (yloglab[mid][flab] == 10.0) {
2154 if (val_exp % exfrac)
2155 val_exp += abs(-val_exp % exfrac);
2157 X1 = im->xorigin + im->xsize;
2162 value = yloglab[mid][flab] * pow(10.0, val_exp);
2163 if (AlmostEqual2sComplement(value, pre_value, 4))
2164 break; /* it seems we are not converging */
2166 Y0 = ytr(im, value);
2167 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2169 /* major grid line */
2171 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2172 gfx_line(im, X1, Y0, X1 + 2, Y0,
2173 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2174 gfx_dashed_line(im, X0 - 2, Y0,
2179 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2181 if (im->extra_flags & FORCE_UNITS_SI) {
2186 scale = floor(val_exp / 3.0);
2188 pvalue = pow(10.0, val_exp % 3);
2190 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2191 pvalue *= yloglab[mid][flab];
2192 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2193 && ((scale + si_symbcenter) >= 0))
2194 symbol = si_symbol[scale + si_symbcenter];
2197 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2199 sprintf(graph_label, "%3.0e", value);
2203 text_prop[TEXT_PROP_AXIS].
2205 im->graph_col[GRC_FONT],
2207 text_prop[TEXT_PROP_AXIS].
2210 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2212 if (mid < 4 && exfrac == 1) {
2213 /* find first and last minor line behind current major line
2214 * i is the first line and j tha last */
2216 min_exp = val_exp - 1;
2217 for (i = 1; yloglab[mid][i] < 10.0; i++);
2218 i = yloglab[mid][i - 1] + 1;
2222 i = yloglab[mid][flab - 1] + 1;
2223 j = yloglab[mid][flab];
2226 /* draw minor lines below current major line */
2227 for (; i < j; i++) {
2229 value = i * pow(10.0, min_exp);
2230 if (value < im->minval)
2232 Y0 = ytr(im, value);
2233 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2238 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2239 gfx_line(im, X1, Y0, X1 + 2, Y0,
2240 GRIDWIDTH, im->graph_col[GRC_GRID]);
2241 gfx_dashed_line(im, X0 - 1, Y0,
2245 graph_col[GRC_GRID],
2246 im->grid_dash_on, im->grid_dash_off);
2248 } else if (exfrac > 1) {
2249 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2250 value = pow(10.0, i);
2251 if (value < im->minval)
2253 Y0 = ytr(im, value);
2254 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2259 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2260 gfx_line(im, X1, Y0, X1 + 2, Y0,
2261 GRIDWIDTH, im->graph_col[GRC_GRID]);
2262 gfx_dashed_line(im, X0 - 1, Y0,
2266 graph_col[GRC_GRID],
2267 im->grid_dash_on, im->grid_dash_off);
2272 if (yloglab[mid][++flab] == 10.0) {
2278 /* draw minor lines after highest major line */
2279 if (mid < 4 && exfrac == 1) {
2280 /* find first and last minor line below current major line
2281 * i is the first line and j tha last */
2283 min_exp = val_exp - 1;
2284 for (i = 1; yloglab[mid][i] < 10.0; i++);
2285 i = yloglab[mid][i - 1] + 1;
2289 i = yloglab[mid][flab - 1] + 1;
2290 j = yloglab[mid][flab];
2293 /* draw minor lines below current major line */
2294 for (; i < j; i++) {
2296 value = i * pow(10.0, min_exp);
2297 if (value < im->minval)
2299 Y0 = ytr(im, value);
2300 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2304 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2305 gfx_line(im, X1, Y0, X1 + 2, Y0,
2306 GRIDWIDTH, im->graph_col[GRC_GRID]);
2307 gfx_dashed_line(im, X0 - 1, Y0,
2311 graph_col[GRC_GRID],
2312 im->grid_dash_on, im->grid_dash_off);
2315 /* fancy minor gridlines */
2316 else if (exfrac > 1) {
2317 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2318 value = pow(10.0, i);
2319 if (value < im->minval)
2321 Y0 = ytr(im, value);
2322 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2326 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2327 gfx_line(im, X1, Y0, X1 + 2, Y0,
2328 GRIDWIDTH, im->graph_col[GRC_GRID]);
2329 gfx_dashed_line(im, X0 - 1, Y0,
2333 graph_col[GRC_GRID],
2334 im->grid_dash_on, im->grid_dash_off);
2345 int xlab_sel; /* which sort of label and grid ? */
2346 time_t ti, tilab, timajor;
2348 char graph_label[100];
2349 double X0, Y0, Y1; /* points for filled graph and more */
2352 /* the type of time grid is determined by finding
2353 the number of seconds per pixel in the graph */
2354 if (im->xlab_user.minsec == -1) {
2355 factor = (im->end - im->start) / im->xsize;
2357 while (xlab[xlab_sel + 1].minsec !=
2358 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2360 } /* pick the last one */
2361 while (xlab[xlab_sel - 1].minsec ==
2362 xlab[xlab_sel].minsec
2363 && xlab[xlab_sel].length > (im->end - im->start)) {
2365 } /* go back to the smallest size */
2366 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2367 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2368 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2369 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2370 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2371 im->xlab_user.labst = xlab[xlab_sel].labst;
2372 im->xlab_user.precis = xlab[xlab_sel].precis;
2373 im->xlab_user.stst = xlab[xlab_sel].stst;
2376 /* y coords are the same for every line ... */
2378 Y1 = im->yorigin - im->ysize;
2379 /* paint the minor grid */
2380 if (!(im->extra_flags & NOMINOR)) {
2381 for (ti = find_first_time(im->start,
2389 find_first_time(im->start,
2396 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2398 /* are we inside the graph ? */
2399 if (ti < im->start || ti > im->end)
2401 while (timajor < ti) {
2402 timajor = find_next_time(timajor,
2405 mgridtm, im->xlab_user.mgridst);
2408 continue; /* skip as falls on major grid line */
2410 gfx_line(im, X0, Y1 - 2, X0, Y1,
2411 GRIDWIDTH, im->graph_col[GRC_GRID]);
2412 gfx_line(im, X0, Y0, X0, Y0 + 2,
2413 GRIDWIDTH, im->graph_col[GRC_GRID]);
2414 gfx_dashed_line(im, X0, Y0 + 1, X0,
2417 graph_col[GRC_GRID],
2418 im->grid_dash_on, im->grid_dash_off);
2422 /* paint the major grid */
2423 for (ti = find_first_time(im->start,
2431 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2433 /* are we inside the graph ? */
2434 if (ti < im->start || ti > im->end)
2437 gfx_line(im, X0, Y1 - 2, X0, Y1,
2438 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2439 gfx_line(im, X0, Y0, X0, Y0 + 3,
2440 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2441 gfx_dashed_line(im, X0, Y0 + 3, X0,
2445 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2447 /* paint the labels below the graph */
2449 find_first_time(im->start -
2458 im->xlab_user.precis / 2;
2459 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2461 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2462 /* are we inside the graph ? */
2463 if (tilab < im->start || tilab > im->end)
2466 localtime_r(&tilab, &tm);
2467 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2469 # error "your libc has no strftime I guess we'll abort the exercise here."
2474 im->graph_col[GRC_FONT],
2476 text_prop[TEXT_PROP_AXIS].
2479 GFX_H_CENTER, GFX_V_TOP, graph_label);
2488 /* draw x and y axis */
2489 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2490 im->xorigin+im->xsize,im->yorigin-im->ysize,
2491 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2493 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2494 im->xorigin+im->xsize,im->yorigin-im->ysize,
2495 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2497 gfx_line(im, im->xorigin - 4,
2499 im->xorigin + im->xsize +
2500 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2501 gfx_line(im, im->xorigin,
2504 im->yorigin - im->ysize -
2505 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2506 /* arrow for X and Y axis direction */
2507 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 */
2508 im->graph_col[GRC_ARROW]);
2510 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 */
2511 im->graph_col[GRC_ARROW]);
2520 double X0, Y0; /* points for filled graph and more */
2521 struct gfx_color_t water_color;
2523 /* draw 3d border */
2524 gfx_new_area(im, 0, im->yimg,
2525 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2526 gfx_add_point(im, im->ximg - 2, 2);
2527 gfx_add_point(im, im->ximg, 0);
2528 gfx_add_point(im, 0, 0);
2530 gfx_new_area(im, 2, im->yimg - 2,
2532 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2533 gfx_add_point(im, im->ximg, 0);
2534 gfx_add_point(im, im->ximg, im->yimg);
2535 gfx_add_point(im, 0, im->yimg);
2537 if (im->draw_x_grid == 1)
2539 if (im->draw_y_grid == 1) {
2540 if (im->logarithmic) {
2541 res = horizontal_log_grid(im);
2543 res = draw_horizontal_grid(im);
2546 /* dont draw horizontal grid if there is no min and max val */
2548 char *nodata = "No Data found";
2550 gfx_text(im, im->ximg / 2,
2553 im->graph_col[GRC_FONT],
2555 text_prop[TEXT_PROP_AXIS].
2558 GFX_H_CENTER, GFX_V_CENTER, nodata);
2562 /* yaxis unit description */
2567 im->graph_col[GRC_FONT],
2569 text_prop[TEXT_PROP_UNIT].
2572 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2576 im->graph_col[GRC_FONT],
2578 text_prop[TEXT_PROP_TITLE].
2580 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2581 /* rrdtool 'logo' */
2582 water_color = im->graph_col[GRC_FONT];
2583 water_color.alpha = 0.3;
2584 gfx_text(im, im->ximg - 4, 5,
2587 text_prop[TEXT_PROP_WATERMARK].
2588 font_desc, im->tabwidth,
2589 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2590 /* graph watermark */
2591 if (im->watermark[0] != '\0') {
2593 im->ximg / 2, im->yimg - 6,
2596 text_prop[TEXT_PROP_WATERMARK].
2597 font_desc, im->tabwidth, 0,
2598 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2602 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2603 for (i = 0; i < im->gdes_c; i++) {
2604 if (im->gdes[i].legend[0] == '\0')
2606 /* im->gdes[i].leg_y is the bottom of the legend */
2607 X0 = im->gdes[i].leg_x;
2608 Y0 = im->gdes[i].leg_y;
2609 gfx_text(im, X0, Y0,
2610 im->graph_col[GRC_FONT],
2613 [TEXT_PROP_LEGEND].font_desc,
2615 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2616 /* The legend for GRAPH items starts with "M " to have
2617 enough space for the box */
2618 if (im->gdes[i].gf != GF_PRINT &&
2619 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2623 boxH = gfx_get_text_width(im, 0,
2628 im->tabwidth, "o") * 1.2;
2630 /* shift the box up a bit */
2632 /* make sure transparent colors show up the same way as in the graph */
2635 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2636 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2638 gfx_new_area(im, X0, Y0 - boxV, X0,
2639 Y0, X0 + boxH, Y0, im->gdes[i].col);
2640 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2643 cairo_new_path(im->cr);
2644 cairo_set_line_width(im->cr, 1.0);
2647 gfx_line_fit(im, &X0, &Y0);
2648 gfx_line_fit(im, &X1, &Y1);
2649 cairo_move_to(im->cr, X0, Y0);
2650 cairo_line_to(im->cr, X1, Y0);
2651 cairo_line_to(im->cr, X1, Y1);
2652 cairo_line_to(im->cr, X0, Y1);
2653 cairo_close_path(im->cr);
2654 cairo_set_source_rgba(im->cr,
2666 blue, im->graph_col[GRC_FRAME].alpha);
2667 if (im->gdes[i].dash) {
2668 /* make box borders in legend dashed if the graph is dashed */
2672 cairo_set_dash(im->cr, dashes, 1, 0.0);
2674 cairo_stroke(im->cr);
2675 cairo_restore(im->cr);
2682 /*****************************************************
2683 * lazy check make sure we rely need to create this graph
2684 *****************************************************/
2691 struct stat imgstat;
2694 return 0; /* no lazy option */
2695 if (strlen(im->graphfile) == 0)
2696 return 0; /* inmemory option */
2697 if (stat(im->graphfile, &imgstat) != 0)
2698 return 0; /* can't stat */
2699 /* one pixel in the existing graph is more then what we would
2701 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2703 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2704 return 0; /* the file does not exist */
2705 switch (im->imgformat) {
2707 size = PngSize(fd, &(im->ximg), &(im->yimg));
2717 int graph_size_location(
2722 /* The actual size of the image to draw is determined from
2723 ** several sources. The size given on the command line is
2724 ** the graph area but we need more as we have to draw labels
2725 ** and other things outside the graph area
2728 int Xvertical = 0, Ytitle =
2729 0, Xylabel = 0, Xmain = 0, Ymain =
2730 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2732 if (im->extra_flags & ONLY_GRAPH) {
2734 im->ximg = im->xsize;
2735 im->yimg = im->ysize;
2736 im->yorigin = im->ysize;
2741 /** +---+--------------------------------------------+
2742 ** | y |...............graph title..................|
2743 ** | +---+-------------------------------+--------+
2746 ** | i | a | | pie |
2747 ** | s | x | main graph area | chart |
2752 ** | l | b +-------------------------------+--------+
2753 ** | e | l | x axis labels | |
2754 ** +---+---+-------------------------------+--------+
2755 ** |....................legends.....................|
2756 ** +------------------------------------------------+
2758 ** +------------------------------------------------+
2761 if (im->ylegend[0] != '\0') {
2762 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2765 if (im->title[0] != '\0') {
2766 /* The title is placed "inbetween" two text lines so it
2767 ** automatically has some vertical spacing. The horizontal
2768 ** spacing is added here, on each side.
2770 /* if necessary, reduce the font size of the title until it fits the image width */
2771 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2775 if (im->draw_x_grid) {
2776 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2778 if (im->draw_y_grid || im->forceleftspace) {
2780 gfx_get_text_width(im, 0,
2785 im->tabwidth, "0") * im->unitslength;
2789 if (im->extra_flags & FULL_SIZE_MODE) {
2790 /* The actual size of the image to draw has been determined by the user.
2791 ** The graph area is the space remaining after accounting for the legend,
2792 ** the watermark, the pie chart, the axis labels, and the title.
2795 im->ximg = im->xsize;
2796 im->yimg = im->ysize;
2797 im->yorigin = im->ysize;
2800 im->yorigin += Ytitle;
2801 /* Now calculate the total size. Insert some spacing where
2802 desired. im->xorigin and im->yorigin need to correspond
2803 with the lower left corner of the main graph area or, if
2804 this one is not set, the imaginary box surrounding the
2806 /* Initial size calculation for the main graph area */
2807 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2809 Xmain -= Xspacing; /* put space between main graph area and right edge */
2810 im->xorigin = Xspacing + Xylabel;
2811 /* the length of the title should not influence with width of the graph
2812 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2813 if (Xvertical) { /* unit description */
2815 im->xorigin += Xvertical;
2819 /* The vertical size of the image is known in advance. The main graph area
2820 ** (Ymain) and im->yorigin must be set according to the space requirements
2821 ** of the legend and the axis labels.
2823 if (im->extra_flags & NOLEGEND) {
2824 /* set dimensions correctly if using full size mode with no legend */
2827 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2828 Ymain = im->yorigin;
2830 /* Determine where to place the legends onto the image.
2831 ** Set Ymain and adjust im->yorigin to match the space requirements.
2833 if (leg_place(im, &Ymain) == -1)
2838 /* remove title space *or* some padding above the graph from the main graph area */
2842 Ymain -= 1.5 * Yspacing;
2845 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2846 if (im->watermark[0] != '\0') {
2847 Ymain -= Ywatermark;
2851 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2853 /* The actual size of the image to draw is determined from
2854 ** several sources. The size given on the command line is
2855 ** the graph area but we need more as we have to draw labels
2856 ** and other things outside the graph area.
2859 if (im->ylegend[0] != '\0') {
2860 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2864 if (im->title[0] != '\0') {
2865 /* The title is placed "inbetween" two text lines so it
2866 ** automatically has some vertical spacing. The horizontal
2867 ** spacing is added here, on each side.
2869 /* don't care for the with of the title
2870 Xtitle = gfx_get_text_width(im->canvas, 0,
2871 im->text_prop[TEXT_PROP_TITLE].font,
2872 im->text_prop[TEXT_PROP_TITLE].size,
2874 im->title, 0) + 2*Xspacing; */
2875 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2882 /* Now calculate the total size. Insert some spacing where
2883 desired. im->xorigin and im->yorigin need to correspond
2884 with the lower left corner of the main graph area or, if
2885 this one is not set, the imaginary box surrounding the
2888 /* The legend width cannot yet be determined, as a result we
2889 ** have problems adjusting the image to it. For now, we just
2890 ** forget about it at all; the legend will have to fit in the
2891 ** size already allocated.
2893 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2895 im->ximg += Xspacing;
2896 im->xorigin = Xspacing + Xylabel;
2897 /* the length of the title should not influence with width of the graph
2898 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2899 if (Xvertical) { /* unit description */
2900 im->ximg += Xvertical;
2901 im->xorigin += Xvertical;
2904 /* The vertical size is interesting... we need to compare
2905 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2906 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2907 ** in order to start even thinking about Ylegend or Ywatermark.
2909 ** Do it in three portions: First calculate the inner part,
2910 ** then do the legend, then adjust the total height of the img,
2911 ** adding space for a watermark if one exists;
2913 /* reserve space for main and/or pie */
2914 im->yimg = Ymain + Yxlabel;
2915 im->yorigin = im->yimg - Yxlabel;
2916 /* reserve space for the title *or* some padding above the graph */
2919 im->yorigin += Ytitle;
2921 im->yimg += 1.5 * Yspacing;
2922 im->yorigin += 1.5 * Yspacing;
2924 /* reserve space for padding below the graph */
2925 im->yimg += Yspacing;
2926 /* Determine where to place the legends onto the image.
2927 ** Adjust im->yimg to match the space requirements.
2929 if (leg_place(im, 0) == -1)
2931 if (im->watermark[0] != '\0') {
2932 im->yimg += Ywatermark;
2940 static cairo_status_t cairo_output(
2944 unsigned int length)
2946 image_desc_t *im = closure;
2948 im->rendered_image =
2949 realloc(im->rendered_image, im->rendered_image_size + length);
2950 if (im->rendered_image == NULL)
2951 return CAIRO_STATUS_WRITE_ERROR;
2952 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2953 im->rendered_image_size += length;
2954 return CAIRO_STATUS_SUCCESS;
2957 /* draw that picture thing ... */
2962 int lazy = lazy_check(im);
2963 double areazero = 0.0;
2964 graph_desc_t *lastgdes = NULL;
2967 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
2969 /* if we want and can be lazy ... quit now */
2971 info.u_cnt = im->ximg;
2972 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2973 info.u_cnt = im->yimg;
2974 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2977 /* pull the data from the rrd files ... */
2978 if (data_fetch(im) == -1)
2980 /* evaluate VDEF and CDEF operations ... */
2981 if (data_calc(im) == -1)
2983 /* calculate and PRINT and GPRINT definitions. We have to do it at
2984 * this point because it will affect the length of the legends
2985 * if there are no graph elements (i==0) we stop here ...
2986 * if we are lazy, try to quit ...
2992 if ((i == 0) || lazy)
2995 /**************************************************************
2996 *** Calculating sizes and locations became a bit confusing ***
2997 *** so I moved this into a separate function. ***
2998 **************************************************************/
2999 if (graph_size_location(im, i) == -1)
3002 info.u_cnt = im->xorigin;
3003 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3004 info.u_cnt = im->yorigin - im->ysize;
3005 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3006 info.u_cnt = im->xsize;
3007 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3008 info.u_cnt = im->ysize;
3009 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3010 info.u_cnt = im->ximg;
3011 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3012 info.u_cnt = im->yimg;
3013 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3015 /* get actual drawing data and find min and max values */
3016 if (data_proc(im) == -1)
3018 if (!im->logarithmic) {
3022 /* identify si magnitude Kilo, Mega Giga ? */
3023 if (!im->rigid && !im->logarithmic)
3024 expand_range(im); /* make sure the upper and lower limit are
3027 info.u_val = im->minval;
3028 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3029 info.u_val = im->maxval;
3030 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3032 if (!calc_horizontal_grid(im))
3037 apply_gridfit(im); */
3038 /* the actual graph is created by going through the individual
3039 graph elements and then drawing them */
3040 cairo_surface_destroy(im->surface);
3041 switch (im->imgformat) {
3044 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3045 im->ximg * im->zoom,
3046 im->yimg * im->zoom);
3050 im->surface = strlen(im->graphfile)
3051 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3052 im->yimg * im->zoom)
3053 : cairo_pdf_surface_create_for_stream
3054 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3058 im->surface = strlen(im->graphfile)
3060 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3061 im->yimg * im->zoom)
3062 : cairo_ps_surface_create_for_stream
3063 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3067 im->surface = strlen(im->graphfile)
3069 cairo_svg_surface_create(im->
3071 im->ximg * im->zoom, im->yimg * im->zoom)
3072 : cairo_svg_surface_create_for_stream
3073 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3074 cairo_svg_surface_restrict_to_version
3075 (im->surface, CAIRO_SVG_VERSION_1_1);
3078 cairo_destroy(im->cr);
3079 im->cr = cairo_create(im->surface);
3080 cairo_set_antialias(im->cr, im->graph_antialias);
3081 cairo_scale(im->cr, im->zoom, im->zoom);
3082 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3083 gfx_new_area(im, 0, 0, 0, im->yimg,
3084 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3085 gfx_add_point(im, im->ximg, 0);
3087 gfx_new_area(im, im->xorigin,
3090 im->xsize, im->yorigin,
3093 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3094 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3096 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3097 im->xsize, im->ysize + 2.0);
3099 if (im->minval > 0.0)
3100 areazero = im->minval;
3101 if (im->maxval < 0.0)
3102 areazero = im->maxval;
3103 for (i = 0; i < im->gdes_c; i++) {
3104 switch (im->gdes[i].gf) {
3118 for (ii = 0; ii < im->xsize; ii++) {
3119 if (!isnan(im->gdes[i].p_data[ii])
3120 && im->gdes[i].p_data[ii] != 0.0) {
3121 if (im->gdes[i].yrule > 0) {
3128 im->ysize, 1.0, im->gdes[i].col);
3129 } else if (im->gdes[i].yrule < 0) {
3132 im->yorigin - im->ysize,
3137 im->ysize, 1.0, im->gdes[i].col);
3144 /* fix data points at oo and -oo */
3145 for (ii = 0; ii < im->xsize; ii++) {
3146 if (isinf(im->gdes[i].p_data[ii])) {
3147 if (im->gdes[i].p_data[ii] > 0) {
3148 im->gdes[i].p_data[ii] = im->maxval;
3150 im->gdes[i].p_data[ii] = im->minval;
3156 /* *******************************************************
3161 -------|--t-1--t--------------------------------
3163 if we know the value at time t was a then
3164 we draw a square from t-1 to t with the value a.
3166 ********************************************************* */
3167 if (im->gdes[i].col.alpha != 0.0) {
3168 /* GF_LINE and friend */
3169 if (im->gdes[i].gf == GF_LINE) {
3170 double last_y = 0.0;
3174 cairo_new_path(im->cr);
3175 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3176 if (im->gdes[i].dash) {
3177 cairo_set_dash(im->cr,
3178 im->gdes[i].p_dashes,
3179 im->gdes[i].ndash, im->gdes[i].offset);
3182 for (ii = 1; ii < im->xsize; ii++) {
3183 if (isnan(im->gdes[i].p_data[ii])
3184 || (im->slopemode == 1
3185 && isnan(im->gdes[i].p_data[ii - 1]))) {
3190 last_y = ytr(im, im->gdes[i].p_data[ii]);
3191 if (im->slopemode == 0) {
3192 double x = ii - 1 + im->xorigin;
3195 gfx_line_fit(im, &x, &y);
3196 cairo_move_to(im->cr, x, y);
3197 x = ii + im->xorigin;
3199 gfx_line_fit(im, &x, &y);
3200 cairo_line_to(im->cr, x, y);
3202 double x = ii - 1 + im->xorigin;
3204 ytr(im, im->gdes[i].p_data[ii - 1]);
3205 gfx_line_fit(im, &x, &y);
3206 cairo_move_to(im->cr, x, y);
3207 x = ii + im->xorigin;
3209 gfx_line_fit(im, &x, &y);
3210 cairo_line_to(im->cr, x, y);
3214 double x1 = ii + im->xorigin;
3215 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3217 if (im->slopemode == 0
3218 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3219 double x = ii - 1 + im->xorigin;
3222 gfx_line_fit(im, &x, &y);
3223 cairo_line_to(im->cr, x, y);
3226 gfx_line_fit(im, &x1, &y1);
3227 cairo_line_to(im->cr, x1, y1);
3230 cairo_set_source_rgba(im->cr,
3236 col.blue, im->gdes[i].col.alpha);
3237 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3238 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3239 cairo_stroke(im->cr);
3240 cairo_restore(im->cr);
3244 (double *) malloc(sizeof(double) * im->xsize * 2);
3246 (double *) malloc(sizeof(double) * im->xsize * 2);
3248 (double *) malloc(sizeof(double) * im->xsize * 2);
3250 (double *) malloc(sizeof(double) * im->xsize * 2);
3253 for (ii = 0; ii <= im->xsize; ii++) {
3256 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3262 AlmostEqual2sComplement(foreY
3266 AlmostEqual2sComplement(foreY
3276 foreY[cntI], im->gdes[i].col);
3277 while (cntI < idxI) {
3282 AlmostEqual2sComplement(foreY
3286 AlmostEqual2sComplement(foreY
3293 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3295 gfx_add_point(im, backX[idxI], backY[idxI]);
3301 AlmostEqual2sComplement(backY
3305 AlmostEqual2sComplement(backY
3312 gfx_add_point(im, backX[idxI], backY[idxI]);
3322 if (ii == im->xsize)
3324 if (im->slopemode == 0 && ii == 0) {
3327 if (isnan(im->gdes[i].p_data[ii])) {
3331 ytop = ytr(im, im->gdes[i].p_data[ii]);
3332 if (lastgdes && im->gdes[i].stack) {
3333 ybase = ytr(im, lastgdes->p_data[ii]);
3335 ybase = ytr(im, areazero);
3337 if (ybase == ytop) {
3343 double extra = ytop;
3348 if (im->slopemode == 0) {
3349 backY[++idxI] = ybase - 0.2;
3350 backX[idxI] = ii + im->xorigin - 1;
3351 foreY[idxI] = ytop + 0.2;
3352 foreX[idxI] = ii + im->xorigin - 1;
3354 backY[++idxI] = ybase - 0.2;
3355 backX[idxI] = ii + im->xorigin;
3356 foreY[idxI] = ytop + 0.2;
3357 foreX[idxI] = ii + im->xorigin;
3359 /* close up any remaining area */
3364 } /* else GF_LINE */
3366 /* if color != 0x0 */
3367 /* make sure we do not run into trouble when stacking on NaN */
3368 for (ii = 0; ii < im->xsize; ii++) {
3369 if (isnan(im->gdes[i].p_data[ii])) {
3370 if (lastgdes && (im->gdes[i].stack)) {
3371 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3373 im->gdes[i].p_data[ii] = areazero;
3377 lastgdes = &(im->gdes[i]);
3381 ("STACK should already be turned into LINE or AREA here");
3386 cairo_reset_clip(im->cr);
3388 /* grid_paint also does the text */
3389 if (!(im->extra_flags & ONLY_GRAPH))
3391 if (!(im->extra_flags & ONLY_GRAPH))
3393 /* the RULES are the last thing to paint ... */
3394 for (i = 0; i < im->gdes_c; i++) {
3396 switch (im->gdes[i].gf) {
3398 if (im->gdes[i].yrule >= im->minval
3399 && im->gdes[i].yrule <= im->maxval) {
3401 if (im->gdes[i].dash) {
3402 cairo_set_dash(im->cr,
3403 im->gdes[i].p_dashes,
3404 im->gdes[i].ndash, im->gdes[i].offset);
3406 gfx_line(im, im->xorigin,
3407 ytr(im, im->gdes[i].yrule),
3408 im->xorigin + im->xsize,
3409 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3410 cairo_stroke(im->cr);
3411 cairo_restore(im->cr);
3415 if (im->gdes[i].xrule >= im->start
3416 && im->gdes[i].xrule <= im->end) {
3418 if (im->gdes[i].dash) {
3419 cairo_set_dash(im->cr,
3420 im->gdes[i].p_dashes,
3421 im->gdes[i].ndash, im->gdes[i].offset);
3424 xtr(im, im->gdes[i].xrule),
3425 im->yorigin, xtr(im,
3429 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3430 cairo_stroke(im->cr);
3431 cairo_restore(im->cr);
3440 switch (im->imgformat) {
3443 cairo_status_t status;
3445 status = strlen(im->graphfile) ?
3446 cairo_surface_write_to_png(im->surface, im->graphfile)
3447 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3450 if (status != CAIRO_STATUS_SUCCESS) {
3451 rrd_set_error("Could not save png to '%s'", im->graphfile);
3457 if (strlen(im->graphfile)) {
3458 cairo_show_page(im->cr);
3460 cairo_surface_finish(im->surface);
3469 /*****************************************************
3471 *****************************************************/
3478 if ((im->gdes = (graph_desc_t *)
3479 rrd_realloc(im->gdes, (im->gdes_c)
3480 * sizeof(graph_desc_t))) == NULL) {
3481 rrd_set_error("realloc graph_descs");
3486 im->gdes[im->gdes_c - 1].step = im->step;
3487 im->gdes[im->gdes_c - 1].step_orig = im->step;
3488 im->gdes[im->gdes_c - 1].stack = 0;
3489 im->gdes[im->gdes_c - 1].linewidth = 0;
3490 im->gdes[im->gdes_c - 1].debug = 0;
3491 im->gdes[im->gdes_c - 1].start = im->start;
3492 im->gdes[im->gdes_c - 1].start_orig = im->start;
3493 im->gdes[im->gdes_c - 1].end = im->end;
3494 im->gdes[im->gdes_c - 1].end_orig = im->end;
3495 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3496 im->gdes[im->gdes_c - 1].data = NULL;
3497 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3498 im->gdes[im->gdes_c - 1].data_first = 0;
3499 im->gdes[im->gdes_c - 1].p_data = NULL;
3500 im->gdes[im->gdes_c - 1].rpnp = NULL;
3501 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3502 im->gdes[im->gdes_c - 1].shift = 0.0;
3503 im->gdes[im->gdes_c - 1].dash = 0;
3504 im->gdes[im->gdes_c - 1].ndash = 0;
3505 im->gdes[im->gdes_c - 1].offset = 0;
3506 im->gdes[im->gdes_c - 1].col.red = 0.0;
3507 im->gdes[im->gdes_c - 1].col.green = 0.0;
3508 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3509 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3510 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3511 im->gdes[im->gdes_c - 1].format[0] = '\0';
3512 im->gdes[im->gdes_c - 1].strftm = 0;
3513 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3514 im->gdes[im->gdes_c - 1].ds = -1;
3515 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3516 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3517 im->gdes[im->gdes_c - 1].yrule = DNAN;
3518 im->gdes[im->gdes_c - 1].xrule = 0;
3522 /* copies input untill the first unescaped colon is found
3523 or until input ends. backslashes have to be escaped as well */
3525 const char *const input,
3531 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3532 if (input[inp] == '\\'
3533 && input[inp + 1] != '\0'
3534 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3535 output[outp++] = input[++inp];
3537 output[outp++] = input[inp];
3540 output[outp] = '\0';
3544 /* Now just a wrapper around rrd_graph_v */
3556 rrd_info_t *grinfo = NULL;
3559 grinfo = rrd_graph_v(argc, argv);
3565 if (strcmp(walker->key, "image_info") == 0) {
3568 rrd_realloc((*prdata),
3569 (prlines + 1) * sizeof(char *))) == NULL) {
3570 rrd_set_error("realloc prdata");
3573 /* imginfo goes to position 0 in the prdata array */
3574 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3575 + 2) * sizeof(char));
3576 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3577 (*prdata)[prlines] = NULL;
3579 /* skip anything else */
3580 walker = walker->next;
3588 if (strcmp(walker->key, "image_width") == 0) {
3589 *xsize = walker->value.u_int;
3590 } else if (strcmp(walker->key, "image_height") == 0) {
3591 *ysize = walker->value.u_int;
3592 } else if (strcmp(walker->key, "value_min") == 0) {
3593 *ymin = walker->value.u_val;
3594 } else if (strcmp(walker->key, "value_max") == 0) {
3595 *ymax = walker->value.u_val;
3596 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3599 rrd_realloc((*prdata),
3600 (prlines + 1) * sizeof(char *))) == NULL) {
3601 rrd_set_error("realloc prdata");
3604 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3605 + 2) * sizeof(char));
3606 (*prdata)[prlines] = NULL;
3607 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3608 } else if (strcmp(walker->key, "image") == 0) {
3609 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3610 (stream ? stream : stdout));
3612 /* skip anything else */
3613 walker = walker->next;
3615 rrd_info_free(grinfo);
3620 /* Some surgery done on this function, it became ridiculously big.
3622 ** - initializing now in rrd_graph_init()
3623 ** - options parsing now in rrd_graph_options()
3624 ** - script parsing now in rrd_graph_script()
3626 rrd_info_t *rrd_graph_v(
3632 rrd_graph_init(&im);
3633 /* a dummy surface so that we can measure text sizes for placements */
3635 rrd_graph_options(argc, argv, &im);
3636 if (rrd_test_error()) {
3637 rrd_info_free(im.grinfo);
3642 if (optind >= argc) {
3643 rrd_info_free(im.grinfo);
3645 rrd_set_error("missing filename");
3649 if (strlen(argv[optind]) >= MAXPATH) {
3650 rrd_set_error("filename (including path) too long");
3651 rrd_info_free(im.grinfo);
3656 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3657 im.graphfile[MAXPATH - 1] = '\0';
3659 if (strcmp(im.graphfile, "-") == 0) {
3660 im.graphfile[0] = '\0';
3663 rrd_graph_script(argc, argv, &im, 1);
3664 if (rrd_test_error()) {
3665 rrd_info_free(im.grinfo);
3670 /* Everything is now read and the actual work can start */
3672 if (graph_paint(&im) == -1) {
3673 rrd_info_free(im.grinfo);
3679 /* The image is generated and needs to be output.
3680 ** Also, if needed, print a line with information about the image.
3687 filename = im.graphfile + strlen(im.graphfile);
3688 while (filename > im.graphfile) {
3689 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3694 sprintf_alloc(im.imginfo,
3697 im.ximg), (long) (im.zoom * im.yimg));
3698 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3701 if (im.rendered_image) {
3704 img.u_blo.size = im.rendered_image_size;
3705 img.u_blo.ptr = im.rendered_image;
3706 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3715 image_desc_t *im,int prop,char *font, double size ){
3717 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3718 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3719 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3722 im->text_prop[prop].size = size;
3724 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3725 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3729 void rrd_graph_init(
3734 char *deffont = getenv("RRD_DEFAULT_FONT");
3735 static PangoFontMap *fontmap = NULL;
3736 PangoContext *context;
3741 #ifdef HAVE_SETLOCALE
3742 setlocale(LC_TIME, "");
3743 #ifdef HAVE_MBSTOWCS
3744 setlocale(LC_CTYPE, "");
3748 im->daemon_addr = NULL;
3749 im->draw_x_grid = 1;
3750 im->draw_y_grid = 1;
3751 im->extra_flags = 0;
3752 im->font_options = cairo_font_options_create();
3753 im->forceleftspace = 0;
3756 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3757 im->grid_dash_off = 1;
3758 im->grid_dash_on = 1;
3760 im->grinfo = (rrd_info_t *) NULL;
3761 im->grinfo_current = (rrd_info_t *) NULL;
3762 im->imgformat = IF_PNG;
3765 im->logarithmic = 0;
3771 im->rendered_image_size = 0;
3772 im->rendered_image = NULL;
3776 im->tabwidth = 40.0;
3777 im->title[0] = '\0';
3778 im->unitsexponent = 9999;
3779 im->unitslength = 6;
3780 im->viewfactor = 1.0;
3781 im->watermark[0] = '\0';
3782 im->with_markup = 0;
3784 im->xlab_user.minsec = -1;
3787 im->ygridstep = DNAN;
3789 im->ylegend[0] = '\0';
3794 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3795 im->cr = cairo_create(im->surface);
3797 for (i = 0; i < DIM(text_prop); i++) {
3798 im->text_prop[i].size = -1;
3799 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3802 if (fontmap == NULL){
3803 fontmap = pango_cairo_font_map_get_default();
3806 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3808 pango_cairo_context_set_resolution(context, 100);
3810 pango_cairo_update_context(im->cr,context);
3812 im->layout = pango_layout_new(context);
3814 // im->layout = pango_cairo_create_layout(im->cr);
3817 cairo_font_options_set_hint_style
3818 (im->font_options, CAIRO_HINT_STYLE_FULL);
3819 cairo_font_options_set_hint_metrics
3820 (im->font_options, CAIRO_HINT_METRICS_ON);
3821 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3825 for (i = 0; i < DIM(graph_col); i++)
3826 im->graph_col[i] = graph_col[i];
3832 void rrd_graph_options(
3839 char *parsetime_error = NULL;
3840 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3841 time_t start_tmp = 0, end_tmp = 0;
3843 rrd_time_value_t start_tv, end_tv;
3844 long unsigned int color;
3845 char *old_locale = "";
3847 /* defines for long options without a short equivalent. should be bytes,
3848 and may not collide with (the ASCII value of) short options */
3849 #define LONGOPT_UNITS_SI 255
3852 struct option long_options[] = {
3853 { "start", required_argument, 0, 's'},
3854 { "end", required_argument, 0, 'e'},
3855 { "x-grid", required_argument, 0, 'x'},
3856 { "y-grid", required_argument, 0, 'y'},
3857 { "vertical-label", required_argument, 0, 'v'},
3858 { "width", required_argument, 0, 'w'},
3859 { "height", required_argument, 0, 'h'},
3860 { "full-size-mode", no_argument, 0, 'D'},
3861 { "interlaced", no_argument, 0, 'i'},
3862 { "upper-limit", required_argument, 0, 'u'},
3863 { "lower-limit", required_argument, 0, 'l'},
3864 { "rigid", no_argument, 0, 'r'},
3865 { "base", required_argument, 0, 'b'},
3866 { "logarithmic", no_argument, 0, 'o'},
3867 { "color", required_argument, 0, 'c'},
3868 { "font", required_argument, 0, 'n'},
3869 { "title", required_argument, 0, 't'},
3870 { "imginfo", required_argument, 0, 'f'},
3871 { "imgformat", required_argument, 0, 'a'},
3872 { "lazy", no_argument, 0, 'z'},
3873 { "zoom", required_argument, 0, 'm'},
3874 { "no-legend", no_argument, 0, 'g'},
3875 { "force-rules-legend", no_argument, 0, 'F'},
3876 { "only-graph", no_argument, 0, 'j'},
3877 { "alt-y-grid", no_argument, 0, 'Y'},
3878 { "no-minor", no_argument, 0, 'I'},
3879 { "slope-mode", no_argument, 0, 'E'},
3880 { "alt-autoscale", no_argument, 0, 'A'},
3881 { "alt-autoscale-min", no_argument, 0, 'J'},
3882 { "alt-autoscale-max", no_argument, 0, 'M'},
3883 { "no-gridfit", no_argument, 0, 'N'},
3884 { "units-exponent", required_argument, 0, 'X'},
3885 { "units-length", required_argument, 0, 'L'},
3886 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3887 { "step", required_argument, 0, 'S'},
3888 { "tabwidth", required_argument, 0, 'T'},
3889 { "font-render-mode", required_argument, 0, 'R'},
3890 { "graph-render-mode", required_argument, 0, 'G'},
3891 { "font-smoothing-threshold", required_argument, 0, 'B'},
3892 { "watermark", required_argument, 0, 'W'},
3893 { "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 */
3894 { "pango-markup", no_argument, 0, 'P'},
3895 { "daemon", required_argument, 0, 'd'},
3901 opterr = 0; /* initialize getopt */
3902 rrd_parsetime("end-24h", &start_tv);
3903 rrd_parsetime("now", &end_tv);
3905 int option_index = 0;
3907 int col_start, col_end;
3909 opt = getopt_long(argc, argv,
3910 "s:e:x:y:v:w:h:D:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:kPd:",
3911 long_options, &option_index);
3916 im->extra_flags |= NOMINOR;
3919 im->extra_flags |= ALTYGRID;
3922 im->extra_flags |= ALTAUTOSCALE;
3925 im->extra_flags |= ALTAUTOSCALE_MIN;
3928 im->extra_flags |= ALTAUTOSCALE_MAX;
3931 im->extra_flags |= ONLY_GRAPH;
3934 im->extra_flags |= NOLEGEND;
3937 im->extra_flags |= FORCE_RULES_LEGEND;
3939 case LONGOPT_UNITS_SI:
3940 if (im->extra_flags & FORCE_UNITS) {
3941 rrd_set_error("--units can only be used once!");
3942 setlocale(LC_NUMERIC, old_locale);
3945 if (strcmp(optarg, "si") == 0)
3946 im->extra_flags |= FORCE_UNITS_SI;
3948 rrd_set_error("invalid argument for --units: %s", optarg);
3953 im->unitsexponent = atoi(optarg);
3956 im->unitslength = atoi(optarg);
3957 im->forceleftspace = 1;
3960 old_locale = setlocale(LC_NUMERIC, "C");
3961 im->tabwidth = atof(optarg);
3962 setlocale(LC_NUMERIC, old_locale);
3965 old_locale = setlocale(LC_NUMERIC, "C");
3966 im->step = atoi(optarg);
3967 setlocale(LC_NUMERIC, old_locale);
3973 im->with_markup = 1;
3976 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3977 rrd_set_error("start time: %s", parsetime_error);
3982 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3983 rrd_set_error("end time: %s", parsetime_error);
3988 if (strcmp(optarg, "none") == 0) {
3989 im->draw_x_grid = 0;
3993 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3995 &im->xlab_user.gridst,
3997 &im->xlab_user.mgridst,
3999 &im->xlab_user.labst,
4000 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4001 strncpy(im->xlab_form, optarg + stroff,
4002 sizeof(im->xlab_form) - 1);
4003 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4005 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4006 rrd_set_error("unknown keyword %s", scan_gtm);
4009 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4011 rrd_set_error("unknown keyword %s", scan_mtm);
4014 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4015 rrd_set_error("unknown keyword %s", scan_ltm);
4018 im->xlab_user.minsec = 1;
4019 im->xlab_user.stst = im->xlab_form;
4021 rrd_set_error("invalid x-grid format");
4027 if (strcmp(optarg, "none") == 0) {
4028 im->draw_y_grid = 0;
4031 old_locale = setlocale(LC_NUMERIC, "C");
4032 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4033 setlocale(LC_NUMERIC, old_locale);
4034 if (im->ygridstep <= 0) {
4035 rrd_set_error("grid step must be > 0");
4037 } else if (im->ylabfact < 1) {
4038 rrd_set_error("label factor must be > 0");
4042 setlocale(LC_NUMERIC, old_locale);
4043 rrd_set_error("invalid y-grid format");
4048 strncpy(im->ylegend, optarg, 150);
4049 im->ylegend[150] = '\0';
4052 old_locale = setlocale(LC_NUMERIC, "C");
4053 im->maxval = atof(optarg);
4054 setlocale(LC_NUMERIC, old_locale);
4057 old_locale = setlocale(LC_NUMERIC, "C");
4058 im->minval = atof(optarg);
4059 setlocale(LC_NUMERIC, old_locale);
4062 im->base = atol(optarg);
4063 if (im->base != 1024 && im->base != 1000) {
4065 ("the only sensible value for base apart from 1000 is 1024");
4070 long_tmp = atol(optarg);
4071 if (long_tmp < 10) {
4072 rrd_set_error("width below 10 pixels");
4075 im->xsize = long_tmp;
4078 long_tmp = atol(optarg);
4079 if (long_tmp < 10) {
4080 rrd_set_error("height below 10 pixels");
4083 im->ysize = long_tmp;
4086 im->extra_flags |= FULL_SIZE_MODE;
4089 /* interlaced png not supported at the moment */
4095 im->imginfo = optarg;
4099 (im->imgformat = if_conv(optarg)) == -1) {
4100 rrd_set_error("unsupported graphics format '%s'", optarg);
4111 im->logarithmic = 1;
4115 "%10[A-Z]#%n%8lx%n",
4116 col_nam, &col_start, &color, &col_end) == 2) {
4118 int col_len = col_end - col_start;
4123 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4131 (((color & 0xF000) *
4132 0x11000) | ((color & 0x0F00) *
4133 0x01100) | ((color &
4136 ((color & 0x000F) * 0x00011)
4140 color = (color << 8) + 0xff /* shift left by 8 */ ;
4145 rrd_set_error("the color format is #RRGGBB[AA]");
4148 if ((ci = grc_conv(col_nam)) != -1) {
4149 im->graph_col[ci] = gfx_hex_to_col(color);
4151 rrd_set_error("invalid color name '%s'", col_nam);
4155 rrd_set_error("invalid color def format");
4164 old_locale = setlocale(LC_NUMERIC, "C");
4165 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4166 int sindex, propidx;
4168 setlocale(LC_NUMERIC, old_locale);
4169 if ((sindex = text_prop_conv(prop)) != -1) {
4170 for (propidx = sindex;
4171 propidx < TEXT_PROP_LAST; propidx++) {
4173 rrd_set_font_desc(im,propidx,NULL,size);
4175 if ((int) strlen(optarg) > end) {
4176 if (optarg[end] == ':') {
4177 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4180 ("expected : after font size in '%s'",
4185 /* only run the for loop for DEFAULT (0) for
4186 all others, we break here. woodo programming */
4187 if (propidx == sindex && sindex != 0)
4191 rrd_set_error("invalid fonttag '%s'", prop);
4195 setlocale(LC_NUMERIC, old_locale);
4196 rrd_set_error("invalid text property format");
4202 old_locale = setlocale(LC_NUMERIC, "C");
4203 im->zoom = atof(optarg);
4204 setlocale(LC_NUMERIC, old_locale);
4205 if (im->zoom <= 0.0) {
4206 rrd_set_error("zoom factor must be > 0");
4211 strncpy(im->title, optarg, 150);
4212 im->title[150] = '\0';
4215 if (strcmp(optarg, "normal") == 0) {
4216 cairo_font_options_set_antialias
4217 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4218 cairo_font_options_set_hint_style
4219 (im->font_options, CAIRO_HINT_STYLE_FULL);
4220 } else if (strcmp(optarg, "light") == 0) {
4221 cairo_font_options_set_antialias
4222 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4223 cairo_font_options_set_hint_style
4224 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4225 } else if (strcmp(optarg, "mono") == 0) {
4226 cairo_font_options_set_antialias
4227 (im->font_options, CAIRO_ANTIALIAS_NONE);
4228 cairo_font_options_set_hint_style
4229 (im->font_options, CAIRO_HINT_STYLE_FULL);
4231 rrd_set_error("unknown font-render-mode '%s'", optarg);
4236 if (strcmp(optarg, "normal") == 0)
4237 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4238 else if (strcmp(optarg, "mono") == 0)
4239 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4241 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4246 /* not supported curently */
4249 strncpy(im->watermark, optarg, 100);
4250 im->watermark[99] = '\0';
4254 if (im->daemon_addr != NULL)
4256 rrd_set_error ("You cannot specify --daemon "
4261 im->daemon_addr = strdup(optarg);
4262 if (im->daemon_addr == NULL)
4264 rrd_set_error("strdup failed");
4272 rrd_set_error("unknown option '%c'", optopt);
4274 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4279 { /* try to connect to rrdcached */
4280 int status = rrdc_connect(im->daemon_addr);
4281 if (status != 0) return;
4284 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4285 pango_layout_context_changed(im->layout);
4289 if (im->logarithmic && im->minval <= 0) {
4291 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4295 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4296 /* error string is set in rrd_parsetime.c */
4300 if (start_tmp < 3600 * 24 * 365 * 10) {
4302 ("the first entry to fetch should be after 1980 (%ld)",
4307 if (end_tmp < start_tmp) {
4309 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4313 im->start = start_tmp;
4315 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4318 int rrd_graph_color(
4326 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4328 color = strstr(var, "#");
4329 if (color == NULL) {
4330 if (optional == 0) {
4331 rrd_set_error("Found no color in %s", err);
4338 long unsigned int col;
4340 rest = strstr(color, ":");
4347 sscanf(color, "#%6lx%n", &col, &n);
4348 col = (col << 8) + 0xff /* shift left by 8 */ ;
4350 rrd_set_error("Color problem in %s", err);
4353 sscanf(color, "#%8lx%n", &col, &n);
4357 rrd_set_error("Color problem in %s", err);
4359 if (rrd_test_error())
4361 gdp->col = gfx_hex_to_col(col);
4374 while (*ptr != '\0')
4375 if (*ptr++ == '%') {
4377 /* line cannot end with percent char */
4380 /* '%s', '%S' and '%%' are allowed */
4381 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4383 /* %c is allowed (but use only with vdef!) */
4384 else if (*ptr == 'c') {
4389 /* or else '% 6.2lf' and such are allowed */
4391 /* optional padding character */
4392 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4394 /* This should take care of 'm.n' with all three optional */
4395 while (*ptr >= '0' && *ptr <= '9')
4399 while (*ptr >= '0' && *ptr <= '9')
4401 /* Either 'le', 'lf' or 'lg' must follow here */
4404 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4419 const char *const str)
4421 /* A VDEF currently is either "func" or "param,func"
4422 * so the parsing is rather simple. Change if needed.
4430 old_locale = setlocale(LC_NUMERIC, "C");
4431 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4432 setlocale(LC_NUMERIC, old_locale);
4433 if (n == (int) strlen(str)) { /* matched */
4437 sscanf(str, "%29[A-Z]%n", func, &n);
4438 if (n == (int) strlen(str)) { /* matched */
4442 ("Unknown function string '%s' in VDEF '%s'",
4447 if (!strcmp("PERCENT", func))
4448 gdes->vf.op = VDEF_PERCENT;
4449 else if (!strcmp("MAXIMUM", func))
4450 gdes->vf.op = VDEF_MAXIMUM;
4451 else if (!strcmp("AVERAGE", func))
4452 gdes->vf.op = VDEF_AVERAGE;
4453 else if (!strcmp("STDEV", func))
4454 gdes->vf.op = VDEF_STDEV;
4455 else if (!strcmp("MINIMUM", func))
4456 gdes->vf.op = VDEF_MINIMUM;
4457 else if (!strcmp("TOTAL", func))
4458 gdes->vf.op = VDEF_TOTAL;
4459 else if (!strcmp("FIRST", func))
4460 gdes->vf.op = VDEF_FIRST;
4461 else if (!strcmp("LAST", func))
4462 gdes->vf.op = VDEF_LAST;
4463 else if (!strcmp("LSLSLOPE", func))
4464 gdes->vf.op = VDEF_LSLSLOPE;
4465 else if (!strcmp("LSLINT", func))
4466 gdes->vf.op = VDEF_LSLINT;
4467 else if (!strcmp("LSLCORREL", func))
4468 gdes->vf.op = VDEF_LSLCORREL;
4471 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4474 switch (gdes->vf.op) {
4476 if (isnan(param)) { /* no parameter given */
4478 ("Function '%s' needs parameter in VDEF '%s'\n",
4482 if (param >= 0.0 && param <= 100.0) {
4483 gdes->vf.param = param;
4484 gdes->vf.val = DNAN; /* undefined */
4485 gdes->vf.when = 0; /* undefined */
4488 ("Parameter '%f' out of range in VDEF '%s'\n",
4489 param, gdes->vname);
4502 case VDEF_LSLCORREL:
4504 gdes->vf.param = DNAN;
4505 gdes->vf.val = DNAN;
4509 ("Function '%s' needs no parameter in VDEF '%s'\n",
4523 graph_desc_t *src, *dst;
4527 dst = &im->gdes[gdi];
4528 src = &im->gdes[dst->vidx];
4529 data = src->data + src->ds;
4531 steps = (src->end - src->start) / src->step;
4534 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4535 src->start, src->end, steps);
4537 switch (dst->vf.op) {
4541 if ((array = malloc(steps * sizeof(double))) == NULL) {
4542 rrd_set_error("malloc VDEV_PERCENT");
4545 for (step = 0; step < steps; step++) {
4546 array[step] = data[step * src->ds_cnt];
4548 qsort(array, step, sizeof(double), vdef_percent_compar);
4549 field = (steps - 1) * dst->vf.param / 100;
4550 dst->vf.val = array[field];
4551 dst->vf.when = 0; /* no time component */
4554 for (step = 0; step < steps; step++)
4555 printf("DEBUG: %3li:%10.2f %c\n",
4556 step, array[step], step == field ? '*' : ' ');
4562 while (step != steps && isnan(data[step * src->ds_cnt]))
4564 if (step == steps) {
4568 dst->vf.val = data[step * src->ds_cnt];
4569 dst->vf.when = src->start + (step + 1) * src->step;
4571 while (step != steps) {
4572 if (finite(data[step * src->ds_cnt])) {
4573 if (data[step * src->ds_cnt] > dst->vf.val) {
4574 dst->vf.val = data[step * src->ds_cnt];
4575 dst->vf.when = src->start + (step + 1) * src->step;
4586 double average = 0.0;
4588 for (step = 0; step < steps; step++) {
4589 if (finite(data[step * src->ds_cnt])) {
4590 sum += data[step * src->ds_cnt];
4595 if (dst->vf.op == VDEF_TOTAL) {
4596 dst->vf.val = sum * src->step;
4597 dst->vf.when = 0; /* no time component */
4598 } else if (dst->vf.op == VDEF_AVERAGE) {
4599 dst->vf.val = sum / cnt;
4600 dst->vf.when = 0; /* no time component */
4602 average = sum / cnt;
4604 for (step = 0; step < steps; step++) {
4605 if (finite(data[step * src->ds_cnt])) {
4606 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4609 dst->vf.val = pow(sum / cnt, 0.5);
4610 dst->vf.when = 0; /* no time component */
4620 while (step != steps && isnan(data[step * src->ds_cnt]))
4622 if (step == steps) {
4626 dst->vf.val = data[step * src->ds_cnt];
4627 dst->vf.when = src->start + (step + 1) * src->step;
4629 while (step != steps) {
4630 if (finite(data[step * src->ds_cnt])) {
4631 if (data[step * src->ds_cnt] < dst->vf.val) {
4632 dst->vf.val = data[step * src->ds_cnt];
4633 dst->vf.when = src->start + (step + 1) * src->step;
4640 /* The time value returned here is one step before the
4641 * actual time value. This is the start of the first
4645 while (step != steps && isnan(data[step * src->ds_cnt]))
4647 if (step == steps) { /* all entries were NaN */
4651 dst->vf.val = data[step * src->ds_cnt];
4652 dst->vf.when = src->start + step * src->step;
4656 /* The time value returned here is the
4657 * actual time value. This is the end of the last
4661 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4663 if (step < 0) { /* all entries were NaN */
4667 dst->vf.val = data[step * src->ds_cnt];
4668 dst->vf.when = src->start + (step + 1) * src->step;
4673 case VDEF_LSLCORREL:{
4674 /* Bestfit line by linear least squares method */
4677 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4684 for (step = 0; step < steps; step++) {
4685 if (finite(data[step * src->ds_cnt])) {
4688 SUMxx += step * step;
4689 SUMxy += step * data[step * src->ds_cnt];
4690 SUMy += data[step * src->ds_cnt];
4691 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4695 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4696 y_intercept = (SUMy - slope * SUMx) / cnt;
4699 (SUMx * SUMy) / cnt) /
4701 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4703 if (dst->vf.op == VDEF_LSLSLOPE) {
4704 dst->vf.val = slope;
4706 } else if (dst->vf.op == VDEF_LSLINT) {
4707 dst->vf.val = y_intercept;
4709 } else if (dst->vf.op == VDEF_LSLCORREL) {
4710 dst->vf.val = correl;
4723 /* NaN < -INF < finite_values < INF */
4724 int vdef_percent_compar(
4730 /* Equality is not returned; this doesn't hurt except
4731 * (maybe) for a little performance.
4734 /* First catch NaN values. They are smallest */
4735 if (isnan(*(double *) a))
4737 if (isnan(*(double *) b))
4739 /* NaN doesn't reach this part so INF and -INF are extremes.
4740 * The sign from isinf() is compatible with the sign we return
4742 if (isinf(*(double *) a))
4743 return isinf(*(double *) a);
4744 if (isinf(*(double *) b))
4745 return isinf(*(double *) b);
4746 /* If we reach this, both values must be finite */
4747 if (*(double *) a < *(double *) b)
4756 rrd_info_type_t type,
4757 rrd_infoval_t value)
4759 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4760 if (im->grinfo == NULL) {
4761 im->grinfo = im->grinfo_current;