1 /****************************************************************************
2 * RRDtool 1.3.1 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"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
39 text_prop_t text_prop[] = {
40 {8.0, RRD_DEFAULT_FONT,NULL}
42 {9.0, RRD_DEFAULT_FONT,NULL}
44 {7.0, RRD_DEFAULT_FONT,NULL}
46 {8.0, RRD_DEFAULT_FONT,NULL}
48 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
50 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
54 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
56 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
58 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
60 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
62 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
64 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
66 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
68 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
70 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
72 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
73 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
75 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
77 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
79 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
81 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
83 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
86 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
89 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
92 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
94 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
95 365 * 24 * 3600, "%y"}
97 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
100 /* sensible y label intervals ...*/
124 {20.0, {1, 5, 10, 20}
130 {100.0, {1, 2, 5, 10}
133 {200.0, {1, 5, 10, 20}
136 {500.0, {1, 2, 4, 10}
144 gfx_color_t graph_col[] = /* default colors */
146 {1.00, 1.00, 1.00, 1.00}, /* canvas */
147 {0.95, 0.95, 0.95, 1.00}, /* background */
148 {0.81, 0.81, 0.81, 1.00}, /* shade A */
149 {0.62, 0.62, 0.62, 1.00}, /* shade B */
150 {0.56, 0.56, 0.56, 0.75}, /* grid */
151 {0.87, 0.31, 0.31, 0.60}, /* major grid */
152 {0.00, 0.00, 0.00, 1.00}, /* font */
153 {0.50, 0.12, 0.12, 1.00}, /* arrow */
154 {0.12, 0.12, 0.12, 1.00}, /* axis */
155 {0.00, 0.00, 0.00, 1.00} /* frame */
162 # define DPRINT(x) (void)(printf x, printf("\n"))
168 /* initialize with xtr(im,0); */
176 pixie = (double) im->xsize / (double) (im->end - im->start);
179 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
182 /* translate data values into y coordinates */
191 if (!im->logarithmic)
192 pixie = (double) im->ysize / (im->maxval - im->minval);
195 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
197 } else if (!im->logarithmic) {
198 yval = im->yorigin - pixie * (value - im->minval);
200 if (value < im->minval) {
203 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
211 /* conversion function for symbolic entry names */
214 #define conv_if(VV,VVV) \
215 if (strcmp(#VV, string) == 0) return VVV ;
221 conv_if(PRINT, GF_PRINT);
222 conv_if(GPRINT, GF_GPRINT);
223 conv_if(COMMENT, GF_COMMENT);
224 conv_if(HRULE, GF_HRULE);
225 conv_if(VRULE, GF_VRULE);
226 conv_if(LINE, GF_LINE);
227 conv_if(AREA, GF_AREA);
228 conv_if(STACK, GF_STACK);
229 conv_if(TICK, GF_TICK);
230 conv_if(TEXTALIGN, GF_TEXTALIGN);
231 conv_if(DEF, GF_DEF);
232 conv_if(CDEF, GF_CDEF);
233 conv_if(VDEF, GF_VDEF);
234 conv_if(XPORT, GF_XPORT);
235 conv_if(SHIFT, GF_SHIFT);
240 enum gfx_if_en if_conv(
244 conv_if(PNG, IF_PNG);
245 conv_if(SVG, IF_SVG);
246 conv_if(EPS, IF_EPS);
247 conv_if(PDF, IF_PDF);
252 enum tmt_en tmt_conv(
256 conv_if(SECOND, TMT_SECOND);
257 conv_if(MINUTE, TMT_MINUTE);
258 conv_if(HOUR, TMT_HOUR);
259 conv_if(DAY, TMT_DAY);
260 conv_if(WEEK, TMT_WEEK);
261 conv_if(MONTH, TMT_MONTH);
262 conv_if(YEAR, TMT_YEAR);
266 enum grc_en grc_conv(
270 conv_if(BACK, GRC_BACK);
271 conv_if(CANVAS, GRC_CANVAS);
272 conv_if(SHADEA, GRC_SHADEA);
273 conv_if(SHADEB, GRC_SHADEB);
274 conv_if(GRID, GRC_GRID);
275 conv_if(MGRID, GRC_MGRID);
276 conv_if(FONT, GRC_FONT);
277 conv_if(ARROW, GRC_ARROW);
278 conv_if(AXIS, GRC_AXIS);
279 conv_if(FRAME, GRC_FRAME);
284 enum text_prop_en text_prop_conv(
288 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
289 conv_if(TITLE, TEXT_PROP_TITLE);
290 conv_if(AXIS, TEXT_PROP_AXIS);
291 conv_if(UNIT, TEXT_PROP_UNIT);
292 conv_if(LEGEND, TEXT_PROP_LEGEND);
293 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
304 cairo_status_t status = 0;
308 for (i = 0; i < (unsigned) im->gdes_c; i++) {
309 if (im->gdes[i].data_first) {
310 /* careful here, because a single pointer can occur several times */
311 free(im->gdes[i].data);
312 if (im->gdes[i].ds_namv) {
313 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
314 free(im->gdes[i].ds_namv[ii]);
315 free(im->gdes[i].ds_namv);
318 /* free allocated memory used for dashed lines */
319 if (im->gdes[i].p_dashes != NULL)
320 free(im->gdes[i].p_dashes);
322 free(im->gdes[i].p_data);
323 free(im->gdes[i].rpnp);
326 if (im->font_options)
327 cairo_font_options_destroy(im->font_options);
330 status = cairo_status(im->cr);
331 cairo_destroy(im->cr);
333 if (im->rendered_image) {
334 free(im->rendered_image);
338 g_object_unref (im->layout);
342 cairo_surface_destroy(im->surface);
345 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
346 cairo_status_to_string(status));
351 /* find SI magnitude symbol for the given number*/
353 image_desc_t *im, /* image description */
359 char *symbol[] = { "a", /* 10e-18 Atto */
360 "f", /* 10e-15 Femto */
361 "p", /* 10e-12 Pico */
362 "n", /* 10e-9 Nano */
363 "u", /* 10e-6 Micro */
364 "m", /* 10e-3 Milli */
369 "T", /* 10e12 Tera */
370 "P", /* 10e15 Peta */
377 if (*value == 0.0 || isnan(*value)) {
381 sindex = floor(log(fabs(*value)) / log((double) im->base));
382 *magfact = pow((double) im->base, (double) sindex);
383 (*value) /= (*magfact);
385 if (sindex <= symbcenter && sindex >= -symbcenter) {
386 (*symb_ptr) = symbol[sindex + symbcenter];
393 static char si_symbol[] = {
394 'a', /* 10e-18 Atto */
395 'f', /* 10e-15 Femto */
396 'p', /* 10e-12 Pico */
397 'n', /* 10e-9 Nano */
398 'u', /* 10e-6 Micro */
399 'm', /* 10e-3 Milli */
404 'T', /* 10e12 Tera */
405 'P', /* 10e15 Peta */
408 static const int si_symbcenter = 6;
410 /* find SI magnitude symbol for the numbers on the y-axis*/
412 image_desc_t *im /* image description */
416 double digits, viewdigits = 0;
419 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
420 log((double) im->base));
422 if (im->unitsexponent != 9999) {
423 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
424 viewdigits = floor(im->unitsexponent / 3);
429 im->magfact = pow((double) im->base, digits);
432 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
435 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
437 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
438 ((viewdigits + si_symbcenter) >= 0))
439 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
444 /* move min and max values around to become sensible */
449 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
450 600.0, 500.0, 400.0, 300.0, 250.0,
451 200.0, 125.0, 100.0, 90.0, 80.0,
452 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
453 25.0, 20.0, 10.0, 9.0, 8.0,
454 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
455 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
456 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
459 double scaled_min, scaled_max;
466 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
467 im->minval, im->maxval, im->magfact);
470 if (isnan(im->ygridstep)) {
471 if (im->extra_flags & ALTAUTOSCALE) {
472 /* measure the amplitude of the function. Make sure that
473 graph boundaries are slightly higher then max/min vals
474 so we can see amplitude on the graph */
477 delt = im->maxval - im->minval;
479 fact = 2.0 * pow(10.0,
481 (max(fabs(im->minval), fabs(im->maxval)) /
484 adj = (fact - delt) * 0.55;
487 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
488 im->minval, im->maxval, delt, fact, adj);
493 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
494 /* measure the amplitude of the function. Make sure that
495 graph boundaries are slightly lower than min vals
496 so we can see amplitude on the graph */
497 adj = (im->maxval - im->minval) * 0.1;
499 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
500 /* measure the amplitude of the function. Make sure that
501 graph boundaries are slightly higher than max vals
502 so we can see amplitude on the graph */
503 adj = (im->maxval - im->minval) * 0.1;
506 scaled_min = im->minval / im->magfact;
507 scaled_max = im->maxval / im->magfact;
509 for (i = 1; sensiblevalues[i] > 0; i++) {
510 if (sensiblevalues[i - 1] >= scaled_min &&
511 sensiblevalues[i] <= scaled_min)
512 im->minval = sensiblevalues[i] * (im->magfact);
514 if (-sensiblevalues[i - 1] <= scaled_min &&
515 -sensiblevalues[i] >= scaled_min)
516 im->minval = -sensiblevalues[i - 1] * (im->magfact);
518 if (sensiblevalues[i - 1] >= scaled_max &&
519 sensiblevalues[i] <= scaled_max)
520 im->maxval = sensiblevalues[i - 1] * (im->magfact);
522 if (-sensiblevalues[i - 1] <= scaled_max &&
523 -sensiblevalues[i] >= scaled_max)
524 im->maxval = -sensiblevalues[i] * (im->magfact);
528 /* adjust min and max to the grid definition if there is one */
529 im->minval = (double) im->ylabfact * im->ygridstep *
530 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
531 im->maxval = (double) im->ylabfact * im->ygridstep *
532 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
536 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
537 im->minval, im->maxval, im->magfact);
545 if (isnan(im->minval) || isnan(im->maxval))
548 if (im->logarithmic) {
549 double ya, yb, ypix, ypixfrac;
550 double log10_range = log10(im->maxval) - log10(im->minval);
552 ya = pow((double) 10, floor(log10(im->minval)));
553 while (ya < im->minval)
556 return; /* don't have y=10^x gridline */
558 if (yb <= im->maxval) {
559 /* we have at least 2 y=10^x gridlines.
560 Make sure distance between them in pixels
561 are an integer by expanding im->maxval */
562 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
563 double factor = y_pixel_delta / floor(y_pixel_delta);
564 double new_log10_range = factor * log10_range;
565 double new_ymax_log10 = log10(im->minval) + new_log10_range;
567 im->maxval = pow(10, new_ymax_log10);
568 ytr(im, DNAN); /* reset precalc */
569 log10_range = log10(im->maxval) - log10(im->minval);
571 /* make sure first y=10^x gridline is located on
572 integer pixel position by moving scale slightly
573 downwards (sub-pixel movement) */
574 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
575 ypixfrac = ypix - floor(ypix);
576 if (ypixfrac > 0 && ypixfrac < 1) {
577 double yfrac = ypixfrac / im->ysize;
579 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
580 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
581 ytr(im, DNAN); /* reset precalc */
584 /* Make sure we have an integer pixel distance between
585 each minor gridline */
586 double ypos1 = ytr(im, im->minval);
587 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
588 double y_pixel_delta = ypos1 - ypos2;
589 double factor = y_pixel_delta / floor(y_pixel_delta);
590 double new_range = factor * (im->maxval - im->minval);
591 double gridstep = im->ygrid_scale.gridstep;
592 double minor_y, minor_y_px, minor_y_px_frac;
594 if (im->maxval > 0.0)
595 im->maxval = im->minval + new_range;
597 im->minval = im->maxval - new_range;
598 ytr(im, DNAN); /* reset precalc */
599 /* make sure first minor gridline is on integer pixel y coord */
600 minor_y = gridstep * floor(im->minval / gridstep);
601 while (minor_y < im->minval)
603 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
604 minor_y_px_frac = minor_y_px - floor(minor_y_px);
605 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
606 double yfrac = minor_y_px_frac / im->ysize;
607 double range = im->maxval - im->minval;
609 im->minval = im->minval - yfrac * range;
610 im->maxval = im->maxval - yfrac * range;
611 ytr(im, DNAN); /* reset precalc */
613 calc_horizontal_grid(im); /* recalc with changed im->maxval */
617 /* reduce data reimplementation by Alex */
620 enum cf_en cf, /* which consolidation function ? */
621 unsigned long cur_step, /* step the data currently is in */
622 time_t *start, /* start, end and step as requested ... */
623 time_t *end, /* ... by the application will be ... */
624 unsigned long *step, /* ... adjusted to represent reality */
625 unsigned long *ds_cnt, /* number of data sources in file */
627 { /* two dimensional array containing the data */
628 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
629 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
631 rrd_value_t *srcptr, *dstptr;
633 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
636 row_cnt = ((*end) - (*start)) / cur_step;
642 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
643 row_cnt, reduce_factor, *start, *end, cur_step);
644 for (col = 0; col < row_cnt; col++) {
645 printf("time %10lu: ", *start + (col + 1) * cur_step);
646 for (i = 0; i < *ds_cnt; i++)
647 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
652 /* We have to combine [reduce_factor] rows of the source
653 ** into one row for the destination. Doing this we also
654 ** need to take care to combine the correct rows. First
655 ** alter the start and end time so that they are multiples
656 ** of the new step time. We cannot reduce the amount of
657 ** time so we have to move the end towards the future and
658 ** the start towards the past.
660 end_offset = (*end) % (*step);
661 start_offset = (*start) % (*step);
663 /* If there is a start offset (which cannot be more than
664 ** one destination row), skip the appropriate number of
665 ** source rows and one destination row. The appropriate
666 ** number is what we do know (start_offset/cur_step) of
667 ** the new interval (*step/cur_step aka reduce_factor).
670 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
671 printf("row_cnt before: %lu\n", row_cnt);
674 (*start) = (*start) - start_offset;
675 skiprows = reduce_factor - start_offset / cur_step;
676 srcptr += skiprows * *ds_cnt;
677 for (col = 0; col < (*ds_cnt); col++)
682 printf("row_cnt between: %lu\n", row_cnt);
685 /* At the end we have some rows that are not going to be
686 ** used, the amount is end_offset/cur_step
689 (*end) = (*end) - end_offset + (*step);
690 skiprows = end_offset / cur_step;
694 printf("row_cnt after: %lu\n", row_cnt);
697 /* Sanity check: row_cnt should be multiple of reduce_factor */
698 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
700 if (row_cnt % reduce_factor) {
701 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
702 row_cnt, reduce_factor);
703 printf("BUG in reduce_data()\n");
707 /* Now combine reduce_factor intervals at a time
708 ** into one interval for the destination.
711 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
712 for (col = 0; col < (*ds_cnt); col++) {
713 rrd_value_t newval = DNAN;
714 unsigned long validval = 0;
716 for (i = 0; i < reduce_factor; i++) {
717 if (isnan(srcptr[i * (*ds_cnt) + col])) {
722 newval = srcptr[i * (*ds_cnt) + col];
731 newval += srcptr[i * (*ds_cnt) + col];
734 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
737 /* an interval contains a failure if any subintervals contained a failure */
739 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
742 newval = srcptr[i * (*ds_cnt) + col];
768 srcptr += (*ds_cnt) * reduce_factor;
769 row_cnt -= reduce_factor;
771 /* If we had to alter the endtime, we didn't have enough
772 ** source rows to fill the last row. Fill it with NaN.
775 for (col = 0; col < (*ds_cnt); col++)
778 row_cnt = ((*end) - (*start)) / *step;
780 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
781 row_cnt, *start, *end, *step);
782 for (col = 0; col < row_cnt; col++) {
783 printf("time %10lu: ", *start + (col + 1) * (*step));
784 for (i = 0; i < *ds_cnt; i++)
785 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
792 /* get the data required for the graphs from the
801 /* pull the data from the rrd files ... */
802 for (i = 0; i < (int) im->gdes_c; i++) {
803 /* only GF_DEF elements fetch data */
804 if (im->gdes[i].gf != GF_DEF)
808 /* do we have it already ? */
809 for (ii = 0; ii < i; ii++) {
810 if (im->gdes[ii].gf != GF_DEF)
812 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
813 && (im->gdes[i].cf == im->gdes[ii].cf)
814 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
815 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
816 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
817 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
818 /* OK, the data is already there.
819 ** Just copy the header portion
821 im->gdes[i].start = im->gdes[ii].start;
822 im->gdes[i].end = im->gdes[ii].end;
823 im->gdes[i].step = im->gdes[ii].step;
824 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
825 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
826 im->gdes[i].data = im->gdes[ii].data;
827 im->gdes[i].data_first = 0;
834 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
836 if ((rrd_fetch_fn(im->gdes[i].rrd,
842 &im->gdes[i].ds_namv,
843 &im->gdes[i].data)) == -1) {
846 im->gdes[i].data_first = 1;
848 if (ft_step < im->gdes[i].step) {
849 reduce_data(im->gdes[i].cf_reduce,
854 &im->gdes[i].ds_cnt, &im->gdes[i].data);
856 im->gdes[i].step = ft_step;
860 /* lets see if the required data source is really there */
861 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
862 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
866 if (im->gdes[i].ds == -1) {
867 rrd_set_error("No DS called '%s' in '%s'",
868 im->gdes[i].ds_nam, im->gdes[i].rrd);
876 /* evaluate the expressions in the CDEF functions */
878 /*************************************************************
880 *************************************************************/
882 long find_var_wrapper(
886 return find_var((image_desc_t *) arg1, key);
889 /* find gdes containing var*/
896 for (ii = 0; ii < im->gdes_c - 1; ii++) {
897 if ((im->gdes[ii].gf == GF_DEF
898 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
899 && (strcmp(im->gdes[ii].vname, key) == 0)) {
906 /* find the largest common denominator for all the numbers
907 in the 0 terminated num array */
914 for (i = 0; num[i + 1] != 0; i++) {
916 rest = num[i] % num[i + 1];
922 /* return i==0?num[i]:num[i-1]; */
926 /* run the rpn calculator on all the VDEF and CDEF arguments */
933 long *steparray, rpi;
938 rpnstack_init(&rpnstack);
940 for (gdi = 0; gdi < im->gdes_c; gdi++) {
941 /* Look for GF_VDEF and GF_CDEF in the same loop,
942 * so CDEFs can use VDEFs and vice versa
944 switch (im->gdes[gdi].gf) {
948 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
950 /* remove current shift */
951 vdp->start -= vdp->shift;
952 vdp->end -= vdp->shift;
955 if (im->gdes[gdi].shidx >= 0)
956 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
959 vdp->shift = im->gdes[gdi].shval;
961 /* normalize shift to multiple of consolidated step */
962 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
965 vdp->start += vdp->shift;
966 vdp->end += vdp->shift;
970 /* A VDEF has no DS. This also signals other parts
971 * of rrdtool that this is a VDEF value, not a CDEF.
973 im->gdes[gdi].ds_cnt = 0;
974 if (vdef_calc(im, gdi)) {
975 rrd_set_error("Error processing VDEF '%s'",
976 im->gdes[gdi].vname);
977 rpnstack_free(&rpnstack);
982 im->gdes[gdi].ds_cnt = 1;
983 im->gdes[gdi].ds = 0;
984 im->gdes[gdi].data_first = 1;
985 im->gdes[gdi].start = 0;
986 im->gdes[gdi].end = 0;
991 /* Find the variables in the expression.
992 * - VDEF variables are substituted by their values
993 * and the opcode is changed into OP_NUMBER.
994 * - CDEF variables are analized for their step size,
995 * the lowest common denominator of all the step
996 * sizes of the data sources involved is calculated
997 * and the resulting number is the step size for the
998 * resulting data source.
1000 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1001 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1002 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1003 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1005 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1008 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1009 im->gdes[gdi].vname, im->gdes[ptr].vname);
1010 printf("DEBUG: value from vdef is %f\n",
1011 im->gdes[ptr].vf.val);
1013 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1014 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1015 } else { /* normal variables and PREF(variables) */
1017 /* add one entry to the array that keeps track of the step sizes of the
1018 * data sources going into the CDEF. */
1020 rrd_realloc(steparray,
1022 1) * sizeof(*steparray))) == NULL) {
1023 rrd_set_error("realloc steparray");
1024 rpnstack_free(&rpnstack);
1028 steparray[stepcnt - 1] = im->gdes[ptr].step;
1030 /* adjust start and end of cdef (gdi) so
1031 * that it runs from the latest start point
1032 * to the earliest endpoint of any of the
1033 * rras involved (ptr)
1036 if (im->gdes[gdi].start < im->gdes[ptr].start)
1037 im->gdes[gdi].start = im->gdes[ptr].start;
1039 if (im->gdes[gdi].end == 0 ||
1040 im->gdes[gdi].end > im->gdes[ptr].end)
1041 im->gdes[gdi].end = im->gdes[ptr].end;
1043 /* store pointer to the first element of
1044 * the rra providing data for variable,
1045 * further save step size and data source
1048 im->gdes[gdi].rpnp[rpi].data =
1049 im->gdes[ptr].data + im->gdes[ptr].ds;
1050 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1051 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1053 /* backoff the *.data ptr; this is done so
1054 * rpncalc() function doesn't have to treat
1055 * the first case differently
1057 } /* if ds_cnt != 0 */
1058 } /* if OP_VARIABLE */
1059 } /* loop through all rpi */
1061 /* move the data pointers to the correct period */
1062 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1063 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1064 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1065 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1067 im->gdes[gdi].start - im->gdes[ptr].start;
1070 im->gdes[gdi].rpnp[rpi].data +=
1071 (diff / im->gdes[ptr].step) *
1072 im->gdes[ptr].ds_cnt;
1076 if (steparray == NULL) {
1077 rrd_set_error("rpn expressions without DEF"
1078 " or CDEF variables are not supported");
1079 rpnstack_free(&rpnstack);
1082 steparray[stepcnt] = 0;
1083 /* Now find the resulting step. All steps in all
1084 * used RRAs have to be visited
1086 im->gdes[gdi].step = lcd(steparray);
1088 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1089 im->gdes[gdi].start)
1090 / im->gdes[gdi].step)
1091 * sizeof(double))) == NULL) {
1092 rrd_set_error("malloc im->gdes[gdi].data");
1093 rpnstack_free(&rpnstack);
1097 /* Step through the new cdef results array and
1098 * calculate the values
1100 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1101 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1102 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1104 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1105 * in this case we are advancing by timesteps;
1106 * we use the fact that time_t is a synonym for long
1108 if (rpn_calc(rpnp, &rpnstack, (long) now,
1109 im->gdes[gdi].data, ++dataidx) == -1) {
1110 /* rpn_calc sets the error string */
1111 rpnstack_free(&rpnstack);
1114 } /* enumerate over time steps within a CDEF */
1119 } /* enumerate over CDEFs */
1120 rpnstack_free(&rpnstack);
1124 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1125 /* yes we are loosing precision by doing tos with floats instead of doubles
1126 but it seems more stable this way. */
1128 static int AlmostEqual2sComplement(
1134 int aInt = *(int *) &A;
1135 int bInt = *(int *) &B;
1138 /* Make sure maxUlps is non-negative and small enough that the
1139 default NAN won't compare as equal to anything. */
1141 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1143 /* Make aInt lexicographically ordered as a twos-complement int */
1146 aInt = 0x80000000l - aInt;
1148 /* Make bInt lexicographically ordered as a twos-complement int */
1151 bInt = 0x80000000l - bInt;
1153 intDiff = abs(aInt - bInt);
1155 if (intDiff <= maxUlps)
1161 /* massage data so, that we get one value for each x coordinate in the graph */
1166 double pixstep = (double) (im->end - im->start)
1167 / (double) im->xsize; /* how much time
1168 passes in one pixel */
1170 double minval = DNAN, maxval = DNAN;
1172 unsigned long gr_time;
1174 /* memory for the processed data */
1175 for (i = 0; i < im->gdes_c; i++) {
1176 if ((im->gdes[i].gf == GF_LINE) ||
1177 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1178 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1179 * sizeof(rrd_value_t))) == NULL) {
1180 rrd_set_error("malloc data_proc");
1186 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1189 gr_time = im->start + pixstep * i; /* time of the current step */
1192 for (ii = 0; ii < im->gdes_c; ii++) {
1195 switch (im->gdes[ii].gf) {
1199 if (!im->gdes[ii].stack)
1201 value = im->gdes[ii].yrule;
1202 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1203 /* The time of the data doesn't necessarily match
1204 ** the time of the graph. Beware.
1206 vidx = im->gdes[ii].vidx;
1207 if (im->gdes[vidx].gf == GF_VDEF) {
1208 value = im->gdes[vidx].vf.val;
1210 if (((long int) gr_time >=
1211 (long int) im->gdes[vidx].start)
1212 && ((long int) gr_time <=
1213 (long int) im->gdes[vidx].end)) {
1214 value = im->gdes[vidx].data[(unsigned long)
1220 im->gdes[vidx].step)
1221 * im->gdes[vidx].ds_cnt +
1228 if (!isnan(value)) {
1230 im->gdes[ii].p_data[i] = paintval;
1231 /* GF_TICK: the data values are not
1232 ** relevant for min and max
1234 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1235 if ((isnan(minval) || paintval < minval) &&
1236 !(im->logarithmic && paintval <= 0.0))
1238 if (isnan(maxval) || paintval > maxval)
1242 im->gdes[ii].p_data[i] = DNAN;
1247 ("STACK should already be turned into LINE or AREA here");
1256 /* if min or max have not been asigned a value this is because
1257 there was no data in the graph ... this is not good ...
1258 lets set these to dummy values then ... */
1260 if (im->logarithmic) {
1261 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1262 minval = 0.0; /* catching this right away below */
1265 /* in logarithm mode, where minval is smaller or equal
1266 to 0 make the beast just way smaller than maxval */
1268 minval = maxval / 10e8;
1271 if (isnan(minval) || isnan(maxval)) {
1277 /* adjust min and max values given by the user */
1278 /* for logscale we add something on top */
1279 if (isnan(im->minval)
1280 || ((!im->rigid) && im->minval > minval)
1282 if (im->logarithmic)
1283 im->minval = minval / 2.0;
1285 im->minval = minval;
1287 if (isnan(im->maxval)
1288 || (!im->rigid && im->maxval < maxval)
1290 if (im->logarithmic)
1291 im->maxval = maxval * 2.0;
1293 im->maxval = maxval;
1296 /* make sure min is smaller than max */
1297 if (im->minval > im->maxval) {
1299 im->minval = 0.99 * im->maxval;
1301 im->minval = 1.01 * im->maxval;
1304 /* make sure min and max are not equal */
1305 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1311 /* make sure min and max are not both zero */
1312 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1321 /* identify the point where the first gridline, label ... gets placed */
1323 time_t find_first_time(
1324 time_t start, /* what is the initial time */
1325 enum tmt_en baseint, /* what is the basic interval */
1326 long basestep /* how many if these do we jump a time */
1331 localtime_r(&start, &tm);
1335 tm. tm_sec -= tm.tm_sec % basestep;
1340 tm. tm_min -= tm.tm_min % basestep;
1346 tm. tm_hour -= tm.tm_hour % basestep;
1350 /* we do NOT look at the basestep for this ... */
1357 /* we do NOT look at the basestep for this ... */
1361 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1363 if (tm.tm_wday == 0)
1364 tm. tm_mday -= 7; /* we want the *previous* monday */
1372 tm. tm_mon -= tm.tm_mon % basestep;
1383 tm.tm_year + 1900) %basestep;
1389 /* identify the point where the next gridline, label ... gets placed */
1390 time_t find_next_time(
1391 time_t current, /* what is the initial time */
1392 enum tmt_en baseint, /* what is the basic interval */
1393 long basestep /* how many if these do we jump a time */
1399 localtime_r(¤t, &tm);
1404 tm. tm_sec += basestep;
1408 tm. tm_min += basestep;
1412 tm. tm_hour += basestep;
1416 tm. tm_mday += basestep;
1420 tm. tm_mday += 7 * basestep;
1424 tm. tm_mon += basestep;
1428 tm. tm_year += basestep;
1430 madetime = mktime(&tm);
1431 } while (madetime == -1); /* this is necessary to skip impssible times
1432 like the daylight saving time skips */
1438 /* calculate values required for PRINT and GPRINT functions */
1443 long i, ii, validsteps;
1446 int graphelement = 0;
1449 double magfact = -1;
1454 /* wow initializing tmvdef is quite a task :-) */
1455 time_t now = time(NULL);
1457 localtime_r(&now, &tmvdef);
1458 for (i = 0; i < im->gdes_c; i++) {
1459 vidx = im->gdes[i].vidx;
1460 switch (im->gdes[i].gf) {
1463 /* PRINT and GPRINT can now print VDEF generated values.
1464 * There's no need to do any calculations on them as these
1465 * calculations were already made.
1467 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1468 printval = im->gdes[vidx].vf.val;
1469 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1470 } else { /* need to calculate max,min,avg etcetera */
1471 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1472 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1475 for (ii = im->gdes[vidx].ds;
1476 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1477 if (!finite(im->gdes[vidx].data[ii]))
1479 if (isnan(printval)) {
1480 printval = im->gdes[vidx].data[ii];
1485 switch (im->gdes[i].cf) {
1489 case CF_DEVSEASONAL:
1493 printval += im->gdes[vidx].data[ii];
1496 printval = min(printval, im->gdes[vidx].data[ii]);
1500 printval = max(printval, im->gdes[vidx].data[ii]);
1503 printval = im->gdes[vidx].data[ii];
1506 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1507 if (validsteps > 1) {
1508 printval = (printval / validsteps);
1511 } /* prepare printval */
1513 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1514 /* Magfact is set to -1 upon entry to print_calc. If it
1515 * is still less than 0, then we need to run auto_scale.
1516 * Otherwise, put the value into the correct units. If
1517 * the value is 0, then do not set the symbol or magnification
1518 * so next the calculation will be performed again. */
1519 if (magfact < 0.0) {
1520 auto_scale(im, &printval, &si_symb, &magfact);
1521 if (printval == 0.0)
1524 printval /= magfact;
1526 *(++percent_s) = 's';
1527 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1528 auto_scale(im, &printval, &si_symb, &magfact);
1531 if (im->gdes[i].gf == GF_PRINT) {
1532 rrd_infoval_t prline;
1534 if (im->gdes[i].strftm) {
1535 prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
1536 strftime(prline.u_str,
1537 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1538 } else if (bad_format(im->gdes[i].format)) {
1540 ("bad format for PRINT in '%s'", im->gdes[i].format);
1544 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1548 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1553 if (im->gdes[i].strftm) {
1554 strftime(im->gdes[i].legend,
1555 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1557 if (bad_format(im->gdes[i].format)) {
1559 ("bad format for GPRINT in '%s'",
1560 im->gdes[i].format);
1563 #ifdef HAVE_SNPRINTF
1564 snprintf(im->gdes[i].legend,
1566 im->gdes[i].format, printval, si_symb);
1568 sprintf(im->gdes[i].legend,
1569 im->gdes[i].format, printval, si_symb);
1581 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1582 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1587 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1588 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1597 #ifdef WITH_PIECHART
1605 ("STACK should already be turned into LINE or AREA here");
1610 return graphelement;
1614 /* place legends with color spots */
1620 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1621 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1622 int fill = 0, fill_last;
1625 int leg_y = im->yimg;
1626 int leg_y_prev = im->yimg;
1629 int i, ii, mark = 0;
1630 char prt_fctn; /*special printfunctions */
1631 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1635 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1636 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1637 rrd_set_error("malloc for legspace");
1641 if (im->extra_flags & FULL_SIZE_MODE)
1642 leg_y = leg_y_prev =
1643 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1644 for (i = 0; i < im->gdes_c; i++) {
1646 /* hide legends for rules which are not displayed */
1647 if (im->gdes[i].gf == GF_TEXTALIGN) {
1648 default_txtalign = im->gdes[i].txtalign;
1651 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1652 if (im->gdes[i].gf == GF_HRULE
1653 && (im->gdes[i].yrule <
1654 im->minval || im->gdes[i].yrule > im->maxval))
1655 im->gdes[i].legend[0] = '\0';
1656 if (im->gdes[i].gf == GF_VRULE
1657 && (im->gdes[i].xrule <
1658 im->start || im->gdes[i].xrule > im->end))
1659 im->gdes[i].legend[0] = '\0';
1662 /* turn \\t into tab */
1663 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1664 memmove(tab, tab + 1, strlen(tab));
1667 leg_cc = strlen(im->gdes[i].legend);
1668 /* is there a controle code ant the end of the legend string ? */
1669 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1670 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1672 im->gdes[i].legend[leg_cc] = '\0';
1676 /* only valid control codes */
1677 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1681 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1684 ("Unknown control code at the end of '%s\\%c'",
1685 im->gdes[i].legend, prt_fctn);
1689 if (prt_fctn == 'n') {
1693 /* remove exess space from the end of the legend for \g */
1694 while (prt_fctn == 'g' &&
1695 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1697 im->gdes[i].legend[leg_cc] = '\0';
1702 /* no interleg space if string ends in \g */
1703 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1705 fill += legspace[i];
1708 gfx_get_text_width(im,
1714 im->tabwidth, im->gdes[i].legend);
1719 /* who said there was a special tag ... ? */
1720 if (prt_fctn == 'g') {
1724 if (prt_fctn == '\0') {
1725 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1726 /* just one legend item is left right or center */
1727 switch (default_txtalign) {
1742 /* is it time to place the legends ? */
1743 if (fill > im->ximg - 2 * border) {
1751 if (leg_c == 1 && prt_fctn == 'j') {
1757 if (prt_fctn != '\0') {
1759 if (leg_c >= 2 && prt_fctn == 'j') {
1760 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1764 if (prt_fctn == 'c')
1765 leg_x = (im->ximg - fill) / 2.0;
1766 if (prt_fctn == 'r')
1767 leg_x = im->ximg - fill - border;
1768 for (ii = mark; ii <= i; ii++) {
1769 if (im->gdes[ii].legend[0] == '\0')
1770 continue; /* skip empty legends */
1771 im->gdes[ii].leg_x = leg_x;
1772 im->gdes[ii].leg_y = leg_y;
1774 gfx_get_text_width(im, leg_x,
1779 im->tabwidth, im->gdes[ii].legend)
1784 if (im->extra_flags & FULL_SIZE_MODE) {
1785 /* only add y space if there was text on the line */
1786 if (leg_x > border || prt_fctn == 's')
1787 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1788 if (prt_fctn == 's')
1789 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1791 if (leg_x > border || prt_fctn == 's')
1792 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1793 if (prt_fctn == 's')
1794 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1802 if (im->extra_flags & FULL_SIZE_MODE) {
1803 if (leg_y != leg_y_prev) {
1804 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1806 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1810 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1818 /* create a grid on the graph. it determines what to do
1819 from the values of xsize, start and end */
1821 /* the xaxis labels are determined from the number of seconds per pixel
1822 in the requested graph */
1824 int calc_horizontal_grid(
1832 int decimals, fractionals;
1834 im->ygrid_scale.labfact = 2;
1835 range = im->maxval - im->minval;
1836 scaledrange = range / im->magfact;
1837 /* does the scale of this graph make it impossible to put lines
1838 on it? If so, give up. */
1839 if (isnan(scaledrange)) {
1843 /* find grid spaceing */
1845 if (isnan(im->ygridstep)) {
1846 if (im->extra_flags & ALTYGRID) {
1847 /* find the value with max number of digits. Get number of digits */
1850 (max(fabs(im->maxval), fabs(im->minval)) *
1851 im->viewfactor / im->magfact));
1852 if (decimals <= 0) /* everything is small. make place for zero */
1854 im->ygrid_scale.gridstep =
1856 floor(log10(range * im->viewfactor / im->magfact))) /
1857 im->viewfactor * im->magfact;
1858 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1859 im->ygrid_scale.gridstep = 0.1;
1860 /* should have at least 5 lines but no more then 15 */
1861 if (range / im->ygrid_scale.gridstep < 5
1862 && im->ygrid_scale.gridstep >= 30)
1863 im->ygrid_scale.gridstep /= 10;
1864 if (range / im->ygrid_scale.gridstep > 15)
1865 im->ygrid_scale.gridstep *= 10;
1866 if (range / im->ygrid_scale.gridstep > 5) {
1867 im->ygrid_scale.labfact = 1;
1868 if (range / im->ygrid_scale.gridstep > 8
1869 || im->ygrid_scale.gridstep <
1870 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1871 im->ygrid_scale.labfact = 2;
1873 im->ygrid_scale.gridstep /= 5;
1874 im->ygrid_scale.labfact = 5;
1878 (im->ygrid_scale.gridstep *
1879 (double) im->ygrid_scale.labfact * im->viewfactor /
1881 if (fractionals < 0) { /* small amplitude. */
1882 int len = decimals - fractionals + 1;
1884 if (im->unitslength < len + 2)
1885 im->unitslength = len + 2;
1886 sprintf(im->ygrid_scale.labfmt,
1888 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1890 int len = decimals + 1;
1892 if (im->unitslength < len + 2)
1893 im->unitslength = len + 2;
1894 sprintf(im->ygrid_scale.labfmt,
1895 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1897 } else { /* classic rrd grid */
1898 for (i = 0; ylab[i].grid > 0; i++) {
1899 pixel = im->ysize / (scaledrange / ylab[i].grid);
1905 for (i = 0; i < 4; i++) {
1906 if (pixel * ylab[gridind].lfac[i] >=
1907 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1908 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1913 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1916 im->ygrid_scale.gridstep = im->ygridstep;
1917 im->ygrid_scale.labfact = im->ylabfact;
1922 int draw_horizontal_grid(
1928 char graph_label[100];
1930 double X0 = im->xorigin;
1931 double X1 = im->xorigin + im->xsize;
1932 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1933 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1937 im->ygrid_scale.gridstep /
1938 (double) im->magfact * (double) im->viewfactor;
1939 MaxY = scaledstep * (double) egrid;
1940 for (i = sgrid; i <= egrid; i++) {
1942 im->ygrid_scale.gridstep * i);
1944 im->ygrid_scale.gridstep * (i + 1));
1946 if (floor(Y0 + 0.5) >=
1947 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1948 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1949 with the chosen settings. Add a label if required by settings, or if
1950 there is only one label so far and the next grid line is out of bounds. */
1951 if (i % im->ygrid_scale.labfact == 0
1953 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1954 if (im->symbol == ' ') {
1955 if (im->extra_flags & ALTYGRID) {
1956 sprintf(graph_label,
1957 im->ygrid_scale.labfmt,
1958 scaledstep * (double) i);
1961 sprintf(graph_label, "%4.1f",
1962 scaledstep * (double) i);
1964 sprintf(graph_label, "%4.0f",
1965 scaledstep * (double) i);
1969 char sisym = (i == 0 ? ' ' : im->symbol);
1971 if (im->extra_flags & ALTYGRID) {
1972 sprintf(graph_label,
1973 im->ygrid_scale.labfmt,
1974 scaledstep * (double) i, sisym);
1977 sprintf(graph_label, "%4.1f %c",
1978 scaledstep * (double) i, sisym);
1980 sprintf(graph_label, "%4.0f %c",
1981 scaledstep * (double) i, sisym);
1989 text_prop[TEXT_PROP_AXIS].
1991 im->graph_col[GRC_FONT],
1993 text_prop[TEXT_PROP_AXIS].
1996 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
1997 gfx_line(im, X0 - 2, Y0, X0, Y0,
1998 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1999 gfx_line(im, X1, Y0, X1 + 2, Y0,
2000 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2001 gfx_dashed_line(im, X0 - 2, Y0,
2007 im->grid_dash_on, im->grid_dash_off);
2008 } else if (!(im->extra_flags & NOMINOR)) {
2011 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2012 gfx_line(im, X1, Y0, X1 + 2, Y0,
2013 GRIDWIDTH, im->graph_col[GRC_GRID]);
2014 gfx_dashed_line(im, X0 - 1, Y0,
2018 graph_col[GRC_GRID],
2019 im->grid_dash_on, im->grid_dash_off);
2026 /* this is frexp for base 10 */
2037 iexp = floor(log(fabs(x)) / log(10));
2038 mnt = x / pow(10.0, iexp);
2041 mnt = x / pow(10.0, iexp);
2048 /* logaritmic horizontal grid */
2049 int horizontal_log_grid(
2053 double yloglab[][10] = {
2055 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2057 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2059 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2076 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2078 int i, j, val_exp, min_exp;
2079 double nex; /* number of decades in data */
2080 double logscale; /* scale in logarithmic space */
2081 int exfrac = 1; /* decade spacing */
2082 int mid = -1; /* row in yloglab for major grid */
2083 double mspac; /* smallest major grid spacing (pixels) */
2084 int flab; /* first value in yloglab to use */
2085 double value, tmp, pre_value;
2087 char graph_label[100];
2089 nex = log10(im->maxval / im->minval);
2090 logscale = im->ysize / nex;
2091 /* major spacing for data with high dynamic range */
2092 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2099 /* major spacing for less dynamic data */
2101 /* search best row in yloglab */
2103 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2104 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2107 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2110 /* find first value in yloglab */
2112 yloglab[mid][flab] < 10
2113 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2114 if (yloglab[mid][flab] == 10.0) {
2119 if (val_exp % exfrac)
2120 val_exp += abs(-val_exp % exfrac);
2122 X1 = im->xorigin + im->xsize;
2127 value = yloglab[mid][flab] * pow(10.0, val_exp);
2128 if (AlmostEqual2sComplement(value, pre_value, 4))
2129 break; /* it seems we are not converging */
2131 Y0 = ytr(im, value);
2132 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2134 /* major grid line */
2136 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2137 gfx_line(im, X1, Y0, X1 + 2, Y0,
2138 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2139 gfx_dashed_line(im, X0 - 2, Y0,
2144 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2146 if (im->extra_flags & FORCE_UNITS_SI) {
2151 scale = floor(val_exp / 3.0);
2153 pvalue = pow(10.0, val_exp % 3);
2155 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2156 pvalue *= yloglab[mid][flab];
2157 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2158 && ((scale + si_symbcenter) >= 0))
2159 symbol = si_symbol[scale + si_symbcenter];
2162 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2164 sprintf(graph_label, "%3.0e", value);
2168 text_prop[TEXT_PROP_AXIS].
2170 im->graph_col[GRC_FONT],
2172 text_prop[TEXT_PROP_AXIS].
2175 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2177 if (mid < 4 && exfrac == 1) {
2178 /* find first and last minor line behind current major line
2179 * i is the first line and j tha last */
2181 min_exp = val_exp - 1;
2182 for (i = 1; yloglab[mid][i] < 10.0; i++);
2183 i = yloglab[mid][i - 1] + 1;
2187 i = yloglab[mid][flab - 1] + 1;
2188 j = yloglab[mid][flab];
2191 /* draw minor lines below current major line */
2192 for (; i < j; i++) {
2194 value = i * pow(10.0, min_exp);
2195 if (value < im->minval)
2197 Y0 = ytr(im, value);
2198 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2203 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2204 gfx_line(im, X1, Y0, X1 + 2, Y0,
2205 GRIDWIDTH, im->graph_col[GRC_GRID]);
2206 gfx_dashed_line(im, X0 - 1, Y0,
2210 graph_col[GRC_GRID],
2211 im->grid_dash_on, im->grid_dash_off);
2213 } else if (exfrac > 1) {
2214 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2215 value = pow(10.0, i);
2216 if (value < im->minval)
2218 Y0 = ytr(im, value);
2219 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2224 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2225 gfx_line(im, X1, Y0, X1 + 2, Y0,
2226 GRIDWIDTH, im->graph_col[GRC_GRID]);
2227 gfx_dashed_line(im, X0 - 1, Y0,
2231 graph_col[GRC_GRID],
2232 im->grid_dash_on, im->grid_dash_off);
2237 if (yloglab[mid][++flab] == 10.0) {
2243 /* draw minor lines after highest major line */
2244 if (mid < 4 && exfrac == 1) {
2245 /* find first and last minor line below current major line
2246 * i is the first line and j tha last */
2248 min_exp = val_exp - 1;
2249 for (i = 1; yloglab[mid][i] < 10.0; i++);
2250 i = yloglab[mid][i - 1] + 1;
2254 i = yloglab[mid][flab - 1] + 1;
2255 j = yloglab[mid][flab];
2258 /* draw minor lines below current major line */
2259 for (; i < j; i++) {
2261 value = i * pow(10.0, min_exp);
2262 if (value < im->minval)
2264 Y0 = ytr(im, value);
2265 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2269 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2270 gfx_line(im, X1, Y0, X1 + 2, Y0,
2271 GRIDWIDTH, im->graph_col[GRC_GRID]);
2272 gfx_dashed_line(im, X0 - 1, Y0,
2276 graph_col[GRC_GRID],
2277 im->grid_dash_on, im->grid_dash_off);
2280 /* fancy minor gridlines */
2281 else if (exfrac > 1) {
2282 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2283 value = pow(10.0, i);
2284 if (value < im->minval)
2286 Y0 = ytr(im, value);
2287 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2291 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2292 gfx_line(im, X1, Y0, X1 + 2, Y0,
2293 GRIDWIDTH, im->graph_col[GRC_GRID]);
2294 gfx_dashed_line(im, X0 - 1, Y0,
2298 graph_col[GRC_GRID],
2299 im->grid_dash_on, im->grid_dash_off);
2310 int xlab_sel; /* which sort of label and grid ? */
2311 time_t ti, tilab, timajor;
2313 char graph_label[100];
2314 double X0, Y0, Y1; /* points for filled graph and more */
2317 /* the type of time grid is determined by finding
2318 the number of seconds per pixel in the graph */
2319 if (im->xlab_user.minsec == -1) {
2320 factor = (im->end - im->start) / im->xsize;
2322 while (xlab[xlab_sel + 1].minsec !=
2323 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2325 } /* pick the last one */
2326 while (xlab[xlab_sel - 1].minsec ==
2327 xlab[xlab_sel].minsec
2328 && xlab[xlab_sel].length > (im->end - im->start)) {
2330 } /* go back to the smallest size */
2331 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2332 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2333 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2334 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2335 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2336 im->xlab_user.labst = xlab[xlab_sel].labst;
2337 im->xlab_user.precis = xlab[xlab_sel].precis;
2338 im->xlab_user.stst = xlab[xlab_sel].stst;
2341 /* y coords are the same for every line ... */
2343 Y1 = im->yorigin - im->ysize;
2344 /* paint the minor grid */
2345 if (!(im->extra_flags & NOMINOR)) {
2346 for (ti = find_first_time(im->start,
2354 find_first_time(im->start,
2361 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2363 /* are we inside the graph ? */
2364 if (ti < im->start || ti > im->end)
2366 while (timajor < ti) {
2367 timajor = find_next_time(timajor,
2370 mgridtm, im->xlab_user.mgridst);
2373 continue; /* skip as falls on major grid line */
2375 gfx_line(im, X0, Y1 - 2, X0, Y1,
2376 GRIDWIDTH, im->graph_col[GRC_GRID]);
2377 gfx_line(im, X0, Y0, X0, Y0 + 2,
2378 GRIDWIDTH, im->graph_col[GRC_GRID]);
2379 gfx_dashed_line(im, X0, Y0 + 1, X0,
2382 graph_col[GRC_GRID],
2383 im->grid_dash_on, im->grid_dash_off);
2387 /* paint the major grid */
2388 for (ti = find_first_time(im->start,
2396 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2398 /* are we inside the graph ? */
2399 if (ti < im->start || ti > im->end)
2402 gfx_line(im, X0, Y1 - 2, X0, Y1,
2403 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2404 gfx_line(im, X0, Y0, X0, Y0 + 3,
2405 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2406 gfx_dashed_line(im, X0, Y0 + 3, X0,
2410 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2412 /* paint the labels below the graph */
2414 find_first_time(im->start -
2423 im->xlab_user.precis / 2;
2424 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2426 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2427 /* are we inside the graph ? */
2428 if (tilab < im->start || tilab > im->end)
2431 localtime_r(&tilab, &tm);
2432 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2434 # error "your libc has no strftime I guess we'll abort the exercise here."
2439 im->graph_col[GRC_FONT],
2441 text_prop[TEXT_PROP_AXIS].
2444 GFX_H_CENTER, GFX_V_TOP, graph_label);
2453 /* draw x and y axis */
2454 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2455 im->xorigin+im->xsize,im->yorigin-im->ysize,
2456 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2458 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2459 im->xorigin+im->xsize,im->yorigin-im->ysize,
2460 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2462 gfx_line(im, im->xorigin - 4,
2464 im->xorigin + im->xsize +
2465 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2466 gfx_line(im, im->xorigin,
2469 im->yorigin - im->ysize -
2470 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2471 /* arrow for X and Y axis direction */
2472 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 */
2473 im->graph_col[GRC_ARROW]);
2475 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 */
2476 im->graph_col[GRC_ARROW]);
2485 double X0, Y0; /* points for filled graph and more */
2486 struct gfx_color_t water_color;
2488 /* draw 3d border */
2489 gfx_new_area(im, 0, im->yimg,
2490 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2491 gfx_add_point(im, im->ximg - 2, 2);
2492 gfx_add_point(im, im->ximg, 0);
2493 gfx_add_point(im, 0, 0);
2495 gfx_new_area(im, 2, im->yimg - 2,
2497 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2498 gfx_add_point(im, im->ximg, 0);
2499 gfx_add_point(im, im->ximg, im->yimg);
2500 gfx_add_point(im, 0, im->yimg);
2502 if (im->draw_x_grid == 1)
2504 if (im->draw_y_grid == 1) {
2505 if (im->logarithmic) {
2506 res = horizontal_log_grid(im);
2508 res = draw_horizontal_grid(im);
2511 /* dont draw horizontal grid if there is no min and max val */
2513 char *nodata = "No Data found";
2515 gfx_text(im, im->ximg / 2,
2518 im->graph_col[GRC_FONT],
2520 text_prop[TEXT_PROP_AXIS].
2523 GFX_H_CENTER, GFX_V_CENTER, nodata);
2527 /* yaxis unit description */
2532 im->graph_col[GRC_FONT],
2534 text_prop[TEXT_PROP_UNIT].
2537 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2541 im->graph_col[GRC_FONT],
2543 text_prop[TEXT_PROP_TITLE].
2545 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2546 /* rrdtool 'logo' */
2547 water_color = im->graph_col[GRC_FONT];
2548 water_color.alpha = 0.3;
2549 gfx_text(im, im->ximg - 4, 5,
2552 text_prop[TEXT_PROP_WATERMARK].
2553 font_desc, im->tabwidth,
2554 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2555 /* graph watermark */
2556 if (im->watermark[0] != '\0') {
2558 im->ximg / 2, im->yimg - 6,
2561 text_prop[TEXT_PROP_WATERMARK].
2562 font_desc, im->tabwidth, 0,
2563 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2567 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2568 for (i = 0; i < im->gdes_c; i++) {
2569 if (im->gdes[i].legend[0] == '\0')
2571 /* im->gdes[i].leg_y is the bottom of the legend */
2572 X0 = im->gdes[i].leg_x;
2573 Y0 = im->gdes[i].leg_y;
2574 gfx_text(im, X0, Y0,
2575 im->graph_col[GRC_FONT],
2578 [TEXT_PROP_LEGEND].font_desc,
2580 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2581 /* The legend for GRAPH items starts with "M " to have
2582 enough space for the box */
2583 if (im->gdes[i].gf != GF_PRINT &&
2584 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2588 boxH = gfx_get_text_width(im, 0,
2593 im->tabwidth, "o") * 1.2;
2595 /* shift the box up a bit */
2597 /* make sure transparent colors show up the same way as in the graph */
2600 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2601 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2603 gfx_new_area(im, X0, Y0 - boxV, X0,
2604 Y0, X0 + boxH, Y0, im->gdes[i].col);
2605 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2608 cairo_new_path(im->cr);
2609 cairo_set_line_width(im->cr, 1.0);
2612 gfx_line_fit(im, &X0, &Y0);
2613 gfx_line_fit(im, &X1, &Y1);
2614 cairo_move_to(im->cr, X0, Y0);
2615 cairo_line_to(im->cr, X1, Y0);
2616 cairo_line_to(im->cr, X1, Y1);
2617 cairo_line_to(im->cr, X0, Y1);
2618 cairo_close_path(im->cr);
2619 cairo_set_source_rgba(im->cr,
2631 blue, im->graph_col[GRC_FRAME].alpha);
2632 if (im->gdes[i].dash) {
2633 /* make box borders in legend dashed if the graph is dashed */
2637 cairo_set_dash(im->cr, dashes, 1, 0.0);
2639 cairo_stroke(im->cr);
2640 cairo_restore(im->cr);
2647 /*****************************************************
2648 * lazy check make sure we rely need to create this graph
2649 *****************************************************/
2656 struct stat imgstat;
2659 return 0; /* no lazy option */
2660 if (strlen(im->graphfile) == 0)
2661 return 0; /* inmemory option */
2662 if (stat(im->graphfile, &imgstat) != 0)
2663 return 0; /* can't stat */
2664 /* one pixel in the existing graph is more then what we would
2666 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2668 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2669 return 0; /* the file does not exist */
2670 switch (im->imgformat) {
2672 size = PngSize(fd, &(im->ximg), &(im->yimg));
2682 int graph_size_location(
2687 /* The actual size of the image to draw is determined from
2688 ** several sources. The size given on the command line is
2689 ** the graph area but we need more as we have to draw labels
2690 ** and other things outside the graph area
2693 int Xvertical = 0, Ytitle =
2694 0, Xylabel = 0, Xmain = 0, Ymain =
2695 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2697 if (im->extra_flags & ONLY_GRAPH) {
2699 im->ximg = im->xsize;
2700 im->yimg = im->ysize;
2701 im->yorigin = im->ysize;
2706 /** +---+--------------------------------------------+
2707 ** | y |...............graph title..................|
2708 ** | +---+-------------------------------+--------+
2711 ** | i | a | | pie |
2712 ** | s | x | main graph area | chart |
2717 ** | l | b +-------------------------------+--------+
2718 ** | e | l | x axis labels | |
2719 ** +---+---+-------------------------------+--------+
2720 ** |....................legends.....................|
2721 ** +------------------------------------------------+
2723 ** +------------------------------------------------+
2726 if (im->ylegend[0] != '\0') {
2727 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2730 if (im->title[0] != '\0') {
2731 /* The title is placed "inbetween" two text lines so it
2732 ** automatically has some vertical spacing. The horizontal
2733 ** spacing is added here, on each side.
2735 /* if necessary, reduce the font size of the title until it fits the image width */
2736 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2740 if (im->draw_x_grid) {
2741 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2743 if (im->draw_y_grid || im->forceleftspace) {
2745 gfx_get_text_width(im, 0,
2750 im->tabwidth, "0") * im->unitslength;
2754 if (im->extra_flags & FULL_SIZE_MODE) {
2755 /* The actual size of the image to draw has been determined by the user.
2756 ** The graph area is the space remaining after accounting for the legend,
2757 ** the watermark, the pie chart, the axis labels, and the title.
2760 im->ximg = im->xsize;
2761 im->yimg = im->ysize;
2762 im->yorigin = im->ysize;
2765 im->yorigin += Ytitle;
2766 /* Now calculate the total size. Insert some spacing where
2767 desired. im->xorigin and im->yorigin need to correspond
2768 with the lower left corner of the main graph area or, if
2769 this one is not set, the imaginary box surrounding the
2771 /* Initial size calculation for the main graph area */
2772 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2774 Xmain -= Xspacing; /* put space between main graph area and right edge */
2775 im->xorigin = Xspacing + Xylabel;
2776 /* the length of the title should not influence with width of the graph
2777 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2778 if (Xvertical) { /* unit description */
2780 im->xorigin += Xvertical;
2784 /* The vertical size of the image is known in advance. The main graph area
2785 ** (Ymain) and im->yorigin must be set according to the space requirements
2786 ** of the legend and the axis labels.
2788 if (im->extra_flags & NOLEGEND) {
2789 /* set dimensions correctly if using full size mode with no legend */
2792 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2793 Ymain = im->yorigin;
2795 /* Determine where to place the legends onto the image.
2796 ** Set Ymain and adjust im->yorigin to match the space requirements.
2798 if (leg_place(im, &Ymain) == -1)
2803 /* remove title space *or* some padding above the graph from the main graph area */
2807 Ymain -= 1.5 * Yspacing;
2810 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2811 if (im->watermark[0] != '\0') {
2812 Ymain -= Ywatermark;
2816 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2818 /* The actual size of the image to draw is determined from
2819 ** several sources. The size given on the command line is
2820 ** the graph area but we need more as we have to draw labels
2821 ** and other things outside the graph area.
2824 if (im->ylegend[0] != '\0') {
2825 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2829 if (im->title[0] != '\0') {
2830 /* The title is placed "inbetween" two text lines so it
2831 ** automatically has some vertical spacing. The horizontal
2832 ** spacing is added here, on each side.
2834 /* don't care for the with of the title
2835 Xtitle = gfx_get_text_width(im->canvas, 0,
2836 im->text_prop[TEXT_PROP_TITLE].font,
2837 im->text_prop[TEXT_PROP_TITLE].size,
2839 im->title, 0) + 2*Xspacing; */
2840 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2847 /* Now calculate the total size. Insert some spacing where
2848 desired. im->xorigin and im->yorigin need to correspond
2849 with the lower left corner of the main graph area or, if
2850 this one is not set, the imaginary box surrounding the
2853 /* The legend width cannot yet be determined, as a result we
2854 ** have problems adjusting the image to it. For now, we just
2855 ** forget about it at all; the legend will have to fit in the
2856 ** size already allocated.
2858 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2860 im->ximg += Xspacing;
2861 im->xorigin = Xspacing + Xylabel;
2862 /* the length of the title should not influence with width of the graph
2863 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2864 if (Xvertical) { /* unit description */
2865 im->ximg += Xvertical;
2866 im->xorigin += Xvertical;
2869 /* The vertical size is interesting... we need to compare
2870 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2871 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2872 ** in order to start even thinking about Ylegend or Ywatermark.
2874 ** Do it in three portions: First calculate the inner part,
2875 ** then do the legend, then adjust the total height of the img,
2876 ** adding space for a watermark if one exists;
2878 /* reserve space for main and/or pie */
2879 im->yimg = Ymain + Yxlabel;
2880 im->yorigin = im->yimg - Yxlabel;
2881 /* reserve space for the title *or* some padding above the graph */
2884 im->yorigin += Ytitle;
2886 im->yimg += 1.5 * Yspacing;
2887 im->yorigin += 1.5 * Yspacing;
2889 /* reserve space for padding below the graph */
2890 im->yimg += Yspacing;
2891 /* Determine where to place the legends onto the image.
2892 ** Adjust im->yimg to match the space requirements.
2894 if (leg_place(im, 0) == -1)
2896 if (im->watermark[0] != '\0') {
2897 im->yimg += Ywatermark;
2905 static cairo_status_t cairo_output(
2909 unsigned int length)
2911 image_desc_t *im = closure;
2913 im->rendered_image =
2914 realloc(im->rendered_image, im->rendered_image_size + length);
2915 if (im->rendered_image == NULL)
2916 return CAIRO_STATUS_WRITE_ERROR;
2917 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2918 im->rendered_image_size += length;
2919 return CAIRO_STATUS_SUCCESS;
2922 /* draw that picture thing ... */
2927 int lazy = lazy_check(im);
2928 double areazero = 0.0;
2929 graph_desc_t *lastgdes = NULL;
2932 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
2934 /* if we want and can be lazy ... quit now */
2936 info.u_cnt = im->ximg;
2937 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2938 info.u_cnt = im->yimg;
2939 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2942 /* pull the data from the rrd files ... */
2943 if (data_fetch(im) == -1)
2945 /* evaluate VDEF and CDEF operations ... */
2946 if (data_calc(im) == -1)
2948 /* calculate and PRINT and GPRINT definitions. We have to do it at
2949 * this point because it will affect the length of the legends
2950 * if there are no graph elements (i==0) we stop here ...
2951 * if we are lazy, try to quit ...
2957 if ((i == 0) || lazy)
2960 /**************************************************************
2961 *** Calculating sizes and locations became a bit confusing ***
2962 *** so I moved this into a separate function. ***
2963 **************************************************************/
2964 if (graph_size_location(im, i) == -1)
2967 info.u_cnt = im->xorigin;
2968 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2969 info.u_cnt = im->yorigin - im->ysize;
2970 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2971 info.u_cnt = im->xsize;
2972 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2973 info.u_cnt = im->ysize;
2974 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2975 info.u_cnt = im->ximg;
2976 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2977 info.u_cnt = im->yimg;
2978 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2980 /* get actual drawing data and find min and max values */
2981 if (data_proc(im) == -1)
2983 if (!im->logarithmic) {
2987 /* identify si magnitude Kilo, Mega Giga ? */
2988 if (!im->rigid && !im->logarithmic)
2989 expand_range(im); /* make sure the upper and lower limit are
2992 info.u_val = im->minval;
2993 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
2994 info.u_val = im->maxval;
2995 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
2997 if (!calc_horizontal_grid(im))
3002 apply_gridfit(im); */
3003 /* the actual graph is created by going through the individual
3004 graph elements and then drawing them */
3005 cairo_surface_destroy(im->surface);
3006 switch (im->imgformat) {
3009 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3010 im->ximg * im->zoom,
3011 im->yimg * im->zoom);
3015 im->surface = strlen(im->graphfile)
3016 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3017 im->yimg * im->zoom)
3018 : cairo_pdf_surface_create_for_stream
3019 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3023 im->surface = strlen(im->graphfile)
3025 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3026 im->yimg * im->zoom)
3027 : cairo_ps_surface_create_for_stream
3028 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3032 im->surface = strlen(im->graphfile)
3034 cairo_svg_surface_create(im->
3036 im->ximg * im->zoom, im->yimg * im->zoom)
3037 : cairo_svg_surface_create_for_stream
3038 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3039 cairo_svg_surface_restrict_to_version
3040 (im->surface, CAIRO_SVG_VERSION_1_1);
3043 cairo_destroy(im->cr);
3044 im->cr = cairo_create(im->surface);
3045 cairo_set_antialias(im->cr, im->graph_antialias);
3046 cairo_scale(im->cr, im->zoom, im->zoom);
3047 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3048 gfx_new_area(im, 0, 0, 0, im->yimg,
3049 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3050 gfx_add_point(im, im->ximg, 0);
3052 gfx_new_area(im, im->xorigin,
3055 im->xsize, im->yorigin,
3058 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3059 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3061 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3062 im->xsize, im->ysize + 2.0);
3064 if (im->minval > 0.0)
3065 areazero = im->minval;
3066 if (im->maxval < 0.0)
3067 areazero = im->maxval;
3068 for (i = 0; i < im->gdes_c; i++) {
3069 switch (im->gdes[i].gf) {
3083 for (ii = 0; ii < im->xsize; ii++) {
3084 if (!isnan(im->gdes[i].p_data[ii])
3085 && im->gdes[i].p_data[ii] != 0.0) {
3086 if (im->gdes[i].yrule > 0) {
3093 im->ysize, 1.0, im->gdes[i].col);
3094 } else if (im->gdes[i].yrule < 0) {
3097 im->yorigin - im->ysize,
3102 im->ysize, 1.0, im->gdes[i].col);
3109 /* fix data points at oo and -oo */
3110 for (ii = 0; ii < im->xsize; ii++) {
3111 if (isinf(im->gdes[i].p_data[ii])) {
3112 if (im->gdes[i].p_data[ii] > 0) {
3113 im->gdes[i].p_data[ii] = im->maxval;
3115 im->gdes[i].p_data[ii] = im->minval;
3121 /* *******************************************************
3126 -------|--t-1--t--------------------------------
3128 if we know the value at time t was a then
3129 we draw a square from t-1 to t with the value a.
3131 ********************************************************* */
3132 if (im->gdes[i].col.alpha != 0.0) {
3133 /* GF_LINE and friend */
3134 if (im->gdes[i].gf == GF_LINE) {
3135 double last_y = 0.0;
3139 cairo_new_path(im->cr);
3140 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3141 if (im->gdes[i].dash) {
3142 cairo_set_dash(im->cr,
3143 im->gdes[i].p_dashes,
3144 im->gdes[i].ndash, im->gdes[i].offset);
3147 for (ii = 1; ii < im->xsize; ii++) {
3148 if (isnan(im->gdes[i].p_data[ii])
3149 || (im->slopemode == 1
3150 && isnan(im->gdes[i].p_data[ii - 1]))) {
3155 last_y = ytr(im, im->gdes[i].p_data[ii]);
3156 if (im->slopemode == 0) {
3157 double x = ii - 1 + im->xorigin;
3160 gfx_line_fit(im, &x, &y);
3161 cairo_move_to(im->cr, x, y);
3162 x = ii + im->xorigin;
3164 gfx_line_fit(im, &x, &y);
3165 cairo_line_to(im->cr, x, y);
3167 double x = ii - 1 + im->xorigin;
3169 ytr(im, im->gdes[i].p_data[ii - 1]);
3170 gfx_line_fit(im, &x, &y);
3171 cairo_move_to(im->cr, x, y);
3172 x = ii + im->xorigin;
3174 gfx_line_fit(im, &x, &y);
3175 cairo_line_to(im->cr, x, y);
3179 double x1 = ii + im->xorigin;
3180 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3182 if (im->slopemode == 0
3183 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3184 double x = ii - 1 + im->xorigin;
3187 gfx_line_fit(im, &x, &y);
3188 cairo_line_to(im->cr, x, y);
3191 gfx_line_fit(im, &x1, &y1);
3192 cairo_line_to(im->cr, x1, y1);
3195 cairo_set_source_rgba(im->cr,
3201 col.blue, im->gdes[i].col.alpha);
3202 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3203 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3204 cairo_stroke(im->cr);
3205 cairo_restore(im->cr);
3209 (double *) malloc(sizeof(double) * im->xsize * 2);
3211 (double *) malloc(sizeof(double) * im->xsize * 2);
3213 (double *) malloc(sizeof(double) * im->xsize * 2);
3215 (double *) malloc(sizeof(double) * im->xsize * 2);
3218 for (ii = 0; ii <= im->xsize; ii++) {
3221 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3227 AlmostEqual2sComplement(foreY
3231 AlmostEqual2sComplement(foreY
3241 foreY[cntI], im->gdes[i].col);
3242 while (cntI < idxI) {
3247 AlmostEqual2sComplement(foreY
3251 AlmostEqual2sComplement(foreY
3258 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3260 gfx_add_point(im, backX[idxI], backY[idxI]);
3266 AlmostEqual2sComplement(backY
3270 AlmostEqual2sComplement(backY
3277 gfx_add_point(im, backX[idxI], backY[idxI]);
3287 if (ii == im->xsize)
3289 if (im->slopemode == 0 && ii == 0) {
3292 if (isnan(im->gdes[i].p_data[ii])) {
3296 ytop = ytr(im, im->gdes[i].p_data[ii]);
3297 if (lastgdes && im->gdes[i].stack) {
3298 ybase = ytr(im, lastgdes->p_data[ii]);
3300 ybase = ytr(im, areazero);
3302 if (ybase == ytop) {
3308 double extra = ytop;
3313 if (im->slopemode == 0) {
3314 backY[++idxI] = ybase - 0.2;
3315 backX[idxI] = ii + im->xorigin - 1;
3316 foreY[idxI] = ytop + 0.2;
3317 foreX[idxI] = ii + im->xorigin - 1;
3319 backY[++idxI] = ybase - 0.2;
3320 backX[idxI] = ii + im->xorigin;
3321 foreY[idxI] = ytop + 0.2;
3322 foreX[idxI] = ii + im->xorigin;
3324 /* close up any remaining area */
3329 } /* else GF_LINE */
3331 /* if color != 0x0 */
3332 /* make sure we do not run into trouble when stacking on NaN */
3333 for (ii = 0; ii < im->xsize; ii++) {
3334 if (isnan(im->gdes[i].p_data[ii])) {
3335 if (lastgdes && (im->gdes[i].stack)) {
3336 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3338 im->gdes[i].p_data[ii] = areazero;
3342 lastgdes = &(im->gdes[i]);
3346 ("STACK should already be turned into LINE or AREA here");
3351 cairo_reset_clip(im->cr);
3353 /* grid_paint also does the text */
3354 if (!(im->extra_flags & ONLY_GRAPH))
3356 if (!(im->extra_flags & ONLY_GRAPH))
3358 /* the RULES are the last thing to paint ... */
3359 for (i = 0; i < im->gdes_c; i++) {
3361 switch (im->gdes[i].gf) {
3363 if (im->gdes[i].yrule >= im->minval
3364 && im->gdes[i].yrule <= im->maxval) {
3366 if (im->gdes[i].dash) {
3367 cairo_set_dash(im->cr,
3368 im->gdes[i].p_dashes,
3369 im->gdes[i].ndash, im->gdes[i].offset);
3371 gfx_line(im, im->xorigin,
3372 ytr(im, im->gdes[i].yrule),
3373 im->xorigin + im->xsize,
3374 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3375 cairo_stroke(im->cr);
3376 cairo_restore(im->cr);
3380 if (im->gdes[i].xrule >= im->start
3381 && im->gdes[i].xrule <= im->end) {
3383 if (im->gdes[i].dash) {
3384 cairo_set_dash(im->cr,
3385 im->gdes[i].p_dashes,
3386 im->gdes[i].ndash, im->gdes[i].offset);
3389 xtr(im, im->gdes[i].xrule),
3390 im->yorigin, xtr(im,
3394 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3395 cairo_stroke(im->cr);
3396 cairo_restore(im->cr);
3405 switch (im->imgformat) {
3408 cairo_status_t status;
3410 status = strlen(im->graphfile) ?
3411 cairo_surface_write_to_png(im->surface, im->graphfile)
3412 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3415 if (status != CAIRO_STATUS_SUCCESS) {
3416 rrd_set_error("Could not save png to '%s'", im->graphfile);
3422 if (strlen(im->graphfile)) {
3423 cairo_show_page(im->cr);
3425 cairo_surface_finish(im->surface);
3434 /*****************************************************
3436 *****************************************************/
3443 if ((im->gdes = (graph_desc_t *)
3444 rrd_realloc(im->gdes, (im->gdes_c)
3445 * sizeof(graph_desc_t))) == NULL) {
3446 rrd_set_error("realloc graph_descs");
3451 im->gdes[im->gdes_c - 1].step = im->step;
3452 im->gdes[im->gdes_c - 1].step_orig = im->step;
3453 im->gdes[im->gdes_c - 1].stack = 0;
3454 im->gdes[im->gdes_c - 1].linewidth = 0;
3455 im->gdes[im->gdes_c - 1].debug = 0;
3456 im->gdes[im->gdes_c - 1].start = im->start;
3457 im->gdes[im->gdes_c - 1].start_orig = im->start;
3458 im->gdes[im->gdes_c - 1].end = im->end;
3459 im->gdes[im->gdes_c - 1].end_orig = im->end;
3460 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3461 im->gdes[im->gdes_c - 1].data = NULL;
3462 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3463 im->gdes[im->gdes_c - 1].data_first = 0;
3464 im->gdes[im->gdes_c - 1].p_data = NULL;
3465 im->gdes[im->gdes_c - 1].rpnp = NULL;
3466 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3467 im->gdes[im->gdes_c - 1].shift = 0.0;
3468 im->gdes[im->gdes_c - 1].dash = 0;
3469 im->gdes[im->gdes_c - 1].ndash = 0;
3470 im->gdes[im->gdes_c - 1].offset = 0;
3471 im->gdes[im->gdes_c - 1].col.red = 0.0;
3472 im->gdes[im->gdes_c - 1].col.green = 0.0;
3473 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3474 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3475 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3476 im->gdes[im->gdes_c - 1].format[0] = '\0';
3477 im->gdes[im->gdes_c - 1].strftm = 0;
3478 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3479 im->gdes[im->gdes_c - 1].ds = -1;
3480 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3481 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3482 im->gdes[im->gdes_c - 1].yrule = DNAN;
3483 im->gdes[im->gdes_c - 1].xrule = 0;
3487 /* copies input untill the first unescaped colon is found
3488 or until input ends. backslashes have to be escaped as well */
3490 const char *const input,
3496 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3497 if (input[inp] == '\\'
3498 && input[inp + 1] != '\0'
3499 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3500 output[outp++] = input[++inp];
3502 output[outp++] = input[inp];
3505 output[outp] = '\0';
3509 /* Now just a wrapper around rrd_graph_v */
3521 rrd_info_t *grinfo = NULL;
3524 grinfo = rrd_graph_v(argc, argv);
3530 if (strcmp(walker->key, "image_info") == 0) {
3533 rrd_realloc((*prdata),
3534 (prlines + 1) * sizeof(char *))) == NULL) {
3535 rrd_set_error("realloc prdata");
3538 /* imginfo goes to position 0 in the prdata array */
3539 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3540 + 2) * sizeof(char));
3541 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3542 (*prdata)[prlines] = NULL;
3544 /* skip anything else */
3545 walker = walker->next;
3553 if (strcmp(walker->key, "image_width") == 0) {
3554 *xsize = walker->value.u_int;
3555 } else if (strcmp(walker->key, "image_height") == 0) {
3556 *ysize = walker->value.u_int;
3557 } else if (strcmp(walker->key, "value_min") == 0) {
3558 *ymin = walker->value.u_val;
3559 } else if (strcmp(walker->key, "value_max") == 0) {
3560 *ymax = walker->value.u_val;
3561 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3564 rrd_realloc((*prdata),
3565 (prlines + 1) * sizeof(char *))) == NULL) {
3566 rrd_set_error("realloc prdata");
3569 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3570 + 2) * sizeof(char));
3571 (*prdata)[prlines] = NULL;
3572 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3573 } else if (strcmp(walker->key, "image") == 0) {
3574 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3575 (stream ? stream : stdout));
3577 /* skip anything else */
3578 walker = walker->next;
3580 rrd_info_free(grinfo);
3585 /* Some surgery done on this function, it became ridiculously big.
3587 ** - initializing now in rrd_graph_init()
3588 ** - options parsing now in rrd_graph_options()
3589 ** - script parsing now in rrd_graph_script()
3591 rrd_info_t *rrd_graph_v(
3597 rrd_graph_init(&im);
3598 /* a dummy surface so that we can measure text sizes for placements */
3600 rrd_graph_options(argc, argv, &im);
3601 if (rrd_test_error()) {
3602 rrd_info_free(im.grinfo);
3607 if (optind >= argc) {
3608 rrd_info_free(im.grinfo);
3610 rrd_set_error("missing filename");
3614 if (strlen(argv[optind]) >= MAXPATH) {
3615 rrd_set_error("filename (including path) too long");
3616 rrd_info_free(im.grinfo);
3621 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3622 im.graphfile[MAXPATH - 1] = '\0';
3624 if (strcmp(im.graphfile, "-") == 0) {
3625 im.graphfile[0] = '\0';
3628 rrd_graph_script(argc, argv, &im, 1);
3629 if (rrd_test_error()) {
3630 rrd_info_free(im.grinfo);
3635 /* Everything is now read and the actual work can start */
3637 if (graph_paint(&im) == -1) {
3638 rrd_info_free(im.grinfo);
3644 /* The image is generated and needs to be output.
3645 ** Also, if needed, print a line with information about the image.
3652 filename = im.graphfile + strlen(im.graphfile);
3653 while (filename > im.graphfile) {
3654 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3659 sprintf_alloc(im.imginfo,
3662 im.ximg), (long) (im.zoom * im.yimg));
3663 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3666 if (im.rendered_image) {
3669 img.u_blo.size = im.rendered_image_size;
3670 img.u_blo.ptr = im.rendered_image;
3671 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3680 image_desc_t *im,int prop,char *font, double size ){
3681 static text_prop_t tp_cache[] = { {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}};
3683 if (tp_cache[prop].font_desc == NULL){
3684 tp_cache[prop].font_desc = pango_font_description_new();
3685 im->text_prop[prop].font_desc = pango_font_description_copy (tp_cache[prop].font_desc);
3688 if (font != NULL && strcmp(tp_cache[prop].font,font) != 0){
3689 pango_font_description_free(tp_cache[prop].font_desc);
3690 pango_font_description_free(im->text_prop[prop].font_desc);
3691 tp_cache[prop].font_desc = pango_font_description_from_string( font );
3692 im->text_prop[prop].font_desc = pango_font_description_copy( tp_cache[prop].font_desc );
3693 strncpy(tp_cache[prop].font, font, sizeof(text_prop[prop].font) - 1);
3694 tp_cache[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3695 strcpy(im->text_prop[prop].font,tp_cache[prop].font);
3697 if (size != 0 && size != (tp_cache[prop].size)){
3698 pango_font_description_set_size(tp_cache[prop].font_desc, size * PANGO_SCALE);
3699 pango_font_description_set_size(im->text_prop[prop].font_desc, size * PANGO_SCALE);
3700 im->text_prop[prop].size = size;
3701 tp_cache[prop].size = size;
3705 void rrd_graph_init(
3710 char *deffont = getenv("RRD_DEFAULT_FONT");
3715 #ifdef HAVE_SETLOCALE
3716 setlocale(LC_TIME, "");
3717 #ifdef HAVE_MBSTOWCS
3718 setlocale(LC_CTYPE, "");
3722 im->draw_x_grid = 1;
3723 im->draw_y_grid = 1;
3724 im->extra_flags = 0;
3725 im->font_options = cairo_font_options_create();
3726 im->forceleftspace = 0;
3729 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3730 im->grid_dash_off = 1;
3731 im->grid_dash_on = 1;
3733 im->grinfo = (rrd_info_t *) NULL;
3734 im->grinfo_current = (rrd_info_t *) NULL;
3735 im->imgformat = IF_PNG;
3738 im->logarithmic = 0;
3744 im->rendered_image_size = 0;
3745 im->rendered_image = NULL;
3749 im->tabwidth = 40.0;
3750 im->title[0] = '\0';
3751 im->unitsexponent = 9999;
3752 im->unitslength = 6;
3753 im->viewfactor = 1.0;
3754 im->watermark[0] = '\0';
3755 im->with_markup = 0;
3757 im->xlab_user.minsec = -1;
3760 im->ygridstep = DNAN;
3762 im->ylegend[0] = '\0';
3767 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3768 im->cr = cairo_create(im->surface);
3770 for (i = 0; i < DIM(text_prop); i++) {
3771 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3774 im->layout = pango_cairo_create_layout(im->cr);
3775 pango_cairo_context_set_resolution(pango_layout_get_context(im->layout), 100);
3777 cairo_font_options_set_hint_style
3778 (im->font_options, CAIRO_HINT_STYLE_FULL);
3779 cairo_font_options_set_hint_metrics
3780 (im->font_options, CAIRO_HINT_METRICS_ON);
3781 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3785 for (i = 0; i < DIM(graph_col); i++)
3786 im->graph_col[i] = graph_col[i];
3792 void rrd_graph_options(
3799 char *parsetime_error = NULL;
3800 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3801 time_t start_tmp = 0, end_tmp = 0;
3803 rrd_time_value_t start_tv, end_tv;
3804 long unsigned int color;
3805 char *old_locale = "";
3807 /* defines for long options without a short equivalent. should be bytes,
3808 and may not collide with (the ASCII value of) short options */
3809 #define LONGOPT_UNITS_SI 255
3812 struct option long_options[] = {
3813 { "start", required_argument, 0, 's'},
3814 { "end", required_argument, 0, 'e'},
3815 { "x-grid", required_argument, 0, 'x'},
3816 { "y-grid", required_argument, 0, 'y'},
3817 { "vertical-label", required_argument, 0, 'v'},
3818 { "width", required_argument, 0, 'w'},
3819 { "height", required_argument, 0, 'h'},
3820 { "full-size-mode", no_argument, 0, 'D'},
3821 { "interlaced", no_argument, 0, 'i'},
3822 { "upper-limit", required_argument, 0, 'u'},
3823 { "lower-limit", required_argument, 0, 'l'},
3824 { "rigid", no_argument, 0, 'r'},
3825 { "base", required_argument, 0, 'b'},
3826 { "logarithmic", no_argument, 0, 'o'},
3827 { "color", required_argument, 0, 'c'},
3828 { "font", required_argument, 0, 'n'},
3829 { "title", required_argument, 0, 't'},
3830 { "imginfo", required_argument, 0, 'f'},
3831 { "imgformat", required_argument, 0, 'a'},
3832 { "lazy", no_argument, 0, 'z'},
3833 { "zoom", required_argument, 0, 'm'},
3834 { "no-legend", no_argument, 0, 'g'},
3835 { "force-rules-legend", no_argument, 0, 'F'},
3836 { "only-graph", no_argument, 0, 'j'},
3837 { "alt-y-grid", no_argument, 0, 'Y'},
3838 { "no-minor", no_argument, 0, 'I'},
3839 { "slope-mode", no_argument, 0, 'E'},
3840 { "alt-autoscale", no_argument, 0, 'A'},
3841 { "alt-autoscale-min", no_argument, 0, 'J'},
3842 { "alt-autoscale-max", no_argument, 0, 'M'},
3843 { "no-gridfit", no_argument, 0, 'N'},
3844 { "units-exponent", required_argument, 0, 'X'},
3845 { "units-length", required_argument, 0, 'L'},
3846 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3847 { "step", required_argument, 0, 'S'},
3848 { "tabwidth", required_argument, 0, 'T'},
3849 { "font-render-mode", required_argument, 0, 'R'},
3850 { "graph-render-mode", required_argument, 0, 'G'},
3851 { "font-smoothing-threshold", required_argument, 0, 'B'},
3852 { "watermark", required_argument, 0, 'W'},
3853 { "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 */
3854 { "pango-markup", no_argument, 0, 'P'},
3860 opterr = 0; /* initialize getopt */
3861 rrd_parsetime("end-24h", &start_tv);
3862 rrd_parsetime("now", &end_tv);
3864 int option_index = 0;
3866 int col_start, col_end;
3868 opt = getopt_long(argc, argv,
3869 "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:kP",
3870 long_options, &option_index);
3875 im->extra_flags |= NOMINOR;
3878 im->extra_flags |= ALTYGRID;
3881 im->extra_flags |= ALTAUTOSCALE;
3884 im->extra_flags |= ALTAUTOSCALE_MIN;
3887 im->extra_flags |= ALTAUTOSCALE_MAX;
3890 im->extra_flags |= ONLY_GRAPH;
3893 im->extra_flags |= NOLEGEND;
3896 im->extra_flags |= FORCE_RULES_LEGEND;
3898 case LONGOPT_UNITS_SI:
3899 if (im->extra_flags & FORCE_UNITS) {
3900 rrd_set_error("--units can only be used once!");
3901 setlocale(LC_NUMERIC, old_locale);
3904 if (strcmp(optarg, "si") == 0)
3905 im->extra_flags |= FORCE_UNITS_SI;
3907 rrd_set_error("invalid argument for --units: %s", optarg);
3912 im->unitsexponent = atoi(optarg);
3915 im->unitslength = atoi(optarg);
3916 im->forceleftspace = 1;
3919 old_locale = setlocale(LC_NUMERIC, "C");
3920 im->tabwidth = atof(optarg);
3921 setlocale(LC_NUMERIC, old_locale);
3924 old_locale = setlocale(LC_NUMERIC, "C");
3925 im->step = atoi(optarg);
3926 setlocale(LC_NUMERIC, old_locale);
3932 im->with_markup = 1;
3935 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3936 rrd_set_error("start time: %s", parsetime_error);
3941 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3942 rrd_set_error("end time: %s", parsetime_error);
3947 if (strcmp(optarg, "none") == 0) {
3948 im->draw_x_grid = 0;
3952 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3954 &im->xlab_user.gridst,
3956 &im->xlab_user.mgridst,
3958 &im->xlab_user.labst,
3959 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3960 strncpy(im->xlab_form, optarg + stroff,
3961 sizeof(im->xlab_form) - 1);
3962 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3964 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3965 rrd_set_error("unknown keyword %s", scan_gtm);
3968 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3970 rrd_set_error("unknown keyword %s", scan_mtm);
3973 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
3974 rrd_set_error("unknown keyword %s", scan_ltm);
3977 im->xlab_user.minsec = 1;
3978 im->xlab_user.stst = im->xlab_form;
3980 rrd_set_error("invalid x-grid format");
3986 if (strcmp(optarg, "none") == 0) {
3987 im->draw_y_grid = 0;
3990 old_locale = setlocale(LC_NUMERIC, "C");
3991 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3992 setlocale(LC_NUMERIC, old_locale);
3993 if (im->ygridstep <= 0) {
3994 rrd_set_error("grid step must be > 0");
3996 } else if (im->ylabfact < 1) {
3997 rrd_set_error("label factor must be > 0");
4001 setlocale(LC_NUMERIC, old_locale);
4002 rrd_set_error("invalid y-grid format");
4007 strncpy(im->ylegend, optarg, 150);
4008 im->ylegend[150] = '\0';
4011 old_locale = setlocale(LC_NUMERIC, "C");
4012 im->maxval = atof(optarg);
4013 setlocale(LC_NUMERIC, old_locale);
4016 old_locale = setlocale(LC_NUMERIC, "C");
4017 im->minval = atof(optarg);
4018 setlocale(LC_NUMERIC, old_locale);
4021 im->base = atol(optarg);
4022 if (im->base != 1024 && im->base != 1000) {
4024 ("the only sensible value for base apart from 1000 is 1024");
4029 long_tmp = atol(optarg);
4030 if (long_tmp < 10) {
4031 rrd_set_error("width below 10 pixels");
4034 im->xsize = long_tmp;
4037 long_tmp = atol(optarg);
4038 if (long_tmp < 10) {
4039 rrd_set_error("height below 10 pixels");
4042 im->ysize = long_tmp;
4045 im->extra_flags |= FULL_SIZE_MODE;
4048 /* interlaced png not supported at the moment */
4054 im->imginfo = optarg;
4058 (im->imgformat = if_conv(optarg)) == -1) {
4059 rrd_set_error("unsupported graphics format '%s'", optarg);
4070 im->logarithmic = 1;
4074 "%10[A-Z]#%n%8lx%n",
4075 col_nam, &col_start, &color, &col_end) == 2) {
4077 int col_len = col_end - col_start;
4082 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4090 (((color & 0xF000) *
4091 0x11000) | ((color & 0x0F00) *
4092 0x01100) | ((color &
4095 ((color & 0x000F) * 0x00011)
4099 color = (color << 8) + 0xff /* shift left by 8 */ ;
4104 rrd_set_error("the color format is #RRGGBB[AA]");
4107 if ((ci = grc_conv(col_nam)) != -1) {
4108 im->graph_col[ci] = gfx_hex_to_col(color);
4110 rrd_set_error("invalid color name '%s'", col_nam);
4114 rrd_set_error("invalid color def format");
4123 old_locale = setlocale(LC_NUMERIC, "C");
4124 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4125 int sindex, propidx;
4127 setlocale(LC_NUMERIC, old_locale);
4128 if ((sindex = text_prop_conv(prop)) != -1) {
4129 for (propidx = sindex;
4130 propidx < TEXT_PROP_LAST; propidx++) {
4132 rrd_set_font_desc(im,propidx,NULL,size);
4134 if ((int) strlen(optarg) > end) {
4135 if (optarg[end] == ':') {
4136 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4139 ("expected : after font size in '%s'",
4144 /* only run the for loop for DEFAULT (0) for
4145 all others, we break here. woodo programming */
4146 if (propidx == sindex && sindex != 0)
4150 rrd_set_error("invalid fonttag '%s'", prop);
4154 setlocale(LC_NUMERIC, old_locale);
4155 rrd_set_error("invalid text property format");
4161 old_locale = setlocale(LC_NUMERIC, "C");
4162 im->zoom = atof(optarg);
4163 setlocale(LC_NUMERIC, old_locale);
4164 if (im->zoom <= 0.0) {
4165 rrd_set_error("zoom factor must be > 0");
4170 strncpy(im->title, optarg, 150);
4171 im->title[150] = '\0';
4174 if (strcmp(optarg, "normal") == 0) {
4175 cairo_font_options_set_antialias
4176 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4177 cairo_font_options_set_hint_style
4178 (im->font_options, CAIRO_HINT_STYLE_FULL);
4179 } else if (strcmp(optarg, "light") == 0) {
4180 cairo_font_options_set_antialias
4181 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4182 cairo_font_options_set_hint_style
4183 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4184 } else if (strcmp(optarg, "mono") == 0) {
4185 cairo_font_options_set_antialias
4186 (im->font_options, CAIRO_ANTIALIAS_NONE);
4187 cairo_font_options_set_hint_style
4188 (im->font_options, CAIRO_HINT_STYLE_FULL);
4190 rrd_set_error("unknown font-render-mode '%s'", optarg);
4195 if (strcmp(optarg, "normal") == 0)
4196 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4197 else if (strcmp(optarg, "mono") == 0)
4198 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4200 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4205 /* not supported curently */
4208 strncpy(im->watermark, optarg, 100);
4209 im->watermark[99] = '\0';
4213 rrd_set_error("unknown option '%c'", optopt);
4215 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4220 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4221 pango_layout_context_changed(im->layout);
4225 if (im->logarithmic && im->minval <= 0) {
4227 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4231 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4232 /* error string is set in rrd_parsetime.c */
4236 if (start_tmp < 3600 * 24 * 365 * 10) {
4238 ("the first entry to fetch should be after 1980 (%ld)",
4243 if (end_tmp < start_tmp) {
4245 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4249 im->start = start_tmp;
4251 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4254 int rrd_graph_color(
4262 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4264 color = strstr(var, "#");
4265 if (color == NULL) {
4266 if (optional == 0) {
4267 rrd_set_error("Found no color in %s", err);
4274 long unsigned int col;
4276 rest = strstr(color, ":");
4283 sscanf(color, "#%6lx%n", &col, &n);
4284 col = (col << 8) + 0xff /* shift left by 8 */ ;
4286 rrd_set_error("Color problem in %s", err);
4289 sscanf(color, "#%8lx%n", &col, &n);
4293 rrd_set_error("Color problem in %s", err);
4295 if (rrd_test_error())
4297 gdp->col = gfx_hex_to_col(col);
4310 while (*ptr != '\0')
4311 if (*ptr++ == '%') {
4313 /* line cannot end with percent char */
4316 /* '%s', '%S' and '%%' are allowed */
4317 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4319 /* %c is allowed (but use only with vdef!) */
4320 else if (*ptr == 'c') {
4325 /* or else '% 6.2lf' and such are allowed */
4327 /* optional padding character */
4328 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4330 /* This should take care of 'm.n' with all three optional */
4331 while (*ptr >= '0' && *ptr <= '9')
4335 while (*ptr >= '0' && *ptr <= '9')
4337 /* Either 'le', 'lf' or 'lg' must follow here */
4340 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4355 const char *const str)
4357 /* A VDEF currently is either "func" or "param,func"
4358 * so the parsing is rather simple. Change if needed.
4366 old_locale = setlocale(LC_NUMERIC, "C");
4367 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4368 setlocale(LC_NUMERIC, old_locale);
4369 if (n == (int) strlen(str)) { /* matched */
4373 sscanf(str, "%29[A-Z]%n", func, &n);
4374 if (n == (int) strlen(str)) { /* matched */
4378 ("Unknown function string '%s' in VDEF '%s'",
4383 if (!strcmp("PERCENT", func))
4384 gdes->vf.op = VDEF_PERCENT;
4385 else if (!strcmp("MAXIMUM", func))
4386 gdes->vf.op = VDEF_MAXIMUM;
4387 else if (!strcmp("AVERAGE", func))
4388 gdes->vf.op = VDEF_AVERAGE;
4389 else if (!strcmp("STDEV", func))
4390 gdes->vf.op = VDEF_STDEV;
4391 else if (!strcmp("MINIMUM", func))
4392 gdes->vf.op = VDEF_MINIMUM;
4393 else if (!strcmp("TOTAL", func))
4394 gdes->vf.op = VDEF_TOTAL;
4395 else if (!strcmp("FIRST", func))
4396 gdes->vf.op = VDEF_FIRST;
4397 else if (!strcmp("LAST", func))
4398 gdes->vf.op = VDEF_LAST;
4399 else if (!strcmp("LSLSLOPE", func))
4400 gdes->vf.op = VDEF_LSLSLOPE;
4401 else if (!strcmp("LSLINT", func))
4402 gdes->vf.op = VDEF_LSLINT;
4403 else if (!strcmp("LSLCORREL", func))
4404 gdes->vf.op = VDEF_LSLCORREL;
4407 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4410 switch (gdes->vf.op) {
4412 if (isnan(param)) { /* no parameter given */
4414 ("Function '%s' needs parameter in VDEF '%s'\n",
4418 if (param >= 0.0 && param <= 100.0) {
4419 gdes->vf.param = param;
4420 gdes->vf.val = DNAN; /* undefined */
4421 gdes->vf.when = 0; /* undefined */
4424 ("Parameter '%f' out of range in VDEF '%s'\n",
4425 param, gdes->vname);
4438 case VDEF_LSLCORREL:
4440 gdes->vf.param = DNAN;
4441 gdes->vf.val = DNAN;
4445 ("Function '%s' needs no parameter in VDEF '%s'\n",
4459 graph_desc_t *src, *dst;
4464 dst = &im->gdes[gdi];
4465 src = &im->gdes[dst->vidx];
4466 data = src->data + src->ds;
4468 src->end_orig % (long) src->step ==
4469 0 ? src->end_orig : (src->end_orig + (long) src->step -
4470 src->end_orig % (long) src->step);
4472 steps = (end - src->start) / src->step;
4475 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4476 src->start, src->end_orig, steps);
4478 switch (dst->vf.op) {
4482 if ((array = malloc(steps * sizeof(double))) == NULL) {
4483 rrd_set_error("malloc VDEV_PERCENT");
4486 for (step = 0; step < steps; step++) {
4487 array[step] = data[step * src->ds_cnt];
4489 qsort(array, step, sizeof(double), vdef_percent_compar);
4490 field = (steps - 1) * dst->vf.param / 100;
4491 dst->vf.val = array[field];
4492 dst->vf.when = 0; /* no time component */
4495 for (step = 0; step < steps; step++)
4496 printf("DEBUG: %3li:%10.2f %c\n",
4497 step, array[step], step == field ? '*' : ' ');
4503 while (step != steps && isnan(data[step * src->ds_cnt]))
4505 if (step == steps) {
4509 dst->vf.val = data[step * src->ds_cnt];
4510 dst->vf.when = src->start + (step + 1) * src->step;
4512 while (step != steps) {
4513 if (finite(data[step * src->ds_cnt])) {
4514 if (data[step * src->ds_cnt] > dst->vf.val) {
4515 dst->vf.val = data[step * src->ds_cnt];
4516 dst->vf.when = src->start + (step + 1) * src->step;
4527 double average = 0.0;
4529 for (step = 0; step < steps; step++) {
4530 if (finite(data[step * src->ds_cnt])) {
4531 sum += data[step * src->ds_cnt];
4536 if (dst->vf.op == VDEF_TOTAL) {
4537 dst->vf.val = sum * src->step;
4538 dst->vf.when = 0; /* no time component */
4539 } else if (dst->vf.op == VDEF_AVERAGE) {
4540 dst->vf.val = sum / cnt;
4541 dst->vf.when = 0; /* no time component */
4543 average = sum / cnt;
4545 for (step = 0; step < steps; step++) {
4546 if (finite(data[step * src->ds_cnt])) {
4547 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4550 dst->vf.val = pow(sum / cnt, 0.5);
4551 dst->vf.when = 0; /* no time component */
4561 while (step != steps && isnan(data[step * src->ds_cnt]))
4563 if (step == steps) {
4567 dst->vf.val = data[step * src->ds_cnt];
4568 dst->vf.when = src->start + (step + 1) * src->step;
4570 while (step != steps) {
4571 if (finite(data[step * src->ds_cnt])) {
4572 if (data[step * src->ds_cnt] < dst->vf.val) {
4573 dst->vf.val = data[step * src->ds_cnt];
4574 dst->vf.when = src->start + (step + 1) * src->step;
4581 /* The time value returned here is one step before the
4582 * actual time value. This is the start of the first
4586 while (step != steps && isnan(data[step * src->ds_cnt]))
4588 if (step == steps) { /* all entries were NaN */
4592 dst->vf.val = data[step * src->ds_cnt];
4593 dst->vf.when = src->start + step * src->step;
4597 /* The time value returned here is the
4598 * actual time value. This is the end of the last
4602 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4604 if (step < 0) { /* all entries were NaN */
4608 dst->vf.val = data[step * src->ds_cnt];
4609 dst->vf.when = src->start + (step + 1) * src->step;
4614 case VDEF_LSLCORREL:{
4615 /* Bestfit line by linear least squares method */
4618 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4625 for (step = 0; step < steps; step++) {
4626 if (finite(data[step * src->ds_cnt])) {
4629 SUMxx += step * step;
4630 SUMxy += step * data[step * src->ds_cnt];
4631 SUMy += data[step * src->ds_cnt];
4632 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4636 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4637 y_intercept = (SUMy - slope * SUMx) / cnt;
4640 (SUMx * SUMy) / cnt) /
4642 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4644 if (dst->vf.op == VDEF_LSLSLOPE) {
4645 dst->vf.val = slope;
4647 } else if (dst->vf.op == VDEF_LSLINT) {
4648 dst->vf.val = y_intercept;
4650 } else if (dst->vf.op == VDEF_LSLCORREL) {
4651 dst->vf.val = correl;
4664 /* NaN < -INF < finite_values < INF */
4665 int vdef_percent_compar(
4671 /* Equality is not returned; this doesn't hurt except
4672 * (maybe) for a little performance.
4675 /* First catch NaN values. They are smallest */
4676 if (isnan(*(double *) a))
4678 if (isnan(*(double *) b))
4680 /* NaN doesn't reach this part so INF and -INF are extremes.
4681 * The sign from isinf() is compatible with the sign we return
4683 if (isinf(*(double *) a))
4684 return isinf(*(double *) a);
4685 if (isinf(*(double *) b))
4686 return isinf(*(double *) b);
4687 /* If we reach this, both values must be finite */
4688 if (*(double *) a < *(double *) b)
4697 rrd_info_type_t type,
4698 rrd_infoval_t value)
4700 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4701 if (im->grinfo == NULL) {
4702 im->grinfo = im->grinfo_current;