1 /****************************************************************************
2 * RRDtool 1.3.0 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
28 #include "rrd_graph.h"
29 #include "rrd_client.h"
31 /* some constant definitions */
35 #ifndef RRD_DEFAULT_FONT
36 /* there is special code later to pick Cour.ttf when running on windows */
37 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
40 text_prop_t text_prop[] = {
41 {8.0, RRD_DEFAULT_FONT}
43 {9.0, RRD_DEFAULT_FONT}
45 {7.0, RRD_DEFAULT_FONT}
47 {8.0, RRD_DEFAULT_FONT}
49 {8.0, RRD_DEFAULT_FONT} /* legend */
53 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
55 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
57 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
59 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
61 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
63 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
65 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
67 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
69 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
71 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
72 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
74 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
76 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
78 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
80 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
82 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
85 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
88 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
91 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
93 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
94 365 * 24 * 3600, "%y"}
96 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
99 /* sensible y label intervals ...*/
123 {20.0, {1, 5, 10, 20}
129 {100.0, {1, 2, 5, 10}
132 {200.0, {1, 5, 10, 20}
135 {500.0, {1, 2, 4, 10}
143 gfx_color_t graph_col[] = /* default colors */
145 {1.00, 1.00, 1.00, 1.00}, /* canvas */
146 {0.95, 0.95, 0.95, 1.00}, /* background */
147 {0.81, 0.81, 0.81, 1.00}, /* shade A */
148 {0.62, 0.62, 0.62, 1.00}, /* shade B */
149 {0.56, 0.56, 0.56, 0.75}, /* grid */
150 {0.87, 0.31, 0.31, 0.60}, /* major grid */
151 {0.00, 0.00, 0.00, 1.00}, /* font */
152 {0.50, 0.12, 0.12, 1.00}, /* arrow */
153 {0.12, 0.12, 0.12, 1.00}, /* axis */
154 {0.00, 0.00, 0.00, 1.00} /* frame */
161 # define DPRINT(x) (void)(printf x, printf("\n"))
167 /* initialize with xtr(im,0); */
175 pixie = (double) im->xsize / (double) (im->end - im->start);
178 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
181 /* translate data values into y coordinates */
190 if (!im->logarithmic)
191 pixie = (double) im->ysize / (im->maxval - im->minval);
194 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
196 } else if (!im->logarithmic) {
197 yval = im->yorigin - pixie * (value - im->minval);
199 if (value < im->minval) {
202 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
210 /* conversion function for symbolic entry names */
213 #define conv_if(VV,VVV) \
214 if (strcmp(#VV, string) == 0) return VVV ;
220 conv_if(PRINT, GF_PRINT);
221 conv_if(GPRINT, GF_GPRINT);
222 conv_if(COMMENT, GF_COMMENT);
223 conv_if(HRULE, GF_HRULE);
224 conv_if(VRULE, GF_VRULE);
225 conv_if(LINE, GF_LINE);
226 conv_if(AREA, GF_AREA);
227 conv_if(STACK, GF_STACK);
228 conv_if(TICK, GF_TICK);
229 conv_if(TEXTALIGN, GF_TEXTALIGN);
230 conv_if(DEF, GF_DEF);
231 conv_if(CDEF, GF_CDEF);
232 conv_if(VDEF, GF_VDEF);
233 conv_if(XPORT, GF_XPORT);
234 conv_if(SHIFT, GF_SHIFT);
239 enum gfx_if_en if_conv(
243 conv_if(PNG, IF_PNG);
244 conv_if(SVG, IF_SVG);
245 conv_if(EPS, IF_EPS);
246 conv_if(PDF, IF_PDF);
251 enum tmt_en tmt_conv(
255 conv_if(SECOND, TMT_SECOND);
256 conv_if(MINUTE, TMT_MINUTE);
257 conv_if(HOUR, TMT_HOUR);
258 conv_if(DAY, TMT_DAY);
259 conv_if(WEEK, TMT_WEEK);
260 conv_if(MONTH, TMT_MONTH);
261 conv_if(YEAR, TMT_YEAR);
265 enum grc_en grc_conv(
269 conv_if(BACK, GRC_BACK);
270 conv_if(CANVAS, GRC_CANVAS);
271 conv_if(SHADEA, GRC_SHADEA);
272 conv_if(SHADEB, GRC_SHADEB);
273 conv_if(GRID, GRC_GRID);
274 conv_if(MGRID, GRC_MGRID);
275 conv_if(FONT, GRC_FONT);
276 conv_if(ARROW, GRC_ARROW);
277 conv_if(AXIS, GRC_AXIS);
278 conv_if(FRAME, GRC_FRAME);
283 enum text_prop_en text_prop_conv(
287 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
288 conv_if(TITLE, TEXT_PROP_TITLE);
289 conv_if(AXIS, TEXT_PROP_AXIS);
290 conv_if(UNIT, TEXT_PROP_UNIT);
291 conv_if(LEGEND, TEXT_PROP_LEGEND);
302 cairo_status_t status = 0;
307 if (im->use_rrdcached)
310 im->use_rrdcached = 0;
313 for (i = 0; i < (unsigned) im->gdes_c; i++) {
314 if (im->gdes[i].data_first) {
315 /* careful here, because a single pointer can occur several times */
316 free(im->gdes[i].data);
317 if (im->gdes[i].ds_namv) {
318 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
319 free(im->gdes[i].ds_namv[ii]);
320 free(im->gdes[i].ds_namv);
323 /* free allocated memory used for dashed lines */
324 if (im->gdes[i].p_dashes != NULL)
325 free(im->gdes[i].p_dashes);
327 free(im->gdes[i].p_data);
328 free(im->gdes[i].rpnp);
331 if (im->font_options)
332 cairo_font_options_destroy(im->font_options);
335 status = cairo_status(im->cr);
336 cairo_destroy(im->cr);
338 if (im->rendered_image) {
339 free(im->rendered_image);
342 cairo_surface_destroy(im->surface);
344 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
345 cairo_status_to_string(status));
350 /* find SI magnitude symbol for the given number*/
352 image_desc_t *im, /* image description */
358 char *symbol[] = { "a", /* 10e-18 Atto */
359 "f", /* 10e-15 Femto */
360 "p", /* 10e-12 Pico */
361 "n", /* 10e-9 Nano */
362 "u", /* 10e-6 Micro */
363 "m", /* 10e-3 Milli */
368 "T", /* 10e12 Tera */
369 "P", /* 10e15 Peta */
376 if (*value == 0.0 || isnan(*value)) {
380 sindex = floor(log(fabs(*value)) / log((double) im->base));
381 *magfact = pow((double) im->base, (double) sindex);
382 (*value) /= (*magfact);
384 if (sindex <= symbcenter && sindex >= -symbcenter) {
385 (*symb_ptr) = symbol[sindex + symbcenter];
392 static char si_symbol[] = {
393 'a', /* 10e-18 Atto */
394 'f', /* 10e-15 Femto */
395 'p', /* 10e-12 Pico */
396 'n', /* 10e-9 Nano */
397 'u', /* 10e-6 Micro */
398 'm', /* 10e-3 Milli */
403 'T', /* 10e12 Tera */
404 'P', /* 10e15 Peta */
407 static const int si_symbcenter = 6;
409 /* find SI magnitude symbol for the numbers on the y-axis*/
411 image_desc_t *im /* image description */
415 double digits, viewdigits = 0;
418 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
419 log((double) im->base));
421 if (im->unitsexponent != 9999) {
422 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
423 viewdigits = floor(im->unitsexponent / 3);
428 im->magfact = pow((double) im->base, digits);
431 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
434 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
436 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
437 ((viewdigits + si_symbcenter) >= 0))
438 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
443 /* move min and max values around to become sensible */
448 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
449 600.0, 500.0, 400.0, 300.0, 250.0,
450 200.0, 125.0, 100.0, 90.0, 80.0,
451 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
452 25.0, 20.0, 10.0, 9.0, 8.0,
453 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
454 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
455 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
458 double scaled_min, scaled_max;
465 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
466 im->minval, im->maxval, im->magfact);
469 if (isnan(im->ygridstep)) {
470 if (im->extra_flags & ALTAUTOSCALE) {
471 /* measure the amplitude of the function. Make sure that
472 graph boundaries are slightly higher then max/min vals
473 so we can see amplitude on the graph */
476 delt = im->maxval - im->minval;
478 fact = 2.0 * pow(10.0,
480 (max(fabs(im->minval), fabs(im->maxval)) /
483 adj = (fact - delt) * 0.55;
486 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
487 im->minval, im->maxval, delt, fact, adj);
492 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
493 /* measure the amplitude of the function. Make sure that
494 graph boundaries are slightly lower than min vals
495 so we can see amplitude on the graph */
496 adj = (im->maxval - im->minval) * 0.1;
498 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
499 /* measure the amplitude of the function. Make sure that
500 graph boundaries are slightly higher than max vals
501 so we can see amplitude on the graph */
502 adj = (im->maxval - im->minval) * 0.1;
505 scaled_min = im->minval / im->magfact;
506 scaled_max = im->maxval / im->magfact;
508 for (i = 1; sensiblevalues[i] > 0; i++) {
509 if (sensiblevalues[i - 1] >= scaled_min &&
510 sensiblevalues[i] <= scaled_min)
511 im->minval = sensiblevalues[i] * (im->magfact);
513 if (-sensiblevalues[i - 1] <= scaled_min &&
514 -sensiblevalues[i] >= scaled_min)
515 im->minval = -sensiblevalues[i - 1] * (im->magfact);
517 if (sensiblevalues[i - 1] >= scaled_max &&
518 sensiblevalues[i] <= scaled_max)
519 im->maxval = sensiblevalues[i - 1] * (im->magfact);
521 if (-sensiblevalues[i - 1] <= scaled_max &&
522 -sensiblevalues[i] >= scaled_max)
523 im->maxval = -sensiblevalues[i] * (im->magfact);
527 /* adjust min and max to the grid definition if there is one */
528 im->minval = (double) im->ylabfact * im->ygridstep *
529 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
530 im->maxval = (double) im->ylabfact * im->ygridstep *
531 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
535 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
536 im->minval, im->maxval, im->magfact);
544 if (isnan(im->minval) || isnan(im->maxval))
547 if (im->logarithmic) {
548 double ya, yb, ypix, ypixfrac;
549 double log10_range = log10(im->maxval) - log10(im->minval);
551 ya = pow((double) 10, floor(log10(im->minval)));
552 while (ya < im->minval)
555 return; /* don't have y=10^x gridline */
557 if (yb <= im->maxval) {
558 /* we have at least 2 y=10^x gridlines.
559 Make sure distance between them in pixels
560 are an integer by expanding im->maxval */
561 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
562 double factor = y_pixel_delta / floor(y_pixel_delta);
563 double new_log10_range = factor * log10_range;
564 double new_ymax_log10 = log10(im->minval) + new_log10_range;
566 im->maxval = pow(10, new_ymax_log10);
567 ytr(im, DNAN); /* reset precalc */
568 log10_range = log10(im->maxval) - log10(im->minval);
570 /* make sure first y=10^x gridline is located on
571 integer pixel position by moving scale slightly
572 downwards (sub-pixel movement) */
573 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
574 ypixfrac = ypix - floor(ypix);
575 if (ypixfrac > 0 && ypixfrac < 1) {
576 double yfrac = ypixfrac / im->ysize;
578 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
579 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
580 ytr(im, DNAN); /* reset precalc */
583 /* Make sure we have an integer pixel distance between
584 each minor gridline */
585 double ypos1 = ytr(im, im->minval);
586 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
587 double y_pixel_delta = ypos1 - ypos2;
588 double factor = y_pixel_delta / floor(y_pixel_delta);
589 double new_range = factor * (im->maxval - im->minval);
590 double gridstep = im->ygrid_scale.gridstep;
591 double minor_y, minor_y_px, minor_y_px_frac;
593 if (im->maxval > 0.0)
594 im->maxval = im->minval + new_range;
596 im->minval = im->maxval - new_range;
597 ytr(im, DNAN); /* reset precalc */
598 /* make sure first minor gridline is on integer pixel y coord */
599 minor_y = gridstep * floor(im->minval / gridstep);
600 while (minor_y < im->minval)
602 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
603 minor_y_px_frac = minor_y_px - floor(minor_y_px);
604 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
605 double yfrac = minor_y_px_frac / im->ysize;
606 double range = im->maxval - im->minval;
608 im->minval = im->minval - yfrac * range;
609 im->maxval = im->maxval - yfrac * range;
610 ytr(im, DNAN); /* reset precalc */
612 calc_horizontal_grid(im); /* recalc with changed im->maxval */
616 /* reduce data reimplementation by Alex */
619 enum cf_en cf, /* which consolidation function ? */
620 unsigned long cur_step, /* step the data currently is in */
621 time_t *start, /* start, end and step as requested ... */
622 time_t *end, /* ... by the application will be ... */
623 unsigned long *step, /* ... adjusted to represent reality */
624 unsigned long *ds_cnt, /* number of data sources in file */
626 { /* two dimensional array containing the data */
627 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
628 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
630 rrd_value_t *srcptr, *dstptr;
632 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
635 row_cnt = ((*end) - (*start)) / cur_step;
641 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
642 row_cnt, reduce_factor, *start, *end, cur_step);
643 for (col = 0; col < row_cnt; col++) {
644 printf("time %10lu: ", *start + (col + 1) * cur_step);
645 for (i = 0; i < *ds_cnt; i++)
646 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
651 /* We have to combine [reduce_factor] rows of the source
652 ** into one row for the destination. Doing this we also
653 ** need to take care to combine the correct rows. First
654 ** alter the start and end time so that they are multiples
655 ** of the new step time. We cannot reduce the amount of
656 ** time so we have to move the end towards the future and
657 ** the start towards the past.
659 end_offset = (*end) % (*step);
660 start_offset = (*start) % (*step);
662 /* If there is a start offset (which cannot be more than
663 ** one destination row), skip the appropriate number of
664 ** source rows and one destination row. The appropriate
665 ** number is what we do know (start_offset/cur_step) of
666 ** the new interval (*step/cur_step aka reduce_factor).
669 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
670 printf("row_cnt before: %lu\n", row_cnt);
673 (*start) = (*start) - start_offset;
674 skiprows = reduce_factor - start_offset / cur_step;
675 srcptr += skiprows * *ds_cnt;
676 for (col = 0; col < (*ds_cnt); col++)
681 printf("row_cnt between: %lu\n", row_cnt);
684 /* At the end we have some rows that are not going to be
685 ** used, the amount is end_offset/cur_step
688 (*end) = (*end) - end_offset + (*step);
689 skiprows = end_offset / cur_step;
693 printf("row_cnt after: %lu\n", row_cnt);
696 /* Sanity check: row_cnt should be multiple of reduce_factor */
697 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
699 if (row_cnt % reduce_factor) {
700 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
701 row_cnt, reduce_factor);
702 printf("BUG in reduce_data()\n");
706 /* Now combine reduce_factor intervals at a time
707 ** into one interval for the destination.
710 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
711 for (col = 0; col < (*ds_cnt); col++) {
712 rrd_value_t newval = DNAN;
713 unsigned long validval = 0;
715 for (i = 0; i < reduce_factor; i++) {
716 if (isnan(srcptr[i * (*ds_cnt) + col])) {
721 newval = srcptr[i * (*ds_cnt) + col];
730 newval += srcptr[i * (*ds_cnt) + col];
733 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
736 /* an interval contains a failure if any subintervals contained a failure */
738 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
741 newval = srcptr[i * (*ds_cnt) + col];
767 srcptr += (*ds_cnt) * reduce_factor;
768 row_cnt -= reduce_factor;
770 /* If we had to alter the endtime, we didn't have enough
771 ** source rows to fill the last row. Fill it with NaN.
774 for (col = 0; col < (*ds_cnt); col++)
777 row_cnt = ((*end) - (*start)) / *step;
779 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
780 row_cnt, *start, *end, *step);
781 for (col = 0; col < row_cnt; col++) {
782 printf("time %10lu: ", *start + (col + 1) * (*step));
783 for (i = 0; i < *ds_cnt; i++)
784 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
791 /* get the data required for the graphs from the
800 /* pull the data from the rrd files ... */
801 for (i = 0; i < (int) im->gdes_c; i++) {
802 /* only GF_DEF elements fetch data */
803 if (im->gdes[i].gf != GF_DEF)
807 /* do we have it already ? */
808 for (ii = 0; ii < i; ii++) {
809 if (im->gdes[ii].gf != GF_DEF)
811 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
812 && (im->gdes[i].cf == im->gdes[ii].cf)
813 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
814 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
815 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
816 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
817 /* OK, the data is already there.
818 ** Just copy the header portion
820 im->gdes[i].start = im->gdes[ii].start;
821 im->gdes[i].end = im->gdes[ii].end;
822 im->gdes[i].step = im->gdes[ii].step;
823 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
824 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
825 im->gdes[i].data = im->gdes[ii].data;
826 im->gdes[i].data_first = 0;
833 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
835 if ((rrd_fetch_fn(im->gdes[i].rrd,
840 im->use_rrdcached ? 1 : 0,
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,
1718 im->tabwidth, im->gdes[i].legend);
1723 /* who said there was a special tag ... ? */
1724 if (prt_fctn == 'g') {
1728 if (prt_fctn == '\0') {
1729 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1730 /* just one legend item is left right or center */
1731 switch (default_txtalign) {
1746 /* is it time to place the legends ? */
1747 if (fill > im->ximg - 2 * border) {
1755 if (leg_c == 1 && prt_fctn == 'j') {
1761 if (prt_fctn != '\0') {
1763 if (leg_c >= 2 && prt_fctn == 'j') {
1764 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1768 if (prt_fctn == 'c')
1769 leg_x = (im->ximg - fill) / 2.0;
1770 if (prt_fctn == 'r')
1771 leg_x = im->ximg - fill - border;
1772 for (ii = mark; ii <= i; ii++) {
1773 if (im->gdes[ii].legend[0] == '\0')
1774 continue; /* skip empty legends */
1775 im->gdes[ii].leg_x = leg_x;
1776 im->gdes[ii].leg_y = leg_y;
1778 gfx_get_text_width(im, leg_x,
1787 im->tabwidth, im->gdes[ii].legend)
1792 if (im->extra_flags & FULL_SIZE_MODE) {
1793 /* only add y space if there was text on the line */
1794 if (leg_x > border || prt_fctn == 's')
1795 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1796 if (prt_fctn == 's')
1797 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1799 if (leg_x > border || prt_fctn == 's')
1800 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1801 if (prt_fctn == 's')
1802 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1810 if (im->extra_flags & FULL_SIZE_MODE) {
1811 if (leg_y != leg_y_prev) {
1812 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1814 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1818 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1826 /* create a grid on the graph. it determines what to do
1827 from the values of xsize, start and end */
1829 /* the xaxis labels are determined from the number of seconds per pixel
1830 in the requested graph */
1832 int calc_horizontal_grid(
1840 int decimals, fractionals;
1842 im->ygrid_scale.labfact = 2;
1843 range = im->maxval - im->minval;
1844 scaledrange = range / im->magfact;
1845 /* does the scale of this graph make it impossible to put lines
1846 on it? If so, give up. */
1847 if (isnan(scaledrange)) {
1851 /* find grid spaceing */
1853 if (isnan(im->ygridstep)) {
1854 if (im->extra_flags & ALTYGRID) {
1855 /* find the value with max number of digits. Get number of digits */
1858 (max(fabs(im->maxval), fabs(im->minval)) *
1859 im->viewfactor / im->magfact));
1860 if (decimals <= 0) /* everything is small. make place for zero */
1862 im->ygrid_scale.gridstep =
1864 floor(log10(range * im->viewfactor / im->magfact))) /
1865 im->viewfactor * im->magfact;
1866 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1867 im->ygrid_scale.gridstep = 0.1;
1868 /* should have at least 5 lines but no more then 15 */
1869 if (range / im->ygrid_scale.gridstep < 5
1870 && im->ygrid_scale.gridstep >= 30)
1871 im->ygrid_scale.gridstep /= 10;
1872 if (range / im->ygrid_scale.gridstep > 15)
1873 im->ygrid_scale.gridstep *= 10;
1874 if (range / im->ygrid_scale.gridstep > 5) {
1875 im->ygrid_scale.labfact = 1;
1876 if (range / im->ygrid_scale.gridstep > 8
1877 || im->ygrid_scale.gridstep <
1878 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1879 im->ygrid_scale.labfact = 2;
1881 im->ygrid_scale.gridstep /= 5;
1882 im->ygrid_scale.labfact = 5;
1886 (im->ygrid_scale.gridstep *
1887 (double) im->ygrid_scale.labfact * im->viewfactor /
1889 if (fractionals < 0) { /* small amplitude. */
1890 int len = decimals - fractionals + 1;
1892 if (im->unitslength < len + 2)
1893 im->unitslength = len + 2;
1894 sprintf(im->ygrid_scale.labfmt,
1896 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1898 int len = decimals + 1;
1900 if (im->unitslength < len + 2)
1901 im->unitslength = len + 2;
1902 sprintf(im->ygrid_scale.labfmt,
1903 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1905 } else { /* classic rrd grid */
1906 for (i = 0; ylab[i].grid > 0; i++) {
1907 pixel = im->ysize / (scaledrange / ylab[i].grid);
1913 for (i = 0; i < 4; i++) {
1914 if (pixel * ylab[gridind].lfac[i] >=
1915 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1916 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1921 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1924 im->ygrid_scale.gridstep = im->ygridstep;
1925 im->ygrid_scale.labfact = im->ylabfact;
1930 int draw_horizontal_grid(
1936 char graph_label[100];
1938 double X0 = im->xorigin;
1939 double X1 = im->xorigin + im->xsize;
1940 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1941 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1945 im->ygrid_scale.gridstep /
1946 (double) im->magfact * (double) im->viewfactor;
1947 MaxY = scaledstep * (double) egrid;
1948 for (i = sgrid; i <= egrid; i++) {
1950 im->ygrid_scale.gridstep * i);
1952 im->ygrid_scale.gridstep * (i + 1));
1954 if (floor(Y0 + 0.5) >=
1955 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1956 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1957 with the chosen settings. Add a label if required by settings, or if
1958 there is only one label so far and the next grid line is out of bounds. */
1959 if (i % im->ygrid_scale.labfact == 0
1961 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1962 if (im->symbol == ' ') {
1963 if (im->extra_flags & ALTYGRID) {
1964 sprintf(graph_label,
1965 im->ygrid_scale.labfmt,
1966 scaledstep * (double) i);
1969 sprintf(graph_label, "%4.1f",
1970 scaledstep * (double) i);
1972 sprintf(graph_label, "%4.0f",
1973 scaledstep * (double) i);
1977 char sisym = (i == 0 ? ' ' : im->symbol);
1979 if (im->extra_flags & ALTYGRID) {
1980 sprintf(graph_label,
1981 im->ygrid_scale.labfmt,
1982 scaledstep * (double) i, sisym);
1985 sprintf(graph_label, "%4.1f %c",
1986 scaledstep * (double) i, sisym);
1988 sprintf(graph_label, "%4.0f %c",
1989 scaledstep * (double) i, sisym);
1997 text_prop[TEXT_PROP_AXIS].
1999 im->graph_col[GRC_FONT],
2001 text_prop[TEXT_PROP_AXIS].
2004 text_prop[TEXT_PROP_AXIS].
2005 size, im->tabwidth, 0.0,
2006 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2007 gfx_line(im, X0 - 2, Y0, X0, Y0,
2008 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2009 gfx_line(im, X1, Y0, X1 + 2, Y0,
2010 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2011 gfx_dashed_line(im, X0 - 2, Y0,
2017 im->grid_dash_on, im->grid_dash_off);
2018 } else if (!(im->extra_flags & NOMINOR)) {
2021 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2022 gfx_line(im, X1, Y0, X1 + 2, Y0,
2023 GRIDWIDTH, im->graph_col[GRC_GRID]);
2024 gfx_dashed_line(im, X0 - 1, Y0,
2028 graph_col[GRC_GRID],
2029 im->grid_dash_on, im->grid_dash_off);
2036 /* this is frexp for base 10 */
2047 iexp = floor(log(fabs(x)) / log(10));
2048 mnt = x / pow(10.0, iexp);
2051 mnt = x / pow(10.0, iexp);
2058 /* logaritmic horizontal grid */
2059 int horizontal_log_grid(
2063 double yloglab[][10] = {
2065 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2067 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2069 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2086 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2088 int i, j, val_exp, min_exp;
2089 double nex; /* number of decades in data */
2090 double logscale; /* scale in logarithmic space */
2091 int exfrac = 1; /* decade spacing */
2092 int mid = -1; /* row in yloglab for major grid */
2093 double mspac; /* smallest major grid spacing (pixels) */
2094 int flab; /* first value in yloglab to use */
2095 double value, tmp, pre_value;
2097 char graph_label[100];
2099 nex = log10(im->maxval / im->minval);
2100 logscale = im->ysize / nex;
2101 /* major spacing for data with high dynamic range */
2102 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2109 /* major spacing for less dynamic data */
2111 /* search best row in yloglab */
2113 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2114 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2117 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2120 /* find first value in yloglab */
2122 yloglab[mid][flab] < 10
2123 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2124 if (yloglab[mid][flab] == 10.0) {
2129 if (val_exp % exfrac)
2130 val_exp += abs(-val_exp % exfrac);
2132 X1 = im->xorigin + im->xsize;
2137 value = yloglab[mid][flab] * pow(10.0, val_exp);
2138 if (AlmostEqual2sComplement(value, pre_value, 4))
2139 break; /* it seems we are not converging */
2141 Y0 = ytr(im, value);
2142 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2144 /* major grid line */
2146 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2147 gfx_line(im, X1, Y0, X1 + 2, Y0,
2148 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2149 gfx_dashed_line(im, X0 - 2, Y0,
2154 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2156 if (im->extra_flags & FORCE_UNITS_SI) {
2161 scale = floor(val_exp / 3.0);
2163 pvalue = pow(10.0, val_exp % 3);
2165 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2166 pvalue *= yloglab[mid][flab];
2167 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2168 && ((scale + si_symbcenter) >= 0))
2169 symbol = si_symbol[scale + si_symbcenter];
2172 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2174 sprintf(graph_label, "%3.0e", value);
2178 text_prop[TEXT_PROP_AXIS].
2180 im->graph_col[GRC_FONT],
2182 text_prop[TEXT_PROP_AXIS].
2185 text_prop[TEXT_PROP_AXIS].
2186 size, im->tabwidth, 0.0,
2187 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2189 if (mid < 4 && exfrac == 1) {
2190 /* find first and last minor line behind current major line
2191 * i is the first line and j tha last */
2193 min_exp = val_exp - 1;
2194 for (i = 1; yloglab[mid][i] < 10.0; i++);
2195 i = yloglab[mid][i - 1] + 1;
2199 i = yloglab[mid][flab - 1] + 1;
2200 j = yloglab[mid][flab];
2203 /* draw minor lines below current major line */
2204 for (; i < j; i++) {
2206 value = i * pow(10.0, min_exp);
2207 if (value < im->minval)
2209 Y0 = ytr(im, value);
2210 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2215 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2216 gfx_line(im, X1, Y0, X1 + 2, Y0,
2217 GRIDWIDTH, im->graph_col[GRC_GRID]);
2218 gfx_dashed_line(im, X0 - 1, Y0,
2222 graph_col[GRC_GRID],
2223 im->grid_dash_on, im->grid_dash_off);
2225 } else if (exfrac > 1) {
2226 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2227 value = pow(10.0, i);
2228 if (value < im->minval)
2230 Y0 = ytr(im, value);
2231 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2236 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2237 gfx_line(im, X1, Y0, X1 + 2, Y0,
2238 GRIDWIDTH, im->graph_col[GRC_GRID]);
2239 gfx_dashed_line(im, X0 - 1, Y0,
2243 graph_col[GRC_GRID],
2244 im->grid_dash_on, im->grid_dash_off);
2249 if (yloglab[mid][++flab] == 10.0) {
2255 /* draw minor lines after highest major line */
2256 if (mid < 4 && exfrac == 1) {
2257 /* find first and last minor line below current major line
2258 * i is the first line and j tha last */
2260 min_exp = val_exp - 1;
2261 for (i = 1; yloglab[mid][i] < 10.0; i++);
2262 i = yloglab[mid][i - 1] + 1;
2266 i = yloglab[mid][flab - 1] + 1;
2267 j = yloglab[mid][flab];
2270 /* draw minor lines below current major line */
2271 for (; i < j; i++) {
2273 value = i * pow(10.0, min_exp);
2274 if (value < im->minval)
2276 Y0 = ytr(im, value);
2277 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2281 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2282 gfx_line(im, X1, Y0, X1 + 2, Y0,
2283 GRIDWIDTH, im->graph_col[GRC_GRID]);
2284 gfx_dashed_line(im, X0 - 1, Y0,
2288 graph_col[GRC_GRID],
2289 im->grid_dash_on, im->grid_dash_off);
2292 /* fancy minor gridlines */
2293 else if (exfrac > 1) {
2294 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2295 value = pow(10.0, i);
2296 if (value < im->minval)
2298 Y0 = ytr(im, value);
2299 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2303 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2304 gfx_line(im, X1, Y0, X1 + 2, Y0,
2305 GRIDWIDTH, im->graph_col[GRC_GRID]);
2306 gfx_dashed_line(im, X0 - 1, Y0,
2310 graph_col[GRC_GRID],
2311 im->grid_dash_on, im->grid_dash_off);
2322 int xlab_sel; /* which sort of label and grid ? */
2323 time_t ti, tilab, timajor;
2325 char graph_label[100];
2326 double X0, Y0, Y1; /* points for filled graph and more */
2329 /* the type of time grid is determined by finding
2330 the number of seconds per pixel in the graph */
2331 if (im->xlab_user.minsec == -1) {
2332 factor = (im->end - im->start) / im->xsize;
2334 while (xlab[xlab_sel + 1].minsec !=
2335 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2337 } /* pick the last one */
2338 while (xlab[xlab_sel - 1].minsec ==
2339 xlab[xlab_sel].minsec
2340 && xlab[xlab_sel].length > (im->end - im->start)) {
2342 } /* go back to the smallest size */
2343 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2344 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2345 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2346 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2347 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2348 im->xlab_user.labst = xlab[xlab_sel].labst;
2349 im->xlab_user.precis = xlab[xlab_sel].precis;
2350 im->xlab_user.stst = xlab[xlab_sel].stst;
2353 /* y coords are the same for every line ... */
2355 Y1 = im->yorigin - im->ysize;
2356 /* paint the minor grid */
2357 if (!(im->extra_flags & NOMINOR)) {
2358 for (ti = find_first_time(im->start,
2366 find_first_time(im->start,
2373 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2375 /* are we inside the graph ? */
2376 if (ti < im->start || ti > im->end)
2378 while (timajor < ti) {
2379 timajor = find_next_time(timajor,
2382 mgridtm, im->xlab_user.mgridst);
2385 continue; /* skip as falls on major grid line */
2387 gfx_line(im, X0, Y1 - 2, X0, Y1,
2388 GRIDWIDTH, im->graph_col[GRC_GRID]);
2389 gfx_line(im, X0, Y0, X0, Y0 + 2,
2390 GRIDWIDTH, im->graph_col[GRC_GRID]);
2391 gfx_dashed_line(im, X0, Y0 + 1, X0,
2394 graph_col[GRC_GRID],
2395 im->grid_dash_on, im->grid_dash_off);
2399 /* paint the major grid */
2400 for (ti = find_first_time(im->start,
2408 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2410 /* are we inside the graph ? */
2411 if (ti < im->start || ti > im->end)
2414 gfx_line(im, X0, Y1 - 2, X0, Y1,
2415 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2416 gfx_line(im, X0, Y0, X0, Y0 + 3,
2417 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2418 gfx_dashed_line(im, X0, Y0 + 3, X0,
2422 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2424 /* paint the labels below the graph */
2426 find_first_time(im->start -
2435 im->xlab_user.precis / 2;
2436 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2438 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2439 /* are we inside the graph ? */
2440 if (tilab < im->start || tilab > im->end)
2443 localtime_r(&tilab, &tm);
2444 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2446 # error "your libc has no strftime I guess we'll abort the exercise here."
2451 im->graph_col[GRC_FONT],
2453 text_prop[TEXT_PROP_AXIS].
2456 text_prop[TEXT_PROP_AXIS].
2457 size, im->tabwidth, 0.0,
2458 GFX_H_CENTER, GFX_V_TOP, graph_label);
2467 /* draw x and y axis */
2468 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2469 im->xorigin+im->xsize,im->yorigin-im->ysize,
2470 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2472 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2473 im->xorigin+im->xsize,im->yorigin-im->ysize,
2474 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2476 gfx_line(im, im->xorigin - 4,
2478 im->xorigin + im->xsize +
2479 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2480 gfx_line(im, im->xorigin,
2483 im->yorigin - im->ysize -
2484 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2485 /* arrow for X and Y axis direction */
2486 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 */
2487 im->graph_col[GRC_ARROW]);
2489 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 */
2490 im->graph_col[GRC_ARROW]);
2499 double X0, Y0; /* points for filled graph and more */
2500 struct gfx_color_t water_color;
2502 /* draw 3d border */
2503 gfx_new_area(im, 0, im->yimg,
2504 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2505 gfx_add_point(im, im->ximg - 2, 2);
2506 gfx_add_point(im, im->ximg, 0);
2507 gfx_add_point(im, 0, 0);
2509 gfx_new_area(im, 2, im->yimg - 2,
2511 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2512 gfx_add_point(im, im->ximg, 0);
2513 gfx_add_point(im, im->ximg, im->yimg);
2514 gfx_add_point(im, 0, im->yimg);
2516 if (im->draw_x_grid == 1)
2518 if (im->draw_y_grid == 1) {
2519 if (im->logarithmic) {
2520 res = horizontal_log_grid(im);
2522 res = draw_horizontal_grid(im);
2525 /* dont draw horizontal grid if there is no min and max val */
2527 char *nodata = "No Data found";
2529 gfx_text(im, im->ximg / 2,
2532 im->graph_col[GRC_FONT],
2534 text_prop[TEXT_PROP_AXIS].
2537 text_prop[TEXT_PROP_AXIS].
2538 size, im->tabwidth, 0.0,
2539 GFX_H_CENTER, GFX_V_CENTER, nodata);
2543 /* yaxis unit description */
2548 im->graph_col[GRC_FONT],
2550 text_prop[TEXT_PROP_UNIT].
2553 text_prop[TEXT_PROP_UNIT].
2555 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2559 im->graph_col[GRC_FONT],
2561 text_prop[TEXT_PROP_TITLE].
2564 text_prop[TEXT_PROP_TITLE].
2565 size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2566 /* rrdtool 'logo' */
2567 water_color = im->graph_col[GRC_FONT];
2568 water_color.alpha = 0.3;
2569 gfx_text(im, im->ximg - 4, 5,
2572 text_prop[TEXT_PROP_AXIS].
2573 font, 5.5, im->tabwidth,
2574 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2575 /* graph watermark */
2576 if (im->watermark[0] != '\0') {
2578 im->ximg / 2, im->yimg - 6,
2581 text_prop[TEXT_PROP_AXIS].
2582 font, 5.5, im->tabwidth, 0,
2583 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2587 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2588 for (i = 0; i < im->gdes_c; i++) {
2589 if (im->gdes[i].legend[0] == '\0')
2591 /* im->gdes[i].leg_y is the bottom of the legend */
2592 X0 = im->gdes[i].leg_x;
2593 Y0 = im->gdes[i].leg_y;
2594 gfx_text(im, X0, Y0,
2595 im->graph_col[GRC_FONT],
2598 [TEXT_PROP_LEGEND].font,
2601 [TEXT_PROP_LEGEND].size,
2603 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2604 /* The legend for GRAPH items starts with "M " to have
2605 enough space for the box */
2606 if (im->gdes[i].gf != GF_PRINT &&
2607 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2611 boxH = gfx_get_text_width(im, 0,
2619 size, im->tabwidth, "o") * 1.2;
2621 /* shift the box up a bit */
2623 /* make sure transparent colors show up the same way as in the graph */
2626 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2627 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2629 gfx_new_area(im, X0, Y0 - boxV, X0,
2630 Y0, X0 + boxH, Y0, im->gdes[i].col);
2631 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2634 cairo_new_path(im->cr);
2635 cairo_set_line_width(im->cr, 1.0);
2638 gfx_line_fit(im, &X0, &Y0);
2639 gfx_line_fit(im, &X1, &Y1);
2640 cairo_move_to(im->cr, X0, Y0);
2641 cairo_line_to(im->cr, X1, Y0);
2642 cairo_line_to(im->cr, X1, Y1);
2643 cairo_line_to(im->cr, X0, Y1);
2644 cairo_close_path(im->cr);
2645 cairo_set_source_rgba(im->cr,
2657 blue, im->graph_col[GRC_FRAME].alpha);
2658 if (im->gdes[i].dash) {
2659 /* make box borders in legend dashed if the graph is dashed */
2663 cairo_set_dash(im->cr, dashes, 1, 0.0);
2665 cairo_stroke(im->cr);
2666 cairo_restore(im->cr);
2673 /*****************************************************
2674 * lazy check make sure we rely need to create this graph
2675 *****************************************************/
2682 struct stat imgstat;
2685 return 0; /* no lazy option */
2686 if (strlen(im->graphfile) == 0)
2687 return 0; /* inmemory option */
2688 if (stat(im->graphfile, &imgstat) != 0)
2689 return 0; /* can't stat */
2690 /* one pixel in the existing graph is more then what we would
2692 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2694 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2695 return 0; /* the file does not exist */
2696 switch (im->imgformat) {
2698 size = PngSize(fd, &(im->ximg), &(im->yimg));
2708 int graph_size_location(
2713 /* The actual size of the image to draw is determined from
2714 ** several sources. The size given on the command line is
2715 ** the graph area but we need more as we have to draw labels
2716 ** and other things outside the graph area
2719 int Xvertical = 0, Ytitle =
2720 0, Xylabel = 0, Xmain = 0, Ymain =
2721 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2723 if (im->extra_flags & ONLY_GRAPH) {
2725 im->ximg = im->xsize;
2726 im->yimg = im->ysize;
2727 im->yorigin = im->ysize;
2732 /** +---+--------------------------------------------+
2733 ** | y |...............graph title..................|
2734 ** | +---+-------------------------------+--------+
2737 ** | i | a | | pie |
2738 ** | s | x | main graph area | chart |
2743 ** | l | b +-------------------------------+--------+
2744 ** | e | l | x axis labels | |
2745 ** +---+---+-------------------------------+--------+
2746 ** |....................legends.....................|
2747 ** +------------------------------------------------+
2749 ** +------------------------------------------------+
2752 if (im->ylegend[0] != '\0') {
2753 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2756 if (im->title[0] != '\0') {
2757 /* The title is placed "inbetween" two text lines so it
2758 ** automatically has some vertical spacing. The horizontal
2759 ** spacing is added here, on each side.
2761 /* if necessary, reduce the font size of the title until it fits the image width */
2762 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2766 if (im->draw_x_grid) {
2767 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2769 if (im->draw_y_grid || im->forceleftspace) {
2771 gfx_get_text_width(im, 0,
2779 size, im->tabwidth, "0") * im->unitslength;
2783 if (im->extra_flags & FULL_SIZE_MODE) {
2784 /* The actual size of the image to draw has been determined by the user.
2785 ** The graph area is the space remaining after accounting for the legend,
2786 ** the watermark, the pie chart, the axis labels, and the title.
2789 im->ximg = im->xsize;
2790 im->yimg = im->ysize;
2791 im->yorigin = im->ysize;
2794 im->yorigin += Ytitle;
2795 /* Now calculate the total size. Insert some spacing where
2796 desired. im->xorigin and im->yorigin need to correspond
2797 with the lower left corner of the main graph area or, if
2798 this one is not set, the imaginary box surrounding the
2800 /* Initial size calculation for the main graph area */
2801 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2803 Xmain -= Xspacing; /* put space between main graph area and right edge */
2804 im->xorigin = Xspacing + Xylabel;
2805 /* the length of the title should not influence with width of the graph
2806 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2807 if (Xvertical) { /* unit description */
2809 im->xorigin += Xvertical;
2813 /* The vertical size of the image is known in advance. The main graph area
2814 ** (Ymain) and im->yorigin must be set according to the space requirements
2815 ** of the legend and the axis labels.
2817 if (im->extra_flags & NOLEGEND) {
2818 /* set dimensions correctly if using full size mode with no legend */
2821 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2822 Ymain = im->yorigin;
2824 /* Determine where to place the legends onto the image.
2825 ** Set Ymain and adjust im->yorigin to match the space requirements.
2827 if (leg_place(im, &Ymain) == -1)
2832 /* remove title space *or* some padding above the graph from the main graph area */
2836 Ymain -= 1.5 * Yspacing;
2839 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2840 if (im->watermark[0] != '\0') {
2841 Ymain -= Ywatermark;
2845 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2847 /* The actual size of the image to draw is determined from
2848 ** several sources. The size given on the command line is
2849 ** the graph area but we need more as we have to draw labels
2850 ** and other things outside the graph area.
2853 if (im->ylegend[0] != '\0') {
2854 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2858 if (im->title[0] != '\0') {
2859 /* The title is placed "inbetween" two text lines so it
2860 ** automatically has some vertical spacing. The horizontal
2861 ** spacing is added here, on each side.
2863 /* don't care for the with of the title
2864 Xtitle = gfx_get_text_width(im->canvas, 0,
2865 im->text_prop[TEXT_PROP_TITLE].font,
2866 im->text_prop[TEXT_PROP_TITLE].size,
2868 im->title, 0) + 2*Xspacing; */
2869 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2876 /* Now calculate the total size. Insert some spacing where
2877 desired. im->xorigin and im->yorigin need to correspond
2878 with the lower left corner of the main graph area or, if
2879 this one is not set, the imaginary box surrounding the
2882 /* The legend width cannot yet be determined, as a result we
2883 ** have problems adjusting the image to it. For now, we just
2884 ** forget about it at all; the legend will have to fit in the
2885 ** size already allocated.
2887 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2889 im->ximg += Xspacing;
2890 im->xorigin = Xspacing + Xylabel;
2891 /* the length of the title should not influence with width of the graph
2892 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2893 if (Xvertical) { /* unit description */
2894 im->ximg += Xvertical;
2895 im->xorigin += Xvertical;
2898 /* The vertical size is interesting... we need to compare
2899 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2900 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2901 ** in order to start even thinking about Ylegend or Ywatermark.
2903 ** Do it in three portions: First calculate the inner part,
2904 ** then do the legend, then adjust the total height of the img,
2905 ** adding space for a watermark if one exists;
2907 /* reserve space for main and/or pie */
2908 im->yimg = Ymain + Yxlabel;
2909 im->yorigin = im->yimg - Yxlabel;
2910 /* reserve space for the title *or* some padding above the graph */
2913 im->yorigin += Ytitle;
2915 im->yimg += 1.5 * Yspacing;
2916 im->yorigin += 1.5 * Yspacing;
2918 /* reserve space for padding below the graph */
2919 im->yimg += Yspacing;
2920 /* Determine where to place the legends onto the image.
2921 ** Adjust im->yimg to match the space requirements.
2923 if (leg_place(im, 0) == -1)
2925 if (im->watermark[0] != '\0') {
2926 im->yimg += Ywatermark;
2934 static cairo_status_t cairo_output(
2938 unsigned int length)
2940 image_desc_t *im = closure;
2942 im->rendered_image =
2943 realloc(im->rendered_image, im->rendered_image_size + length);
2944 if (im->rendered_image == NULL)
2945 return CAIRO_STATUS_WRITE_ERROR;
2946 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2947 im->rendered_image_size += length;
2948 return CAIRO_STATUS_SUCCESS;
2951 /* draw that picture thing ... */
2956 int lazy = lazy_check(im);
2957 double areazero = 0.0;
2958 graph_desc_t *lastgdes = NULL;
2960 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2962 /* if we are lazy and there is nothing to PRINT ... quit now */
2963 if (lazy && im->prt_c == 0) {
2964 info.u_cnt = im->ximg;
2965 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2966 info.u_cnt = im->yimg;
2967 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2970 /* pull the data from the rrd files ... */
2971 if (data_fetch(im) == -1)
2973 /* evaluate VDEF and CDEF operations ... */
2974 if (data_calc(im) == -1)
2976 /* calculate and PRINT and GPRINT definitions. We have to do it at
2977 * this point because it will affect the length of the legends
2978 * if there are no graph elements (i==0) we stop here ...
2979 * if we are lazy, try to quit ...
2985 if ((i == 0) || lazy)
2988 /**************************************************************
2989 *** Calculating sizes and locations became a bit confusing ***
2990 *** so I moved this into a separate function. ***
2991 **************************************************************/
2992 if (graph_size_location(im, i) == -1)
2995 info.u_cnt = im->xorigin;
2996 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2997 info.u_cnt = im->yorigin - im->ysize;
2998 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2999 info.u_cnt = im->xsize;
3000 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3001 info.u_cnt = im->ysize;
3002 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3003 info.u_cnt = im->ximg;
3004 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3005 info.u_cnt = im->yimg;
3006 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3008 /* get actual drawing data and find min and max values */
3009 if (data_proc(im) == -1)
3011 if (!im->logarithmic) {
3015 /* identify si magnitude Kilo, Mega Giga ? */
3016 if (!im->rigid && !im->logarithmic)
3017 expand_range(im); /* make sure the upper and lower limit are
3020 info.u_val = im->minval;
3021 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3022 info.u_val = im->maxval;
3023 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3025 if (!calc_horizontal_grid(im))
3030 apply_gridfit(im); */
3031 /* the actual graph is created by going through the individual
3032 graph elements and then drawing them */
3033 cairo_surface_destroy(im->surface);
3034 switch (im->imgformat) {
3037 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3038 im->ximg * im->zoom,
3039 im->yimg * im->zoom);
3043 im->surface = strlen(im->graphfile)
3044 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3045 im->yimg * im->zoom)
3046 : cairo_pdf_surface_create_for_stream
3047 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3051 im->surface = strlen(im->graphfile)
3053 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3054 im->yimg * im->zoom)
3055 : cairo_ps_surface_create_for_stream
3056 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3060 im->surface = strlen(im->graphfile)
3062 cairo_svg_surface_create(im->
3064 im->ximg * im->zoom, im->yimg * im->zoom)
3065 : cairo_svg_surface_create_for_stream
3066 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3067 cairo_svg_surface_restrict_to_version
3068 (im->surface, CAIRO_SVG_VERSION_1_1);
3071 im->cr = cairo_create(im->surface);
3072 cairo_set_antialias(im->cr, im->graph_antialias);
3073 cairo_scale(im->cr, im->zoom, im->zoom);
3074 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3075 gfx_new_area(im, 0, 0, 0, im->yimg,
3076 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3077 gfx_add_point(im, im->ximg, 0);
3079 gfx_new_area(im, im->xorigin,
3082 im->xsize, im->yorigin,
3085 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3086 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3088 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3089 im->xsize, im->ysize + 2.0);
3091 if (im->minval > 0.0)
3092 areazero = im->minval;
3093 if (im->maxval < 0.0)
3094 areazero = im->maxval;
3095 for (i = 0; i < im->gdes_c; i++) {
3096 switch (im->gdes[i].gf) {
3110 for (ii = 0; ii < im->xsize; ii++) {
3111 if (!isnan(im->gdes[i].p_data[ii])
3112 && im->gdes[i].p_data[ii] != 0.0) {
3113 if (im->gdes[i].yrule > 0) {
3120 im->ysize, 1.0, im->gdes[i].col);
3121 } else if (im->gdes[i].yrule < 0) {
3124 im->yorigin - im->ysize,
3129 im->ysize, 1.0, im->gdes[i].col);
3136 /* fix data points at oo and -oo */
3137 for (ii = 0; ii < im->xsize; ii++) {
3138 if (isinf(im->gdes[i].p_data[ii])) {
3139 if (im->gdes[i].p_data[ii] > 0) {
3140 im->gdes[i].p_data[ii] = im->maxval;
3142 im->gdes[i].p_data[ii] = im->minval;
3148 /* *******************************************************
3153 -------|--t-1--t--------------------------------
3155 if we know the value at time t was a then
3156 we draw a square from t-1 to t with the value a.
3158 ********************************************************* */
3159 if (im->gdes[i].col.alpha != 0.0) {
3160 /* GF_LINE and friend */
3161 if (im->gdes[i].gf == GF_LINE) {
3162 double last_y = 0.0;
3166 cairo_new_path(im->cr);
3167 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3168 if (im->gdes[i].dash) {
3169 cairo_set_dash(im->cr,
3170 im->gdes[i].p_dashes,
3171 im->gdes[i].ndash, im->gdes[i].offset);
3174 for (ii = 1; ii < im->xsize; ii++) {
3175 if (isnan(im->gdes[i].p_data[ii])
3176 || (im->slopemode == 1
3177 && isnan(im->gdes[i].p_data[ii - 1]))) {
3182 last_y = ytr(im, im->gdes[i].p_data[ii]);
3183 if (im->slopemode == 0) {
3184 double x = ii - 1 + im->xorigin;
3187 gfx_line_fit(im, &x, &y);
3188 cairo_move_to(im->cr, x, y);
3189 x = ii + im->xorigin;
3191 gfx_line_fit(im, &x, &y);
3192 cairo_line_to(im->cr, x, y);
3194 double x = ii - 1 + im->xorigin;
3196 ytr(im, im->gdes[i].p_data[ii - 1]);
3197 gfx_line_fit(im, &x, &y);
3198 cairo_move_to(im->cr, x, y);
3199 x = ii + im->xorigin;
3201 gfx_line_fit(im, &x, &y);
3202 cairo_line_to(im->cr, x, y);
3206 double x1 = ii + im->xorigin;
3207 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3209 if (im->slopemode == 0
3210 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3211 double x = ii - 1 + im->xorigin;
3214 gfx_line_fit(im, &x, &y);
3215 cairo_line_to(im->cr, x, y);
3218 gfx_line_fit(im, &x1, &y1);
3219 cairo_line_to(im->cr, x1, y1);
3222 cairo_set_source_rgba(im->cr,
3228 col.blue, im->gdes[i].col.alpha);
3229 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3230 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3231 cairo_stroke(im->cr);
3232 cairo_restore(im->cr);
3236 (double *) malloc(sizeof(double) * im->xsize * 2);
3238 (double *) malloc(sizeof(double) * im->xsize * 2);
3240 (double *) malloc(sizeof(double) * im->xsize * 2);
3242 (double *) malloc(sizeof(double) * im->xsize * 2);
3245 for (ii = 0; ii <= im->xsize; ii++) {
3248 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3254 AlmostEqual2sComplement(foreY
3258 AlmostEqual2sComplement(foreY
3268 foreY[cntI], im->gdes[i].col);
3269 while (cntI < idxI) {
3274 AlmostEqual2sComplement(foreY
3278 AlmostEqual2sComplement(foreY
3285 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3287 gfx_add_point(im, backX[idxI], backY[idxI]);
3293 AlmostEqual2sComplement(backY
3297 AlmostEqual2sComplement(backY
3304 gfx_add_point(im, backX[idxI], backY[idxI]);
3314 if (ii == im->xsize)
3316 if (im->slopemode == 0 && ii == 0) {
3319 if (isnan(im->gdes[i].p_data[ii])) {
3323 ytop = ytr(im, im->gdes[i].p_data[ii]);
3324 if (lastgdes && im->gdes[i].stack) {
3325 ybase = ytr(im, lastgdes->p_data[ii]);
3327 ybase = ytr(im, areazero);
3329 if (ybase == ytop) {
3335 double extra = ytop;
3340 if (im->slopemode == 0) {
3341 backY[++idxI] = ybase - 0.2;
3342 backX[idxI] = ii + im->xorigin - 1;
3343 foreY[idxI] = ytop + 0.2;
3344 foreX[idxI] = ii + im->xorigin - 1;
3346 backY[++idxI] = ybase - 0.2;
3347 backX[idxI] = ii + im->xorigin;
3348 foreY[idxI] = ytop + 0.2;
3349 foreX[idxI] = ii + im->xorigin;
3351 /* close up any remaining area */
3356 } /* else GF_LINE */
3358 /* if color != 0x0 */
3359 /* make sure we do not run into trouble when stacking on NaN */
3360 for (ii = 0; ii < im->xsize; ii++) {
3361 if (isnan(im->gdes[i].p_data[ii])) {
3362 if (lastgdes && (im->gdes[i].stack)) {
3363 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3365 im->gdes[i].p_data[ii] = areazero;
3369 lastgdes = &(im->gdes[i]);
3373 ("STACK should already be turned into LINE or AREA here");
3378 cairo_reset_clip(im->cr);
3380 /* grid_paint also does the text */
3381 if (!(im->extra_flags & ONLY_GRAPH))
3383 if (!(im->extra_flags & ONLY_GRAPH))
3385 /* the RULES are the last thing to paint ... */
3386 for (i = 0; i < im->gdes_c; i++) {
3388 switch (im->gdes[i].gf) {
3390 if (im->gdes[i].yrule >= im->minval
3391 && im->gdes[i].yrule <= im->maxval) {
3393 if (im->gdes[i].dash) {
3394 cairo_set_dash(im->cr,
3395 im->gdes[i].p_dashes,
3396 im->gdes[i].ndash, im->gdes[i].offset);
3398 gfx_line(im, im->xorigin,
3399 ytr(im, im->gdes[i].yrule),
3400 im->xorigin + im->xsize,
3401 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3402 cairo_stroke(im->cr);
3403 cairo_restore(im->cr);
3407 if (im->gdes[i].xrule >= im->start
3408 && im->gdes[i].xrule <= im->end) {
3410 if (im->gdes[i].dash) {
3411 cairo_set_dash(im->cr,
3412 im->gdes[i].p_dashes,
3413 im->gdes[i].ndash, im->gdes[i].offset);
3416 xtr(im, im->gdes[i].xrule),
3417 im->yorigin, xtr(im,
3421 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3422 cairo_stroke(im->cr);
3423 cairo_restore(im->cr);
3432 switch (im->imgformat) {
3435 cairo_status_t status;
3437 status = strlen(im->graphfile) ?
3438 cairo_surface_write_to_png(im->surface, im->graphfile)
3439 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3442 if (status != CAIRO_STATUS_SUCCESS) {
3443 rrd_set_error("Could not save png to '%s'", im->graphfile);
3449 if (strlen(im->graphfile)) {
3450 cairo_show_page(im->cr);
3452 cairo_surface_finish(im->surface);
3461 /*****************************************************
3463 *****************************************************/
3470 if ((im->gdes = (graph_desc_t *)
3471 rrd_realloc(im->gdes, (im->gdes_c)
3472 * sizeof(graph_desc_t))) == NULL) {
3473 rrd_set_error("realloc graph_descs");
3478 im->gdes[im->gdes_c - 1].step = im->step;
3479 im->gdes[im->gdes_c - 1].step_orig = im->step;
3480 im->gdes[im->gdes_c - 1].stack = 0;
3481 im->gdes[im->gdes_c - 1].linewidth = 0;
3482 im->gdes[im->gdes_c - 1].debug = 0;
3483 im->gdes[im->gdes_c - 1].start = im->start;
3484 im->gdes[im->gdes_c - 1].start_orig = im->start;
3485 im->gdes[im->gdes_c - 1].end = im->end;
3486 im->gdes[im->gdes_c - 1].end_orig = im->end;
3487 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3488 im->gdes[im->gdes_c - 1].data = NULL;
3489 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3490 im->gdes[im->gdes_c - 1].data_first = 0;
3491 im->gdes[im->gdes_c - 1].p_data = NULL;
3492 im->gdes[im->gdes_c - 1].rpnp = NULL;
3493 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3494 im->gdes[im->gdes_c - 1].shift = 0.0;
3495 im->gdes[im->gdes_c - 1].dash = 0;
3496 im->gdes[im->gdes_c - 1].ndash = 0;
3497 im->gdes[im->gdes_c - 1].offset = 0;
3498 im->gdes[im->gdes_c - 1].col.red = 0.0;
3499 im->gdes[im->gdes_c - 1].col.green = 0.0;
3500 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3501 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3502 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3503 im->gdes[im->gdes_c - 1].format[0] = '\0';
3504 im->gdes[im->gdes_c - 1].strftm = 0;
3505 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3506 im->gdes[im->gdes_c - 1].ds = -1;
3507 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3508 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3509 im->gdes[im->gdes_c - 1].yrule = DNAN;
3510 im->gdes[im->gdes_c - 1].xrule = 0;
3514 /* copies input untill the first unescaped colon is found
3515 or until input ends. backslashes have to be escaped as well */
3517 const char *const input,
3523 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3524 if (input[inp] == '\\'
3525 && input[inp + 1] != '\0'
3526 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3527 output[outp++] = input[++inp];
3529 output[outp++] = input[inp];
3532 output[outp] = '\0';
3536 /* Now just a wrapper around rrd_graph_v */
3548 rrd_info_t *grinfo = NULL;
3551 grinfo = rrd_graph_v(argc, argv);
3557 if (strcmp(walker->key, "image_info") == 0) {
3560 rrd_realloc((*prdata),
3561 (prlines + 1) * sizeof(char *))) == NULL) {
3562 rrd_set_error("realloc prdata");
3565 /* imginfo goes to position 0 in the prdata array */
3566 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3567 + 2) * sizeof(char));
3568 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3569 (*prdata)[prlines] = NULL;
3571 /* skip anything else */
3572 walker = walker->next;
3580 if (strcmp(walker->key, "image_width") == 0) {
3581 *xsize = walker->value.u_int;
3582 } else if (strcmp(walker->key, "image_height") == 0) {
3583 *ysize = walker->value.u_int;
3584 } else if (strcmp(walker->key, "value_min") == 0) {
3585 *ymin = walker->value.u_val;
3586 } else if (strcmp(walker->key, "value_max") == 0) {
3587 *ymax = walker->value.u_val;
3588 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3591 rrd_realloc((*prdata),
3592 (prlines + 1) * sizeof(char *))) == NULL) {
3593 rrd_set_error("realloc prdata");
3596 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3597 + 2) * sizeof(char));
3598 (*prdata)[prlines] = NULL;
3599 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3600 } else if (strcmp(walker->key, "image") == 0) {
3601 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3602 (stream ? stream : stdout));
3604 /* skip anything else */
3605 walker = walker->next;
3607 rrd_info_free(grinfo);
3612 /* Some surgery done on this function, it became ridiculously big.
3614 ** - initializing now in rrd_graph_init()
3615 ** - options parsing now in rrd_graph_options()
3616 ** - script parsing now in rrd_graph_script()
3618 rrd_info_t *rrd_graph_v(
3625 rrd_graph_init(&im);
3626 /* a dummy surface so that we can measure text sizes for placements */
3627 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3628 im.cr = cairo_create(im.surface);
3629 rrd_graph_options(argc, argv, &im);
3630 if (rrd_test_error()) {
3631 rrd_info_free(im.grinfo);
3636 if (optind >= argc) {
3637 rrd_info_free(im.grinfo);
3639 rrd_set_error("missing filename");
3643 if (strlen(argv[optind]) >= MAXPATH) {
3644 rrd_set_error("filename (including path) too long");
3645 rrd_info_free(im.grinfo);
3650 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3651 im.graphfile[MAXPATH - 1] = '\0';
3653 if (strcmp(im.graphfile, "-") == 0) {
3654 im.graphfile[0] = '\0';
3657 rrd_graph_script(argc, argv, &im, 1);
3658 if (rrd_test_error()) {
3659 rrd_info_free(im.grinfo);
3664 /* Everything is now read and the actual work can start */
3666 if (graph_paint(&im) == -1) {
3667 rrd_info_free(im.grinfo);
3673 /* The image is generated and needs to be output.
3674 ** Also, if needed, print a line with information about the image.
3681 sprintf_alloc(im.imginfo,
3684 im.ximg), (long) (im.zoom * im.yimg));
3685 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3688 if (im.rendered_image) {
3691 img.u_blo.size = im.rendered_image_size;
3692 img.u_blo.ptr = im.rendered_image;
3693 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3700 void rrd_graph_init(
3709 #ifdef HAVE_SETLOCALE
3710 setlocale(LC_TIME, "");
3711 #ifdef HAVE_MBSTOWCS
3712 setlocale(LC_CTYPE, "");
3717 im->draw_x_grid = 1;
3718 im->draw_y_grid = 1;
3719 im->extra_flags = 0;
3720 im->font_options = cairo_font_options_create();
3721 im->forceleftspace = 0;
3724 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3725 im->grid_dash_off = 1;
3726 im->grid_dash_on = 1;
3728 im->grinfo = (rrd_info_t *) NULL;
3729 im->grinfo_current = (rrd_info_t *) NULL;
3730 im->imgformat = IF_PNG;
3732 im->use_rrdcached = 0;
3734 im->logarithmic = 0;
3740 im->rendered_image_size = 0;
3741 im->rendered_image = NULL;
3746 im->tabwidth = 40.0;
3747 im->title[0] = '\0';
3748 im->unitsexponent = 9999;
3749 im->unitslength = 6;
3750 im->viewfactor = 1.0;
3751 im->watermark[0] = '\0';
3752 im->with_markup = 0;
3754 im->xlab_user.minsec = -1;
3757 im->ygridstep = DNAN;
3759 im->ylegend[0] = '\0';
3763 cairo_font_options_set_hint_style
3764 (im->font_options, CAIRO_HINT_STYLE_FULL);
3765 cairo_font_options_set_hint_metrics
3766 (im->font_options, CAIRO_HINT_METRICS_ON);
3767 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3768 for (i = 0; i < DIM(graph_col); i++)
3769 im->graph_col[i] = graph_col[i];
3773 deffont = getenv("RRD_DEFAULT_FONT");
3774 if (deffont != NULL) {
3775 for (i = 0; i < DIM(text_prop); i++) {
3776 strncpy(text_prop[i].font, deffont,
3777 sizeof(text_prop[i].font) - 1);
3778 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3782 for (i = 0; i < DIM(text_prop); i++) {
3783 im->text_prop[i].size = text_prop[i].size;
3784 strcpy(im->text_prop[i].font, text_prop[i].font);
3788 void rrd_graph_options(
3795 char *parsetime_error = NULL;
3796 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3797 time_t start_tmp = 0, end_tmp = 0;
3799 rrd_time_value_t start_tv, end_tv;
3800 long unsigned int color;
3801 char *old_locale = "";
3803 /* defines for long options without a short equivalent. should be bytes,
3804 and may not collide with (the ASCII value of) short options */
3805 #define LONGOPT_UNITS_SI 255
3808 struct option long_options[] = {
3809 { "start", required_argument, 0, 's'},
3810 { "end", required_argument, 0, 'e'},
3811 { "x-grid", required_argument, 0, 'x'},
3812 { "y-grid", required_argument, 0, 'y'},
3813 { "vertical-label", required_argument, 0, 'v'},
3814 { "width", required_argument, 0, 'w'},
3815 { "height", required_argument, 0, 'h'},
3816 { "full-size-mode", no_argument, 0, 'D'},
3817 { "interlaced", no_argument, 0, 'i'},
3818 { "upper-limit", required_argument, 0, 'u'},
3819 { "lower-limit", required_argument, 0, 'l'},
3820 { "rigid", no_argument, 0, 'r'},
3821 { "base", required_argument, 0, 'b'},
3822 { "logarithmic", no_argument, 0, 'o'},
3823 { "color", required_argument, 0, 'c'},
3824 { "font", required_argument, 0, 'n'},
3825 { "title", required_argument, 0, 't'},
3826 { "imginfo", required_argument, 0, 'f'},
3827 { "imgformat", required_argument, 0, 'a'},
3828 { "lazy", no_argument, 0, 'z'},
3829 { "zoom", required_argument, 0, 'm'},
3830 { "no-legend", no_argument, 0, 'g'},
3831 { "force-rules-legend", no_argument, 0, 'F'},
3832 { "only-graph", no_argument, 0, 'j'},
3833 { "alt-y-grid", no_argument, 0, 'Y'},
3834 { "no-minor", no_argument, 0, 'I'},
3835 { "slope-mode", no_argument, 0, 'E'},
3836 { "alt-autoscale", no_argument, 0, 'A'},
3837 { "alt-autoscale-min", no_argument, 0, 'J'},
3838 { "alt-autoscale-max", no_argument, 0, 'M'},
3839 { "no-gridfit", no_argument, 0, 'N'},
3840 { "units-exponent", required_argument, 0, 'X'},
3841 { "units-length", required_argument, 0, 'L'},
3842 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3843 { "step", required_argument, 0, 'S'},
3844 { "tabwidth", required_argument, 0, 'T'},
3845 { "font-render-mode", required_argument, 0, 'R'},
3846 { "graph-render-mode", required_argument, 0, 'G'},
3847 { "font-smoothing-threshold", required_argument, 0, 'B'},
3848 { "watermark", required_argument, 0, 'W'},
3849 { "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 */
3850 { "pango-markup", no_argument, 0, 'P'},
3851 { "daemon", required_argument, 0, 'd'},
3857 opterr = 0; /* initialize getopt */
3858 rrd_parsetime("end-24h", &start_tv);
3859 rrd_parsetime("now", &end_tv);
3861 int option_index = 0;
3863 int col_start, col_end;
3865 opt = getopt_long(argc, argv,
3866 "s:e:x:y:v:w:h:D:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:kPd:",
3867 long_options, &option_index);
3872 im->extra_flags |= NOMINOR;
3875 im->extra_flags |= ALTYGRID;
3878 im->extra_flags |= ALTAUTOSCALE;
3881 im->extra_flags |= ALTAUTOSCALE_MIN;
3884 im->extra_flags |= ALTAUTOSCALE_MAX;
3887 im->extra_flags |= ONLY_GRAPH;
3890 im->extra_flags |= NOLEGEND;
3893 im->extra_flags |= FORCE_RULES_LEGEND;
3895 case LONGOPT_UNITS_SI:
3896 if (im->extra_flags & FORCE_UNITS) {
3897 rrd_set_error("--units can only be used once!");
3898 setlocale(LC_NUMERIC, old_locale);
3901 if (strcmp(optarg, "si") == 0)
3902 im->extra_flags |= FORCE_UNITS_SI;
3904 rrd_set_error("invalid argument for --units: %s", optarg);
3909 im->unitsexponent = atoi(optarg);
3912 im->unitslength = atoi(optarg);
3913 im->forceleftspace = 1;
3916 old_locale = setlocale(LC_NUMERIC, "C");
3917 im->tabwidth = atof(optarg);
3918 setlocale(LC_NUMERIC, old_locale);
3921 old_locale = setlocale(LC_NUMERIC, "C");
3922 im->step = atoi(optarg);
3923 setlocale(LC_NUMERIC, old_locale);
3929 im->with_markup = 1;
3932 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3933 rrd_set_error("start time: %s", parsetime_error);
3938 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3939 rrd_set_error("end time: %s", parsetime_error);
3944 if (strcmp(optarg, "none") == 0) {
3945 im->draw_x_grid = 0;
3949 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3951 &im->xlab_user.gridst,
3953 &im->xlab_user.mgridst,
3955 &im->xlab_user.labst,
3956 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3957 strncpy(im->xlab_form, optarg + stroff,
3958 sizeof(im->xlab_form) - 1);
3959 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3961 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3962 rrd_set_error("unknown keyword %s", scan_gtm);
3965 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3967 rrd_set_error("unknown keyword %s", scan_mtm);
3970 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
3971 rrd_set_error("unknown keyword %s", scan_ltm);
3974 im->xlab_user.minsec = 1;
3975 im->xlab_user.stst = im->xlab_form;
3977 rrd_set_error("invalid x-grid format");
3983 if (strcmp(optarg, "none") == 0) {
3984 im->draw_y_grid = 0;
3987 old_locale = setlocale(LC_NUMERIC, "C");
3988 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3989 setlocale(LC_NUMERIC, old_locale);
3990 if (im->ygridstep <= 0) {
3991 rrd_set_error("grid step must be > 0");
3993 } else if (im->ylabfact < 1) {
3994 rrd_set_error("label factor must be > 0");
3998 setlocale(LC_NUMERIC, old_locale);
3999 rrd_set_error("invalid y-grid format");
4004 strncpy(im->ylegend, optarg, 150);
4005 im->ylegend[150] = '\0';
4008 old_locale = setlocale(LC_NUMERIC, "C");
4009 im->maxval = atof(optarg);
4010 setlocale(LC_NUMERIC, old_locale);
4013 old_locale = setlocale(LC_NUMERIC, "C");
4014 im->minval = atof(optarg);
4015 setlocale(LC_NUMERIC, old_locale);
4018 im->base = atol(optarg);
4019 if (im->base != 1024 && im->base != 1000) {
4021 ("the only sensible value for base apart from 1000 is 1024");
4026 long_tmp = atol(optarg);
4027 if (long_tmp < 10) {
4028 rrd_set_error("width below 10 pixels");
4031 im->xsize = long_tmp;
4034 long_tmp = atol(optarg);
4035 if (long_tmp < 10) {
4036 rrd_set_error("height below 10 pixels");
4039 im->ysize = long_tmp;
4042 im->extra_flags |= FULL_SIZE_MODE;
4045 /* interlaced png not supported at the moment */
4051 im->imginfo = optarg;
4055 (im->imgformat = if_conv(optarg)) == -1) {
4056 rrd_set_error("unsupported graphics format '%s'", optarg);
4067 im->logarithmic = 1;
4071 "%10[A-Z]#%n%8lx%n",
4072 col_nam, &col_start, &color, &col_end) == 2) {
4074 int col_len = col_end - col_start;
4079 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4087 (((color & 0xF000) *
4088 0x11000) | ((color & 0x0F00) *
4089 0x01100) | ((color &
4092 ((color & 0x000F) * 0x00011)
4096 color = (color << 8) + 0xff /* shift left by 8 */ ;
4101 rrd_set_error("the color format is #RRGGBB[AA]");
4104 if ((ci = grc_conv(col_nam)) != -1) {
4105 im->graph_col[ci] = gfx_hex_to_col(color);
4107 rrd_set_error("invalid color name '%s'", col_nam);
4111 rrd_set_error("invalid color def format");
4120 old_locale = setlocale(LC_NUMERIC, "C");
4121 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4122 int sindex, propidx;
4124 setlocale(LC_NUMERIC, old_locale);
4125 if ((sindex = text_prop_conv(prop)) != -1) {
4126 for (propidx = sindex;
4127 propidx < TEXT_PROP_LAST; propidx++) {
4129 im->text_prop[propidx].size = size;
4131 if ((int) strlen(optarg) > end) {
4132 if (optarg[end] == ':') {
4133 strncpy(im->text_prop[propidx].font,
4134 optarg + end + 1, 255);
4135 im->text_prop[propidx].font[255] = '\0';
4138 ("expected : after font size in '%s'",
4143 /* only run the for loop for DEFAULT (0) for
4144 all others, we break here. woodo programming */
4145 if (propidx == sindex && sindex != 0)
4149 rrd_set_error("invalid fonttag '%s'", prop);
4153 setlocale(LC_NUMERIC, old_locale);
4154 rrd_set_error("invalid text property format");
4160 old_locale = setlocale(LC_NUMERIC, "C");
4161 im->zoom = atof(optarg);
4162 setlocale(LC_NUMERIC, old_locale);
4163 if (im->zoom <= 0.0) {
4164 rrd_set_error("zoom factor must be > 0");
4169 strncpy(im->title, optarg, 150);
4170 im->title[150] = '\0';
4173 if (strcmp(optarg, "normal") == 0) {
4174 cairo_font_options_set_antialias
4175 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4176 cairo_font_options_set_hint_style
4177 (im->font_options, CAIRO_HINT_STYLE_FULL);
4178 } else if (strcmp(optarg, "light") == 0) {
4179 cairo_font_options_set_antialias
4180 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4181 cairo_font_options_set_hint_style
4182 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4183 } else if (strcmp(optarg, "mono") == 0) {
4184 cairo_font_options_set_antialias
4185 (im->font_options, CAIRO_ANTIALIAS_NONE);
4186 cairo_font_options_set_hint_style
4187 (im->font_options, CAIRO_HINT_STYLE_FULL);
4189 rrd_set_error("unknown font-render-mode '%s'", optarg);
4194 if (strcmp(optarg, "normal") == 0)
4195 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4196 else if (strcmp(optarg, "mono") == 0)
4197 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4199 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4204 /* not supported curently */
4207 strncpy(im->watermark, optarg, 100);
4208 im->watermark[99] = '\0';
4213 if (im->use_rrdcached)
4215 rrd_set_error ("You cannot specify --daemon "
4219 status = rrdc_connect (optarg);
4222 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4226 im->use_rrdcached = 1;
4231 rrd_set_error("unknown option '%c'", optopt);
4233 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4238 if (im->use_rrdcached == 0)
4242 temp = getenv (ENV_RRDCACHED_ADDRESS);
4247 status = rrdc_connect (temp);
4250 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4254 im->use_rrdcached = 1;
4258 if (im->logarithmic && im->minval <= 0) {
4260 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4264 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4265 /* error string is set in rrd_parsetime.c */
4269 if (start_tmp < 3600 * 24 * 365 * 10) {
4271 ("the first entry to fetch should be after 1980 (%ld)",
4276 if (end_tmp < start_tmp) {
4278 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4282 im->start = start_tmp;
4284 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4287 int rrd_graph_color(
4295 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4297 color = strstr(var, "#");
4298 if (color == NULL) {
4299 if (optional == 0) {
4300 rrd_set_error("Found no color in %s", err);
4307 long unsigned int col;
4309 rest = strstr(color, ":");
4316 sscanf(color, "#%6lx%n", &col, &n);
4317 col = (col << 8) + 0xff /* shift left by 8 */ ;
4319 rrd_set_error("Color problem in %s", err);
4322 sscanf(color, "#%8lx%n", &col, &n);
4326 rrd_set_error("Color problem in %s", err);
4328 if (rrd_test_error())
4330 gdp->col = gfx_hex_to_col(col);
4343 while (*ptr != '\0')
4344 if (*ptr++ == '%') {
4346 /* line cannot end with percent char */
4349 /* '%s', '%S' and '%%' are allowed */
4350 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4352 /* %c is allowed (but use only with vdef!) */
4353 else if (*ptr == 'c') {
4358 /* or else '% 6.2lf' and such are allowed */
4360 /* optional padding character */
4361 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4363 /* This should take care of 'm.n' with all three optional */
4364 while (*ptr >= '0' && *ptr <= '9')
4368 while (*ptr >= '0' && *ptr <= '9')
4370 /* Either 'le', 'lf' or 'lg' must follow here */
4373 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4388 const char *const str)
4390 /* A VDEF currently is either "func" or "param,func"
4391 * so the parsing is rather simple. Change if needed.
4399 old_locale = setlocale(LC_NUMERIC, "C");
4400 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4401 setlocale(LC_NUMERIC, old_locale);
4402 if (n == (int) strlen(str)) { /* matched */
4406 sscanf(str, "%29[A-Z]%n", func, &n);
4407 if (n == (int) strlen(str)) { /* matched */
4411 ("Unknown function string '%s' in VDEF '%s'",
4416 if (!strcmp("PERCENT", func))
4417 gdes->vf.op = VDEF_PERCENT;
4418 else if (!strcmp("MAXIMUM", func))
4419 gdes->vf.op = VDEF_MAXIMUM;
4420 else if (!strcmp("AVERAGE", func))
4421 gdes->vf.op = VDEF_AVERAGE;
4422 else if (!strcmp("STDEV", func))
4423 gdes->vf.op = VDEF_STDEV;
4424 else if (!strcmp("MINIMUM", func))
4425 gdes->vf.op = VDEF_MINIMUM;
4426 else if (!strcmp("TOTAL", func))
4427 gdes->vf.op = VDEF_TOTAL;
4428 else if (!strcmp("FIRST", func))
4429 gdes->vf.op = VDEF_FIRST;
4430 else if (!strcmp("LAST", func))
4431 gdes->vf.op = VDEF_LAST;
4432 else if (!strcmp("LSLSLOPE", func))
4433 gdes->vf.op = VDEF_LSLSLOPE;
4434 else if (!strcmp("LSLINT", func))
4435 gdes->vf.op = VDEF_LSLINT;
4436 else if (!strcmp("LSLCORREL", func))
4437 gdes->vf.op = VDEF_LSLCORREL;
4440 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4443 switch (gdes->vf.op) {
4445 if (isnan(param)) { /* no parameter given */
4447 ("Function '%s' needs parameter in VDEF '%s'\n",
4451 if (param >= 0.0 && param <= 100.0) {
4452 gdes->vf.param = param;
4453 gdes->vf.val = DNAN; /* undefined */
4454 gdes->vf.when = 0; /* undefined */
4457 ("Parameter '%f' out of range in VDEF '%s'\n",
4458 param, gdes->vname);
4471 case VDEF_LSLCORREL:
4473 gdes->vf.param = DNAN;
4474 gdes->vf.val = DNAN;
4478 ("Function '%s' needs no parameter in VDEF '%s'\n",
4492 graph_desc_t *src, *dst;
4497 dst = &im->gdes[gdi];
4498 src = &im->gdes[dst->vidx];
4499 data = src->data + src->ds;
4501 src->end_orig % (long)src->step ==
4502 0 ? src->end_orig : (src->end_orig + (long)src->step -
4503 src->end_orig % (long)src->step);
4505 steps = (end - src->start) / src->step;
4508 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4509 src->start, src->end_orig, steps);
4511 switch (dst->vf.op) {
4515 if ((array = malloc(steps * sizeof(double))) == NULL) {
4516 rrd_set_error("malloc VDEV_PERCENT");
4519 for (step = 0; step < steps; step++) {
4520 array[step] = data[step * src->ds_cnt];
4522 qsort(array, step, sizeof(double), vdef_percent_compar);
4523 field = (steps - 1) * dst->vf.param / 100;
4524 dst->vf.val = array[field];
4525 dst->vf.when = 0; /* no time component */
4528 for (step = 0; step < steps; step++)
4529 printf("DEBUG: %3li:%10.2f %c\n",
4530 step, array[step], step == field ? '*' : ' ');
4536 while (step != steps && isnan(data[step * src->ds_cnt]))
4538 if (step == steps) {
4542 dst->vf.val = data[step * src->ds_cnt];
4543 dst->vf.when = src->start + (step + 1) * src->step;
4545 while (step != steps) {
4546 if (finite(data[step * src->ds_cnt])) {
4547 if (data[step * src->ds_cnt] > dst->vf.val) {
4548 dst->vf.val = data[step * src->ds_cnt];
4549 dst->vf.when = src->start + (step + 1) * src->step;
4560 double average = 0.0;
4562 for (step = 0; step < steps; step++) {
4563 if (finite(data[step * src->ds_cnt])) {
4564 sum += data[step * src->ds_cnt];
4569 if (dst->vf.op == VDEF_TOTAL) {
4570 dst->vf.val = sum * src->step;
4571 dst->vf.when = 0; /* no time component */
4572 } else if (dst->vf.op == VDEF_AVERAGE) {
4573 dst->vf.val = sum / cnt;
4574 dst->vf.when = 0; /* no time component */
4576 average = sum / cnt;
4578 for (step = 0; step < steps; step++) {
4579 if (finite(data[step * src->ds_cnt])) {
4580 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4583 dst->vf.val = pow(sum / cnt, 0.5);
4584 dst->vf.when = 0; /* no time component */
4594 while (step != steps && isnan(data[step * src->ds_cnt]))
4596 if (step == steps) {
4600 dst->vf.val = data[step * src->ds_cnt];
4601 dst->vf.when = src->start + (step + 1) * src->step;
4603 while (step != steps) {
4604 if (finite(data[step * src->ds_cnt])) {
4605 if (data[step * src->ds_cnt] < dst->vf.val) {
4606 dst->vf.val = data[step * src->ds_cnt];
4607 dst->vf.when = src->start + (step + 1) * src->step;
4614 /* The time value returned here is one step before the
4615 * actual time value. This is the start of the first
4619 while (step != steps && isnan(data[step * src->ds_cnt]))
4621 if (step == steps) { /* all entries were NaN */
4625 dst->vf.val = data[step * src->ds_cnt];
4626 dst->vf.when = src->start + step * src->step;
4630 /* The time value returned here is the
4631 * actual time value. This is the end of the last
4635 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4637 if (step < 0) { /* all entries were NaN */
4641 dst->vf.val = data[step * src->ds_cnt];
4642 dst->vf.when = src->start + (step + 1) * src->step;
4647 case VDEF_LSLCORREL:{
4648 /* Bestfit line by linear least squares method */
4651 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4658 for (step = 0; step < steps; step++) {
4659 if (finite(data[step * src->ds_cnt])) {
4662 SUMxx += step * step;
4663 SUMxy += step * data[step * src->ds_cnt];
4664 SUMy += data[step * src->ds_cnt];
4665 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4669 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4670 y_intercept = (SUMy - slope * SUMx) / cnt;
4673 (SUMx * SUMy) / cnt) /
4675 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4677 if (dst->vf.op == VDEF_LSLSLOPE) {
4678 dst->vf.val = slope;
4680 } else if (dst->vf.op == VDEF_LSLINT) {
4681 dst->vf.val = y_intercept;
4683 } else if (dst->vf.op == VDEF_LSLCORREL) {
4684 dst->vf.val = correl;
4697 /* NaN < -INF < finite_values < INF */
4698 int vdef_percent_compar(
4704 /* Equality is not returned; this doesn't hurt except
4705 * (maybe) for a little performance.
4708 /* First catch NaN values. They are smallest */
4709 if (isnan(*(double *) a))
4711 if (isnan(*(double *) b))
4713 /* NaN doesn't reach this part so INF and -INF are extremes.
4714 * The sign from isinf() is compatible with the sign we return
4716 if (isinf(*(double *) a))
4717 return isinf(*(double *) a);
4718 if (isinf(*(double *) b))
4719 return isinf(*(double *) b);
4720 /* If we reach this, both values must be finite */
4721 if (*(double *) a < *(double *) b)
4730 rrd_info_type_t type,
4731 rrd_infoval_t value)
4733 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4734 if (im->grinfo == NULL) {
4735 im->grinfo = im->grinfo_current;