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 */
836 * - a connection to the daemon has been established
837 * - this is the first occurrence of that RRD file
839 if (im->use_rrdcached)
844 for (ii = 0; ii < i; ii++)
846 if (strcmp (im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
855 status = rrdc_flush (im->gdes[i].rrd);
858 rrd_set_error ("rrdc_flush (%s) failed with status %i.",
859 im->gdes[i].rrd, status);
863 } /* if (im->use_rrdcached) */
865 if ((rrd_fetch_fn(im->gdes[i].rrd,
871 &im->gdes[i].ds_namv,
872 &im->gdes[i].data)) == -1) {
875 im->gdes[i].data_first = 1;
877 if (ft_step < im->gdes[i].step) {
878 reduce_data(im->gdes[i].cf_reduce,
883 &im->gdes[i].ds_cnt, &im->gdes[i].data);
885 im->gdes[i].step = ft_step;
889 /* lets see if the required data source is really there */
890 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
891 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
895 if (im->gdes[i].ds == -1) {
896 rrd_set_error("No DS called '%s' in '%s'",
897 im->gdes[i].ds_nam, im->gdes[i].rrd);
905 /* evaluate the expressions in the CDEF functions */
907 /*************************************************************
909 *************************************************************/
911 long find_var_wrapper(
915 return find_var((image_desc_t *) arg1, key);
918 /* find gdes containing var*/
925 for (ii = 0; ii < im->gdes_c - 1; ii++) {
926 if ((im->gdes[ii].gf == GF_DEF
927 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
928 && (strcmp(im->gdes[ii].vname, key) == 0)) {
935 /* find the largest common denominator for all the numbers
936 in the 0 terminated num array */
943 for (i = 0; num[i + 1] != 0; i++) {
945 rest = num[i] % num[i + 1];
951 /* return i==0?num[i]:num[i-1]; */
955 /* run the rpn calculator on all the VDEF and CDEF arguments */
962 long *steparray, rpi;
967 rpnstack_init(&rpnstack);
969 for (gdi = 0; gdi < im->gdes_c; gdi++) {
970 /* Look for GF_VDEF and GF_CDEF in the same loop,
971 * so CDEFs can use VDEFs and vice versa
973 switch (im->gdes[gdi].gf) {
977 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
979 /* remove current shift */
980 vdp->start -= vdp->shift;
981 vdp->end -= vdp->shift;
984 if (im->gdes[gdi].shidx >= 0)
985 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
988 vdp->shift = im->gdes[gdi].shval;
990 /* normalize shift to multiple of consolidated step */
991 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
994 vdp->start += vdp->shift;
995 vdp->end += vdp->shift;
999 /* A VDEF has no DS. This also signals other parts
1000 * of rrdtool that this is a VDEF value, not a CDEF.
1002 im->gdes[gdi].ds_cnt = 0;
1003 if (vdef_calc(im, gdi)) {
1004 rrd_set_error("Error processing VDEF '%s'",
1005 im->gdes[gdi].vname);
1006 rpnstack_free(&rpnstack);
1011 im->gdes[gdi].ds_cnt = 1;
1012 im->gdes[gdi].ds = 0;
1013 im->gdes[gdi].data_first = 1;
1014 im->gdes[gdi].start = 0;
1015 im->gdes[gdi].end = 0;
1020 /* Find the variables in the expression.
1021 * - VDEF variables are substituted by their values
1022 * and the opcode is changed into OP_NUMBER.
1023 * - CDEF variables are analized for their step size,
1024 * the lowest common denominator of all the step
1025 * sizes of the data sources involved is calculated
1026 * and the resulting number is the step size for the
1027 * resulting data source.
1029 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1030 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1031 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1032 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1034 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1037 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1038 im->gdes[gdi].vname, im->gdes[ptr].vname);
1039 printf("DEBUG: value from vdef is %f\n",
1040 im->gdes[ptr].vf.val);
1042 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1043 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1044 } else { /* normal variables and PREF(variables) */
1046 /* add one entry to the array that keeps track of the step sizes of the
1047 * data sources going into the CDEF. */
1049 rrd_realloc(steparray,
1051 1) * sizeof(*steparray))) == NULL) {
1052 rrd_set_error("realloc steparray");
1053 rpnstack_free(&rpnstack);
1057 steparray[stepcnt - 1] = im->gdes[ptr].step;
1059 /* adjust start and end of cdef (gdi) so
1060 * that it runs from the latest start point
1061 * to the earliest endpoint of any of the
1062 * rras involved (ptr)
1065 if (im->gdes[gdi].start < im->gdes[ptr].start)
1066 im->gdes[gdi].start = im->gdes[ptr].start;
1068 if (im->gdes[gdi].end == 0 ||
1069 im->gdes[gdi].end > im->gdes[ptr].end)
1070 im->gdes[gdi].end = im->gdes[ptr].end;
1072 /* store pointer to the first element of
1073 * the rra providing data for variable,
1074 * further save step size and data source
1077 im->gdes[gdi].rpnp[rpi].data =
1078 im->gdes[ptr].data + im->gdes[ptr].ds;
1079 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1080 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1082 /* backoff the *.data ptr; this is done so
1083 * rpncalc() function doesn't have to treat
1084 * the first case differently
1086 } /* if ds_cnt != 0 */
1087 } /* if OP_VARIABLE */
1088 } /* loop through all rpi */
1090 /* move the data pointers to the correct period */
1091 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1092 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1093 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1094 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1096 im->gdes[gdi].start - im->gdes[ptr].start;
1099 im->gdes[gdi].rpnp[rpi].data +=
1100 (diff / im->gdes[ptr].step) *
1101 im->gdes[ptr].ds_cnt;
1105 if (steparray == NULL) {
1106 rrd_set_error("rpn expressions without DEF"
1107 " or CDEF variables are not supported");
1108 rpnstack_free(&rpnstack);
1111 steparray[stepcnt] = 0;
1112 /* Now find the resulting step. All steps in all
1113 * used RRAs have to be visited
1115 im->gdes[gdi].step = lcd(steparray);
1117 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1118 im->gdes[gdi].start)
1119 / im->gdes[gdi].step)
1120 * sizeof(double))) == NULL) {
1121 rrd_set_error("malloc im->gdes[gdi].data");
1122 rpnstack_free(&rpnstack);
1126 /* Step through the new cdef results array and
1127 * calculate the values
1129 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1130 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1131 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1133 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1134 * in this case we are advancing by timesteps;
1135 * we use the fact that time_t is a synonym for long
1137 if (rpn_calc(rpnp, &rpnstack, (long) now,
1138 im->gdes[gdi].data, ++dataidx) == -1) {
1139 /* rpn_calc sets the error string */
1140 rpnstack_free(&rpnstack);
1143 } /* enumerate over time steps within a CDEF */
1148 } /* enumerate over CDEFs */
1149 rpnstack_free(&rpnstack);
1153 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1154 /* yes we are loosing precision by doing tos with floats instead of doubles
1155 but it seems more stable this way. */
1157 static int AlmostEqual2sComplement(
1163 int aInt = *(int *) &A;
1164 int bInt = *(int *) &B;
1167 /* Make sure maxUlps is non-negative and small enough that the
1168 default NAN won't compare as equal to anything. */
1170 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1172 /* Make aInt lexicographically ordered as a twos-complement int */
1175 aInt = 0x80000000l - aInt;
1177 /* Make bInt lexicographically ordered as a twos-complement int */
1180 bInt = 0x80000000l - bInt;
1182 intDiff = abs(aInt - bInt);
1184 if (intDiff <= maxUlps)
1190 /* massage data so, that we get one value for each x coordinate in the graph */
1195 double pixstep = (double) (im->end - im->start)
1196 / (double) im->xsize; /* how much time
1197 passes in one pixel */
1199 double minval = DNAN, maxval = DNAN;
1201 unsigned long gr_time;
1203 /* memory for the processed data */
1204 for (i = 0; i < im->gdes_c; i++) {
1205 if ((im->gdes[i].gf == GF_LINE) ||
1206 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1207 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1208 * sizeof(rrd_value_t))) == NULL) {
1209 rrd_set_error("malloc data_proc");
1215 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1218 gr_time = im->start + pixstep * i; /* time of the current step */
1221 for (ii = 0; ii < im->gdes_c; ii++) {
1224 switch (im->gdes[ii].gf) {
1228 if (!im->gdes[ii].stack)
1230 value = im->gdes[ii].yrule;
1231 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1232 /* The time of the data doesn't necessarily match
1233 ** the time of the graph. Beware.
1235 vidx = im->gdes[ii].vidx;
1236 if (im->gdes[vidx].gf == GF_VDEF) {
1237 value = im->gdes[vidx].vf.val;
1239 if (((long int) gr_time >=
1240 (long int) im->gdes[vidx].start)
1241 && ((long int) gr_time <=
1242 (long int) im->gdes[vidx].end)) {
1243 value = im->gdes[vidx].data[(unsigned long)
1249 im->gdes[vidx].step)
1250 * im->gdes[vidx].ds_cnt +
1257 if (!isnan(value)) {
1259 im->gdes[ii].p_data[i] = paintval;
1260 /* GF_TICK: the data values are not
1261 ** relevant for min and max
1263 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1264 if ((isnan(minval) || paintval < minval) &&
1265 !(im->logarithmic && paintval <= 0.0))
1267 if (isnan(maxval) || paintval > maxval)
1271 im->gdes[ii].p_data[i] = DNAN;
1276 ("STACK should already be turned into LINE or AREA here");
1285 /* if min or max have not been asigned a value this is because
1286 there was no data in the graph ... this is not good ...
1287 lets set these to dummy values then ... */
1289 if (im->logarithmic) {
1290 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1291 minval = 0.0; /* catching this right away below */
1294 /* in logarithm mode, where minval is smaller or equal
1295 to 0 make the beast just way smaller than maxval */
1297 minval = maxval / 10e8;
1300 if (isnan(minval) || isnan(maxval)) {
1306 /* adjust min and max values given by the user */
1307 /* for logscale we add something on top */
1308 if (isnan(im->minval)
1309 || ((!im->rigid) && im->minval > minval)
1311 if (im->logarithmic)
1312 im->minval = minval / 2.0;
1314 im->minval = minval;
1316 if (isnan(im->maxval)
1317 || (!im->rigid && im->maxval < maxval)
1319 if (im->logarithmic)
1320 im->maxval = maxval * 2.0;
1322 im->maxval = maxval;
1325 /* make sure min is smaller than max */
1326 if (im->minval > im->maxval) {
1328 im->minval = 0.99 * im->maxval;
1330 im->minval = 1.01 * im->maxval;
1333 /* make sure min and max are not equal */
1334 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1340 /* make sure min and max are not both zero */
1341 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1350 /* identify the point where the first gridline, label ... gets placed */
1352 time_t find_first_time(
1353 time_t start, /* what is the initial time */
1354 enum tmt_en baseint, /* what is the basic interval */
1355 long basestep /* how many if these do we jump a time */
1360 localtime_r(&start, &tm);
1364 tm. tm_sec -= tm.tm_sec % basestep;
1369 tm. tm_min -= tm.tm_min % basestep;
1375 tm. tm_hour -= tm.tm_hour % basestep;
1379 /* we do NOT look at the basestep for this ... */
1386 /* we do NOT look at the basestep for this ... */
1390 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1392 if (tm.tm_wday == 0)
1393 tm. tm_mday -= 7; /* we want the *previous* monday */
1401 tm. tm_mon -= tm.tm_mon % basestep;
1412 tm.tm_year + 1900) %basestep;
1418 /* identify the point where the next gridline, label ... gets placed */
1419 time_t find_next_time(
1420 time_t current, /* what is the initial time */
1421 enum tmt_en baseint, /* what is the basic interval */
1422 long basestep /* how many if these do we jump a time */
1428 localtime_r(¤t, &tm);
1433 tm. tm_sec += basestep;
1437 tm. tm_min += basestep;
1441 tm. tm_hour += basestep;
1445 tm. tm_mday += basestep;
1449 tm. tm_mday += 7 * basestep;
1453 tm. tm_mon += basestep;
1457 tm. tm_year += basestep;
1459 madetime = mktime(&tm);
1460 } while (madetime == -1); /* this is necessary to skip impssible times
1461 like the daylight saving time skips */
1467 /* calculate values required for PRINT and GPRINT functions */
1472 long i, ii, validsteps;
1475 int graphelement = 0;
1478 double magfact = -1;
1483 /* wow initializing tmvdef is quite a task :-) */
1484 time_t now = time(NULL);
1486 localtime_r(&now, &tmvdef);
1487 for (i = 0; i < im->gdes_c; i++) {
1488 vidx = im->gdes[i].vidx;
1489 switch (im->gdes[i].gf) {
1492 /* PRINT and GPRINT can now print VDEF generated values.
1493 * There's no need to do any calculations on them as these
1494 * calculations were already made.
1496 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1497 printval = im->gdes[vidx].vf.val;
1498 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1499 } else { /* need to calculate max,min,avg etcetera */
1500 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1501 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1504 for (ii = im->gdes[vidx].ds;
1505 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1506 if (!finite(im->gdes[vidx].data[ii]))
1508 if (isnan(printval)) {
1509 printval = im->gdes[vidx].data[ii];
1514 switch (im->gdes[i].cf) {
1518 case CF_DEVSEASONAL:
1522 printval += im->gdes[vidx].data[ii];
1525 printval = min(printval, im->gdes[vidx].data[ii]);
1529 printval = max(printval, im->gdes[vidx].data[ii]);
1532 printval = im->gdes[vidx].data[ii];
1535 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1536 if (validsteps > 1) {
1537 printval = (printval / validsteps);
1540 } /* prepare printval */
1542 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1543 /* Magfact is set to -1 upon entry to print_calc. If it
1544 * is still less than 0, then we need to run auto_scale.
1545 * Otherwise, put the value into the correct units. If
1546 * the value is 0, then do not set the symbol or magnification
1547 * so next the calculation will be performed again. */
1548 if (magfact < 0.0) {
1549 auto_scale(im, &printval, &si_symb, &magfact);
1550 if (printval == 0.0)
1553 printval /= magfact;
1555 *(++percent_s) = 's';
1556 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1557 auto_scale(im, &printval, &si_symb, &magfact);
1560 if (im->gdes[i].gf == GF_PRINT) {
1561 rrd_infoval_t prline;
1563 if (im->gdes[i].strftm) {
1564 prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
1565 strftime(prline.u_str,
1566 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1567 } else if (bad_format(im->gdes[i].format)) {
1569 ("bad format for PRINT in '%s'", im->gdes[i].format);
1573 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1577 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1582 if (im->gdes[i].strftm) {
1583 strftime(im->gdes[i].legend,
1584 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1586 if (bad_format(im->gdes[i].format)) {
1588 ("bad format for GPRINT in '%s'",
1589 im->gdes[i].format);
1592 #ifdef HAVE_SNPRINTF
1593 snprintf(im->gdes[i].legend,
1595 im->gdes[i].format, printval, si_symb);
1597 sprintf(im->gdes[i].legend,
1598 im->gdes[i].format, printval, si_symb);
1610 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1611 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1616 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1617 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1626 #ifdef WITH_PIECHART
1634 ("STACK should already be turned into LINE or AREA here");
1639 return graphelement;
1643 /* place legends with color spots */
1649 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1650 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1651 int fill = 0, fill_last;
1654 int leg_y = im->yimg;
1655 int leg_y_prev = im->yimg;
1658 int i, ii, mark = 0;
1659 char prt_fctn; /*special printfunctions */
1660 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1664 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1665 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1666 rrd_set_error("malloc for legspace");
1670 if (im->extra_flags & FULL_SIZE_MODE)
1671 leg_y = leg_y_prev =
1672 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1673 for (i = 0; i < im->gdes_c; i++) {
1675 /* hide legends for rules which are not displayed */
1676 if (im->gdes[i].gf == GF_TEXTALIGN) {
1677 default_txtalign = im->gdes[i].txtalign;
1680 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1681 if (im->gdes[i].gf == GF_HRULE
1682 && (im->gdes[i].yrule <
1683 im->minval || im->gdes[i].yrule > im->maxval))
1684 im->gdes[i].legend[0] = '\0';
1685 if (im->gdes[i].gf == GF_VRULE
1686 && (im->gdes[i].xrule <
1687 im->start || im->gdes[i].xrule > im->end))
1688 im->gdes[i].legend[0] = '\0';
1691 /* turn \\t into tab */
1692 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1693 memmove(tab, tab + 1, strlen(tab));
1696 leg_cc = strlen(im->gdes[i].legend);
1697 /* is there a controle code ant the end of the legend string ? */
1698 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1699 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1701 im->gdes[i].legend[leg_cc] = '\0';
1705 /* only valid control codes */
1706 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1710 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1713 ("Unknown control code at the end of '%s\\%c'",
1714 im->gdes[i].legend, prt_fctn);
1718 if (prt_fctn == 'n') {
1722 /* remove exess space from the end of the legend for \g */
1723 while (prt_fctn == 'g' &&
1724 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1726 im->gdes[i].legend[leg_cc] = '\0';
1731 /* no interleg space if string ends in \g */
1732 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1734 fill += legspace[i];
1737 gfx_get_text_width(im,
1747 im->tabwidth, im->gdes[i].legend);
1752 /* who said there was a special tag ... ? */
1753 if (prt_fctn == 'g') {
1757 if (prt_fctn == '\0') {
1758 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1759 /* just one legend item is left right or center */
1760 switch (default_txtalign) {
1775 /* is it time to place the legends ? */
1776 if (fill > im->ximg - 2 * border) {
1784 if (leg_c == 1 && prt_fctn == 'j') {
1790 if (prt_fctn != '\0') {
1792 if (leg_c >= 2 && prt_fctn == 'j') {
1793 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1797 if (prt_fctn == 'c')
1798 leg_x = (im->ximg - fill) / 2.0;
1799 if (prt_fctn == 'r')
1800 leg_x = im->ximg - fill - border;
1801 for (ii = mark; ii <= i; ii++) {
1802 if (im->gdes[ii].legend[0] == '\0')
1803 continue; /* skip empty legends */
1804 im->gdes[ii].leg_x = leg_x;
1805 im->gdes[ii].leg_y = leg_y;
1807 gfx_get_text_width(im, leg_x,
1816 im->tabwidth, im->gdes[ii].legend)
1821 if (im->extra_flags & FULL_SIZE_MODE) {
1822 /* only add y space if there was text on the line */
1823 if (leg_x > border || prt_fctn == 's')
1824 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1825 if (prt_fctn == 's')
1826 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1828 if (leg_x > border || prt_fctn == 's')
1829 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1830 if (prt_fctn == 's')
1831 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1839 if (im->extra_flags & FULL_SIZE_MODE) {
1840 if (leg_y != leg_y_prev) {
1841 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1843 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1847 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1855 /* create a grid on the graph. it determines what to do
1856 from the values of xsize, start and end */
1858 /* the xaxis labels are determined from the number of seconds per pixel
1859 in the requested graph */
1861 int calc_horizontal_grid(
1869 int decimals, fractionals;
1871 im->ygrid_scale.labfact = 2;
1872 range = im->maxval - im->minval;
1873 scaledrange = range / im->magfact;
1874 /* does the scale of this graph make it impossible to put lines
1875 on it? If so, give up. */
1876 if (isnan(scaledrange)) {
1880 /* find grid spaceing */
1882 if (isnan(im->ygridstep)) {
1883 if (im->extra_flags & ALTYGRID) {
1884 /* find the value with max number of digits. Get number of digits */
1887 (max(fabs(im->maxval), fabs(im->minval)) *
1888 im->viewfactor / im->magfact));
1889 if (decimals <= 0) /* everything is small. make place for zero */
1891 im->ygrid_scale.gridstep =
1893 floor(log10(range * im->viewfactor / im->magfact))) /
1894 im->viewfactor * im->magfact;
1895 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1896 im->ygrid_scale.gridstep = 0.1;
1897 /* should have at least 5 lines but no more then 15 */
1898 if (range / im->ygrid_scale.gridstep < 5
1899 && im->ygrid_scale.gridstep >= 30)
1900 im->ygrid_scale.gridstep /= 10;
1901 if (range / im->ygrid_scale.gridstep > 15)
1902 im->ygrid_scale.gridstep *= 10;
1903 if (range / im->ygrid_scale.gridstep > 5) {
1904 im->ygrid_scale.labfact = 1;
1905 if (range / im->ygrid_scale.gridstep > 8
1906 || im->ygrid_scale.gridstep <
1907 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1908 im->ygrid_scale.labfact = 2;
1910 im->ygrid_scale.gridstep /= 5;
1911 im->ygrid_scale.labfact = 5;
1915 (im->ygrid_scale.gridstep *
1916 (double) im->ygrid_scale.labfact * im->viewfactor /
1918 if (fractionals < 0) { /* small amplitude. */
1919 int len = decimals - fractionals + 1;
1921 if (im->unitslength < len + 2)
1922 im->unitslength = len + 2;
1923 sprintf(im->ygrid_scale.labfmt,
1925 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1927 int len = decimals + 1;
1929 if (im->unitslength < len + 2)
1930 im->unitslength = len + 2;
1931 sprintf(im->ygrid_scale.labfmt,
1932 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1934 } else { /* classic rrd grid */
1935 for (i = 0; ylab[i].grid > 0; i++) {
1936 pixel = im->ysize / (scaledrange / ylab[i].grid);
1942 for (i = 0; i < 4; i++) {
1943 if (pixel * ylab[gridind].lfac[i] >=
1944 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1945 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1950 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1953 im->ygrid_scale.gridstep = im->ygridstep;
1954 im->ygrid_scale.labfact = im->ylabfact;
1959 int draw_horizontal_grid(
1965 char graph_label[100];
1967 double X0 = im->xorigin;
1968 double X1 = im->xorigin + im->xsize;
1969 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1970 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1974 im->ygrid_scale.gridstep /
1975 (double) im->magfact * (double) im->viewfactor;
1976 MaxY = scaledstep * (double) egrid;
1977 for (i = sgrid; i <= egrid; i++) {
1979 im->ygrid_scale.gridstep * i);
1981 im->ygrid_scale.gridstep * (i + 1));
1983 if (floor(Y0 + 0.5) >=
1984 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1985 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1986 with the chosen settings. Add a label if required by settings, or if
1987 there is only one label so far and the next grid line is out of bounds. */
1988 if (i % im->ygrid_scale.labfact == 0
1990 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1991 if (im->symbol == ' ') {
1992 if (im->extra_flags & ALTYGRID) {
1993 sprintf(graph_label,
1994 im->ygrid_scale.labfmt,
1995 scaledstep * (double) i);
1998 sprintf(graph_label, "%4.1f",
1999 scaledstep * (double) i);
2001 sprintf(graph_label, "%4.0f",
2002 scaledstep * (double) i);
2006 char sisym = (i == 0 ? ' ' : im->symbol);
2008 if (im->extra_flags & ALTYGRID) {
2009 sprintf(graph_label,
2010 im->ygrid_scale.labfmt,
2011 scaledstep * (double) i, sisym);
2014 sprintf(graph_label, "%4.1f %c",
2015 scaledstep * (double) i, sisym);
2017 sprintf(graph_label, "%4.0f %c",
2018 scaledstep * (double) i, sisym);
2026 text_prop[TEXT_PROP_AXIS].
2028 im->graph_col[GRC_FONT],
2030 text_prop[TEXT_PROP_AXIS].
2033 text_prop[TEXT_PROP_AXIS].
2034 size, im->tabwidth, 0.0,
2035 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2036 gfx_line(im, X0 - 2, Y0, X0, Y0,
2037 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2038 gfx_line(im, X1, Y0, X1 + 2, Y0,
2039 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2040 gfx_dashed_line(im, X0 - 2, Y0,
2046 im->grid_dash_on, im->grid_dash_off);
2047 } else if (!(im->extra_flags & NOMINOR)) {
2050 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2051 gfx_line(im, X1, Y0, X1 + 2, Y0,
2052 GRIDWIDTH, im->graph_col[GRC_GRID]);
2053 gfx_dashed_line(im, X0 - 1, Y0,
2057 graph_col[GRC_GRID],
2058 im->grid_dash_on, im->grid_dash_off);
2065 /* this is frexp for base 10 */
2076 iexp = floor(log(fabs(x)) / log(10));
2077 mnt = x / pow(10.0, iexp);
2080 mnt = x / pow(10.0, iexp);
2087 /* logaritmic horizontal grid */
2088 int horizontal_log_grid(
2092 double yloglab[][10] = {
2094 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2096 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2098 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2115 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2117 int i, j, val_exp, min_exp;
2118 double nex; /* number of decades in data */
2119 double logscale; /* scale in logarithmic space */
2120 int exfrac = 1; /* decade spacing */
2121 int mid = -1; /* row in yloglab for major grid */
2122 double mspac; /* smallest major grid spacing (pixels) */
2123 int flab; /* first value in yloglab to use */
2124 double value, tmp, pre_value;
2126 char graph_label[100];
2128 nex = log10(im->maxval / im->minval);
2129 logscale = im->ysize / nex;
2130 /* major spacing for data with high dynamic range */
2131 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2138 /* major spacing for less dynamic data */
2140 /* search best row in yloglab */
2142 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2143 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2146 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2149 /* find first value in yloglab */
2151 yloglab[mid][flab] < 10
2152 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2153 if (yloglab[mid][flab] == 10.0) {
2158 if (val_exp % exfrac)
2159 val_exp += abs(-val_exp % exfrac);
2161 X1 = im->xorigin + im->xsize;
2166 value = yloglab[mid][flab] * pow(10.0, val_exp);
2167 if (AlmostEqual2sComplement(value, pre_value, 4))
2168 break; /* it seems we are not converging */
2170 Y0 = ytr(im, value);
2171 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2173 /* major grid line */
2175 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2176 gfx_line(im, X1, Y0, X1 + 2, Y0,
2177 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2178 gfx_dashed_line(im, X0 - 2, Y0,
2183 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2185 if (im->extra_flags & FORCE_UNITS_SI) {
2190 scale = floor(val_exp / 3.0);
2192 pvalue = pow(10.0, val_exp % 3);
2194 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2195 pvalue *= yloglab[mid][flab];
2196 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2197 && ((scale + si_symbcenter) >= 0))
2198 symbol = si_symbol[scale + si_symbcenter];
2201 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2203 sprintf(graph_label, "%3.0e", value);
2207 text_prop[TEXT_PROP_AXIS].
2209 im->graph_col[GRC_FONT],
2211 text_prop[TEXT_PROP_AXIS].
2214 text_prop[TEXT_PROP_AXIS].
2215 size, im->tabwidth, 0.0,
2216 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2218 if (mid < 4 && exfrac == 1) {
2219 /* find first and last minor line behind current major line
2220 * i is the first line and j tha last */
2222 min_exp = val_exp - 1;
2223 for (i = 1; yloglab[mid][i] < 10.0; i++);
2224 i = yloglab[mid][i - 1] + 1;
2228 i = yloglab[mid][flab - 1] + 1;
2229 j = yloglab[mid][flab];
2232 /* draw minor lines below current major line */
2233 for (; i < j; i++) {
2235 value = i * pow(10.0, min_exp);
2236 if (value < im->minval)
2238 Y0 = ytr(im, value);
2239 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2244 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2245 gfx_line(im, X1, Y0, X1 + 2, Y0,
2246 GRIDWIDTH, im->graph_col[GRC_GRID]);
2247 gfx_dashed_line(im, X0 - 1, Y0,
2251 graph_col[GRC_GRID],
2252 im->grid_dash_on, im->grid_dash_off);
2254 } else if (exfrac > 1) {
2255 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2256 value = pow(10.0, i);
2257 if (value < im->minval)
2259 Y0 = ytr(im, value);
2260 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2265 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2266 gfx_line(im, X1, Y0, X1 + 2, Y0,
2267 GRIDWIDTH, im->graph_col[GRC_GRID]);
2268 gfx_dashed_line(im, X0 - 1, Y0,
2272 graph_col[GRC_GRID],
2273 im->grid_dash_on, im->grid_dash_off);
2278 if (yloglab[mid][++flab] == 10.0) {
2284 /* draw minor lines after highest major line */
2285 if (mid < 4 && exfrac == 1) {
2286 /* find first and last minor line below current major line
2287 * i is the first line and j tha last */
2289 min_exp = val_exp - 1;
2290 for (i = 1; yloglab[mid][i] < 10.0; i++);
2291 i = yloglab[mid][i - 1] + 1;
2295 i = yloglab[mid][flab - 1] + 1;
2296 j = yloglab[mid][flab];
2299 /* draw minor lines below current major line */
2300 for (; i < j; i++) {
2302 value = i * pow(10.0, min_exp);
2303 if (value < im->minval)
2305 Y0 = ytr(im, value);
2306 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2310 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2311 gfx_line(im, X1, Y0, X1 + 2, Y0,
2312 GRIDWIDTH, im->graph_col[GRC_GRID]);
2313 gfx_dashed_line(im, X0 - 1, Y0,
2317 graph_col[GRC_GRID],
2318 im->grid_dash_on, im->grid_dash_off);
2321 /* fancy minor gridlines */
2322 else if (exfrac > 1) {
2323 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2324 value = pow(10.0, i);
2325 if (value < im->minval)
2327 Y0 = ytr(im, value);
2328 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2332 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2333 gfx_line(im, X1, Y0, X1 + 2, Y0,
2334 GRIDWIDTH, im->graph_col[GRC_GRID]);
2335 gfx_dashed_line(im, X0 - 1, Y0,
2339 graph_col[GRC_GRID],
2340 im->grid_dash_on, im->grid_dash_off);
2351 int xlab_sel; /* which sort of label and grid ? */
2352 time_t ti, tilab, timajor;
2354 char graph_label[100];
2355 double X0, Y0, Y1; /* points for filled graph and more */
2358 /* the type of time grid is determined by finding
2359 the number of seconds per pixel in the graph */
2360 if (im->xlab_user.minsec == -1) {
2361 factor = (im->end - im->start) / im->xsize;
2363 while (xlab[xlab_sel + 1].minsec !=
2364 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2366 } /* pick the last one */
2367 while (xlab[xlab_sel - 1].minsec ==
2368 xlab[xlab_sel].minsec
2369 && xlab[xlab_sel].length > (im->end - im->start)) {
2371 } /* go back to the smallest size */
2372 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2373 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2374 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2375 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2376 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2377 im->xlab_user.labst = xlab[xlab_sel].labst;
2378 im->xlab_user.precis = xlab[xlab_sel].precis;
2379 im->xlab_user.stst = xlab[xlab_sel].stst;
2382 /* y coords are the same for every line ... */
2384 Y1 = im->yorigin - im->ysize;
2385 /* paint the minor grid */
2386 if (!(im->extra_flags & NOMINOR)) {
2387 for (ti = find_first_time(im->start,
2395 find_first_time(im->start,
2402 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2404 /* are we inside the graph ? */
2405 if (ti < im->start || ti > im->end)
2407 while (timajor < ti) {
2408 timajor = find_next_time(timajor,
2411 mgridtm, im->xlab_user.mgridst);
2414 continue; /* skip as falls on major grid line */
2416 gfx_line(im, X0, Y1 - 2, X0, Y1,
2417 GRIDWIDTH, im->graph_col[GRC_GRID]);
2418 gfx_line(im, X0, Y0, X0, Y0 + 2,
2419 GRIDWIDTH, im->graph_col[GRC_GRID]);
2420 gfx_dashed_line(im, X0, Y0 + 1, X0,
2423 graph_col[GRC_GRID],
2424 im->grid_dash_on, im->grid_dash_off);
2428 /* paint the major grid */
2429 for (ti = find_first_time(im->start,
2437 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2439 /* are we inside the graph ? */
2440 if (ti < im->start || ti > im->end)
2443 gfx_line(im, X0, Y1 - 2, X0, Y1,
2444 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2445 gfx_line(im, X0, Y0, X0, Y0 + 3,
2446 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2447 gfx_dashed_line(im, X0, Y0 + 3, X0,
2451 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2453 /* paint the labels below the graph */
2455 find_first_time(im->start -
2464 im->xlab_user.precis / 2;
2465 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2467 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2468 /* are we inside the graph ? */
2469 if (tilab < im->start || tilab > im->end)
2472 localtime_r(&tilab, &tm);
2473 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2475 # error "your libc has no strftime I guess we'll abort the exercise here."
2480 im->graph_col[GRC_FONT],
2482 text_prop[TEXT_PROP_AXIS].
2485 text_prop[TEXT_PROP_AXIS].
2486 size, im->tabwidth, 0.0,
2487 GFX_H_CENTER, GFX_V_TOP, graph_label);
2496 /* draw x and y axis */
2497 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2498 im->xorigin+im->xsize,im->yorigin-im->ysize,
2499 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2501 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2502 im->xorigin+im->xsize,im->yorigin-im->ysize,
2503 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2505 gfx_line(im, im->xorigin - 4,
2507 im->xorigin + im->xsize +
2508 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2509 gfx_line(im, im->xorigin,
2512 im->yorigin - im->ysize -
2513 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2514 /* arrow for X and Y axis direction */
2515 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 */
2516 im->graph_col[GRC_ARROW]);
2518 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 */
2519 im->graph_col[GRC_ARROW]);
2528 double X0, Y0; /* points for filled graph and more */
2529 struct gfx_color_t water_color;
2531 /* draw 3d border */
2532 gfx_new_area(im, 0, im->yimg,
2533 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2534 gfx_add_point(im, im->ximg - 2, 2);
2535 gfx_add_point(im, im->ximg, 0);
2536 gfx_add_point(im, 0, 0);
2538 gfx_new_area(im, 2, im->yimg - 2,
2540 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2541 gfx_add_point(im, im->ximg, 0);
2542 gfx_add_point(im, im->ximg, im->yimg);
2543 gfx_add_point(im, 0, im->yimg);
2545 if (im->draw_x_grid == 1)
2547 if (im->draw_y_grid == 1) {
2548 if (im->logarithmic) {
2549 res = horizontal_log_grid(im);
2551 res = draw_horizontal_grid(im);
2554 /* dont draw horizontal grid if there is no min and max val */
2556 char *nodata = "No Data found";
2558 gfx_text(im, im->ximg / 2,
2561 im->graph_col[GRC_FONT],
2563 text_prop[TEXT_PROP_AXIS].
2566 text_prop[TEXT_PROP_AXIS].
2567 size, im->tabwidth, 0.0,
2568 GFX_H_CENTER, GFX_V_CENTER, nodata);
2572 /* yaxis unit description */
2577 im->graph_col[GRC_FONT],
2579 text_prop[TEXT_PROP_UNIT].
2582 text_prop[TEXT_PROP_UNIT].
2584 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2588 im->graph_col[GRC_FONT],
2590 text_prop[TEXT_PROP_TITLE].
2593 text_prop[TEXT_PROP_TITLE].
2594 size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2595 /* rrdtool 'logo' */
2596 water_color = im->graph_col[GRC_FONT];
2597 water_color.alpha = 0.3;
2598 gfx_text(im, im->ximg - 4, 5,
2601 text_prop[TEXT_PROP_AXIS].
2602 font, 5.5, im->tabwidth,
2603 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2604 /* graph watermark */
2605 if (im->watermark[0] != '\0') {
2607 im->ximg / 2, im->yimg - 6,
2610 text_prop[TEXT_PROP_AXIS].
2611 font, 5.5, im->tabwidth, 0,
2612 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2616 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2617 for (i = 0; i < im->gdes_c; i++) {
2618 if (im->gdes[i].legend[0] == '\0')
2620 /* im->gdes[i].leg_y is the bottom of the legend */
2621 X0 = im->gdes[i].leg_x;
2622 Y0 = im->gdes[i].leg_y;
2623 gfx_text(im, X0, Y0,
2624 im->graph_col[GRC_FONT],
2627 [TEXT_PROP_LEGEND].font,
2630 [TEXT_PROP_LEGEND].size,
2632 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2633 /* The legend for GRAPH items starts with "M " to have
2634 enough space for the box */
2635 if (im->gdes[i].gf != GF_PRINT &&
2636 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2640 boxH = gfx_get_text_width(im, 0,
2648 size, im->tabwidth, "o") * 1.2;
2650 /* shift the box up a bit */
2652 /* make sure transparent colors show up the same way as in the graph */
2655 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2656 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2658 gfx_new_area(im, X0, Y0 - boxV, X0,
2659 Y0, X0 + boxH, Y0, im->gdes[i].col);
2660 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2663 cairo_new_path(im->cr);
2664 cairo_set_line_width(im->cr, 1.0);
2667 gfx_line_fit(im, &X0, &Y0);
2668 gfx_line_fit(im, &X1, &Y1);
2669 cairo_move_to(im->cr, X0, Y0);
2670 cairo_line_to(im->cr, X1, Y0);
2671 cairo_line_to(im->cr, X1, Y1);
2672 cairo_line_to(im->cr, X0, Y1);
2673 cairo_close_path(im->cr);
2674 cairo_set_source_rgba(im->cr,
2686 blue, im->graph_col[GRC_FRAME].alpha);
2687 if (im->gdes[i].dash) {
2688 /* make box borders in legend dashed if the graph is dashed */
2692 cairo_set_dash(im->cr, dashes, 1, 0.0);
2694 cairo_stroke(im->cr);
2695 cairo_restore(im->cr);
2702 /*****************************************************
2703 * lazy check make sure we rely need to create this graph
2704 *****************************************************/
2711 struct stat imgstat;
2714 return 0; /* no lazy option */
2715 if (strlen(im->graphfile) == 0)
2716 return 0; /* inmemory option */
2717 if (stat(im->graphfile, &imgstat) != 0)
2718 return 0; /* can't stat */
2719 /* one pixel in the existing graph is more then what we would
2721 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2723 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2724 return 0; /* the file does not exist */
2725 switch (im->imgformat) {
2727 size = PngSize(fd, &(im->ximg), &(im->yimg));
2737 int graph_size_location(
2742 /* The actual size of the image to draw is determined from
2743 ** several sources. The size given on the command line is
2744 ** the graph area but we need more as we have to draw labels
2745 ** and other things outside the graph area
2748 int Xvertical = 0, Ytitle =
2749 0, Xylabel = 0, Xmain = 0, Ymain =
2750 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2752 if (im->extra_flags & ONLY_GRAPH) {
2754 im->ximg = im->xsize;
2755 im->yimg = im->ysize;
2756 im->yorigin = im->ysize;
2761 /** +---+--------------------------------------------+
2762 ** | y |...............graph title..................|
2763 ** | +---+-------------------------------+--------+
2766 ** | i | a | | pie |
2767 ** | s | x | main graph area | chart |
2772 ** | l | b +-------------------------------+--------+
2773 ** | e | l | x axis labels | |
2774 ** +---+---+-------------------------------+--------+
2775 ** |....................legends.....................|
2776 ** +------------------------------------------------+
2778 ** +------------------------------------------------+
2781 if (im->ylegend[0] != '\0') {
2782 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2785 if (im->title[0] != '\0') {
2786 /* The title is placed "inbetween" two text lines so it
2787 ** automatically has some vertical spacing. The horizontal
2788 ** spacing is added here, on each side.
2790 /* if necessary, reduce the font size of the title until it fits the image width */
2791 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2795 if (im->draw_x_grid) {
2796 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2798 if (im->draw_y_grid || im->forceleftspace) {
2800 gfx_get_text_width(im, 0,
2808 size, im->tabwidth, "0") * im->unitslength;
2812 if (im->extra_flags & FULL_SIZE_MODE) {
2813 /* The actual size of the image to draw has been determined by the user.
2814 ** The graph area is the space remaining after accounting for the legend,
2815 ** the watermark, the pie chart, the axis labels, and the title.
2818 im->ximg = im->xsize;
2819 im->yimg = im->ysize;
2820 im->yorigin = im->ysize;
2823 im->yorigin += Ytitle;
2824 /* Now calculate the total size. Insert some spacing where
2825 desired. im->xorigin and im->yorigin need to correspond
2826 with the lower left corner of the main graph area or, if
2827 this one is not set, the imaginary box surrounding the
2829 /* Initial size calculation for the main graph area */
2830 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2832 Xmain -= Xspacing; /* put space between main graph area and right edge */
2833 im->xorigin = Xspacing + Xylabel;
2834 /* the length of the title should not influence with width of the graph
2835 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2836 if (Xvertical) { /* unit description */
2838 im->xorigin += Xvertical;
2842 /* The vertical size of the image is known in advance. The main graph area
2843 ** (Ymain) and im->yorigin must be set according to the space requirements
2844 ** of the legend and the axis labels.
2846 if (im->extra_flags & NOLEGEND) {
2847 /* set dimensions correctly if using full size mode with no legend */
2850 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2851 Ymain = im->yorigin;
2853 /* Determine where to place the legends onto the image.
2854 ** Set Ymain and adjust im->yorigin to match the space requirements.
2856 if (leg_place(im, &Ymain) == -1)
2861 /* remove title space *or* some padding above the graph from the main graph area */
2865 Ymain -= 1.5 * Yspacing;
2868 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2869 if (im->watermark[0] != '\0') {
2870 Ymain -= Ywatermark;
2874 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2876 /* The actual size of the image to draw is determined from
2877 ** several sources. The size given on the command line is
2878 ** the graph area but we need more as we have to draw labels
2879 ** and other things outside the graph area.
2882 if (im->ylegend[0] != '\0') {
2883 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2887 if (im->title[0] != '\0') {
2888 /* The title is placed "inbetween" two text lines so it
2889 ** automatically has some vertical spacing. The horizontal
2890 ** spacing is added here, on each side.
2892 /* don't care for the with of the title
2893 Xtitle = gfx_get_text_width(im->canvas, 0,
2894 im->text_prop[TEXT_PROP_TITLE].font,
2895 im->text_prop[TEXT_PROP_TITLE].size,
2897 im->title, 0) + 2*Xspacing; */
2898 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2905 /* Now calculate the total size. Insert some spacing where
2906 desired. im->xorigin and im->yorigin need to correspond
2907 with the lower left corner of the main graph area or, if
2908 this one is not set, the imaginary box surrounding the
2911 /* The legend width cannot yet be determined, as a result we
2912 ** have problems adjusting the image to it. For now, we just
2913 ** forget about it at all; the legend will have to fit in the
2914 ** size already allocated.
2916 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2918 im->ximg += Xspacing;
2919 im->xorigin = Xspacing + Xylabel;
2920 /* the length of the title should not influence with width of the graph
2921 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2922 if (Xvertical) { /* unit description */
2923 im->ximg += Xvertical;
2924 im->xorigin += Xvertical;
2927 /* The vertical size is interesting... we need to compare
2928 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2929 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2930 ** in order to start even thinking about Ylegend or Ywatermark.
2932 ** Do it in three portions: First calculate the inner part,
2933 ** then do the legend, then adjust the total height of the img,
2934 ** adding space for a watermark if one exists;
2936 /* reserve space for main and/or pie */
2937 im->yimg = Ymain + Yxlabel;
2938 im->yorigin = im->yimg - Yxlabel;
2939 /* reserve space for the title *or* some padding above the graph */
2942 im->yorigin += Ytitle;
2944 im->yimg += 1.5 * Yspacing;
2945 im->yorigin += 1.5 * Yspacing;
2947 /* reserve space for padding below the graph */
2948 im->yimg += Yspacing;
2949 /* Determine where to place the legends onto the image.
2950 ** Adjust im->yimg to match the space requirements.
2952 if (leg_place(im, 0) == -1)
2954 if (im->watermark[0] != '\0') {
2955 im->yimg += Ywatermark;
2963 static cairo_status_t cairo_output(
2967 unsigned int length)
2969 image_desc_t *im = closure;
2971 im->rendered_image =
2972 realloc(im->rendered_image, im->rendered_image_size + length);
2973 if (im->rendered_image == NULL)
2974 return CAIRO_STATUS_WRITE_ERROR;
2975 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2976 im->rendered_image_size += length;
2977 return CAIRO_STATUS_SUCCESS;
2980 /* draw that picture thing ... */
2985 int lazy = lazy_check(im);
2986 double areazero = 0.0;
2987 graph_desc_t *lastgdes = NULL;
2989 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2991 /* if we are lazy and there is nothing to PRINT ... quit now */
2992 if (lazy && im->prt_c == 0) {
2993 info.u_cnt = im->ximg;
2994 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2995 info.u_cnt = im->yimg;
2996 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2999 /* pull the data from the rrd files ... */
3000 if (data_fetch(im) == -1)
3002 /* evaluate VDEF and CDEF operations ... */
3003 if (data_calc(im) == -1)
3005 /* calculate and PRINT and GPRINT definitions. We have to do it at
3006 * this point because it will affect the length of the legends
3007 * if there are no graph elements (i==0) we stop here ...
3008 * if we are lazy, try to quit ...
3014 if ((i == 0) || lazy)
3017 /**************************************************************
3018 *** Calculating sizes and locations became a bit confusing ***
3019 *** so I moved this into a separate function. ***
3020 **************************************************************/
3021 if (graph_size_location(im, i) == -1)
3024 info.u_cnt = im->xorigin;
3025 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3026 info.u_cnt = im->yorigin - im->ysize;
3027 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3028 info.u_cnt = im->xsize;
3029 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3030 info.u_cnt = im->ysize;
3031 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3032 info.u_cnt = im->ximg;
3033 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3034 info.u_cnt = im->yimg;
3035 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3037 /* get actual drawing data and find min and max values */
3038 if (data_proc(im) == -1)
3040 if (!im->logarithmic) {
3044 /* identify si magnitude Kilo, Mega Giga ? */
3045 if (!im->rigid && !im->logarithmic)
3046 expand_range(im); /* make sure the upper and lower limit are
3049 info.u_val = im->minval;
3050 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3051 info.u_val = im->maxval;
3052 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3054 if (!calc_horizontal_grid(im))
3059 apply_gridfit(im); */
3060 /* the actual graph is created by going through the individual
3061 graph elements and then drawing them */
3062 cairo_surface_destroy(im->surface);
3063 switch (im->imgformat) {
3066 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3067 im->ximg * im->zoom,
3068 im->yimg * im->zoom);
3072 im->surface = strlen(im->graphfile)
3073 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3074 im->yimg * im->zoom)
3075 : cairo_pdf_surface_create_for_stream
3076 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3080 im->surface = strlen(im->graphfile)
3082 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3083 im->yimg * im->zoom)
3084 : cairo_ps_surface_create_for_stream
3085 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3089 im->surface = strlen(im->graphfile)
3091 cairo_svg_surface_create(im->
3093 im->ximg * im->zoom, im->yimg * im->zoom)
3094 : cairo_svg_surface_create_for_stream
3095 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3096 cairo_svg_surface_restrict_to_version
3097 (im->surface, CAIRO_SVG_VERSION_1_1);
3100 im->cr = cairo_create(im->surface);
3101 cairo_set_antialias(im->cr, im->graph_antialias);
3102 cairo_scale(im->cr, im->zoom, im->zoom);
3103 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3104 gfx_new_area(im, 0, 0, 0, im->yimg,
3105 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3106 gfx_add_point(im, im->ximg, 0);
3108 gfx_new_area(im, im->xorigin,
3111 im->xsize, im->yorigin,
3114 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3115 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3117 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3118 im->xsize, im->ysize + 2.0);
3120 if (im->minval > 0.0)
3121 areazero = im->minval;
3122 if (im->maxval < 0.0)
3123 areazero = im->maxval;
3124 for (i = 0; i < im->gdes_c; i++) {
3125 switch (im->gdes[i].gf) {
3139 for (ii = 0; ii < im->xsize; ii++) {
3140 if (!isnan(im->gdes[i].p_data[ii])
3141 && im->gdes[i].p_data[ii] != 0.0) {
3142 if (im->gdes[i].yrule > 0) {
3149 im->ysize, 1.0, im->gdes[i].col);
3150 } else if (im->gdes[i].yrule < 0) {
3153 im->yorigin - im->ysize,
3158 im->ysize, 1.0, im->gdes[i].col);
3165 /* fix data points at oo and -oo */
3166 for (ii = 0; ii < im->xsize; ii++) {
3167 if (isinf(im->gdes[i].p_data[ii])) {
3168 if (im->gdes[i].p_data[ii] > 0) {
3169 im->gdes[i].p_data[ii] = im->maxval;
3171 im->gdes[i].p_data[ii] = im->minval;
3177 /* *******************************************************
3182 -------|--t-1--t--------------------------------
3184 if we know the value at time t was a then
3185 we draw a square from t-1 to t with the value a.
3187 ********************************************************* */
3188 if (im->gdes[i].col.alpha != 0.0) {
3189 /* GF_LINE and friend */
3190 if (im->gdes[i].gf == GF_LINE) {
3191 double last_y = 0.0;
3195 cairo_new_path(im->cr);
3196 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3197 if (im->gdes[i].dash) {
3198 cairo_set_dash(im->cr,
3199 im->gdes[i].p_dashes,
3200 im->gdes[i].ndash, im->gdes[i].offset);
3203 for (ii = 1; ii < im->xsize; ii++) {
3204 if (isnan(im->gdes[i].p_data[ii])
3205 || (im->slopemode == 1
3206 && isnan(im->gdes[i].p_data[ii - 1]))) {
3211 last_y = ytr(im, im->gdes[i].p_data[ii]);
3212 if (im->slopemode == 0) {
3213 double x = ii - 1 + im->xorigin;
3216 gfx_line_fit(im, &x, &y);
3217 cairo_move_to(im->cr, x, y);
3218 x = ii + im->xorigin;
3220 gfx_line_fit(im, &x, &y);
3221 cairo_line_to(im->cr, x, y);
3223 double x = ii - 1 + im->xorigin;
3225 ytr(im, im->gdes[i].p_data[ii - 1]);
3226 gfx_line_fit(im, &x, &y);
3227 cairo_move_to(im->cr, x, y);
3228 x = ii + im->xorigin;
3230 gfx_line_fit(im, &x, &y);
3231 cairo_line_to(im->cr, x, y);
3235 double x1 = ii + im->xorigin;
3236 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3238 if (im->slopemode == 0
3239 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3240 double x = ii - 1 + im->xorigin;
3243 gfx_line_fit(im, &x, &y);
3244 cairo_line_to(im->cr, x, y);
3247 gfx_line_fit(im, &x1, &y1);
3248 cairo_line_to(im->cr, x1, y1);
3251 cairo_set_source_rgba(im->cr,
3257 col.blue, im->gdes[i].col.alpha);
3258 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3259 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3260 cairo_stroke(im->cr);
3261 cairo_restore(im->cr);
3265 (double *) malloc(sizeof(double) * im->xsize * 2);
3267 (double *) malloc(sizeof(double) * im->xsize * 2);
3269 (double *) malloc(sizeof(double) * im->xsize * 2);
3271 (double *) malloc(sizeof(double) * im->xsize * 2);
3274 for (ii = 0; ii <= im->xsize; ii++) {
3277 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3283 AlmostEqual2sComplement(foreY
3287 AlmostEqual2sComplement(foreY
3297 foreY[cntI], im->gdes[i].col);
3298 while (cntI < idxI) {
3303 AlmostEqual2sComplement(foreY
3307 AlmostEqual2sComplement(foreY
3314 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3316 gfx_add_point(im, backX[idxI], backY[idxI]);
3322 AlmostEqual2sComplement(backY
3326 AlmostEqual2sComplement(backY
3333 gfx_add_point(im, backX[idxI], backY[idxI]);
3343 if (ii == im->xsize)
3345 if (im->slopemode == 0 && ii == 0) {
3348 if (isnan(im->gdes[i].p_data[ii])) {
3352 ytop = ytr(im, im->gdes[i].p_data[ii]);
3353 if (lastgdes && im->gdes[i].stack) {
3354 ybase = ytr(im, lastgdes->p_data[ii]);
3356 ybase = ytr(im, areazero);
3358 if (ybase == ytop) {
3364 double extra = ytop;
3369 if (im->slopemode == 0) {
3370 backY[++idxI] = ybase - 0.2;
3371 backX[idxI] = ii + im->xorigin - 1;
3372 foreY[idxI] = ytop + 0.2;
3373 foreX[idxI] = ii + im->xorigin - 1;
3375 backY[++idxI] = ybase - 0.2;
3376 backX[idxI] = ii + im->xorigin;
3377 foreY[idxI] = ytop + 0.2;
3378 foreX[idxI] = ii + im->xorigin;
3380 /* close up any remaining area */
3385 } /* else GF_LINE */
3387 /* if color != 0x0 */
3388 /* make sure we do not run into trouble when stacking on NaN */
3389 for (ii = 0; ii < im->xsize; ii++) {
3390 if (isnan(im->gdes[i].p_data[ii])) {
3391 if (lastgdes && (im->gdes[i].stack)) {
3392 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3394 im->gdes[i].p_data[ii] = areazero;
3398 lastgdes = &(im->gdes[i]);
3402 ("STACK should already be turned into LINE or AREA here");
3407 cairo_reset_clip(im->cr);
3409 /* grid_paint also does the text */
3410 if (!(im->extra_flags & ONLY_GRAPH))
3412 if (!(im->extra_flags & ONLY_GRAPH))
3414 /* the RULES are the last thing to paint ... */
3415 for (i = 0; i < im->gdes_c; i++) {
3417 switch (im->gdes[i].gf) {
3419 if (im->gdes[i].yrule >= im->minval
3420 && im->gdes[i].yrule <= im->maxval) {
3422 if (im->gdes[i].dash) {
3423 cairo_set_dash(im->cr,
3424 im->gdes[i].p_dashes,
3425 im->gdes[i].ndash, im->gdes[i].offset);
3427 gfx_line(im, im->xorigin,
3428 ytr(im, im->gdes[i].yrule),
3429 im->xorigin + im->xsize,
3430 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3431 cairo_stroke(im->cr);
3432 cairo_restore(im->cr);
3436 if (im->gdes[i].xrule >= im->start
3437 && im->gdes[i].xrule <= im->end) {
3439 if (im->gdes[i].dash) {
3440 cairo_set_dash(im->cr,
3441 im->gdes[i].p_dashes,
3442 im->gdes[i].ndash, im->gdes[i].offset);
3445 xtr(im, im->gdes[i].xrule),
3446 im->yorigin, xtr(im,
3450 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3451 cairo_stroke(im->cr);
3452 cairo_restore(im->cr);
3461 switch (im->imgformat) {
3464 cairo_status_t status;
3466 status = strlen(im->graphfile) ?
3467 cairo_surface_write_to_png(im->surface, im->graphfile)
3468 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3471 if (status != CAIRO_STATUS_SUCCESS) {
3472 rrd_set_error("Could not save png to '%s'", im->graphfile);
3478 if (strlen(im->graphfile)) {
3479 cairo_show_page(im->cr);
3481 cairo_surface_finish(im->surface);
3490 /*****************************************************
3492 *****************************************************/
3499 if ((im->gdes = (graph_desc_t *)
3500 rrd_realloc(im->gdes, (im->gdes_c)
3501 * sizeof(graph_desc_t))) == NULL) {
3502 rrd_set_error("realloc graph_descs");
3507 im->gdes[im->gdes_c - 1].step = im->step;
3508 im->gdes[im->gdes_c - 1].step_orig = im->step;
3509 im->gdes[im->gdes_c - 1].stack = 0;
3510 im->gdes[im->gdes_c - 1].linewidth = 0;
3511 im->gdes[im->gdes_c - 1].debug = 0;
3512 im->gdes[im->gdes_c - 1].start = im->start;
3513 im->gdes[im->gdes_c - 1].start_orig = im->start;
3514 im->gdes[im->gdes_c - 1].end = im->end;
3515 im->gdes[im->gdes_c - 1].end_orig = im->end;
3516 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3517 im->gdes[im->gdes_c - 1].data = NULL;
3518 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3519 im->gdes[im->gdes_c - 1].data_first = 0;
3520 im->gdes[im->gdes_c - 1].p_data = NULL;
3521 im->gdes[im->gdes_c - 1].rpnp = NULL;
3522 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3523 im->gdes[im->gdes_c - 1].shift = 0.0;
3524 im->gdes[im->gdes_c - 1].dash = 0;
3525 im->gdes[im->gdes_c - 1].ndash = 0;
3526 im->gdes[im->gdes_c - 1].offset = 0;
3527 im->gdes[im->gdes_c - 1].col.red = 0.0;
3528 im->gdes[im->gdes_c - 1].col.green = 0.0;
3529 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3530 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3531 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3532 im->gdes[im->gdes_c - 1].format[0] = '\0';
3533 im->gdes[im->gdes_c - 1].strftm = 0;
3534 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3535 im->gdes[im->gdes_c - 1].ds = -1;
3536 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3537 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3538 im->gdes[im->gdes_c - 1].yrule = DNAN;
3539 im->gdes[im->gdes_c - 1].xrule = 0;
3543 /* copies input untill the first unescaped colon is found
3544 or until input ends. backslashes have to be escaped as well */
3546 const char *const input,
3552 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3553 if (input[inp] == '\\'
3554 && input[inp + 1] != '\0'
3555 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3556 output[outp++] = input[++inp];
3558 output[outp++] = input[inp];
3561 output[outp] = '\0';
3565 /* Now just a wrapper around rrd_graph_v */
3577 rrd_info_t *grinfo = NULL;
3580 grinfo = rrd_graph_v(argc, argv);
3586 if (strcmp(walker->key, "image_info") == 0) {
3589 rrd_realloc((*prdata),
3590 (prlines + 1) * sizeof(char *))) == NULL) {
3591 rrd_set_error("realloc prdata");
3594 /* imginfo goes to position 0 in the prdata array */
3595 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3596 + 2) * sizeof(char));
3597 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3598 (*prdata)[prlines] = NULL;
3600 /* skip anything else */
3601 walker = walker->next;
3609 if (strcmp(walker->key, "image_width") == 0) {
3610 *xsize = walker->value.u_int;
3611 } else if (strcmp(walker->key, "image_height") == 0) {
3612 *ysize = walker->value.u_int;
3613 } else if (strcmp(walker->key, "value_min") == 0) {
3614 *ymin = walker->value.u_val;
3615 } else if (strcmp(walker->key, "value_max") == 0) {
3616 *ymax = walker->value.u_val;
3617 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3620 rrd_realloc((*prdata),
3621 (prlines + 1) * sizeof(char *))) == NULL) {
3622 rrd_set_error("realloc prdata");
3625 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3626 + 2) * sizeof(char));
3627 (*prdata)[prlines] = NULL;
3628 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3629 } else if (strcmp(walker->key, "image") == 0) {
3630 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3631 (stream ? stream : stdout));
3633 /* skip anything else */
3634 walker = walker->next;
3636 rrd_info_free(grinfo);
3641 /* Some surgery done on this function, it became ridiculously big.
3643 ** - initializing now in rrd_graph_init()
3644 ** - options parsing now in rrd_graph_options()
3645 ** - script parsing now in rrd_graph_script()
3647 rrd_info_t *rrd_graph_v(
3654 rrd_graph_init(&im);
3655 /* a dummy surface so that we can measure text sizes for placements */
3656 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3657 im.cr = cairo_create(im.surface);
3658 rrd_graph_options(argc, argv, &im);
3659 if (rrd_test_error()) {
3660 rrd_info_free(im.grinfo);
3665 if (optind >= argc) {
3666 rrd_info_free(im.grinfo);
3668 rrd_set_error("missing filename");
3672 if (strlen(argv[optind]) >= MAXPATH) {
3673 rrd_set_error("filename (including path) too long");
3674 rrd_info_free(im.grinfo);
3679 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3680 im.graphfile[MAXPATH - 1] = '\0';
3682 if (strcmp(im.graphfile, "-") == 0) {
3683 im.graphfile[0] = '\0';
3686 rrd_graph_script(argc, argv, &im, 1);
3687 if (rrd_test_error()) {
3688 rrd_info_free(im.grinfo);
3693 /* Everything is now read and the actual work can start */
3695 if (graph_paint(&im) == -1) {
3696 rrd_info_free(im.grinfo);
3702 /* The image is generated and needs to be output.
3703 ** Also, if needed, print a line with information about the image.
3710 sprintf_alloc(im.imginfo,
3713 im.ximg), (long) (im.zoom * im.yimg));
3714 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3717 if (im.rendered_image) {
3720 img.u_blo.size = im.rendered_image_size;
3721 img.u_blo.ptr = im.rendered_image;
3722 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3729 void rrd_graph_init(
3738 #ifdef HAVE_SETLOCALE
3739 setlocale(LC_TIME, "");
3740 #ifdef HAVE_MBSTOWCS
3741 setlocale(LC_CTYPE, "");
3746 im->draw_x_grid = 1;
3747 im->draw_y_grid = 1;
3748 im->extra_flags = 0;
3749 im->font_options = cairo_font_options_create();
3750 im->forceleftspace = 0;
3753 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3754 im->grid_dash_off = 1;
3755 im->grid_dash_on = 1;
3757 im->grinfo = (rrd_info_t *) NULL;
3758 im->grinfo_current = (rrd_info_t *) NULL;
3759 im->imgformat = IF_PNG;
3761 im->use_rrdcached = 0;
3763 im->logarithmic = 0;
3769 im->rendered_image_size = 0;
3770 im->rendered_image = NULL;
3775 im->tabwidth = 40.0;
3776 im->title[0] = '\0';
3777 im->unitsexponent = 9999;
3778 im->unitslength = 6;
3779 im->viewfactor = 1.0;
3780 im->watermark[0] = '\0';
3781 im->with_markup = 0;
3783 im->xlab_user.minsec = -1;
3786 im->ygridstep = DNAN;
3788 im->ylegend[0] = '\0';
3792 cairo_font_options_set_hint_style
3793 (im->font_options, CAIRO_HINT_STYLE_FULL);
3794 cairo_font_options_set_hint_metrics
3795 (im->font_options, CAIRO_HINT_METRICS_ON);
3796 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3797 for (i = 0; i < DIM(graph_col); i++)
3798 im->graph_col[i] = graph_col[i];
3802 deffont = getenv("RRD_DEFAULT_FONT");
3803 if (deffont != NULL) {
3804 for (i = 0; i < DIM(text_prop); i++) {
3805 strncpy(text_prop[i].font, deffont,
3806 sizeof(text_prop[i].font) - 1);
3807 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3811 for (i = 0; i < DIM(text_prop); i++) {
3812 im->text_prop[i].size = text_prop[i].size;
3813 strcpy(im->text_prop[i].font, text_prop[i].font);
3817 void rrd_graph_options(
3824 char *parsetime_error = NULL;
3825 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3826 time_t start_tmp = 0, end_tmp = 0;
3828 rrd_time_value_t start_tv, end_tv;
3829 long unsigned int color;
3830 char *old_locale = "";
3832 /* defines for long options without a short equivalent. should be bytes,
3833 and may not collide with (the ASCII value of) short options */
3834 #define LONGOPT_UNITS_SI 255
3837 struct option long_options[] = {
3838 { "start", required_argument, 0, 's'},
3839 { "end", required_argument, 0, 'e'},
3840 { "x-grid", required_argument, 0, 'x'},
3841 { "y-grid", required_argument, 0, 'y'},
3842 { "vertical-label", required_argument, 0, 'v'},
3843 { "width", required_argument, 0, 'w'},
3844 { "height", required_argument, 0, 'h'},
3845 { "full-size-mode", no_argument, 0, 'D'},
3846 { "interlaced", no_argument, 0, 'i'},
3847 { "upper-limit", required_argument, 0, 'u'},
3848 { "lower-limit", required_argument, 0, 'l'},
3849 { "rigid", no_argument, 0, 'r'},
3850 { "base", required_argument, 0, 'b'},
3851 { "logarithmic", no_argument, 0, 'o'},
3852 { "color", required_argument, 0, 'c'},
3853 { "font", required_argument, 0, 'n'},
3854 { "title", required_argument, 0, 't'},
3855 { "imginfo", required_argument, 0, 'f'},
3856 { "imgformat", required_argument, 0, 'a'},
3857 { "lazy", no_argument, 0, 'z'},
3858 { "zoom", required_argument, 0, 'm'},
3859 { "no-legend", no_argument, 0, 'g'},
3860 { "force-rules-legend", no_argument, 0, 'F'},
3861 { "only-graph", no_argument, 0, 'j'},
3862 { "alt-y-grid", no_argument, 0, 'Y'},
3863 { "no-minor", no_argument, 0, 'I'},
3864 { "slope-mode", no_argument, 0, 'E'},
3865 { "alt-autoscale", no_argument, 0, 'A'},
3866 { "alt-autoscale-min", no_argument, 0, 'J'},
3867 { "alt-autoscale-max", no_argument, 0, 'M'},
3868 { "no-gridfit", no_argument, 0, 'N'},
3869 { "units-exponent", required_argument, 0, 'X'},
3870 { "units-length", required_argument, 0, 'L'},
3871 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3872 { "step", required_argument, 0, 'S'},
3873 { "tabwidth", required_argument, 0, 'T'},
3874 { "font-render-mode", required_argument, 0, 'R'},
3875 { "graph-render-mode", required_argument, 0, 'G'},
3876 { "font-smoothing-threshold", required_argument, 0, 'B'},
3877 { "watermark", required_argument, 0, 'W'},
3878 { "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 */
3879 { "pango-markup", no_argument, 0, 'P'},
3880 { "daemon", required_argument, 0, 'd'},
3886 opterr = 0; /* initialize getopt */
3887 rrd_parsetime("end-24h", &start_tv);
3888 rrd_parsetime("now", &end_tv);
3890 int option_index = 0;
3892 int col_start, col_end;
3894 opt = getopt_long(argc, argv,
3895 "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:",
3896 long_options, &option_index);
3901 im->extra_flags |= NOMINOR;
3904 im->extra_flags |= ALTYGRID;
3907 im->extra_flags |= ALTAUTOSCALE;
3910 im->extra_flags |= ALTAUTOSCALE_MIN;
3913 im->extra_flags |= ALTAUTOSCALE_MAX;
3916 im->extra_flags |= ONLY_GRAPH;
3919 im->extra_flags |= NOLEGEND;
3922 im->extra_flags |= FORCE_RULES_LEGEND;
3924 case LONGOPT_UNITS_SI:
3925 if (im->extra_flags & FORCE_UNITS) {
3926 rrd_set_error("--units can only be used once!");
3927 setlocale(LC_NUMERIC, old_locale);
3930 if (strcmp(optarg, "si") == 0)
3931 im->extra_flags |= FORCE_UNITS_SI;
3933 rrd_set_error("invalid argument for --units: %s", optarg);
3938 im->unitsexponent = atoi(optarg);
3941 im->unitslength = atoi(optarg);
3942 im->forceleftspace = 1;
3945 old_locale = setlocale(LC_NUMERIC, "C");
3946 im->tabwidth = atof(optarg);
3947 setlocale(LC_NUMERIC, old_locale);
3950 old_locale = setlocale(LC_NUMERIC, "C");
3951 im->step = atoi(optarg);
3952 setlocale(LC_NUMERIC, old_locale);
3958 im->with_markup = 1;
3961 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3962 rrd_set_error("start time: %s", parsetime_error);
3967 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3968 rrd_set_error("end time: %s", parsetime_error);
3973 if (strcmp(optarg, "none") == 0) {
3974 im->draw_x_grid = 0;
3978 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3980 &im->xlab_user.gridst,
3982 &im->xlab_user.mgridst,
3984 &im->xlab_user.labst,
3985 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3986 strncpy(im->xlab_form, optarg + stroff,
3987 sizeof(im->xlab_form) - 1);
3988 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3990 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3991 rrd_set_error("unknown keyword %s", scan_gtm);
3994 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3996 rrd_set_error("unknown keyword %s", scan_mtm);
3999 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4000 rrd_set_error("unknown keyword %s", scan_ltm);
4003 im->xlab_user.minsec = 1;
4004 im->xlab_user.stst = im->xlab_form;
4006 rrd_set_error("invalid x-grid format");
4012 if (strcmp(optarg, "none") == 0) {
4013 im->draw_y_grid = 0;
4016 old_locale = setlocale(LC_NUMERIC, "C");
4017 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4018 setlocale(LC_NUMERIC, old_locale);
4019 if (im->ygridstep <= 0) {
4020 rrd_set_error("grid step must be > 0");
4022 } else if (im->ylabfact < 1) {
4023 rrd_set_error("label factor must be > 0");
4027 setlocale(LC_NUMERIC, old_locale);
4028 rrd_set_error("invalid y-grid format");
4033 strncpy(im->ylegend, optarg, 150);
4034 im->ylegend[150] = '\0';
4037 old_locale = setlocale(LC_NUMERIC, "C");
4038 im->maxval = atof(optarg);
4039 setlocale(LC_NUMERIC, old_locale);
4042 old_locale = setlocale(LC_NUMERIC, "C");
4043 im->minval = atof(optarg);
4044 setlocale(LC_NUMERIC, old_locale);
4047 im->base = atol(optarg);
4048 if (im->base != 1024 && im->base != 1000) {
4050 ("the only sensible value for base apart from 1000 is 1024");
4055 long_tmp = atol(optarg);
4056 if (long_tmp < 10) {
4057 rrd_set_error("width below 10 pixels");
4060 im->xsize = long_tmp;
4063 long_tmp = atol(optarg);
4064 if (long_tmp < 10) {
4065 rrd_set_error("height below 10 pixels");
4068 im->ysize = long_tmp;
4071 im->extra_flags |= FULL_SIZE_MODE;
4074 /* interlaced png not supported at the moment */
4080 im->imginfo = optarg;
4084 (im->imgformat = if_conv(optarg)) == -1) {
4085 rrd_set_error("unsupported graphics format '%s'", optarg);
4096 im->logarithmic = 1;
4100 "%10[A-Z]#%n%8lx%n",
4101 col_nam, &col_start, &color, &col_end) == 2) {
4103 int col_len = col_end - col_start;
4108 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4116 (((color & 0xF000) *
4117 0x11000) | ((color & 0x0F00) *
4118 0x01100) | ((color &
4121 ((color & 0x000F) * 0x00011)
4125 color = (color << 8) + 0xff /* shift left by 8 */ ;
4130 rrd_set_error("the color format is #RRGGBB[AA]");
4133 if ((ci = grc_conv(col_nam)) != -1) {
4134 im->graph_col[ci] = gfx_hex_to_col(color);
4136 rrd_set_error("invalid color name '%s'", col_nam);
4140 rrd_set_error("invalid color def format");
4149 old_locale = setlocale(LC_NUMERIC, "C");
4150 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4151 int sindex, propidx;
4153 setlocale(LC_NUMERIC, old_locale);
4154 if ((sindex = text_prop_conv(prop)) != -1) {
4155 for (propidx = sindex;
4156 propidx < TEXT_PROP_LAST; propidx++) {
4158 im->text_prop[propidx].size = size;
4160 if ((int) strlen(optarg) > end) {
4161 if (optarg[end] == ':') {
4162 strncpy(im->text_prop[propidx].font,
4163 optarg + end + 1, 255);
4164 im->text_prop[propidx].font[255] = '\0';
4167 ("expected : after font size in '%s'",
4172 /* only run the for loop for DEFAULT (0) for
4173 all others, we break here. woodo programming */
4174 if (propidx == sindex && sindex != 0)
4178 rrd_set_error("invalid fonttag '%s'", prop);
4182 setlocale(LC_NUMERIC, old_locale);
4183 rrd_set_error("invalid text property format");
4189 old_locale = setlocale(LC_NUMERIC, "C");
4190 im->zoom = atof(optarg);
4191 setlocale(LC_NUMERIC, old_locale);
4192 if (im->zoom <= 0.0) {
4193 rrd_set_error("zoom factor must be > 0");
4198 strncpy(im->title, optarg, 150);
4199 im->title[150] = '\0';
4202 if (strcmp(optarg, "normal") == 0) {
4203 cairo_font_options_set_antialias
4204 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4205 cairo_font_options_set_hint_style
4206 (im->font_options, CAIRO_HINT_STYLE_FULL);
4207 } else if (strcmp(optarg, "light") == 0) {
4208 cairo_font_options_set_antialias
4209 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4210 cairo_font_options_set_hint_style
4211 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4212 } else if (strcmp(optarg, "mono") == 0) {
4213 cairo_font_options_set_antialias
4214 (im->font_options, CAIRO_ANTIALIAS_NONE);
4215 cairo_font_options_set_hint_style
4216 (im->font_options, CAIRO_HINT_STYLE_FULL);
4218 rrd_set_error("unknown font-render-mode '%s'", optarg);
4223 if (strcmp(optarg, "normal") == 0)
4224 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4225 else if (strcmp(optarg, "mono") == 0)
4226 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4228 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4233 /* not supported curently */
4236 strncpy(im->watermark, optarg, 100);
4237 im->watermark[99] = '\0';
4242 if (im->use_rrdcached)
4244 rrd_set_error ("You cannot specify --daemon "
4248 status = rrdc_connect (optarg);
4251 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4255 im->use_rrdcached = 1;
4260 rrd_set_error("unknown option '%c'", optopt);
4262 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4267 if (im->use_rrdcached == 0)
4271 temp = getenv (ENV_RRDCACHED_ADDRESS);
4276 status = rrdc_connect (temp);
4279 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4283 im->use_rrdcached = 1;
4287 if (im->logarithmic && im->minval <= 0) {
4289 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4293 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4294 /* error string is set in rrd_parsetime.c */
4298 if (start_tmp < 3600 * 24 * 365 * 10) {
4300 ("the first entry to fetch should be after 1980 (%ld)",
4305 if (end_tmp < start_tmp) {
4307 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4311 im->start = start_tmp;
4313 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4316 int rrd_graph_color(
4324 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4326 color = strstr(var, "#");
4327 if (color == NULL) {
4328 if (optional == 0) {
4329 rrd_set_error("Found no color in %s", err);
4336 long unsigned int col;
4338 rest = strstr(color, ":");
4345 sscanf(color, "#%6lx%n", &col, &n);
4346 col = (col << 8) + 0xff /* shift left by 8 */ ;
4348 rrd_set_error("Color problem in %s", err);
4351 sscanf(color, "#%8lx%n", &col, &n);
4355 rrd_set_error("Color problem in %s", err);
4357 if (rrd_test_error())
4359 gdp->col = gfx_hex_to_col(col);
4372 while (*ptr != '\0')
4373 if (*ptr++ == '%') {
4375 /* line cannot end with percent char */
4378 /* '%s', '%S' and '%%' are allowed */
4379 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4381 /* %c is allowed (but use only with vdef!) */
4382 else if (*ptr == 'c') {
4387 /* or else '% 6.2lf' and such are allowed */
4389 /* optional padding character */
4390 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4392 /* This should take care of 'm.n' with all three optional */
4393 while (*ptr >= '0' && *ptr <= '9')
4397 while (*ptr >= '0' && *ptr <= '9')
4399 /* Either 'le', 'lf' or 'lg' must follow here */
4402 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4417 const char *const str)
4419 /* A VDEF currently is either "func" or "param,func"
4420 * so the parsing is rather simple. Change if needed.
4428 old_locale = setlocale(LC_NUMERIC, "C");
4429 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4430 setlocale(LC_NUMERIC, old_locale);
4431 if (n == (int) strlen(str)) { /* matched */
4435 sscanf(str, "%29[A-Z]%n", func, &n);
4436 if (n == (int) strlen(str)) { /* matched */
4440 ("Unknown function string '%s' in VDEF '%s'",
4445 if (!strcmp("PERCENT", func))
4446 gdes->vf.op = VDEF_PERCENT;
4447 else if (!strcmp("MAXIMUM", func))
4448 gdes->vf.op = VDEF_MAXIMUM;
4449 else if (!strcmp("AVERAGE", func))
4450 gdes->vf.op = VDEF_AVERAGE;
4451 else if (!strcmp("STDEV", func))
4452 gdes->vf.op = VDEF_STDEV;
4453 else if (!strcmp("MINIMUM", func))
4454 gdes->vf.op = VDEF_MINIMUM;
4455 else if (!strcmp("TOTAL", func))
4456 gdes->vf.op = VDEF_TOTAL;
4457 else if (!strcmp("FIRST", func))
4458 gdes->vf.op = VDEF_FIRST;
4459 else if (!strcmp("LAST", func))
4460 gdes->vf.op = VDEF_LAST;
4461 else if (!strcmp("LSLSLOPE", func))
4462 gdes->vf.op = VDEF_LSLSLOPE;
4463 else if (!strcmp("LSLINT", func))
4464 gdes->vf.op = VDEF_LSLINT;
4465 else if (!strcmp("LSLCORREL", func))
4466 gdes->vf.op = VDEF_LSLCORREL;
4469 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4472 switch (gdes->vf.op) {
4474 if (isnan(param)) { /* no parameter given */
4476 ("Function '%s' needs parameter in VDEF '%s'\n",
4480 if (param >= 0.0 && param <= 100.0) {
4481 gdes->vf.param = param;
4482 gdes->vf.val = DNAN; /* undefined */
4483 gdes->vf.when = 0; /* undefined */
4486 ("Parameter '%f' out of range in VDEF '%s'\n",
4487 param, gdes->vname);
4500 case VDEF_LSLCORREL:
4502 gdes->vf.param = DNAN;
4503 gdes->vf.val = DNAN;
4507 ("Function '%s' needs no parameter in VDEF '%s'\n",
4521 graph_desc_t *src, *dst;
4526 dst = &im->gdes[gdi];
4527 src = &im->gdes[dst->vidx];
4528 data = src->data + src->ds;
4530 src->end_orig % (long)src->step ==
4531 0 ? src->end_orig : (src->end_orig + (long)src->step -
4532 src->end_orig % (long)src->step);
4534 steps = (end - src->start) / src->step;
4537 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4538 src->start, src->end_orig, steps);
4540 switch (dst->vf.op) {
4544 if ((array = malloc(steps * sizeof(double))) == NULL) {
4545 rrd_set_error("malloc VDEV_PERCENT");
4548 for (step = 0; step < steps; step++) {
4549 array[step] = data[step * src->ds_cnt];
4551 qsort(array, step, sizeof(double), vdef_percent_compar);
4552 field = (steps - 1) * dst->vf.param / 100;
4553 dst->vf.val = array[field];
4554 dst->vf.when = 0; /* no time component */
4557 for (step = 0; step < steps; step++)
4558 printf("DEBUG: %3li:%10.2f %c\n",
4559 step, array[step], step == field ? '*' : ' ');
4565 while (step != steps && isnan(data[step * src->ds_cnt]))
4567 if (step == steps) {
4571 dst->vf.val = data[step * src->ds_cnt];
4572 dst->vf.when = src->start + (step + 1) * src->step;
4574 while (step != steps) {
4575 if (finite(data[step * src->ds_cnt])) {
4576 if (data[step * src->ds_cnt] > dst->vf.val) {
4577 dst->vf.val = data[step * src->ds_cnt];
4578 dst->vf.when = src->start + (step + 1) * src->step;
4589 double average = 0.0;
4591 for (step = 0; step < steps; step++) {
4592 if (finite(data[step * src->ds_cnt])) {
4593 sum += data[step * src->ds_cnt];
4598 if (dst->vf.op == VDEF_TOTAL) {
4599 dst->vf.val = sum * src->step;
4600 dst->vf.when = 0; /* no time component */
4601 } else if (dst->vf.op == VDEF_AVERAGE) {
4602 dst->vf.val = sum / cnt;
4603 dst->vf.when = 0; /* no time component */
4605 average = sum / cnt;
4607 for (step = 0; step < steps; step++) {
4608 if (finite(data[step * src->ds_cnt])) {
4609 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4612 dst->vf.val = pow(sum / cnt, 0.5);
4613 dst->vf.when = 0; /* no time component */
4623 while (step != steps && isnan(data[step * src->ds_cnt]))
4625 if (step == steps) {
4629 dst->vf.val = data[step * src->ds_cnt];
4630 dst->vf.when = src->start + (step + 1) * src->step;
4632 while (step != steps) {
4633 if (finite(data[step * src->ds_cnt])) {
4634 if (data[step * src->ds_cnt] < dst->vf.val) {
4635 dst->vf.val = data[step * src->ds_cnt];
4636 dst->vf.when = src->start + (step + 1) * src->step;
4643 /* The time value returned here is one step before the
4644 * actual time value. This is the start of the first
4648 while (step != steps && isnan(data[step * src->ds_cnt]))
4650 if (step == steps) { /* all entries were NaN */
4654 dst->vf.val = data[step * src->ds_cnt];
4655 dst->vf.when = src->start + step * src->step;
4659 /* The time value returned here is the
4660 * actual time value. This is the end of the last
4664 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4666 if (step < 0) { /* all entries were NaN */
4670 dst->vf.val = data[step * src->ds_cnt];
4671 dst->vf.when = src->start + (step + 1) * src->step;
4676 case VDEF_LSLCORREL:{
4677 /* Bestfit line by linear least squares method */
4680 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4687 for (step = 0; step < steps; step++) {
4688 if (finite(data[step * src->ds_cnt])) {
4691 SUMxx += step * step;
4692 SUMxy += step * data[step * src->ds_cnt];
4693 SUMy += data[step * src->ds_cnt];
4694 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4698 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4699 y_intercept = (SUMy - slope * SUMx) / cnt;
4702 (SUMx * SUMy) / cnt) /
4704 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4706 if (dst->vf.op == VDEF_LSLSLOPE) {
4707 dst->vf.val = slope;
4709 } else if (dst->vf.op == VDEF_LSLINT) {
4710 dst->vf.val = y_intercept;
4712 } else if (dst->vf.op == VDEF_LSLCORREL) {
4713 dst->vf.val = correl;
4726 /* NaN < -INF < finite_values < INF */
4727 int vdef_percent_compar(
4733 /* Equality is not returned; this doesn't hurt except
4734 * (maybe) for a little performance.
4737 /* First catch NaN values. They are smallest */
4738 if (isnan(*(double *) a))
4740 if (isnan(*(double *) b))
4742 /* NaN doesn't reach this part so INF and -INF are extremes.
4743 * The sign from isinf() is compatible with the sign we return
4745 if (isinf(*(double *) a))
4746 return isinf(*(double *) a);
4747 if (isinf(*(double *) b))
4748 return isinf(*(double *) b);
4749 /* If we reach this, both values must be finite */
4750 if (*(double *) a < *(double *) b)
4759 rrd_info_type_t type,
4760 rrd_infoval_t value)
4762 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4763 if (im->grinfo == NULL) {
4764 im->grinfo = im->grinfo_current;