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)
2965 /* pull the data from the rrd files ... */
2966 if (data_fetch(im) == -1)
2968 /* evaluate VDEF and CDEF operations ... */
2969 if (data_calc(im) == -1)
2971 /* calculate and PRINT and GPRINT definitions. We have to do it at
2972 * this point because it will affect the length of the legends
2973 * if there are no graph elements we stop here ...
2974 * if we are lazy, try to quit ...
2979 if ((i == 0) || lazy)
2981 /**************************************************************
2982 *** Calculating sizes and locations became a bit confusing ***
2983 *** so I moved this into a separate function. ***
2984 **************************************************************/
2985 if (graph_size_location(im, i) == -1)
2988 info.u_cnt = im->xorigin;
2989 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2990 info.u_cnt = im->yorigin - im->ysize;
2991 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2992 info.u_cnt = im->xsize;
2993 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2994 info.u_cnt = im->ysize;
2995 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2996 info.u_cnt = im->ximg;
2997 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2998 info.u_cnt = im->yimg;
2999 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3001 /* get actual drawing data and find min and max values */
3002 if (data_proc(im) == -1)
3004 if (!im->logarithmic) {
3008 /* identify si magnitude Kilo, Mega Giga ? */
3009 if (!im->rigid && !im->logarithmic)
3010 expand_range(im); /* make sure the upper and lower limit are
3013 info.u_val = im->minval;
3014 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3015 info.u_val = im->maxval;
3016 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3018 if (!calc_horizontal_grid(im))
3023 apply_gridfit(im); */
3024 /* the actual graph is created by going through the individual
3025 graph elements and then drawing them */
3026 cairo_surface_destroy(im->surface);
3027 switch (im->imgformat) {
3030 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3031 im->ximg * im->zoom,
3032 im->yimg * im->zoom);
3036 im->surface = strlen(im->graphfile)
3037 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3038 im->yimg * im->zoom)
3039 : cairo_pdf_surface_create_for_stream
3040 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3044 im->surface = strlen(im->graphfile)
3046 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3047 im->yimg * im->zoom)
3048 : cairo_ps_surface_create_for_stream
3049 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3053 im->surface = strlen(im->graphfile)
3055 cairo_svg_surface_create(im->
3057 im->ximg * im->zoom, im->yimg * im->zoom)
3058 : cairo_svg_surface_create_for_stream
3059 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3060 cairo_svg_surface_restrict_to_version
3061 (im->surface, CAIRO_SVG_VERSION_1_1);
3064 im->cr = cairo_create(im->surface);
3065 cairo_set_antialias(im->cr, im->graph_antialias);
3066 cairo_scale(im->cr, im->zoom, im->zoom);
3067 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3068 gfx_new_area(im, 0, 0, 0, im->yimg,
3069 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3070 gfx_add_point(im, im->ximg, 0);
3072 gfx_new_area(im, im->xorigin,
3075 im->xsize, im->yorigin,
3078 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3079 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3081 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3082 im->xsize, im->ysize + 2.0);
3084 if (im->minval > 0.0)
3085 areazero = im->minval;
3086 if (im->maxval < 0.0)
3087 areazero = im->maxval;
3088 for (i = 0; i < im->gdes_c; i++) {
3089 switch (im->gdes[i].gf) {
3103 for (ii = 0; ii < im->xsize; ii++) {
3104 if (!isnan(im->gdes[i].p_data[ii])
3105 && im->gdes[i].p_data[ii] != 0.0) {
3106 if (im->gdes[i].yrule > 0) {
3113 im->ysize, 1.0, im->gdes[i].col);
3114 } else if (im->gdes[i].yrule < 0) {
3117 im->yorigin - im->ysize,
3122 im->ysize, 1.0, im->gdes[i].col);
3129 /* fix data points at oo and -oo */
3130 for (ii = 0; ii < im->xsize; ii++) {
3131 if (isinf(im->gdes[i].p_data[ii])) {
3132 if (im->gdes[i].p_data[ii] > 0) {
3133 im->gdes[i].p_data[ii] = im->maxval;
3135 im->gdes[i].p_data[ii] = im->minval;
3141 /* *******************************************************
3146 -------|--t-1--t--------------------------------
3148 if we know the value at time t was a then
3149 we draw a square from t-1 to t with the value a.
3151 ********************************************************* */
3152 if (im->gdes[i].col.alpha != 0.0) {
3153 /* GF_LINE and friend */
3154 if (im->gdes[i].gf == GF_LINE) {
3155 double last_y = 0.0;
3159 cairo_new_path(im->cr);
3160 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3161 if (im->gdes[i].dash) {
3162 cairo_set_dash(im->cr,
3163 im->gdes[i].p_dashes,
3164 im->gdes[i].ndash, im->gdes[i].offset);
3167 for (ii = 1; ii < im->xsize; ii++) {
3168 if (isnan(im->gdes[i].p_data[ii])
3169 || (im->slopemode == 1
3170 && isnan(im->gdes[i].p_data[ii - 1]))) {
3175 last_y = ytr(im, im->gdes[i].p_data[ii]);
3176 if (im->slopemode == 0) {
3177 double x = ii - 1 + im->xorigin;
3180 gfx_line_fit(im, &x, &y);
3181 cairo_move_to(im->cr, x, y);
3182 x = ii + im->xorigin;
3184 gfx_line_fit(im, &x, &y);
3185 cairo_line_to(im->cr, x, y);
3187 double x = ii - 1 + im->xorigin;
3189 ytr(im, im->gdes[i].p_data[ii - 1]);
3190 gfx_line_fit(im, &x, &y);
3191 cairo_move_to(im->cr, x, y);
3192 x = ii + im->xorigin;
3194 gfx_line_fit(im, &x, &y);
3195 cairo_line_to(im->cr, x, y);
3199 double x1 = ii + im->xorigin;
3200 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3202 if (im->slopemode == 0
3203 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3204 double x = ii - 1 + im->xorigin;
3207 gfx_line_fit(im, &x, &y);
3208 cairo_line_to(im->cr, x, y);
3211 gfx_line_fit(im, &x1, &y1);
3212 cairo_line_to(im->cr, x1, y1);
3215 cairo_set_source_rgba(im->cr,
3221 col.blue, im->gdes[i].col.alpha);
3222 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3223 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3224 cairo_stroke(im->cr);
3225 cairo_restore(im->cr);
3229 (double *) malloc(sizeof(double) * im->xsize * 2);
3231 (double *) malloc(sizeof(double) * im->xsize * 2);
3233 (double *) malloc(sizeof(double) * im->xsize * 2);
3235 (double *) malloc(sizeof(double) * im->xsize * 2);
3238 for (ii = 0; ii <= im->xsize; ii++) {
3241 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3247 AlmostEqual2sComplement(foreY
3251 AlmostEqual2sComplement(foreY
3261 foreY[cntI], im->gdes[i].col);
3262 while (cntI < idxI) {
3267 AlmostEqual2sComplement(foreY
3271 AlmostEqual2sComplement(foreY
3278 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3280 gfx_add_point(im, backX[idxI], backY[idxI]);
3286 AlmostEqual2sComplement(backY
3290 AlmostEqual2sComplement(backY
3297 gfx_add_point(im, backX[idxI], backY[idxI]);
3307 if (ii == im->xsize)
3309 if (im->slopemode == 0 && ii == 0) {
3312 if (isnan(im->gdes[i].p_data[ii])) {
3316 ytop = ytr(im, im->gdes[i].p_data[ii]);
3317 if (lastgdes && im->gdes[i].stack) {
3318 ybase = ytr(im, lastgdes->p_data[ii]);
3320 ybase = ytr(im, areazero);
3322 if (ybase == ytop) {
3328 double extra = ytop;
3333 if (im->slopemode == 0) {
3334 backY[++idxI] = ybase - 0.2;
3335 backX[idxI] = ii + im->xorigin - 1;
3336 foreY[idxI] = ytop + 0.2;
3337 foreX[idxI] = ii + im->xorigin - 1;
3339 backY[++idxI] = ybase - 0.2;
3340 backX[idxI] = ii + im->xorigin;
3341 foreY[idxI] = ytop + 0.2;
3342 foreX[idxI] = ii + im->xorigin;
3344 /* close up any remaining area */
3349 } /* else GF_LINE */
3351 /* if color != 0x0 */
3352 /* make sure we do not run into trouble when stacking on NaN */
3353 for (ii = 0; ii < im->xsize; ii++) {
3354 if (isnan(im->gdes[i].p_data[ii])) {
3355 if (lastgdes && (im->gdes[i].stack)) {
3356 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3358 im->gdes[i].p_data[ii] = areazero;
3362 lastgdes = &(im->gdes[i]);
3366 ("STACK should already be turned into LINE or AREA here");
3371 cairo_reset_clip(im->cr);
3373 /* grid_paint also does the text */
3374 if (!(im->extra_flags & ONLY_GRAPH))
3376 if (!(im->extra_flags & ONLY_GRAPH))
3378 /* the RULES are the last thing to paint ... */
3379 for (i = 0; i < im->gdes_c; i++) {
3381 switch (im->gdes[i].gf) {
3383 if (im->gdes[i].yrule >= im->minval
3384 && im->gdes[i].yrule <= im->maxval) {
3386 if (im->gdes[i].dash) {
3387 cairo_set_dash(im->cr,
3388 im->gdes[i].p_dashes,
3389 im->gdes[i].ndash, im->gdes[i].offset);
3391 gfx_line(im, im->xorigin,
3392 ytr(im, im->gdes[i].yrule),
3393 im->xorigin + im->xsize,
3394 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3395 cairo_stroke(im->cr);
3396 cairo_restore(im->cr);
3400 if (im->gdes[i].xrule >= im->start
3401 && im->gdes[i].xrule <= im->end) {
3403 if (im->gdes[i].dash) {
3404 cairo_set_dash(im->cr,
3405 im->gdes[i].p_dashes,
3406 im->gdes[i].ndash, im->gdes[i].offset);
3409 xtr(im, im->gdes[i].xrule),
3410 im->yorigin, xtr(im,
3414 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3415 cairo_stroke(im->cr);
3416 cairo_restore(im->cr);
3425 switch (im->imgformat) {
3428 cairo_status_t status;
3430 status = strlen(im->graphfile) ?
3431 cairo_surface_write_to_png(im->surface, im->graphfile)
3432 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3435 if (status != CAIRO_STATUS_SUCCESS) {
3436 rrd_set_error("Could not save png to '%s'", im->graphfile);
3442 if (strlen(im->graphfile)) {
3443 cairo_show_page(im->cr);
3445 cairo_surface_finish(im->surface);
3454 /*****************************************************
3456 *****************************************************/
3463 if ((im->gdes = (graph_desc_t *)
3464 rrd_realloc(im->gdes, (im->gdes_c)
3465 * sizeof(graph_desc_t))) == NULL) {
3466 rrd_set_error("realloc graph_descs");
3471 im->gdes[im->gdes_c - 1].step = im->step;
3472 im->gdes[im->gdes_c - 1].step_orig = im->step;
3473 im->gdes[im->gdes_c - 1].stack = 0;
3474 im->gdes[im->gdes_c - 1].linewidth = 0;
3475 im->gdes[im->gdes_c - 1].debug = 0;
3476 im->gdes[im->gdes_c - 1].start = im->start;
3477 im->gdes[im->gdes_c - 1].start_orig = im->start;
3478 im->gdes[im->gdes_c - 1].end = im->end;
3479 im->gdes[im->gdes_c - 1].end_orig = im->end;
3480 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3481 im->gdes[im->gdes_c - 1].data = NULL;
3482 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3483 im->gdes[im->gdes_c - 1].data_first = 0;
3484 im->gdes[im->gdes_c - 1].p_data = NULL;
3485 im->gdes[im->gdes_c - 1].rpnp = NULL;
3486 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3487 im->gdes[im->gdes_c - 1].shift = 0.0;
3488 im->gdes[im->gdes_c - 1].dash = 0;
3489 im->gdes[im->gdes_c - 1].ndash = 0;
3490 im->gdes[im->gdes_c - 1].offset = 0;
3491 im->gdes[im->gdes_c - 1].col.red = 0.0;
3492 im->gdes[im->gdes_c - 1].col.green = 0.0;
3493 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3494 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3495 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3496 im->gdes[im->gdes_c - 1].format[0] = '\0';
3497 im->gdes[im->gdes_c - 1].strftm = 0;
3498 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3499 im->gdes[im->gdes_c - 1].ds = -1;
3500 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3501 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3502 im->gdes[im->gdes_c - 1].yrule = DNAN;
3503 im->gdes[im->gdes_c - 1].xrule = 0;
3507 /* copies input untill the first unescaped colon is found
3508 or until input ends. backslashes have to be escaped as well */
3510 const char *const input,
3516 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3517 if (input[inp] == '\\'
3518 && input[inp + 1] != '\0'
3519 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3520 output[outp++] = input[++inp];
3522 output[outp++] = input[inp];
3525 output[outp] = '\0';
3529 /* Now just a wrapper around rrd_graph_v */
3541 rrd_info_t *grinfo = NULL;
3544 grinfo = rrd_graph_v(argc, argv);
3550 if (strcmp(walker->key, "image_info") == 0) {
3553 rrd_realloc((*prdata),
3554 (prlines + 1) * sizeof(char *))) == NULL) {
3555 rrd_set_error("realloc prdata");
3558 /* imginfo goes to position 0 in the prdata array */
3559 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3560 + 2) * sizeof(char));
3561 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3562 (*prdata)[prlines] = NULL;
3564 /* skip anything else */
3565 walker = walker->next;
3569 if (strcmp(walker->key, "image_width") == 0) {
3570 *xsize = walker->value.u_int;
3571 } else if (strcmp(walker->key, "image_height") == 0) {
3572 *ysize = walker->value.u_int;
3573 } else if (strcmp(walker->key, "value_min") == 0) {
3574 *ymin = walker->value.u_val;
3575 } else if (strcmp(walker->key, "value_max") == 0) {
3576 *ymax = walker->value.u_val;
3577 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3580 rrd_realloc((*prdata),
3581 (prlines + 1) * sizeof(char *))) == NULL) {
3582 rrd_set_error("realloc prdata");
3585 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3586 + 2) * sizeof(char));
3587 (*prdata)[prlines] = NULL;
3588 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3589 } else if (strcmp(walker->key, "image") == 0) {
3590 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3591 (stream ? stream : stdout));
3593 /* skip anything else */
3594 walker = walker->next;
3596 rrd_info_free(grinfo);
3601 /* Some surgery done on this function, it became ridiculously big.
3603 ** - initializing now in rrd_graph_init()
3604 ** - options parsing now in rrd_graph_options()
3605 ** - script parsing now in rrd_graph_script()
3607 rrd_info_t *rrd_graph_v(
3614 rrd_graph_init(&im);
3615 /* a dummy surface so that we can measure text sizes for placements */
3616 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3617 im.cr = cairo_create(im.surface);
3618 rrd_graph_options(argc, argv, &im);
3619 if (rrd_test_error()) {
3620 rrd_info_free(im.grinfo);
3625 if (optind >= argc) {
3626 rrd_info_free(im.grinfo);
3628 rrd_set_error("missing filename");
3632 if (strlen(argv[optind]) >= MAXPATH) {
3633 rrd_set_error("filename (including path) too long");
3634 rrd_info_free(im.grinfo);
3639 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3640 im.graphfile[MAXPATH - 1] = '\0';
3642 if (strcmp(im.graphfile, "-") == 0) {
3643 im.graphfile[0] = '\0';
3646 rrd_graph_script(argc, argv, &im, 1);
3647 if (rrd_test_error()) {
3648 rrd_info_free(im.grinfo);
3653 /* Everything is now read and the actual work can start */
3655 if (graph_paint(&im) == -1) {
3656 rrd_info_free(im.grinfo);
3662 /* The image is generated and needs to be output.
3663 ** Also, if needed, print a line with information about the image.
3670 sprintf_alloc(im.imginfo,
3673 im.ximg), (long) (im.zoom * im.yimg));
3674 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3677 if (im.rendered_image) {
3680 img.u_blo.size = im.rendered_image_size;
3681 img.u_blo.ptr = im.rendered_image;
3682 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3689 void rrd_graph_init(
3698 #ifdef HAVE_SETLOCALE
3699 setlocale(LC_TIME, "");
3700 #ifdef HAVE_MBSTOWCS
3701 setlocale(LC_CTYPE, "");
3706 im->draw_x_grid = 1;
3707 im->draw_y_grid = 1;
3708 im->extra_flags = 0;
3709 im->font_options = cairo_font_options_create();
3710 im->forceleftspace = 0;
3713 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3714 im->grid_dash_off = 1;
3715 im->grid_dash_on = 1;
3717 im->grinfo = (rrd_info_t *) NULL;
3718 im->grinfo_current = (rrd_info_t *) NULL;
3719 im->imgformat = IF_PNG;
3721 im->use_rrdcached = 0;
3723 im->logarithmic = 0;
3729 im->rendered_image_size = 0;
3730 im->rendered_image = NULL;
3735 im->tabwidth = 40.0;
3736 im->title[0] = '\0';
3737 im->unitsexponent = 9999;
3738 im->unitslength = 6;
3739 im->viewfactor = 1.0;
3740 im->watermark[0] = '\0';
3741 im->with_markup = 0;
3743 im->xlab_user.minsec = -1;
3746 im->ygridstep = DNAN;
3748 im->ylegend[0] = '\0';
3752 cairo_font_options_set_hint_style
3753 (im->font_options, CAIRO_HINT_STYLE_FULL);
3754 cairo_font_options_set_hint_metrics
3755 (im->font_options, CAIRO_HINT_METRICS_ON);
3756 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3757 for (i = 0; i < DIM(graph_col); i++)
3758 im->graph_col[i] = graph_col[i];
3762 deffont = getenv("RRD_DEFAULT_FONT");
3763 if (deffont != NULL) {
3764 for (i = 0; i < DIM(text_prop); i++) {
3765 strncpy(text_prop[i].font, deffont,
3766 sizeof(text_prop[i].font) - 1);
3767 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3771 for (i = 0; i < DIM(text_prop); i++) {
3772 im->text_prop[i].size = text_prop[i].size;
3773 strcpy(im->text_prop[i].font, text_prop[i].font);
3777 void rrd_graph_options(
3784 char *parsetime_error = NULL;
3785 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3786 time_t start_tmp = 0, end_tmp = 0;
3788 rrd_time_value_t start_tv, end_tv;
3789 long unsigned int color;
3790 char *old_locale = "";
3792 /* defines for long options without a short equivalent. should be bytes,
3793 and may not collide with (the ASCII value of) short options */
3794 #define LONGOPT_UNITS_SI 255
3797 struct option long_options[] = {
3798 { "start", required_argument, 0, 's'},
3799 { "end", required_argument, 0, 'e'},
3800 { "x-grid", required_argument, 0, 'x'},
3801 { "y-grid", required_argument, 0, 'y'},
3802 { "vertical-label", required_argument, 0, 'v'},
3803 { "width", required_argument, 0, 'w'},
3804 { "height", required_argument, 0, 'h'},
3805 { "full-size-mode", no_argument, 0, 'D'},
3806 { "interlaced", no_argument, 0, 'i'},
3807 { "upper-limit", required_argument, 0, 'u'},
3808 { "lower-limit", required_argument, 0, 'l'},
3809 { "rigid", no_argument, 0, 'r'},
3810 { "base", required_argument, 0, 'b'},
3811 { "logarithmic", no_argument, 0, 'o'},
3812 { "color", required_argument, 0, 'c'},
3813 { "font", required_argument, 0, 'n'},
3814 { "title", required_argument, 0, 't'},
3815 { "imginfo", required_argument, 0, 'f'},
3816 { "imgformat", required_argument, 0, 'a'},
3817 { "lazy", no_argument, 0, 'z'},
3818 { "zoom", required_argument, 0, 'm'},
3819 { "no-legend", no_argument, 0, 'g'},
3820 { "force-rules-legend", no_argument, 0, 'F'},
3821 { "only-graph", no_argument, 0, 'j'},
3822 { "alt-y-grid", no_argument, 0, 'Y'},
3823 { "no-minor", no_argument, 0, 'I'},
3824 { "slope-mode", no_argument, 0, 'E'},
3825 { "alt-autoscale", no_argument, 0, 'A'},
3826 { "alt-autoscale-min", no_argument, 0, 'J'},
3827 { "alt-autoscale-max", no_argument, 0, 'M'},
3828 { "no-gridfit", no_argument, 0, 'N'},
3829 { "units-exponent", required_argument, 0, 'X'},
3830 { "units-length", required_argument, 0, 'L'},
3831 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3832 { "step", required_argument, 0, 'S'},
3833 { "tabwidth", required_argument, 0, 'T'},
3834 { "font-render-mode", required_argument, 0, 'R'},
3835 { "graph-render-mode", required_argument, 0, 'G'},
3836 { "font-smoothing-threshold", required_argument, 0, 'B'},
3837 { "watermark", required_argument, 0, 'W'},
3838 { "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 */
3839 { "pango-markup", no_argument, 0, 'P'},
3840 { "daemon", required_argument, 0, 'd'},
3846 opterr = 0; /* initialize getopt */
3847 rrd_parsetime("end-24h", &start_tv);
3848 rrd_parsetime("now", &end_tv);
3850 int option_index = 0;
3852 int col_start, col_end;
3854 opt = getopt_long(argc, argv,
3855 "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:",
3856 long_options, &option_index);
3861 im->extra_flags |= NOMINOR;
3864 im->extra_flags |= ALTYGRID;
3867 im->extra_flags |= ALTAUTOSCALE;
3870 im->extra_flags |= ALTAUTOSCALE_MIN;
3873 im->extra_flags |= ALTAUTOSCALE_MAX;
3876 im->extra_flags |= ONLY_GRAPH;
3879 im->extra_flags |= NOLEGEND;
3882 im->extra_flags |= FORCE_RULES_LEGEND;
3884 case LONGOPT_UNITS_SI:
3885 if (im->extra_flags & FORCE_UNITS) {
3886 rrd_set_error("--units can only be used once!");
3887 setlocale(LC_NUMERIC, old_locale);
3890 if (strcmp(optarg, "si") == 0)
3891 im->extra_flags |= FORCE_UNITS_SI;
3893 rrd_set_error("invalid argument for --units: %s", optarg);
3898 im->unitsexponent = atoi(optarg);
3901 im->unitslength = atoi(optarg);
3902 im->forceleftspace = 1;
3905 old_locale = setlocale(LC_NUMERIC, "C");
3906 im->tabwidth = atof(optarg);
3907 setlocale(LC_NUMERIC, old_locale);
3910 old_locale = setlocale(LC_NUMERIC, "C");
3911 im->step = atoi(optarg);
3912 setlocale(LC_NUMERIC, old_locale);
3918 im->with_markup = 1;
3921 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3922 rrd_set_error("start time: %s", parsetime_error);
3927 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3928 rrd_set_error("end time: %s", parsetime_error);
3933 if (strcmp(optarg, "none") == 0) {
3934 im->draw_x_grid = 0;
3938 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3940 &im->xlab_user.gridst,
3942 &im->xlab_user.mgridst,
3944 &im->xlab_user.labst,
3945 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3946 strncpy(im->xlab_form, optarg + stroff,
3947 sizeof(im->xlab_form) - 1);
3948 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3950 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3951 rrd_set_error("unknown keyword %s", scan_gtm);
3954 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3956 rrd_set_error("unknown keyword %s", scan_mtm);
3959 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
3960 rrd_set_error("unknown keyword %s", scan_ltm);
3963 im->xlab_user.minsec = 1;
3964 im->xlab_user.stst = im->xlab_form;
3966 rrd_set_error("invalid x-grid format");
3972 if (strcmp(optarg, "none") == 0) {
3973 im->draw_y_grid = 0;
3976 old_locale = setlocale(LC_NUMERIC, "C");
3977 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3978 setlocale(LC_NUMERIC, old_locale);
3979 if (im->ygridstep <= 0) {
3980 rrd_set_error("grid step must be > 0");
3982 } else if (im->ylabfact < 1) {
3983 rrd_set_error("label factor must be > 0");
3987 setlocale(LC_NUMERIC, old_locale);
3988 rrd_set_error("invalid y-grid format");
3993 strncpy(im->ylegend, optarg, 150);
3994 im->ylegend[150] = '\0';
3997 old_locale = setlocale(LC_NUMERIC, "C");
3998 im->maxval = atof(optarg);
3999 setlocale(LC_NUMERIC, old_locale);
4002 old_locale = setlocale(LC_NUMERIC, "C");
4003 im->minval = atof(optarg);
4004 setlocale(LC_NUMERIC, old_locale);
4007 im->base = atol(optarg);
4008 if (im->base != 1024 && im->base != 1000) {
4010 ("the only sensible value for base apart from 1000 is 1024");
4015 long_tmp = atol(optarg);
4016 if (long_tmp < 10) {
4017 rrd_set_error("width below 10 pixels");
4020 im->xsize = long_tmp;
4023 long_tmp = atol(optarg);
4024 if (long_tmp < 10) {
4025 rrd_set_error("height below 10 pixels");
4028 im->ysize = long_tmp;
4031 im->extra_flags |= FULL_SIZE_MODE;
4034 /* interlaced png not supported at the moment */
4040 im->imginfo = optarg;
4044 (im->imgformat = if_conv(optarg)) == -1) {
4045 rrd_set_error("unsupported graphics format '%s'", optarg);
4056 im->logarithmic = 1;
4060 "%10[A-Z]#%n%8lx%n",
4061 col_nam, &col_start, &color, &col_end) == 2) {
4063 int col_len = col_end - col_start;
4068 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4076 (((color & 0xF000) *
4077 0x11000) | ((color & 0x0F00) *
4078 0x01100) | ((color &
4081 ((color & 0x000F) * 0x00011)
4085 color = (color << 8) + 0xff /* shift left by 8 */ ;
4090 rrd_set_error("the color format is #RRGGBB[AA]");
4093 if ((ci = grc_conv(col_nam)) != -1) {
4094 im->graph_col[ci] = gfx_hex_to_col(color);
4096 rrd_set_error("invalid color name '%s'", col_nam);
4100 rrd_set_error("invalid color def format");
4109 old_locale = setlocale(LC_NUMERIC, "C");
4110 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4111 int sindex, propidx;
4113 setlocale(LC_NUMERIC, old_locale);
4114 if ((sindex = text_prop_conv(prop)) != -1) {
4115 for (propidx = sindex;
4116 propidx < TEXT_PROP_LAST; propidx++) {
4118 im->text_prop[propidx].size = size;
4120 if ((int) strlen(optarg) > end) {
4121 if (optarg[end] == ':') {
4122 strncpy(im->text_prop[propidx].font,
4123 optarg + end + 1, 255);
4124 im->text_prop[propidx].font[255] = '\0';
4127 ("expected : after font size in '%s'",
4132 /* only run the for loop for DEFAULT (0) for
4133 all others, we break here. woodo programming */
4134 if (propidx == sindex && sindex != 0)
4138 rrd_set_error("invalid fonttag '%s'", prop);
4142 setlocale(LC_NUMERIC, old_locale);
4143 rrd_set_error("invalid text property format");
4149 old_locale = setlocale(LC_NUMERIC, "C");
4150 im->zoom = atof(optarg);
4151 setlocale(LC_NUMERIC, old_locale);
4152 if (im->zoom <= 0.0) {
4153 rrd_set_error("zoom factor must be > 0");
4158 strncpy(im->title, optarg, 150);
4159 im->title[150] = '\0';
4162 if (strcmp(optarg, "normal") == 0) {
4163 cairo_font_options_set_antialias
4164 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4165 cairo_font_options_set_hint_style
4166 (im->font_options, CAIRO_HINT_STYLE_FULL);
4167 } else if (strcmp(optarg, "light") == 0) {
4168 cairo_font_options_set_antialias
4169 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4170 cairo_font_options_set_hint_style
4171 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4172 } else if (strcmp(optarg, "mono") == 0) {
4173 cairo_font_options_set_antialias
4174 (im->font_options, CAIRO_ANTIALIAS_NONE);
4175 cairo_font_options_set_hint_style
4176 (im->font_options, CAIRO_HINT_STYLE_FULL);
4178 rrd_set_error("unknown font-render-mode '%s'", optarg);
4183 if (strcmp(optarg, "normal") == 0)
4184 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4185 else if (strcmp(optarg, "mono") == 0)
4186 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4188 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4193 /* not supported curently */
4196 strncpy(im->watermark, optarg, 100);
4197 im->watermark[99] = '\0';
4202 if (im->use_rrdcached)
4204 rrd_set_error ("You cannot specify --daemon "
4208 status = rrdc_connect (optarg);
4211 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4215 im->use_rrdcached = 1;
4220 rrd_set_error("unknown option '%c'", optopt);
4222 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4227 if (im->logarithmic && im->minval <= 0) {
4229 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4233 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4234 /* error string is set in rrd_parsetime.c */
4238 if (start_tmp < 3600 * 24 * 365 * 10) {
4240 ("the first entry to fetch should be after 1980 (%ld)",
4245 if (end_tmp < start_tmp) {
4247 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4251 im->start = start_tmp;
4253 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4256 int rrd_graph_color(
4264 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4266 color = strstr(var, "#");
4267 if (color == NULL) {
4268 if (optional == 0) {
4269 rrd_set_error("Found no color in %s", err);
4276 long unsigned int col;
4278 rest = strstr(color, ":");
4285 sscanf(color, "#%6lx%n", &col, &n);
4286 col = (col << 8) + 0xff /* shift left by 8 */ ;
4288 rrd_set_error("Color problem in %s", err);
4291 sscanf(color, "#%8lx%n", &col, &n);
4295 rrd_set_error("Color problem in %s", err);
4297 if (rrd_test_error())
4299 gdp->col = gfx_hex_to_col(col);
4312 while (*ptr != '\0')
4313 if (*ptr++ == '%') {
4315 /* line cannot end with percent char */
4318 /* '%s', '%S' and '%%' are allowed */
4319 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4321 /* %c is allowed (but use only with vdef!) */
4322 else if (*ptr == 'c') {
4327 /* or else '% 6.2lf' and such are allowed */
4329 /* optional padding character */
4330 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4332 /* This should take care of 'm.n' with all three optional */
4333 while (*ptr >= '0' && *ptr <= '9')
4337 while (*ptr >= '0' && *ptr <= '9')
4339 /* Either 'le', 'lf' or 'lg' must follow here */
4342 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4357 const char *const str)
4359 /* A VDEF currently is either "func" or "param,func"
4360 * so the parsing is rather simple. Change if needed.
4368 old_locale = setlocale(LC_NUMERIC, "C");
4369 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4370 setlocale(LC_NUMERIC, old_locale);
4371 if (n == (int) strlen(str)) { /* matched */
4375 sscanf(str, "%29[A-Z]%n", func, &n);
4376 if (n == (int) strlen(str)) { /* matched */
4380 ("Unknown function string '%s' in VDEF '%s'",
4385 if (!strcmp("PERCENT", func))
4386 gdes->vf.op = VDEF_PERCENT;
4387 else if (!strcmp("MAXIMUM", func))
4388 gdes->vf.op = VDEF_MAXIMUM;
4389 else if (!strcmp("AVERAGE", func))
4390 gdes->vf.op = VDEF_AVERAGE;
4391 else if (!strcmp("STDEV", func))
4392 gdes->vf.op = VDEF_STDEV;
4393 else if (!strcmp("MINIMUM", func))
4394 gdes->vf.op = VDEF_MINIMUM;
4395 else if (!strcmp("TOTAL", func))
4396 gdes->vf.op = VDEF_TOTAL;
4397 else if (!strcmp("FIRST", func))
4398 gdes->vf.op = VDEF_FIRST;
4399 else if (!strcmp("LAST", func))
4400 gdes->vf.op = VDEF_LAST;
4401 else if (!strcmp("LSLSLOPE", func))
4402 gdes->vf.op = VDEF_LSLSLOPE;
4403 else if (!strcmp("LSLINT", func))
4404 gdes->vf.op = VDEF_LSLINT;
4405 else if (!strcmp("LSLCORREL", func))
4406 gdes->vf.op = VDEF_LSLCORREL;
4409 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4412 switch (gdes->vf.op) {
4414 if (isnan(param)) { /* no parameter given */
4416 ("Function '%s' needs parameter in VDEF '%s'\n",
4420 if (param >= 0.0 && param <= 100.0) {
4421 gdes->vf.param = param;
4422 gdes->vf.val = DNAN; /* undefined */
4423 gdes->vf.when = 0; /* undefined */
4426 ("Parameter '%f' out of range in VDEF '%s'\n",
4427 param, gdes->vname);
4440 case VDEF_LSLCORREL:
4442 gdes->vf.param = DNAN;
4443 gdes->vf.val = DNAN;
4447 ("Function '%s' needs no parameter in VDEF '%s'\n",
4461 graph_desc_t *src, *dst;
4466 dst = &im->gdes[gdi];
4467 src = &im->gdes[dst->vidx];
4468 data = src->data + src->ds;
4470 src->end_orig % src->step ==
4471 0 ? src->end_orig : (src->end_orig + src->step -
4472 src->end_orig % src->step);
4474 steps = (end - src->start) / src->step;
4477 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4478 src->start, src->end_orig, steps);
4480 switch (dst->vf.op) {
4484 if ((array = malloc(steps * sizeof(double))) == NULL) {
4485 rrd_set_error("malloc VDEV_PERCENT");
4488 for (step = 0; step < steps; step++) {
4489 array[step] = data[step * src->ds_cnt];
4491 qsort(array, step, sizeof(double), vdef_percent_compar);
4492 field = (steps - 1) * dst->vf.param / 100;
4493 dst->vf.val = array[field];
4494 dst->vf.when = 0; /* no time component */
4497 for (step = 0; step < steps; step++)
4498 printf("DEBUG: %3li:%10.2f %c\n",
4499 step, array[step], step == field ? '*' : ' ');
4505 while (step != steps && isnan(data[step * src->ds_cnt]))
4507 if (step == steps) {
4511 dst->vf.val = data[step * src->ds_cnt];
4512 dst->vf.when = src->start + (step + 1) * src->step;
4514 while (step != steps) {
4515 if (finite(data[step * src->ds_cnt])) {
4516 if (data[step * src->ds_cnt] > dst->vf.val) {
4517 dst->vf.val = data[step * src->ds_cnt];
4518 dst->vf.when = src->start + (step + 1) * src->step;
4529 double average = 0.0;
4531 for (step = 0; step < steps; step++) {
4532 if (finite(data[step * src->ds_cnt])) {
4533 sum += data[step * src->ds_cnt];
4538 if (dst->vf.op == VDEF_TOTAL) {
4539 dst->vf.val = sum * src->step;
4540 dst->vf.when = 0; /* no time component */
4541 } else if (dst->vf.op == VDEF_AVERAGE) {
4542 dst->vf.val = sum / cnt;
4543 dst->vf.when = 0; /* no time component */
4545 average = sum / cnt;
4547 for (step = 0; step < steps; step++) {
4548 if (finite(data[step * src->ds_cnt])) {
4549 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4552 dst->vf.val = pow(sum / cnt, 0.5);
4553 dst->vf.when = 0; /* no time component */
4563 while (step != steps && isnan(data[step * src->ds_cnt]))
4565 if (step == steps) {
4569 dst->vf.val = data[step * src->ds_cnt];
4570 dst->vf.when = src->start + (step + 1) * src->step;
4572 while (step != steps) {
4573 if (finite(data[step * src->ds_cnt])) {
4574 if (data[step * src->ds_cnt] < dst->vf.val) {
4575 dst->vf.val = data[step * src->ds_cnt];
4576 dst->vf.when = src->start + (step + 1) * src->step;
4583 /* The time value returned here is one step before the
4584 * actual time value. This is the start of the first
4588 while (step != steps && isnan(data[step * src->ds_cnt]))
4590 if (step == steps) { /* all entries were NaN */
4594 dst->vf.val = data[step * src->ds_cnt];
4595 dst->vf.when = src->start + step * src->step;
4599 /* The time value returned here is the
4600 * actual time value. This is the end of the last
4604 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4606 if (step < 0) { /* all entries were NaN */
4610 dst->vf.val = data[step * src->ds_cnt];
4611 dst->vf.when = src->start + (step + 1) * src->step;
4616 case VDEF_LSLCORREL:{
4617 /* Bestfit line by linear least squares method */
4620 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4627 for (step = 0; step < steps; step++) {
4628 if (finite(data[step * src->ds_cnt])) {
4631 SUMxx += step * step;
4632 SUMxy += step * data[step * src->ds_cnt];
4633 SUMy += data[step * src->ds_cnt];
4634 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4638 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4639 y_intercept = (SUMy - slope * SUMx) / cnt;
4642 (SUMx * SUMy) / cnt) /
4644 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4646 if (dst->vf.op == VDEF_LSLSLOPE) {
4647 dst->vf.val = slope;
4649 } else if (dst->vf.op == VDEF_LSLINT) {
4650 dst->vf.val = y_intercept;
4652 } else if (dst->vf.op == VDEF_LSLCORREL) {
4653 dst->vf.val = correl;
4666 /* NaN < -INF < finite_values < INF */
4667 int vdef_percent_compar(
4673 /* Equality is not returned; this doesn't hurt except
4674 * (maybe) for a little performance.
4677 /* First catch NaN values. They are smallest */
4678 if (isnan(*(double *) a))
4680 if (isnan(*(double *) b))
4682 /* NaN doesn't reach this part so INF and -INF are extremes.
4683 * The sign from isinf() is compatible with the sign we return
4685 if (isinf(*(double *) a))
4686 return isinf(*(double *) a);
4687 if (isinf(*(double *) b))
4688 return isinf(*(double *) b);
4689 /* If we reach this, both values must be finite */
4690 if (*(double *) a < *(double *) b)
4699 rrd_info_type_t type,
4700 rrd_infoval_t value)
4702 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4703 if (im->grinfo == NULL) {
4704 im->grinfo = im->grinfo_current;