1 /****************************************************************************
2 * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
28 #include "rrd_graph.h"
29 #include "rrd_client.h"
31 /* some constant definitions */
35 #ifndef RRD_DEFAULT_FONT
36 /* there is special code later to pick Cour.ttf when running on windows */
37 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
40 text_prop_t text_prop[] = {
41 {8.0, RRD_DEFAULT_FONT,NULL}
43 {9.0, RRD_DEFAULT_FONT,NULL}
45 {7.0, RRD_DEFAULT_FONT,NULL}
47 {8.0, RRD_DEFAULT_FONT,NULL}
49 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
51 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
55 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
57 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
59 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
61 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
63 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
65 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
67 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
69 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
71 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
73 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
74 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
76 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
78 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
80 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
82 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
84 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
87 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
90 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
93 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
95 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
96 365 * 24 * 3600, "%y"}
98 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
101 /* sensible y label intervals ...*/
125 {20.0, {1, 5, 10, 20}
131 {100.0, {1, 2, 5, 10}
134 {200.0, {1, 5, 10, 20}
137 {500.0, {1, 2, 4, 10}
145 gfx_color_t graph_col[] = /* default colors */
147 {1.00, 1.00, 1.00, 1.00}, /* canvas */
148 {0.95, 0.95, 0.95, 1.00}, /* background */
149 {0.81, 0.81, 0.81, 1.00}, /* shade A */
150 {0.62, 0.62, 0.62, 1.00}, /* shade B */
151 {0.56, 0.56, 0.56, 0.75}, /* grid */
152 {0.87, 0.31, 0.31, 0.60}, /* major grid */
153 {0.00, 0.00, 0.00, 1.00}, /* font */
154 {0.50, 0.12, 0.12, 1.00}, /* arrow */
155 {0.12, 0.12, 0.12, 1.00}, /* axis */
156 {0.00, 0.00, 0.00, 1.00} /* frame */
163 # define DPRINT(x) (void)(printf x, printf("\n"))
169 /* initialize with xtr(im,0); */
177 pixie = (double) im->xsize / (double) (im->end - im->start);
180 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
183 /* translate data values into y coordinates */
192 if (!im->logarithmic)
193 pixie = (double) im->ysize / (im->maxval - im->minval);
196 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
198 } else if (!im->logarithmic) {
199 yval = im->yorigin - pixie * (value - im->minval);
201 if (value < im->minval) {
204 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
212 /* conversion function for symbolic entry names */
215 #define conv_if(VV,VVV) \
216 if (strcmp(#VV, string) == 0) return VVV ;
222 conv_if(PRINT, GF_PRINT);
223 conv_if(GPRINT, GF_GPRINT);
224 conv_if(COMMENT, GF_COMMENT);
225 conv_if(HRULE, GF_HRULE);
226 conv_if(VRULE, GF_VRULE);
227 conv_if(LINE, GF_LINE);
228 conv_if(AREA, GF_AREA);
229 conv_if(STACK, GF_STACK);
230 conv_if(TICK, GF_TICK);
231 conv_if(TEXTALIGN, GF_TEXTALIGN);
232 conv_if(DEF, GF_DEF);
233 conv_if(CDEF, GF_CDEF);
234 conv_if(VDEF, GF_VDEF);
235 conv_if(XPORT, GF_XPORT);
236 conv_if(SHIFT, GF_SHIFT);
241 enum gfx_if_en if_conv(
245 conv_if(PNG, IF_PNG);
246 conv_if(SVG, IF_SVG);
247 conv_if(EPS, IF_EPS);
248 conv_if(PDF, IF_PDF);
253 enum tmt_en tmt_conv(
257 conv_if(SECOND, TMT_SECOND);
258 conv_if(MINUTE, TMT_MINUTE);
259 conv_if(HOUR, TMT_HOUR);
260 conv_if(DAY, TMT_DAY);
261 conv_if(WEEK, TMT_WEEK);
262 conv_if(MONTH, TMT_MONTH);
263 conv_if(YEAR, TMT_YEAR);
267 enum grc_en grc_conv(
271 conv_if(BACK, GRC_BACK);
272 conv_if(CANVAS, GRC_CANVAS);
273 conv_if(SHADEA, GRC_SHADEA);
274 conv_if(SHADEB, GRC_SHADEB);
275 conv_if(GRID, GRC_GRID);
276 conv_if(MGRID, GRC_MGRID);
277 conv_if(FONT, GRC_FONT);
278 conv_if(ARROW, GRC_ARROW);
279 conv_if(AXIS, GRC_AXIS);
280 conv_if(FRAME, GRC_FRAME);
285 enum text_prop_en text_prop_conv(
289 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
290 conv_if(TITLE, TEXT_PROP_TITLE);
291 conv_if(AXIS, TEXT_PROP_AXIS);
292 conv_if(UNIT, TEXT_PROP_UNIT);
293 conv_if(LEGEND, TEXT_PROP_LEGEND);
294 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
305 cairo_status_t status = 0;
310 if (im->use_rrdcached)
313 im->use_rrdcached = 0;
316 for (i = 0; i < (unsigned) im->gdes_c; i++) {
317 if (im->gdes[i].data_first) {
318 /* careful here, because a single pointer can occur several times */
319 free(im->gdes[i].data);
320 if (im->gdes[i].ds_namv) {
321 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
322 free(im->gdes[i].ds_namv[ii]);
323 free(im->gdes[i].ds_namv);
326 /* free allocated memory used for dashed lines */
327 if (im->gdes[i].p_dashes != NULL)
328 free(im->gdes[i].p_dashes);
330 free(im->gdes[i].p_data);
331 free(im->gdes[i].rpnp);
334 if (im->font_options)
335 cairo_font_options_destroy(im->font_options);
338 status = cairo_status(im->cr);
339 cairo_destroy(im->cr);
341 if (im->rendered_image) {
342 free(im->rendered_image);
346 g_object_unref (im->layout);
350 cairo_surface_destroy(im->surface);
353 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
354 cairo_status_to_string(status));
359 /* find SI magnitude symbol for the given number*/
361 image_desc_t *im, /* image description */
367 char *symbol[] = { "a", /* 10e-18 Atto */
368 "f", /* 10e-15 Femto */
369 "p", /* 10e-12 Pico */
370 "n", /* 10e-9 Nano */
371 "u", /* 10e-6 Micro */
372 "m", /* 10e-3 Milli */
377 "T", /* 10e12 Tera */
378 "P", /* 10e15 Peta */
385 if (*value == 0.0 || isnan(*value)) {
389 sindex = floor(log(fabs(*value)) / log((double) im->base));
390 *magfact = pow((double) im->base, (double) sindex);
391 (*value) /= (*magfact);
393 if (sindex <= symbcenter && sindex >= -symbcenter) {
394 (*symb_ptr) = symbol[sindex + symbcenter];
401 static char si_symbol[] = {
402 'a', /* 10e-18 Atto */
403 'f', /* 10e-15 Femto */
404 'p', /* 10e-12 Pico */
405 'n', /* 10e-9 Nano */
406 'u', /* 10e-6 Micro */
407 'm', /* 10e-3 Milli */
412 'T', /* 10e12 Tera */
413 'P', /* 10e15 Peta */
416 static const int si_symbcenter = 6;
418 /* find SI magnitude symbol for the numbers on the y-axis*/
420 image_desc_t *im /* image description */
424 double digits, viewdigits = 0;
427 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
428 log((double) im->base));
430 if (im->unitsexponent != 9999) {
431 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
432 viewdigits = floor(im->unitsexponent / 3);
437 im->magfact = pow((double) im->base, digits);
440 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
443 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
445 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
446 ((viewdigits + si_symbcenter) >= 0))
447 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
452 /* move min and max values around to become sensible */
457 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
458 600.0, 500.0, 400.0, 300.0, 250.0,
459 200.0, 125.0, 100.0, 90.0, 80.0,
460 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
461 25.0, 20.0, 10.0, 9.0, 8.0,
462 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
463 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
464 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
467 double scaled_min, scaled_max;
474 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
475 im->minval, im->maxval, im->magfact);
478 if (isnan(im->ygridstep)) {
479 if (im->extra_flags & ALTAUTOSCALE) {
480 /* measure the amplitude of the function. Make sure that
481 graph boundaries are slightly higher then max/min vals
482 so we can see amplitude on the graph */
485 delt = im->maxval - im->minval;
487 fact = 2.0 * pow(10.0,
489 (max(fabs(im->minval), fabs(im->maxval)) /
492 adj = (fact - delt) * 0.55;
495 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
496 im->minval, im->maxval, delt, fact, adj);
501 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
502 /* measure the amplitude of the function. Make sure that
503 graph boundaries are slightly lower than min vals
504 so we can see amplitude on the graph */
505 adj = (im->maxval - im->minval) * 0.1;
507 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
508 /* measure the amplitude of the function. Make sure that
509 graph boundaries are slightly higher than max vals
510 so we can see amplitude on the graph */
511 adj = (im->maxval - im->minval) * 0.1;
514 scaled_min = im->minval / im->magfact;
515 scaled_max = im->maxval / im->magfact;
517 for (i = 1; sensiblevalues[i] > 0; i++) {
518 if (sensiblevalues[i - 1] >= scaled_min &&
519 sensiblevalues[i] <= scaled_min)
520 im->minval = sensiblevalues[i] * (im->magfact);
522 if (-sensiblevalues[i - 1] <= scaled_min &&
523 -sensiblevalues[i] >= scaled_min)
524 im->minval = -sensiblevalues[i - 1] * (im->magfact);
526 if (sensiblevalues[i - 1] >= scaled_max &&
527 sensiblevalues[i] <= scaled_max)
528 im->maxval = sensiblevalues[i - 1] * (im->magfact);
530 if (-sensiblevalues[i - 1] <= scaled_max &&
531 -sensiblevalues[i] >= scaled_max)
532 im->maxval = -sensiblevalues[i] * (im->magfact);
536 /* adjust min and max to the grid definition if there is one */
537 im->minval = (double) im->ylabfact * im->ygridstep *
538 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
539 im->maxval = (double) im->ylabfact * im->ygridstep *
540 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
544 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
545 im->minval, im->maxval, im->magfact);
553 if (isnan(im->minval) || isnan(im->maxval))
556 if (im->logarithmic) {
557 double ya, yb, ypix, ypixfrac;
558 double log10_range = log10(im->maxval) - log10(im->minval);
560 ya = pow((double) 10, floor(log10(im->minval)));
561 while (ya < im->minval)
564 return; /* don't have y=10^x gridline */
566 if (yb <= im->maxval) {
567 /* we have at least 2 y=10^x gridlines.
568 Make sure distance between them in pixels
569 are an integer by expanding im->maxval */
570 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
571 double factor = y_pixel_delta / floor(y_pixel_delta);
572 double new_log10_range = factor * log10_range;
573 double new_ymax_log10 = log10(im->minval) + new_log10_range;
575 im->maxval = pow(10, new_ymax_log10);
576 ytr(im, DNAN); /* reset precalc */
577 log10_range = log10(im->maxval) - log10(im->minval);
579 /* make sure first y=10^x gridline is located on
580 integer pixel position by moving scale slightly
581 downwards (sub-pixel movement) */
582 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
583 ypixfrac = ypix - floor(ypix);
584 if (ypixfrac > 0 && ypixfrac < 1) {
585 double yfrac = ypixfrac / im->ysize;
587 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
588 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
589 ytr(im, DNAN); /* reset precalc */
592 /* Make sure we have an integer pixel distance between
593 each minor gridline */
594 double ypos1 = ytr(im, im->minval);
595 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
596 double y_pixel_delta = ypos1 - ypos2;
597 double factor = y_pixel_delta / floor(y_pixel_delta);
598 double new_range = factor * (im->maxval - im->minval);
599 double gridstep = im->ygrid_scale.gridstep;
600 double minor_y, minor_y_px, minor_y_px_frac;
602 if (im->maxval > 0.0)
603 im->maxval = im->minval + new_range;
605 im->minval = im->maxval - new_range;
606 ytr(im, DNAN); /* reset precalc */
607 /* make sure first minor gridline is on integer pixel y coord */
608 minor_y = gridstep * floor(im->minval / gridstep);
609 while (minor_y < im->minval)
611 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
612 minor_y_px_frac = minor_y_px - floor(minor_y_px);
613 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
614 double yfrac = minor_y_px_frac / im->ysize;
615 double range = im->maxval - im->minval;
617 im->minval = im->minval - yfrac * range;
618 im->maxval = im->maxval - yfrac * range;
619 ytr(im, DNAN); /* reset precalc */
621 calc_horizontal_grid(im); /* recalc with changed im->maxval */
625 /* reduce data reimplementation by Alex */
628 enum cf_en cf, /* which consolidation function ? */
629 unsigned long cur_step, /* step the data currently is in */
630 time_t *start, /* start, end and step as requested ... */
631 time_t *end, /* ... by the application will be ... */
632 unsigned long *step, /* ... adjusted to represent reality */
633 unsigned long *ds_cnt, /* number of data sources in file */
635 { /* two dimensional array containing the data */
636 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
637 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
639 rrd_value_t *srcptr, *dstptr;
641 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
644 row_cnt = ((*end) - (*start)) / cur_step;
650 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
651 row_cnt, reduce_factor, *start, *end, cur_step);
652 for (col = 0; col < row_cnt; col++) {
653 printf("time %10lu: ", *start + (col + 1) * cur_step);
654 for (i = 0; i < *ds_cnt; i++)
655 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
660 /* We have to combine [reduce_factor] rows of the source
661 ** into one row for the destination. Doing this we also
662 ** need to take care to combine the correct rows. First
663 ** alter the start and end time so that they are multiples
664 ** of the new step time. We cannot reduce the amount of
665 ** time so we have to move the end towards the future and
666 ** the start towards the past.
668 end_offset = (*end) % (*step);
669 start_offset = (*start) % (*step);
671 /* If there is a start offset (which cannot be more than
672 ** one destination row), skip the appropriate number of
673 ** source rows and one destination row. The appropriate
674 ** number is what we do know (start_offset/cur_step) of
675 ** the new interval (*step/cur_step aka reduce_factor).
678 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
679 printf("row_cnt before: %lu\n", row_cnt);
682 (*start) = (*start) - start_offset;
683 skiprows = reduce_factor - start_offset / cur_step;
684 srcptr += skiprows * *ds_cnt;
685 for (col = 0; col < (*ds_cnt); col++)
690 printf("row_cnt between: %lu\n", row_cnt);
693 /* At the end we have some rows that are not going to be
694 ** used, the amount is end_offset/cur_step
697 (*end) = (*end) - end_offset + (*step);
698 skiprows = end_offset / cur_step;
702 printf("row_cnt after: %lu\n", row_cnt);
705 /* Sanity check: row_cnt should be multiple of reduce_factor */
706 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
708 if (row_cnt % reduce_factor) {
709 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
710 row_cnt, reduce_factor);
711 printf("BUG in reduce_data()\n");
715 /* Now combine reduce_factor intervals at a time
716 ** into one interval for the destination.
719 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
720 for (col = 0; col < (*ds_cnt); col++) {
721 rrd_value_t newval = DNAN;
722 unsigned long validval = 0;
724 for (i = 0; i < reduce_factor; i++) {
725 if (isnan(srcptr[i * (*ds_cnt) + col])) {
730 newval = srcptr[i * (*ds_cnt) + col];
739 newval += srcptr[i * (*ds_cnt) + col];
742 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
745 /* an interval contains a failure if any subintervals contained a failure */
747 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
750 newval = srcptr[i * (*ds_cnt) + col];
776 srcptr += (*ds_cnt) * reduce_factor;
777 row_cnt -= reduce_factor;
779 /* If we had to alter the endtime, we didn't have enough
780 ** source rows to fill the last row. Fill it with NaN.
783 for (col = 0; col < (*ds_cnt); col++)
786 row_cnt = ((*end) - (*start)) / *step;
788 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
789 row_cnt, *start, *end, *step);
790 for (col = 0; col < row_cnt; col++) {
791 printf("time %10lu: ", *start + (col + 1) * (*step));
792 for (i = 0; i < *ds_cnt; i++)
793 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
800 /* get the data required for the graphs from the
809 /* pull the data from the rrd files ... */
810 for (i = 0; i < (int) im->gdes_c; i++) {
811 /* only GF_DEF elements fetch data */
812 if (im->gdes[i].gf != GF_DEF)
816 /* do we have it already ? */
817 for (ii = 0; ii < i; ii++) {
818 if (im->gdes[ii].gf != GF_DEF)
820 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
821 && (im->gdes[i].cf == im->gdes[ii].cf)
822 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
823 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
824 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
825 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
826 /* OK, the data is already there.
827 ** Just copy the header portion
829 im->gdes[i].start = im->gdes[ii].start;
830 im->gdes[i].end = im->gdes[ii].end;
831 im->gdes[i].step = im->gdes[ii].step;
832 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
833 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
834 im->gdes[i].data = im->gdes[ii].data;
835 im->gdes[i].data_first = 0;
842 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
845 * - a connection to the daemon has been established
846 * - this is the first occurrence of that RRD file
848 if (im->use_rrdcached)
853 for (ii = 0; ii < i; ii++)
855 if (strcmp (im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
864 status = rrdc_flush (im->gdes[i].rrd);
867 rrd_set_error ("rrdc_flush (%s) failed with status %i.",
868 im->gdes[i].rrd, status);
872 } /* if (im->use_rrdcached) */
874 if ((rrd_fetch_fn(im->gdes[i].rrd,
880 &im->gdes[i].ds_namv,
881 &im->gdes[i].data)) == -1) {
884 im->gdes[i].data_first = 1;
886 if (ft_step < im->gdes[i].step) {
887 reduce_data(im->gdes[i].cf_reduce,
892 &im->gdes[i].ds_cnt, &im->gdes[i].data);
894 im->gdes[i].step = ft_step;
898 /* lets see if the required data source is really there */
899 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
900 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
904 if (im->gdes[i].ds == -1) {
905 rrd_set_error("No DS called '%s' in '%s'",
906 im->gdes[i].ds_nam, im->gdes[i].rrd);
914 /* evaluate the expressions in the CDEF functions */
916 /*************************************************************
918 *************************************************************/
920 long find_var_wrapper(
924 return find_var((image_desc_t *) arg1, key);
927 /* find gdes containing var*/
934 for (ii = 0; ii < im->gdes_c - 1; ii++) {
935 if ((im->gdes[ii].gf == GF_DEF
936 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
937 && (strcmp(im->gdes[ii].vname, key) == 0)) {
944 /* find the largest common denominator for all the numbers
945 in the 0 terminated num array */
952 for (i = 0; num[i + 1] != 0; i++) {
954 rest = num[i] % num[i + 1];
960 /* return i==0?num[i]:num[i-1]; */
964 /* run the rpn calculator on all the VDEF and CDEF arguments */
971 long *steparray, rpi;
976 rpnstack_init(&rpnstack);
978 for (gdi = 0; gdi < im->gdes_c; gdi++) {
979 /* Look for GF_VDEF and GF_CDEF in the same loop,
980 * so CDEFs can use VDEFs and vice versa
982 switch (im->gdes[gdi].gf) {
986 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
988 /* remove current shift */
989 vdp->start -= vdp->shift;
990 vdp->end -= vdp->shift;
993 if (im->gdes[gdi].shidx >= 0)
994 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
997 vdp->shift = im->gdes[gdi].shval;
999 /* normalize shift to multiple of consolidated step */
1000 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1003 vdp->start += vdp->shift;
1004 vdp->end += vdp->shift;
1008 /* A VDEF has no DS. This also signals other parts
1009 * of rrdtool that this is a VDEF value, not a CDEF.
1011 im->gdes[gdi].ds_cnt = 0;
1012 if (vdef_calc(im, gdi)) {
1013 rrd_set_error("Error processing VDEF '%s'",
1014 im->gdes[gdi].vname);
1015 rpnstack_free(&rpnstack);
1020 im->gdes[gdi].ds_cnt = 1;
1021 im->gdes[gdi].ds = 0;
1022 im->gdes[gdi].data_first = 1;
1023 im->gdes[gdi].start = 0;
1024 im->gdes[gdi].end = 0;
1029 /* Find the variables in the expression.
1030 * - VDEF variables are substituted by their values
1031 * and the opcode is changed into OP_NUMBER.
1032 * - CDEF variables are analized for their step size,
1033 * the lowest common denominator of all the step
1034 * sizes of the data sources involved is calculated
1035 * and the resulting number is the step size for the
1036 * resulting data source.
1038 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1039 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1040 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1041 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1043 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1046 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1047 im->gdes[gdi].vname, im->gdes[ptr].vname);
1048 printf("DEBUG: value from vdef is %f\n",
1049 im->gdes[ptr].vf.val);
1051 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1052 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1053 } else { /* normal variables and PREF(variables) */
1055 /* add one entry to the array that keeps track of the step sizes of the
1056 * data sources going into the CDEF. */
1058 rrd_realloc(steparray,
1060 1) * sizeof(*steparray))) == NULL) {
1061 rrd_set_error("realloc steparray");
1062 rpnstack_free(&rpnstack);
1066 steparray[stepcnt - 1] = im->gdes[ptr].step;
1068 /* adjust start and end of cdef (gdi) so
1069 * that it runs from the latest start point
1070 * to the earliest endpoint of any of the
1071 * rras involved (ptr)
1074 if (im->gdes[gdi].start < im->gdes[ptr].start)
1075 im->gdes[gdi].start = im->gdes[ptr].start;
1077 if (im->gdes[gdi].end == 0 ||
1078 im->gdes[gdi].end > im->gdes[ptr].end)
1079 im->gdes[gdi].end = im->gdes[ptr].end;
1081 /* store pointer to the first element of
1082 * the rra providing data for variable,
1083 * further save step size and data source
1086 im->gdes[gdi].rpnp[rpi].data =
1087 im->gdes[ptr].data + im->gdes[ptr].ds;
1088 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1089 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1091 /* backoff the *.data ptr; this is done so
1092 * rpncalc() function doesn't have to treat
1093 * the first case differently
1095 } /* if ds_cnt != 0 */
1096 } /* if OP_VARIABLE */
1097 } /* loop through all rpi */
1099 /* move the data pointers to the correct period */
1100 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1101 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1102 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1103 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1105 im->gdes[gdi].start - im->gdes[ptr].start;
1108 im->gdes[gdi].rpnp[rpi].data +=
1109 (diff / im->gdes[ptr].step) *
1110 im->gdes[ptr].ds_cnt;
1114 if (steparray == NULL) {
1115 rrd_set_error("rpn expressions without DEF"
1116 " or CDEF variables are not supported");
1117 rpnstack_free(&rpnstack);
1120 steparray[stepcnt] = 0;
1121 /* Now find the resulting step. All steps in all
1122 * used RRAs have to be visited
1124 im->gdes[gdi].step = lcd(steparray);
1126 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1127 im->gdes[gdi].start)
1128 / im->gdes[gdi].step)
1129 * sizeof(double))) == NULL) {
1130 rrd_set_error("malloc im->gdes[gdi].data");
1131 rpnstack_free(&rpnstack);
1135 /* Step through the new cdef results array and
1136 * calculate the values
1138 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1139 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1140 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1142 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1143 * in this case we are advancing by timesteps;
1144 * we use the fact that time_t is a synonym for long
1146 if (rpn_calc(rpnp, &rpnstack, (long) now,
1147 im->gdes[gdi].data, ++dataidx) == -1) {
1148 /* rpn_calc sets the error string */
1149 rpnstack_free(&rpnstack);
1152 } /* enumerate over time steps within a CDEF */
1157 } /* enumerate over CDEFs */
1158 rpnstack_free(&rpnstack);
1162 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1163 /* yes we are loosing precision by doing tos with floats instead of doubles
1164 but it seems more stable this way. */
1166 static int AlmostEqual2sComplement(
1172 int aInt = *(int *) &A;
1173 int bInt = *(int *) &B;
1176 /* Make sure maxUlps is non-negative and small enough that the
1177 default NAN won't compare as equal to anything. */
1179 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1181 /* Make aInt lexicographically ordered as a twos-complement int */
1184 aInt = 0x80000000l - aInt;
1186 /* Make bInt lexicographically ordered as a twos-complement int */
1189 bInt = 0x80000000l - bInt;
1191 intDiff = abs(aInt - bInt);
1193 if (intDiff <= maxUlps)
1199 /* massage data so, that we get one value for each x coordinate in the graph */
1204 double pixstep = (double) (im->end - im->start)
1205 / (double) im->xsize; /* how much time
1206 passes in one pixel */
1208 double minval = DNAN, maxval = DNAN;
1210 unsigned long gr_time;
1212 /* memory for the processed data */
1213 for (i = 0; i < im->gdes_c; i++) {
1214 if ((im->gdes[i].gf == GF_LINE) ||
1215 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1216 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1217 * sizeof(rrd_value_t))) == NULL) {
1218 rrd_set_error("malloc data_proc");
1224 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1227 gr_time = im->start + pixstep * i; /* time of the current step */
1230 for (ii = 0; ii < im->gdes_c; ii++) {
1233 switch (im->gdes[ii].gf) {
1237 if (!im->gdes[ii].stack)
1239 value = im->gdes[ii].yrule;
1240 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1241 /* The time of the data doesn't necessarily match
1242 ** the time of the graph. Beware.
1244 vidx = im->gdes[ii].vidx;
1245 if (im->gdes[vidx].gf == GF_VDEF) {
1246 value = im->gdes[vidx].vf.val;
1248 if (((long int) gr_time >=
1249 (long int) im->gdes[vidx].start)
1250 && ((long int) gr_time <=
1251 (long int) im->gdes[vidx].end)) {
1252 value = im->gdes[vidx].data[(unsigned long)
1258 im->gdes[vidx].step)
1259 * im->gdes[vidx].ds_cnt +
1266 if (!isnan(value)) {
1268 im->gdes[ii].p_data[i] = paintval;
1269 /* GF_TICK: the data values are not
1270 ** relevant for min and max
1272 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1273 if ((isnan(minval) || paintval < minval) &&
1274 !(im->logarithmic && paintval <= 0.0))
1276 if (isnan(maxval) || paintval > maxval)
1280 im->gdes[ii].p_data[i] = DNAN;
1285 ("STACK should already be turned into LINE or AREA here");
1294 /* if min or max have not been asigned a value this is because
1295 there was no data in the graph ... this is not good ...
1296 lets set these to dummy values then ... */
1298 if (im->logarithmic) {
1299 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1300 minval = 0.0; /* catching this right away below */
1303 /* in logarithm mode, where minval is smaller or equal
1304 to 0 make the beast just way smaller than maxval */
1306 minval = maxval / 10e8;
1309 if (isnan(minval) || isnan(maxval)) {
1315 /* adjust min and max values given by the user */
1316 /* for logscale we add something on top */
1317 if (isnan(im->minval)
1318 || ((!im->rigid) && im->minval > minval)
1320 if (im->logarithmic)
1321 im->minval = minval / 2.0;
1323 im->minval = minval;
1325 if (isnan(im->maxval)
1326 || (!im->rigid && im->maxval < maxval)
1328 if (im->logarithmic)
1329 im->maxval = maxval * 2.0;
1331 im->maxval = maxval;
1334 /* make sure min is smaller than max */
1335 if (im->minval > im->maxval) {
1337 im->minval = 0.99 * im->maxval;
1339 im->minval = 1.01 * im->maxval;
1342 /* make sure min and max are not equal */
1343 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1349 /* make sure min and max are not both zero */
1350 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1359 /* identify the point where the first gridline, label ... gets placed */
1361 time_t find_first_time(
1362 time_t start, /* what is the initial time */
1363 enum tmt_en baseint, /* what is the basic interval */
1364 long basestep /* how many if these do we jump a time */
1369 localtime_r(&start, &tm);
1373 tm. tm_sec -= tm.tm_sec % basestep;
1378 tm. tm_min -= tm.tm_min % basestep;
1384 tm. tm_hour -= tm.tm_hour % basestep;
1388 /* we do NOT look at the basestep for this ... */
1395 /* we do NOT look at the basestep for this ... */
1399 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1401 if (tm.tm_wday == 0)
1402 tm. tm_mday -= 7; /* we want the *previous* monday */
1410 tm. tm_mon -= tm.tm_mon % basestep;
1421 tm.tm_year + 1900) %basestep;
1427 /* identify the point where the next gridline, label ... gets placed */
1428 time_t find_next_time(
1429 time_t current, /* what is the initial time */
1430 enum tmt_en baseint, /* what is the basic interval */
1431 long basestep /* how many if these do we jump a time */
1437 localtime_r(¤t, &tm);
1442 tm. tm_sec += basestep;
1446 tm. tm_min += basestep;
1450 tm. tm_hour += basestep;
1454 tm. tm_mday += basestep;
1458 tm. tm_mday += 7 * basestep;
1462 tm. tm_mon += basestep;
1466 tm. tm_year += basestep;
1468 madetime = mktime(&tm);
1469 } while (madetime == -1); /* this is necessary to skip impssible times
1470 like the daylight saving time skips */
1476 /* calculate values required for PRINT and GPRINT functions */
1481 long i, ii, validsteps;
1484 int graphelement = 0;
1487 double magfact = -1;
1492 /* wow initializing tmvdef is quite a task :-) */
1493 time_t now = time(NULL);
1495 localtime_r(&now, &tmvdef);
1496 for (i = 0; i < im->gdes_c; i++) {
1497 vidx = im->gdes[i].vidx;
1498 switch (im->gdes[i].gf) {
1501 /* PRINT and GPRINT can now print VDEF generated values.
1502 * There's no need to do any calculations on them as these
1503 * calculations were already made.
1505 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1506 printval = im->gdes[vidx].vf.val;
1507 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1508 } else { /* need to calculate max,min,avg etcetera */
1509 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1510 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1513 for (ii = im->gdes[vidx].ds;
1514 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1515 if (!finite(im->gdes[vidx].data[ii]))
1517 if (isnan(printval)) {
1518 printval = im->gdes[vidx].data[ii];
1523 switch (im->gdes[i].cf) {
1527 case CF_DEVSEASONAL:
1531 printval += im->gdes[vidx].data[ii];
1534 printval = min(printval, im->gdes[vidx].data[ii]);
1538 printval = max(printval, im->gdes[vidx].data[ii]);
1541 printval = im->gdes[vidx].data[ii];
1544 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1545 if (validsteps > 1) {
1546 printval = (printval / validsteps);
1549 } /* prepare printval */
1551 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1552 /* Magfact is set to -1 upon entry to print_calc. If it
1553 * is still less than 0, then we need to run auto_scale.
1554 * Otherwise, put the value into the correct units. If
1555 * the value is 0, then do not set the symbol or magnification
1556 * so next the calculation will be performed again. */
1557 if (magfact < 0.0) {
1558 auto_scale(im, &printval, &si_symb, &magfact);
1559 if (printval == 0.0)
1562 printval /= magfact;
1564 *(++percent_s) = 's';
1565 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1566 auto_scale(im, &printval, &si_symb, &magfact);
1569 if (im->gdes[i].gf == GF_PRINT) {
1570 rrd_infoval_t prline;
1572 if (im->gdes[i].strftm) {
1573 prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
1574 strftime(prline.u_str,
1575 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1576 } else if (bad_format(im->gdes[i].format)) {
1578 ("bad format for PRINT in '%s'", im->gdes[i].format);
1582 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1586 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1591 if (im->gdes[i].strftm) {
1592 strftime(im->gdes[i].legend,
1593 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1595 if (bad_format(im->gdes[i].format)) {
1597 ("bad format for GPRINT in '%s'",
1598 im->gdes[i].format);
1601 #ifdef HAVE_SNPRINTF
1602 snprintf(im->gdes[i].legend,
1604 im->gdes[i].format, printval, si_symb);
1606 sprintf(im->gdes[i].legend,
1607 im->gdes[i].format, printval, si_symb);
1619 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1620 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1625 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1626 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1635 #ifdef WITH_PIECHART
1643 ("STACK should already be turned into LINE or AREA here");
1648 return graphelement;
1652 /* place legends with color spots */
1658 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1659 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1660 int fill = 0, fill_last;
1663 int leg_y = im->yimg;
1664 int leg_y_prev = im->yimg;
1667 int i, ii, mark = 0;
1668 char prt_fctn; /*special printfunctions */
1669 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1673 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1674 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1675 rrd_set_error("malloc for legspace");
1679 if (im->extra_flags & FULL_SIZE_MODE)
1680 leg_y = leg_y_prev =
1681 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1682 for (i = 0; i < im->gdes_c; i++) {
1684 /* hide legends for rules which are not displayed */
1685 if (im->gdes[i].gf == GF_TEXTALIGN) {
1686 default_txtalign = im->gdes[i].txtalign;
1689 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1690 if (im->gdes[i].gf == GF_HRULE
1691 && (im->gdes[i].yrule <
1692 im->minval || im->gdes[i].yrule > im->maxval))
1693 im->gdes[i].legend[0] = '\0';
1694 if (im->gdes[i].gf == GF_VRULE
1695 && (im->gdes[i].xrule <
1696 im->start || im->gdes[i].xrule > im->end))
1697 im->gdes[i].legend[0] = '\0';
1700 /* turn \\t into tab */
1701 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1702 memmove(tab, tab + 1, strlen(tab));
1705 leg_cc = strlen(im->gdes[i].legend);
1706 /* is there a controle code ant the end of the legend string ? */
1707 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1708 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1710 im->gdes[i].legend[leg_cc] = '\0';
1714 /* only valid control codes */
1715 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1719 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1722 ("Unknown control code at the end of '%s\\%c'",
1723 im->gdes[i].legend, prt_fctn);
1727 if (prt_fctn == 'n') {
1731 /* remove exess space from the end of the legend for \g */
1732 while (prt_fctn == 'g' &&
1733 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1735 im->gdes[i].legend[leg_cc] = '\0';
1740 /* no interleg space if string ends in \g */
1741 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1743 fill += legspace[i];
1746 gfx_get_text_width(im,
1752 im->tabwidth, im->gdes[i].legend);
1757 /* who said there was a special tag ... ? */
1758 if (prt_fctn == 'g') {
1762 if (prt_fctn == '\0') {
1763 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1764 /* just one legend item is left right or center */
1765 switch (default_txtalign) {
1780 /* is it time to place the legends ? */
1781 if (fill > im->ximg - 2 * border) {
1789 if (leg_c == 1 && prt_fctn == 'j') {
1795 if (prt_fctn != '\0') {
1797 if (leg_c >= 2 && prt_fctn == 'j') {
1798 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1802 if (prt_fctn == 'c')
1803 leg_x = (im->ximg - fill) / 2.0;
1804 if (prt_fctn == 'r')
1805 leg_x = im->ximg - fill - border;
1806 for (ii = mark; ii <= i; ii++) {
1807 if (im->gdes[ii].legend[0] == '\0')
1808 continue; /* skip empty legends */
1809 im->gdes[ii].leg_x = leg_x;
1810 im->gdes[ii].leg_y = leg_y;
1812 gfx_get_text_width(im, leg_x,
1817 im->tabwidth, im->gdes[ii].legend)
1822 if (im->extra_flags & FULL_SIZE_MODE) {
1823 /* only add y space if there was text on the line */
1824 if (leg_x > border || prt_fctn == 's')
1825 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1826 if (prt_fctn == 's')
1827 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1829 if (leg_x > border || prt_fctn == 's')
1830 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1831 if (prt_fctn == 's')
1832 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1840 if (im->extra_flags & FULL_SIZE_MODE) {
1841 if (leg_y != leg_y_prev) {
1842 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1844 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1848 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1856 /* create a grid on the graph. it determines what to do
1857 from the values of xsize, start and end */
1859 /* the xaxis labels are determined from the number of seconds per pixel
1860 in the requested graph */
1862 int calc_horizontal_grid(
1870 int decimals, fractionals;
1872 im->ygrid_scale.labfact = 2;
1873 range = im->maxval - im->minval;
1874 scaledrange = range / im->magfact;
1875 /* does the scale of this graph make it impossible to put lines
1876 on it? If so, give up. */
1877 if (isnan(scaledrange)) {
1881 /* find grid spaceing */
1883 if (isnan(im->ygridstep)) {
1884 if (im->extra_flags & ALTYGRID) {
1885 /* find the value with max number of digits. Get number of digits */
1888 (max(fabs(im->maxval), fabs(im->minval)) *
1889 im->viewfactor / im->magfact));
1890 if (decimals <= 0) /* everything is small. make place for zero */
1892 im->ygrid_scale.gridstep =
1894 floor(log10(range * im->viewfactor / im->magfact))) /
1895 im->viewfactor * im->magfact;
1896 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1897 im->ygrid_scale.gridstep = 0.1;
1898 /* should have at least 5 lines but no more then 15 */
1899 if (range / im->ygrid_scale.gridstep < 5
1900 && im->ygrid_scale.gridstep >= 30)
1901 im->ygrid_scale.gridstep /= 10;
1902 if (range / im->ygrid_scale.gridstep > 15)
1903 im->ygrid_scale.gridstep *= 10;
1904 if (range / im->ygrid_scale.gridstep > 5) {
1905 im->ygrid_scale.labfact = 1;
1906 if (range / im->ygrid_scale.gridstep > 8
1907 || im->ygrid_scale.gridstep <
1908 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1909 im->ygrid_scale.labfact = 2;
1911 im->ygrid_scale.gridstep /= 5;
1912 im->ygrid_scale.labfact = 5;
1916 (im->ygrid_scale.gridstep *
1917 (double) im->ygrid_scale.labfact * im->viewfactor /
1919 if (fractionals < 0) { /* small amplitude. */
1920 int len = decimals - fractionals + 1;
1922 if (im->unitslength < len + 2)
1923 im->unitslength = len + 2;
1924 sprintf(im->ygrid_scale.labfmt,
1926 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1928 int len = decimals + 1;
1930 if (im->unitslength < len + 2)
1931 im->unitslength = len + 2;
1932 sprintf(im->ygrid_scale.labfmt,
1933 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1935 } else { /* classic rrd grid */
1936 for (i = 0; ylab[i].grid > 0; i++) {
1937 pixel = im->ysize / (scaledrange / ylab[i].grid);
1943 for (i = 0; i < 4; i++) {
1944 if (pixel * ylab[gridind].lfac[i] >=
1945 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1946 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1951 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1954 im->ygrid_scale.gridstep = im->ygridstep;
1955 im->ygrid_scale.labfact = im->ylabfact;
1960 int draw_horizontal_grid(
1966 char graph_label[100];
1968 double X0 = im->xorigin;
1969 double X1 = im->xorigin + im->xsize;
1970 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1971 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1975 im->ygrid_scale.gridstep /
1976 (double) im->magfact * (double) im->viewfactor;
1977 MaxY = scaledstep * (double) egrid;
1978 for (i = sgrid; i <= egrid; i++) {
1980 im->ygrid_scale.gridstep * i);
1982 im->ygrid_scale.gridstep * (i + 1));
1984 if (floor(Y0 + 0.5) >=
1985 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1986 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1987 with the chosen settings. Add a label if required by settings, or if
1988 there is only one label so far and the next grid line is out of bounds. */
1989 if (i % im->ygrid_scale.labfact == 0
1991 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1992 if (im->symbol == ' ') {
1993 if (im->extra_flags & ALTYGRID) {
1994 sprintf(graph_label,
1995 im->ygrid_scale.labfmt,
1996 scaledstep * (double) i);
1999 sprintf(graph_label, "%4.1f",
2000 scaledstep * (double) i);
2002 sprintf(graph_label, "%4.0f",
2003 scaledstep * (double) i);
2007 char sisym = (i == 0 ? ' ' : im->symbol);
2009 if (im->extra_flags & ALTYGRID) {
2010 sprintf(graph_label,
2011 im->ygrid_scale.labfmt,
2012 scaledstep * (double) i, sisym);
2015 sprintf(graph_label, "%4.1f %c",
2016 scaledstep * (double) i, sisym);
2018 sprintf(graph_label, "%4.0f %c",
2019 scaledstep * (double) i, sisym);
2027 text_prop[TEXT_PROP_AXIS].
2029 im->graph_col[GRC_FONT],
2031 text_prop[TEXT_PROP_AXIS].
2034 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2035 gfx_line(im, X0 - 2, Y0, X0, Y0,
2036 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2037 gfx_line(im, X1, Y0, X1 + 2, Y0,
2038 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2039 gfx_dashed_line(im, X0 - 2, Y0,
2045 im->grid_dash_on, im->grid_dash_off);
2046 } else if (!(im->extra_flags & NOMINOR)) {
2049 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2050 gfx_line(im, X1, Y0, X1 + 2, Y0,
2051 GRIDWIDTH, im->graph_col[GRC_GRID]);
2052 gfx_dashed_line(im, X0 - 1, Y0,
2056 graph_col[GRC_GRID],
2057 im->grid_dash_on, im->grid_dash_off);
2064 /* this is frexp for base 10 */
2075 iexp = floor(log(fabs(x)) / log(10));
2076 mnt = x / pow(10.0, iexp);
2079 mnt = x / pow(10.0, iexp);
2086 /* logaritmic horizontal grid */
2087 int horizontal_log_grid(
2091 double yloglab[][10] = {
2093 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2095 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2097 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2114 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2116 int i, j, val_exp, min_exp;
2117 double nex; /* number of decades in data */
2118 double logscale; /* scale in logarithmic space */
2119 int exfrac = 1; /* decade spacing */
2120 int mid = -1; /* row in yloglab for major grid */
2121 double mspac; /* smallest major grid spacing (pixels) */
2122 int flab; /* first value in yloglab to use */
2123 double value, tmp, pre_value;
2125 char graph_label[100];
2127 nex = log10(im->maxval / im->minval);
2128 logscale = im->ysize / nex;
2129 /* major spacing for data with high dynamic range */
2130 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2137 /* major spacing for less dynamic data */
2139 /* search best row in yloglab */
2141 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2142 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2145 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2148 /* find first value in yloglab */
2150 yloglab[mid][flab] < 10
2151 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2152 if (yloglab[mid][flab] == 10.0) {
2157 if (val_exp % exfrac)
2158 val_exp += abs(-val_exp % exfrac);
2160 X1 = im->xorigin + im->xsize;
2165 value = yloglab[mid][flab] * pow(10.0, val_exp);
2166 if (AlmostEqual2sComplement(value, pre_value, 4))
2167 break; /* it seems we are not converging */
2169 Y0 = ytr(im, value);
2170 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2172 /* major grid line */
2174 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2175 gfx_line(im, X1, Y0, X1 + 2, Y0,
2176 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2177 gfx_dashed_line(im, X0 - 2, Y0,
2182 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2184 if (im->extra_flags & FORCE_UNITS_SI) {
2189 scale = floor(val_exp / 3.0);
2191 pvalue = pow(10.0, val_exp % 3);
2193 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2194 pvalue *= yloglab[mid][flab];
2195 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2196 && ((scale + si_symbcenter) >= 0))
2197 symbol = si_symbol[scale + si_symbcenter];
2200 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2202 sprintf(graph_label, "%3.0e", value);
2206 text_prop[TEXT_PROP_AXIS].
2208 im->graph_col[GRC_FONT],
2210 text_prop[TEXT_PROP_AXIS].
2213 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2215 if (mid < 4 && exfrac == 1) {
2216 /* find first and last minor line behind current major line
2217 * i is the first line and j tha last */
2219 min_exp = val_exp - 1;
2220 for (i = 1; yloglab[mid][i] < 10.0; i++);
2221 i = yloglab[mid][i - 1] + 1;
2225 i = yloglab[mid][flab - 1] + 1;
2226 j = yloglab[mid][flab];
2229 /* draw minor lines below current major line */
2230 for (; i < j; i++) {
2232 value = i * pow(10.0, min_exp);
2233 if (value < im->minval)
2235 Y0 = ytr(im, value);
2236 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2241 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2242 gfx_line(im, X1, Y0, X1 + 2, Y0,
2243 GRIDWIDTH, im->graph_col[GRC_GRID]);
2244 gfx_dashed_line(im, X0 - 1, Y0,
2248 graph_col[GRC_GRID],
2249 im->grid_dash_on, im->grid_dash_off);
2251 } else if (exfrac > 1) {
2252 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2253 value = pow(10.0, i);
2254 if (value < im->minval)
2256 Y0 = ytr(im, value);
2257 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2262 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2263 gfx_line(im, X1, Y0, X1 + 2, Y0,
2264 GRIDWIDTH, im->graph_col[GRC_GRID]);
2265 gfx_dashed_line(im, X0 - 1, Y0,
2269 graph_col[GRC_GRID],
2270 im->grid_dash_on, im->grid_dash_off);
2275 if (yloglab[mid][++flab] == 10.0) {
2281 /* draw minor lines after highest major line */
2282 if (mid < 4 && exfrac == 1) {
2283 /* find first and last minor line below current major line
2284 * i is the first line and j tha last */
2286 min_exp = val_exp - 1;
2287 for (i = 1; yloglab[mid][i] < 10.0; i++);
2288 i = yloglab[mid][i - 1] + 1;
2292 i = yloglab[mid][flab - 1] + 1;
2293 j = yloglab[mid][flab];
2296 /* draw minor lines below current major line */
2297 for (; i < j; i++) {
2299 value = i * pow(10.0, min_exp);
2300 if (value < im->minval)
2302 Y0 = ytr(im, value);
2303 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2307 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2308 gfx_line(im, X1, Y0, X1 + 2, Y0,
2309 GRIDWIDTH, im->graph_col[GRC_GRID]);
2310 gfx_dashed_line(im, X0 - 1, Y0,
2314 graph_col[GRC_GRID],
2315 im->grid_dash_on, im->grid_dash_off);
2318 /* fancy minor gridlines */
2319 else if (exfrac > 1) {
2320 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2321 value = pow(10.0, i);
2322 if (value < im->minval)
2324 Y0 = ytr(im, value);
2325 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2329 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2330 gfx_line(im, X1, Y0, X1 + 2, Y0,
2331 GRIDWIDTH, im->graph_col[GRC_GRID]);
2332 gfx_dashed_line(im, X0 - 1, Y0,
2336 graph_col[GRC_GRID],
2337 im->grid_dash_on, im->grid_dash_off);
2348 int xlab_sel; /* which sort of label and grid ? */
2349 time_t ti, tilab, timajor;
2351 char graph_label[100];
2352 double X0, Y0, Y1; /* points for filled graph and more */
2355 /* the type of time grid is determined by finding
2356 the number of seconds per pixel in the graph */
2357 if (im->xlab_user.minsec == -1) {
2358 factor = (im->end - im->start) / im->xsize;
2360 while (xlab[xlab_sel + 1].minsec !=
2361 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2363 } /* pick the last one */
2364 while (xlab[xlab_sel - 1].minsec ==
2365 xlab[xlab_sel].minsec
2366 && xlab[xlab_sel].length > (im->end - im->start)) {
2368 } /* go back to the smallest size */
2369 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2370 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2371 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2372 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2373 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2374 im->xlab_user.labst = xlab[xlab_sel].labst;
2375 im->xlab_user.precis = xlab[xlab_sel].precis;
2376 im->xlab_user.stst = xlab[xlab_sel].stst;
2379 /* y coords are the same for every line ... */
2381 Y1 = im->yorigin - im->ysize;
2382 /* paint the minor grid */
2383 if (!(im->extra_flags & NOMINOR)) {
2384 for (ti = find_first_time(im->start,
2392 find_first_time(im->start,
2399 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2401 /* are we inside the graph ? */
2402 if (ti < im->start || ti > im->end)
2404 while (timajor < ti) {
2405 timajor = find_next_time(timajor,
2408 mgridtm, im->xlab_user.mgridst);
2411 continue; /* skip as falls on major grid line */
2413 gfx_line(im, X0, Y1 - 2, X0, Y1,
2414 GRIDWIDTH, im->graph_col[GRC_GRID]);
2415 gfx_line(im, X0, Y0, X0, Y0 + 2,
2416 GRIDWIDTH, im->graph_col[GRC_GRID]);
2417 gfx_dashed_line(im, X0, Y0 + 1, X0,
2420 graph_col[GRC_GRID],
2421 im->grid_dash_on, im->grid_dash_off);
2425 /* paint the major grid */
2426 for (ti = find_first_time(im->start,
2434 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2436 /* are we inside the graph ? */
2437 if (ti < im->start || ti > im->end)
2440 gfx_line(im, X0, Y1 - 2, X0, Y1,
2441 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2442 gfx_line(im, X0, Y0, X0, Y0 + 3,
2443 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2444 gfx_dashed_line(im, X0, Y0 + 3, X0,
2448 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2450 /* paint the labels below the graph */
2452 find_first_time(im->start -
2461 im->xlab_user.precis / 2;
2462 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2464 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2465 /* are we inside the graph ? */
2466 if (tilab < im->start || tilab > im->end)
2469 localtime_r(&tilab, &tm);
2470 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2472 # error "your libc has no strftime I guess we'll abort the exercise here."
2477 im->graph_col[GRC_FONT],
2479 text_prop[TEXT_PROP_AXIS].
2482 GFX_H_CENTER, GFX_V_TOP, graph_label);
2491 /* draw x and y axis */
2492 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2493 im->xorigin+im->xsize,im->yorigin-im->ysize,
2494 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2496 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2497 im->xorigin+im->xsize,im->yorigin-im->ysize,
2498 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2500 gfx_line(im, im->xorigin - 4,
2502 im->xorigin + im->xsize +
2503 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2504 gfx_line(im, im->xorigin,
2507 im->yorigin - im->ysize -
2508 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2509 /* arrow for X and Y axis direction */
2510 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 */
2511 im->graph_col[GRC_ARROW]);
2513 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 */
2514 im->graph_col[GRC_ARROW]);
2523 double X0, Y0; /* points for filled graph and more */
2524 struct gfx_color_t water_color;
2526 /* draw 3d border */
2527 gfx_new_area(im, 0, im->yimg,
2528 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2529 gfx_add_point(im, im->ximg - 2, 2);
2530 gfx_add_point(im, im->ximg, 0);
2531 gfx_add_point(im, 0, 0);
2533 gfx_new_area(im, 2, im->yimg - 2,
2535 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2536 gfx_add_point(im, im->ximg, 0);
2537 gfx_add_point(im, im->ximg, im->yimg);
2538 gfx_add_point(im, 0, im->yimg);
2540 if (im->draw_x_grid == 1)
2542 if (im->draw_y_grid == 1) {
2543 if (im->logarithmic) {
2544 res = horizontal_log_grid(im);
2546 res = draw_horizontal_grid(im);
2549 /* dont draw horizontal grid if there is no min and max val */
2551 char *nodata = "No Data found";
2553 gfx_text(im, im->ximg / 2,
2556 im->graph_col[GRC_FONT],
2558 text_prop[TEXT_PROP_AXIS].
2561 GFX_H_CENTER, GFX_V_CENTER, nodata);
2565 /* yaxis unit description */
2570 im->graph_col[GRC_FONT],
2572 text_prop[TEXT_PROP_UNIT].
2575 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2579 im->graph_col[GRC_FONT],
2581 text_prop[TEXT_PROP_TITLE].
2583 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2584 /* rrdtool 'logo' */
2585 water_color = im->graph_col[GRC_FONT];
2586 water_color.alpha = 0.3;
2587 gfx_text(im, im->ximg - 4, 5,
2590 text_prop[TEXT_PROP_WATERMARK].
2591 font_desc, im->tabwidth,
2592 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2593 /* graph watermark */
2594 if (im->watermark[0] != '\0') {
2596 im->ximg / 2, im->yimg - 6,
2599 text_prop[TEXT_PROP_WATERMARK].
2600 font_desc, im->tabwidth, 0,
2601 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2605 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2606 for (i = 0; i < im->gdes_c; i++) {
2607 if (im->gdes[i].legend[0] == '\0')
2609 /* im->gdes[i].leg_y is the bottom of the legend */
2610 X0 = im->gdes[i].leg_x;
2611 Y0 = im->gdes[i].leg_y;
2612 gfx_text(im, X0, Y0,
2613 im->graph_col[GRC_FONT],
2616 [TEXT_PROP_LEGEND].font_desc,
2618 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2619 /* The legend for GRAPH items starts with "M " to have
2620 enough space for the box */
2621 if (im->gdes[i].gf != GF_PRINT &&
2622 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2626 boxH = gfx_get_text_width(im, 0,
2631 im->tabwidth, "o") * 1.2;
2633 /* shift the box up a bit */
2635 /* make sure transparent colors show up the same way as in the graph */
2638 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2639 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2641 gfx_new_area(im, X0, Y0 - boxV, X0,
2642 Y0, X0 + boxH, Y0, im->gdes[i].col);
2643 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2646 cairo_new_path(im->cr);
2647 cairo_set_line_width(im->cr, 1.0);
2650 gfx_line_fit(im, &X0, &Y0);
2651 gfx_line_fit(im, &X1, &Y1);
2652 cairo_move_to(im->cr, X0, Y0);
2653 cairo_line_to(im->cr, X1, Y0);
2654 cairo_line_to(im->cr, X1, Y1);
2655 cairo_line_to(im->cr, X0, Y1);
2656 cairo_close_path(im->cr);
2657 cairo_set_source_rgba(im->cr,
2669 blue, im->graph_col[GRC_FRAME].alpha);
2670 if (im->gdes[i].dash) {
2671 /* make box borders in legend dashed if the graph is dashed */
2675 cairo_set_dash(im->cr, dashes, 1, 0.0);
2677 cairo_stroke(im->cr);
2678 cairo_restore(im->cr);
2685 /*****************************************************
2686 * lazy check make sure we rely need to create this graph
2687 *****************************************************/
2694 struct stat imgstat;
2697 return 0; /* no lazy option */
2698 if (strlen(im->graphfile) == 0)
2699 return 0; /* inmemory option */
2700 if (stat(im->graphfile, &imgstat) != 0)
2701 return 0; /* can't stat */
2702 /* one pixel in the existing graph is more then what we would
2704 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2706 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2707 return 0; /* the file does not exist */
2708 switch (im->imgformat) {
2710 size = PngSize(fd, &(im->ximg), &(im->yimg));
2720 int graph_size_location(
2725 /* The actual size of the image to draw is determined from
2726 ** several sources. The size given on the command line is
2727 ** the graph area but we need more as we have to draw labels
2728 ** and other things outside the graph area
2731 int Xvertical = 0, Ytitle =
2732 0, Xylabel = 0, Xmain = 0, Ymain =
2733 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2735 if (im->extra_flags & ONLY_GRAPH) {
2737 im->ximg = im->xsize;
2738 im->yimg = im->ysize;
2739 im->yorigin = im->ysize;
2744 /** +---+--------------------------------------------+
2745 ** | y |...............graph title..................|
2746 ** | +---+-------------------------------+--------+
2749 ** | i | a | | pie |
2750 ** | s | x | main graph area | chart |
2755 ** | l | b +-------------------------------+--------+
2756 ** | e | l | x axis labels | |
2757 ** +---+---+-------------------------------+--------+
2758 ** |....................legends.....................|
2759 ** +------------------------------------------------+
2761 ** +------------------------------------------------+
2764 if (im->ylegend[0] != '\0') {
2765 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2768 if (im->title[0] != '\0') {
2769 /* The title is placed "inbetween" two text lines so it
2770 ** automatically has some vertical spacing. The horizontal
2771 ** spacing is added here, on each side.
2773 /* if necessary, reduce the font size of the title until it fits the image width */
2774 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2778 if (im->draw_x_grid) {
2779 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2781 if (im->draw_y_grid || im->forceleftspace) {
2783 gfx_get_text_width(im, 0,
2788 im->tabwidth, "0") * im->unitslength;
2792 if (im->extra_flags & FULL_SIZE_MODE) {
2793 /* The actual size of the image to draw has been determined by the user.
2794 ** The graph area is the space remaining after accounting for the legend,
2795 ** the watermark, the pie chart, the axis labels, and the title.
2798 im->ximg = im->xsize;
2799 im->yimg = im->ysize;
2800 im->yorigin = im->ysize;
2803 im->yorigin += Ytitle;
2804 /* Now calculate the total size. Insert some spacing where
2805 desired. im->xorigin and im->yorigin need to correspond
2806 with the lower left corner of the main graph area or, if
2807 this one is not set, the imaginary box surrounding the
2809 /* Initial size calculation for the main graph area */
2810 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2812 Xmain -= Xspacing; /* put space between main graph area and right edge */
2813 im->xorigin = Xspacing + Xylabel;
2814 /* the length of the title should not influence with width of the graph
2815 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2816 if (Xvertical) { /* unit description */
2818 im->xorigin += Xvertical;
2822 /* The vertical size of the image is known in advance. The main graph area
2823 ** (Ymain) and im->yorigin must be set according to the space requirements
2824 ** of the legend and the axis labels.
2826 if (im->extra_flags & NOLEGEND) {
2827 /* set dimensions correctly if using full size mode with no legend */
2830 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2831 Ymain = im->yorigin;
2833 /* Determine where to place the legends onto the image.
2834 ** Set Ymain and adjust im->yorigin to match the space requirements.
2836 if (leg_place(im, &Ymain) == -1)
2841 /* remove title space *or* some padding above the graph from the main graph area */
2845 Ymain -= 1.5 * Yspacing;
2848 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2849 if (im->watermark[0] != '\0') {
2850 Ymain -= Ywatermark;
2854 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2856 /* The actual size of the image to draw is determined from
2857 ** several sources. The size given on the command line is
2858 ** the graph area but we need more as we have to draw labels
2859 ** and other things outside the graph area.
2862 if (im->ylegend[0] != '\0') {
2863 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2867 if (im->title[0] != '\0') {
2868 /* The title is placed "inbetween" two text lines so it
2869 ** automatically has some vertical spacing. The horizontal
2870 ** spacing is added here, on each side.
2872 /* don't care for the with of the title
2873 Xtitle = gfx_get_text_width(im->canvas, 0,
2874 im->text_prop[TEXT_PROP_TITLE].font,
2875 im->text_prop[TEXT_PROP_TITLE].size,
2877 im->title, 0) + 2*Xspacing; */
2878 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2885 /* Now calculate the total size. Insert some spacing where
2886 desired. im->xorigin and im->yorigin need to correspond
2887 with the lower left corner of the main graph area or, if
2888 this one is not set, the imaginary box surrounding the
2891 /* The legend width cannot yet be determined, as a result we
2892 ** have problems adjusting the image to it. For now, we just
2893 ** forget about it at all; the legend will have to fit in the
2894 ** size already allocated.
2896 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2898 im->ximg += Xspacing;
2899 im->xorigin = Xspacing + Xylabel;
2900 /* the length of the title should not influence with width of the graph
2901 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2902 if (Xvertical) { /* unit description */
2903 im->ximg += Xvertical;
2904 im->xorigin += Xvertical;
2907 /* The vertical size is interesting... we need to compare
2908 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2909 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2910 ** in order to start even thinking about Ylegend or Ywatermark.
2912 ** Do it in three portions: First calculate the inner part,
2913 ** then do the legend, then adjust the total height of the img,
2914 ** adding space for a watermark if one exists;
2916 /* reserve space for main and/or pie */
2917 im->yimg = Ymain + Yxlabel;
2918 im->yorigin = im->yimg - Yxlabel;
2919 /* reserve space for the title *or* some padding above the graph */
2922 im->yorigin += Ytitle;
2924 im->yimg += 1.5 * Yspacing;
2925 im->yorigin += 1.5 * Yspacing;
2927 /* reserve space for padding below the graph */
2928 im->yimg += Yspacing;
2929 /* Determine where to place the legends onto the image.
2930 ** Adjust im->yimg to match the space requirements.
2932 if (leg_place(im, 0) == -1)
2934 if (im->watermark[0] != '\0') {
2935 im->yimg += Ywatermark;
2943 static cairo_status_t cairo_output(
2947 unsigned int length)
2949 image_desc_t *im = closure;
2951 im->rendered_image =
2952 realloc(im->rendered_image, im->rendered_image_size + length);
2953 if (im->rendered_image == NULL)
2954 return CAIRO_STATUS_WRITE_ERROR;
2955 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2956 im->rendered_image_size += length;
2957 return CAIRO_STATUS_SUCCESS;
2960 /* draw that picture thing ... */
2965 int lazy = lazy_check(im);
2966 double areazero = 0.0;
2967 graph_desc_t *lastgdes = NULL;
2970 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
2972 /* if we want and can be lazy ... quit now */
2974 info.u_cnt = im->ximg;
2975 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2976 info.u_cnt = im->yimg;
2977 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2980 /* pull the data from the rrd files ... */
2981 if (data_fetch(im) == -1)
2983 /* evaluate VDEF and CDEF operations ... */
2984 if (data_calc(im) == -1)
2986 /* calculate and PRINT and GPRINT definitions. We have to do it at
2987 * this point because it will affect the length of the legends
2988 * if there are no graph elements (i==0) we stop here ...
2989 * if we are lazy, try to quit ...
2995 if ((i == 0) || lazy)
2998 /**************************************************************
2999 *** Calculating sizes and locations became a bit confusing ***
3000 *** so I moved this into a separate function. ***
3001 **************************************************************/
3002 if (graph_size_location(im, i) == -1)
3005 info.u_cnt = im->xorigin;
3006 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3007 info.u_cnt = im->yorigin - im->ysize;
3008 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3009 info.u_cnt = im->xsize;
3010 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3011 info.u_cnt = im->ysize;
3012 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3013 info.u_cnt = im->ximg;
3014 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3015 info.u_cnt = im->yimg;
3016 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3018 /* get actual drawing data and find min and max values */
3019 if (data_proc(im) == -1)
3021 if (!im->logarithmic) {
3025 /* identify si magnitude Kilo, Mega Giga ? */
3026 if (!im->rigid && !im->logarithmic)
3027 expand_range(im); /* make sure the upper and lower limit are
3030 info.u_val = im->minval;
3031 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3032 info.u_val = im->maxval;
3033 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3035 if (!calc_horizontal_grid(im))
3040 apply_gridfit(im); */
3041 /* the actual graph is created by going through the individual
3042 graph elements and then drawing them */
3043 cairo_surface_destroy(im->surface);
3044 switch (im->imgformat) {
3047 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3048 im->ximg * im->zoom,
3049 im->yimg * im->zoom);
3053 im->surface = strlen(im->graphfile)
3054 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3055 im->yimg * im->zoom)
3056 : cairo_pdf_surface_create_for_stream
3057 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3061 im->surface = strlen(im->graphfile)
3063 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3064 im->yimg * im->zoom)
3065 : cairo_ps_surface_create_for_stream
3066 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3070 im->surface = strlen(im->graphfile)
3072 cairo_svg_surface_create(im->
3074 im->ximg * im->zoom, im->yimg * im->zoom)
3075 : cairo_svg_surface_create_for_stream
3076 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3077 cairo_svg_surface_restrict_to_version
3078 (im->surface, CAIRO_SVG_VERSION_1_1);
3081 cairo_destroy(im->cr);
3082 im->cr = cairo_create(im->surface);
3083 cairo_set_antialias(im->cr, im->graph_antialias);
3084 cairo_scale(im->cr, im->zoom, im->zoom);
3085 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3086 gfx_new_area(im, 0, 0, 0, im->yimg,
3087 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3088 gfx_add_point(im, im->ximg, 0);
3090 gfx_new_area(im, im->xorigin,
3093 im->xsize, im->yorigin,
3096 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3097 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3099 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3100 im->xsize, im->ysize + 2.0);
3102 if (im->minval > 0.0)
3103 areazero = im->minval;
3104 if (im->maxval < 0.0)
3105 areazero = im->maxval;
3106 for (i = 0; i < im->gdes_c; i++) {
3107 switch (im->gdes[i].gf) {
3121 for (ii = 0; ii < im->xsize; ii++) {
3122 if (!isnan(im->gdes[i].p_data[ii])
3123 && im->gdes[i].p_data[ii] != 0.0) {
3124 if (im->gdes[i].yrule > 0) {
3131 im->ysize, 1.0, im->gdes[i].col);
3132 } else if (im->gdes[i].yrule < 0) {
3135 im->yorigin - im->ysize,
3140 im->ysize, 1.0, im->gdes[i].col);
3147 /* fix data points at oo and -oo */
3148 for (ii = 0; ii < im->xsize; ii++) {
3149 if (isinf(im->gdes[i].p_data[ii])) {
3150 if (im->gdes[i].p_data[ii] > 0) {
3151 im->gdes[i].p_data[ii] = im->maxval;
3153 im->gdes[i].p_data[ii] = im->minval;
3159 /* *******************************************************
3164 -------|--t-1--t--------------------------------
3166 if we know the value at time t was a then
3167 we draw a square from t-1 to t with the value a.
3169 ********************************************************* */
3170 if (im->gdes[i].col.alpha != 0.0) {
3171 /* GF_LINE and friend */
3172 if (im->gdes[i].gf == GF_LINE) {
3173 double last_y = 0.0;
3177 cairo_new_path(im->cr);
3178 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3179 if (im->gdes[i].dash) {
3180 cairo_set_dash(im->cr,
3181 im->gdes[i].p_dashes,
3182 im->gdes[i].ndash, im->gdes[i].offset);
3185 for (ii = 1; ii < im->xsize; ii++) {
3186 if (isnan(im->gdes[i].p_data[ii])
3187 || (im->slopemode == 1
3188 && isnan(im->gdes[i].p_data[ii - 1]))) {
3193 last_y = ytr(im, im->gdes[i].p_data[ii]);
3194 if (im->slopemode == 0) {
3195 double x = ii - 1 + im->xorigin;
3198 gfx_line_fit(im, &x, &y);
3199 cairo_move_to(im->cr, x, y);
3200 x = ii + im->xorigin;
3202 gfx_line_fit(im, &x, &y);
3203 cairo_line_to(im->cr, x, y);
3205 double x = ii - 1 + im->xorigin;
3207 ytr(im, im->gdes[i].p_data[ii - 1]);
3208 gfx_line_fit(im, &x, &y);
3209 cairo_move_to(im->cr, x, y);
3210 x = ii + im->xorigin;
3212 gfx_line_fit(im, &x, &y);
3213 cairo_line_to(im->cr, x, y);
3217 double x1 = ii + im->xorigin;
3218 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3220 if (im->slopemode == 0
3221 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3222 double x = ii - 1 + im->xorigin;
3225 gfx_line_fit(im, &x, &y);
3226 cairo_line_to(im->cr, x, y);
3229 gfx_line_fit(im, &x1, &y1);
3230 cairo_line_to(im->cr, x1, y1);
3233 cairo_set_source_rgba(im->cr,
3239 col.blue, im->gdes[i].col.alpha);
3240 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3241 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3242 cairo_stroke(im->cr);
3243 cairo_restore(im->cr);
3247 (double *) malloc(sizeof(double) * im->xsize * 2);
3249 (double *) malloc(sizeof(double) * im->xsize * 2);
3251 (double *) malloc(sizeof(double) * im->xsize * 2);
3253 (double *) malloc(sizeof(double) * im->xsize * 2);
3256 for (ii = 0; ii <= im->xsize; ii++) {
3259 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3265 AlmostEqual2sComplement(foreY
3269 AlmostEqual2sComplement(foreY
3279 foreY[cntI], im->gdes[i].col);
3280 while (cntI < idxI) {
3285 AlmostEqual2sComplement(foreY
3289 AlmostEqual2sComplement(foreY
3296 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3298 gfx_add_point(im, backX[idxI], backY[idxI]);
3304 AlmostEqual2sComplement(backY
3308 AlmostEqual2sComplement(backY
3315 gfx_add_point(im, backX[idxI], backY[idxI]);
3325 if (ii == im->xsize)
3327 if (im->slopemode == 0 && ii == 0) {
3330 if (isnan(im->gdes[i].p_data[ii])) {
3334 ytop = ytr(im, im->gdes[i].p_data[ii]);
3335 if (lastgdes && im->gdes[i].stack) {
3336 ybase = ytr(im, lastgdes->p_data[ii]);
3338 ybase = ytr(im, areazero);
3340 if (ybase == ytop) {
3346 double extra = ytop;
3351 if (im->slopemode == 0) {
3352 backY[++idxI] = ybase - 0.2;
3353 backX[idxI] = ii + im->xorigin - 1;
3354 foreY[idxI] = ytop + 0.2;
3355 foreX[idxI] = ii + im->xorigin - 1;
3357 backY[++idxI] = ybase - 0.2;
3358 backX[idxI] = ii + im->xorigin;
3359 foreY[idxI] = ytop + 0.2;
3360 foreX[idxI] = ii + im->xorigin;
3362 /* close up any remaining area */
3367 } /* else GF_LINE */
3369 /* if color != 0x0 */
3370 /* make sure we do not run into trouble when stacking on NaN */
3371 for (ii = 0; ii < im->xsize; ii++) {
3372 if (isnan(im->gdes[i].p_data[ii])) {
3373 if (lastgdes && (im->gdes[i].stack)) {
3374 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3376 im->gdes[i].p_data[ii] = areazero;
3380 lastgdes = &(im->gdes[i]);
3384 ("STACK should already be turned into LINE or AREA here");
3389 cairo_reset_clip(im->cr);
3391 /* grid_paint also does the text */
3392 if (!(im->extra_flags & ONLY_GRAPH))
3394 if (!(im->extra_flags & ONLY_GRAPH))
3396 /* the RULES are the last thing to paint ... */
3397 for (i = 0; i < im->gdes_c; i++) {
3399 switch (im->gdes[i].gf) {
3401 if (im->gdes[i].yrule >= im->minval
3402 && im->gdes[i].yrule <= im->maxval) {
3404 if (im->gdes[i].dash) {
3405 cairo_set_dash(im->cr,
3406 im->gdes[i].p_dashes,
3407 im->gdes[i].ndash, im->gdes[i].offset);
3409 gfx_line(im, im->xorigin,
3410 ytr(im, im->gdes[i].yrule),
3411 im->xorigin + im->xsize,
3412 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3413 cairo_stroke(im->cr);
3414 cairo_restore(im->cr);
3418 if (im->gdes[i].xrule >= im->start
3419 && im->gdes[i].xrule <= im->end) {
3421 if (im->gdes[i].dash) {
3422 cairo_set_dash(im->cr,
3423 im->gdes[i].p_dashes,
3424 im->gdes[i].ndash, im->gdes[i].offset);
3427 xtr(im, im->gdes[i].xrule),
3428 im->yorigin, xtr(im,
3432 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3433 cairo_stroke(im->cr);
3434 cairo_restore(im->cr);
3443 switch (im->imgformat) {
3446 cairo_status_t status;
3448 status = strlen(im->graphfile) ?
3449 cairo_surface_write_to_png(im->surface, im->graphfile)
3450 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3453 if (status != CAIRO_STATUS_SUCCESS) {
3454 rrd_set_error("Could not save png to '%s'", im->graphfile);
3460 if (strlen(im->graphfile)) {
3461 cairo_show_page(im->cr);
3463 cairo_surface_finish(im->surface);
3472 /*****************************************************
3474 *****************************************************/
3481 if ((im->gdes = (graph_desc_t *)
3482 rrd_realloc(im->gdes, (im->gdes_c)
3483 * sizeof(graph_desc_t))) == NULL) {
3484 rrd_set_error("realloc graph_descs");
3489 im->gdes[im->gdes_c - 1].step = im->step;
3490 im->gdes[im->gdes_c - 1].step_orig = im->step;
3491 im->gdes[im->gdes_c - 1].stack = 0;
3492 im->gdes[im->gdes_c - 1].linewidth = 0;
3493 im->gdes[im->gdes_c - 1].debug = 0;
3494 im->gdes[im->gdes_c - 1].start = im->start;
3495 im->gdes[im->gdes_c - 1].start_orig = im->start;
3496 im->gdes[im->gdes_c - 1].end = im->end;
3497 im->gdes[im->gdes_c - 1].end_orig = im->end;
3498 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3499 im->gdes[im->gdes_c - 1].data = NULL;
3500 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3501 im->gdes[im->gdes_c - 1].data_first = 0;
3502 im->gdes[im->gdes_c - 1].p_data = NULL;
3503 im->gdes[im->gdes_c - 1].rpnp = NULL;
3504 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3505 im->gdes[im->gdes_c - 1].shift = 0.0;
3506 im->gdes[im->gdes_c - 1].dash = 0;
3507 im->gdes[im->gdes_c - 1].ndash = 0;
3508 im->gdes[im->gdes_c - 1].offset = 0;
3509 im->gdes[im->gdes_c - 1].col.red = 0.0;
3510 im->gdes[im->gdes_c - 1].col.green = 0.0;
3511 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3512 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3513 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3514 im->gdes[im->gdes_c - 1].format[0] = '\0';
3515 im->gdes[im->gdes_c - 1].strftm = 0;
3516 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3517 im->gdes[im->gdes_c - 1].ds = -1;
3518 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3519 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3520 im->gdes[im->gdes_c - 1].yrule = DNAN;
3521 im->gdes[im->gdes_c - 1].xrule = 0;
3525 /* copies input untill the first unescaped colon is found
3526 or until input ends. backslashes have to be escaped as well */
3528 const char *const input,
3534 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3535 if (input[inp] == '\\'
3536 && input[inp + 1] != '\0'
3537 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3538 output[outp++] = input[++inp];
3540 output[outp++] = input[inp];
3543 output[outp] = '\0';
3547 /* Now just a wrapper around rrd_graph_v */
3559 rrd_info_t *grinfo = NULL;
3562 grinfo = rrd_graph_v(argc, argv);
3568 if (strcmp(walker->key, "image_info") == 0) {
3571 rrd_realloc((*prdata),
3572 (prlines + 1) * sizeof(char *))) == NULL) {
3573 rrd_set_error("realloc prdata");
3576 /* imginfo goes to position 0 in the prdata array */
3577 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3578 + 2) * sizeof(char));
3579 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3580 (*prdata)[prlines] = NULL;
3582 /* skip anything else */
3583 walker = walker->next;
3591 if (strcmp(walker->key, "image_width") == 0) {
3592 *xsize = walker->value.u_int;
3593 } else if (strcmp(walker->key, "image_height") == 0) {
3594 *ysize = walker->value.u_int;
3595 } else if (strcmp(walker->key, "value_min") == 0) {
3596 *ymin = walker->value.u_val;
3597 } else if (strcmp(walker->key, "value_max") == 0) {
3598 *ymax = walker->value.u_val;
3599 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3602 rrd_realloc((*prdata),
3603 (prlines + 1) * sizeof(char *))) == NULL) {
3604 rrd_set_error("realloc prdata");
3607 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3608 + 2) * sizeof(char));
3609 (*prdata)[prlines] = NULL;
3610 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3611 } else if (strcmp(walker->key, "image") == 0) {
3612 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3613 (stream ? stream : stdout));
3615 /* skip anything else */
3616 walker = walker->next;
3618 rrd_info_free(grinfo);
3623 /* Some surgery done on this function, it became ridiculously big.
3625 ** - initializing now in rrd_graph_init()
3626 ** - options parsing now in rrd_graph_options()
3627 ** - script parsing now in rrd_graph_script()
3629 rrd_info_t *rrd_graph_v(
3635 rrd_graph_init(&im);
3636 /* a dummy surface so that we can measure text sizes for placements */
3638 rrd_graph_options(argc, argv, &im);
3639 if (rrd_test_error()) {
3640 rrd_info_free(im.grinfo);
3645 if (optind >= argc) {
3646 rrd_info_free(im.grinfo);
3648 rrd_set_error("missing filename");
3652 if (strlen(argv[optind]) >= MAXPATH) {
3653 rrd_set_error("filename (including path) too long");
3654 rrd_info_free(im.grinfo);
3659 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3660 im.graphfile[MAXPATH - 1] = '\0';
3662 if (strcmp(im.graphfile, "-") == 0) {
3663 im.graphfile[0] = '\0';
3666 rrd_graph_script(argc, argv, &im, 1);
3667 if (rrd_test_error()) {
3668 rrd_info_free(im.grinfo);
3673 /* Everything is now read and the actual work can start */
3675 if (graph_paint(&im) == -1) {
3676 rrd_info_free(im.grinfo);
3682 /* The image is generated and needs to be output.
3683 ** Also, if needed, print a line with information about the image.
3690 filename = im.graphfile + strlen(im.graphfile);
3691 while (filename > im.graphfile) {
3692 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3697 sprintf_alloc(im.imginfo,
3700 im.ximg), (long) (im.zoom * im.yimg));
3701 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3704 if (im.rendered_image) {
3707 img.u_blo.size = im.rendered_image_size;
3708 img.u_blo.ptr = im.rendered_image;
3709 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3718 image_desc_t *im,int prop,char *font, double size ){
3720 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3721 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3722 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3725 im->text_prop[prop].size = size;
3727 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3728 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3732 void rrd_graph_init(
3737 char *deffont = getenv("RRD_DEFAULT_FONT");
3738 static PangoFontMap *fontmap = NULL;
3739 PangoContext *context;
3744 #ifdef HAVE_SETLOCALE
3745 setlocale(LC_TIME, "");
3746 #ifdef HAVE_MBSTOWCS
3747 setlocale(LC_CTYPE, "");
3751 im->draw_x_grid = 1;
3752 im->draw_y_grid = 1;
3753 im->extra_flags = 0;
3754 im->font_options = cairo_font_options_create();
3755 im->forceleftspace = 0;
3758 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3759 im->grid_dash_off = 1;
3760 im->grid_dash_on = 1;
3762 im->grinfo = (rrd_info_t *) NULL;
3763 im->grinfo_current = (rrd_info_t *) NULL;
3764 im->imgformat = IF_PNG;
3766 im->use_rrdcached = 0;
3768 im->logarithmic = 0;
3774 im->rendered_image_size = 0;
3775 im->rendered_image = NULL;
3779 im->tabwidth = 40.0;
3780 im->title[0] = '\0';
3781 im->unitsexponent = 9999;
3782 im->unitslength = 6;
3783 im->viewfactor = 1.0;
3784 im->watermark[0] = '\0';
3785 im->with_markup = 0;
3787 im->xlab_user.minsec = -1;
3790 im->ygridstep = DNAN;
3792 im->ylegend[0] = '\0';
3797 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3798 im->cr = cairo_create(im->surface);
3800 for (i = 0; i < DIM(text_prop); i++) {
3801 im->text_prop[i].size = -1;
3802 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3805 if (fontmap == NULL){
3806 fontmap = pango_cairo_font_map_get_default();
3809 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3811 pango_cairo_context_set_resolution(context, 100);
3813 pango_cairo_update_context(im->cr,context);
3815 im->layout = pango_layout_new(context);
3817 // im->layout = pango_cairo_create_layout(im->cr);
3820 cairo_font_options_set_hint_style
3821 (im->font_options, CAIRO_HINT_STYLE_FULL);
3822 cairo_font_options_set_hint_metrics
3823 (im->font_options, CAIRO_HINT_METRICS_ON);
3824 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3828 for (i = 0; i < DIM(graph_col); i++)
3829 im->graph_col[i] = graph_col[i];
3835 void rrd_graph_options(
3842 char *parsetime_error = NULL;
3843 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3844 time_t start_tmp = 0, end_tmp = 0;
3846 rrd_time_value_t start_tv, end_tv;
3847 long unsigned int color;
3848 char *old_locale = "";
3850 /* defines for long options without a short equivalent. should be bytes,
3851 and may not collide with (the ASCII value of) short options */
3852 #define LONGOPT_UNITS_SI 255
3855 struct option long_options[] = {
3856 { "start", required_argument, 0, 's'},
3857 { "end", required_argument, 0, 'e'},
3858 { "x-grid", required_argument, 0, 'x'},
3859 { "y-grid", required_argument, 0, 'y'},
3860 { "vertical-label", required_argument, 0, 'v'},
3861 { "width", required_argument, 0, 'w'},
3862 { "height", required_argument, 0, 'h'},
3863 { "full-size-mode", no_argument, 0, 'D'},
3864 { "interlaced", no_argument, 0, 'i'},
3865 { "upper-limit", required_argument, 0, 'u'},
3866 { "lower-limit", required_argument, 0, 'l'},
3867 { "rigid", no_argument, 0, 'r'},
3868 { "base", required_argument, 0, 'b'},
3869 { "logarithmic", no_argument, 0, 'o'},
3870 { "color", required_argument, 0, 'c'},
3871 { "font", required_argument, 0, 'n'},
3872 { "title", required_argument, 0, 't'},
3873 { "imginfo", required_argument, 0, 'f'},
3874 { "imgformat", required_argument, 0, 'a'},
3875 { "lazy", no_argument, 0, 'z'},
3876 { "zoom", required_argument, 0, 'm'},
3877 { "no-legend", no_argument, 0, 'g'},
3878 { "force-rules-legend", no_argument, 0, 'F'},
3879 { "only-graph", no_argument, 0, 'j'},
3880 { "alt-y-grid", no_argument, 0, 'Y'},
3881 { "no-minor", no_argument, 0, 'I'},
3882 { "slope-mode", no_argument, 0, 'E'},
3883 { "alt-autoscale", no_argument, 0, 'A'},
3884 { "alt-autoscale-min", no_argument, 0, 'J'},
3885 { "alt-autoscale-max", no_argument, 0, 'M'},
3886 { "no-gridfit", no_argument, 0, 'N'},
3887 { "units-exponent", required_argument, 0, 'X'},
3888 { "units-length", required_argument, 0, 'L'},
3889 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3890 { "step", required_argument, 0, 'S'},
3891 { "tabwidth", required_argument, 0, 'T'},
3892 { "font-render-mode", required_argument, 0, 'R'},
3893 { "graph-render-mode", required_argument, 0, 'G'},
3894 { "font-smoothing-threshold", required_argument, 0, 'B'},
3895 { "watermark", required_argument, 0, 'W'},
3896 { "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 */
3897 { "pango-markup", no_argument, 0, 'P'},
3898 { "daemon", required_argument, 0, 'd'},
3904 opterr = 0; /* initialize getopt */
3905 rrd_parsetime("end-24h", &start_tv);
3906 rrd_parsetime("now", &end_tv);
3908 int option_index = 0;
3910 int col_start, col_end;
3912 opt = getopt_long(argc, argv,
3913 "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:",
3914 long_options, &option_index);
3919 im->extra_flags |= NOMINOR;
3922 im->extra_flags |= ALTYGRID;
3925 im->extra_flags |= ALTAUTOSCALE;
3928 im->extra_flags |= ALTAUTOSCALE_MIN;
3931 im->extra_flags |= ALTAUTOSCALE_MAX;
3934 im->extra_flags |= ONLY_GRAPH;
3937 im->extra_flags |= NOLEGEND;
3940 im->extra_flags |= FORCE_RULES_LEGEND;
3942 case LONGOPT_UNITS_SI:
3943 if (im->extra_flags & FORCE_UNITS) {
3944 rrd_set_error("--units can only be used once!");
3945 setlocale(LC_NUMERIC, old_locale);
3948 if (strcmp(optarg, "si") == 0)
3949 im->extra_flags |= FORCE_UNITS_SI;
3951 rrd_set_error("invalid argument for --units: %s", optarg);
3956 im->unitsexponent = atoi(optarg);
3959 im->unitslength = atoi(optarg);
3960 im->forceleftspace = 1;
3963 old_locale = setlocale(LC_NUMERIC, "C");
3964 im->tabwidth = atof(optarg);
3965 setlocale(LC_NUMERIC, old_locale);
3968 old_locale = setlocale(LC_NUMERIC, "C");
3969 im->step = atoi(optarg);
3970 setlocale(LC_NUMERIC, old_locale);
3976 im->with_markup = 1;
3979 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3980 rrd_set_error("start time: %s", parsetime_error);
3985 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3986 rrd_set_error("end time: %s", parsetime_error);
3991 if (strcmp(optarg, "none") == 0) {
3992 im->draw_x_grid = 0;
3996 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3998 &im->xlab_user.gridst,
4000 &im->xlab_user.mgridst,
4002 &im->xlab_user.labst,
4003 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4004 strncpy(im->xlab_form, optarg + stroff,
4005 sizeof(im->xlab_form) - 1);
4006 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4008 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4009 rrd_set_error("unknown keyword %s", scan_gtm);
4012 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4014 rrd_set_error("unknown keyword %s", scan_mtm);
4017 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4018 rrd_set_error("unknown keyword %s", scan_ltm);
4021 im->xlab_user.minsec = 1;
4022 im->xlab_user.stst = im->xlab_form;
4024 rrd_set_error("invalid x-grid format");
4030 if (strcmp(optarg, "none") == 0) {
4031 im->draw_y_grid = 0;
4034 old_locale = setlocale(LC_NUMERIC, "C");
4035 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4036 setlocale(LC_NUMERIC, old_locale);
4037 if (im->ygridstep <= 0) {
4038 rrd_set_error("grid step must be > 0");
4040 } else if (im->ylabfact < 1) {
4041 rrd_set_error("label factor must be > 0");
4045 setlocale(LC_NUMERIC, old_locale);
4046 rrd_set_error("invalid y-grid format");
4051 strncpy(im->ylegend, optarg, 150);
4052 im->ylegend[150] = '\0';
4055 old_locale = setlocale(LC_NUMERIC, "C");
4056 im->maxval = atof(optarg);
4057 setlocale(LC_NUMERIC, old_locale);
4060 old_locale = setlocale(LC_NUMERIC, "C");
4061 im->minval = atof(optarg);
4062 setlocale(LC_NUMERIC, old_locale);
4065 im->base = atol(optarg);
4066 if (im->base != 1024 && im->base != 1000) {
4068 ("the only sensible value for base apart from 1000 is 1024");
4073 long_tmp = atol(optarg);
4074 if (long_tmp < 10) {
4075 rrd_set_error("width below 10 pixels");
4078 im->xsize = long_tmp;
4081 long_tmp = atol(optarg);
4082 if (long_tmp < 10) {
4083 rrd_set_error("height below 10 pixels");
4086 im->ysize = long_tmp;
4089 im->extra_flags |= FULL_SIZE_MODE;
4092 /* interlaced png not supported at the moment */
4098 im->imginfo = optarg;
4102 (im->imgformat = if_conv(optarg)) == -1) {
4103 rrd_set_error("unsupported graphics format '%s'", optarg);
4114 im->logarithmic = 1;
4118 "%10[A-Z]#%n%8lx%n",
4119 col_nam, &col_start, &color, &col_end) == 2) {
4121 int col_len = col_end - col_start;
4126 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4134 (((color & 0xF000) *
4135 0x11000) | ((color & 0x0F00) *
4136 0x01100) | ((color &
4139 ((color & 0x000F) * 0x00011)
4143 color = (color << 8) + 0xff /* shift left by 8 */ ;
4148 rrd_set_error("the color format is #RRGGBB[AA]");
4151 if ((ci = grc_conv(col_nam)) != -1) {
4152 im->graph_col[ci] = gfx_hex_to_col(color);
4154 rrd_set_error("invalid color name '%s'", col_nam);
4158 rrd_set_error("invalid color def format");
4167 old_locale = setlocale(LC_NUMERIC, "C");
4168 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4169 int sindex, propidx;
4171 setlocale(LC_NUMERIC, old_locale);
4172 if ((sindex = text_prop_conv(prop)) != -1) {
4173 for (propidx = sindex;
4174 propidx < TEXT_PROP_LAST; propidx++) {
4176 rrd_set_font_desc(im,propidx,NULL,size);
4178 if ((int) strlen(optarg) > end) {
4179 if (optarg[end] == ':') {
4180 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4183 ("expected : after font size in '%s'",
4188 /* only run the for loop for DEFAULT (0) for
4189 all others, we break here. woodo programming */
4190 if (propidx == sindex && sindex != 0)
4194 rrd_set_error("invalid fonttag '%s'", prop);
4198 setlocale(LC_NUMERIC, old_locale);
4199 rrd_set_error("invalid text property format");
4205 old_locale = setlocale(LC_NUMERIC, "C");
4206 im->zoom = atof(optarg);
4207 setlocale(LC_NUMERIC, old_locale);
4208 if (im->zoom <= 0.0) {
4209 rrd_set_error("zoom factor must be > 0");
4214 strncpy(im->title, optarg, 150);
4215 im->title[150] = '\0';
4218 if (strcmp(optarg, "normal") == 0) {
4219 cairo_font_options_set_antialias
4220 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4221 cairo_font_options_set_hint_style
4222 (im->font_options, CAIRO_HINT_STYLE_FULL);
4223 } else if (strcmp(optarg, "light") == 0) {
4224 cairo_font_options_set_antialias
4225 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4226 cairo_font_options_set_hint_style
4227 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4228 } else if (strcmp(optarg, "mono") == 0) {
4229 cairo_font_options_set_antialias
4230 (im->font_options, CAIRO_ANTIALIAS_NONE);
4231 cairo_font_options_set_hint_style
4232 (im->font_options, CAIRO_HINT_STYLE_FULL);
4234 rrd_set_error("unknown font-render-mode '%s'", optarg);
4239 if (strcmp(optarg, "normal") == 0)
4240 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4241 else if (strcmp(optarg, "mono") == 0)
4242 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4244 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4249 /* not supported curently */
4252 strncpy(im->watermark, optarg, 100);
4253 im->watermark[99] = '\0';
4258 if (im->use_rrdcached)
4260 rrd_set_error ("You cannot specify --daemon "
4264 status = rrdc_connect (optarg);
4267 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4271 im->use_rrdcached = 1;
4276 rrd_set_error("unknown option '%c'", optopt);
4278 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4283 if (im->use_rrdcached == 0)
4287 temp = getenv (ENV_RRDCACHED_ADDRESS);
4292 status = rrdc_connect (temp);
4295 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4299 im->use_rrdcached = 1;
4303 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4304 pango_layout_context_changed(im->layout);
4308 if (im->logarithmic && im->minval <= 0) {
4310 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4314 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4315 /* error string is set in rrd_parsetime.c */
4319 if (start_tmp < 3600 * 24 * 365 * 10) {
4321 ("the first entry to fetch should be after 1980 (%ld)",
4326 if (end_tmp < start_tmp) {
4328 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4332 im->start = start_tmp;
4334 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4337 int rrd_graph_color(
4345 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4347 color = strstr(var, "#");
4348 if (color == NULL) {
4349 if (optional == 0) {
4350 rrd_set_error("Found no color in %s", err);
4357 long unsigned int col;
4359 rest = strstr(color, ":");
4366 sscanf(color, "#%6lx%n", &col, &n);
4367 col = (col << 8) + 0xff /* shift left by 8 */ ;
4369 rrd_set_error("Color problem in %s", err);
4372 sscanf(color, "#%8lx%n", &col, &n);
4376 rrd_set_error("Color problem in %s", err);
4378 if (rrd_test_error())
4380 gdp->col = gfx_hex_to_col(col);
4393 while (*ptr != '\0')
4394 if (*ptr++ == '%') {
4396 /* line cannot end with percent char */
4399 /* '%s', '%S' and '%%' are allowed */
4400 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4402 /* %c is allowed (but use only with vdef!) */
4403 else if (*ptr == 'c') {
4408 /* or else '% 6.2lf' and such are allowed */
4410 /* optional padding character */
4411 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4413 /* This should take care of 'm.n' with all three optional */
4414 while (*ptr >= '0' && *ptr <= '9')
4418 while (*ptr >= '0' && *ptr <= '9')
4420 /* Either 'le', 'lf' or 'lg' must follow here */
4423 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4438 const char *const str)
4440 /* A VDEF currently is either "func" or "param,func"
4441 * so the parsing is rather simple. Change if needed.
4449 old_locale = setlocale(LC_NUMERIC, "C");
4450 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4451 setlocale(LC_NUMERIC, old_locale);
4452 if (n == (int) strlen(str)) { /* matched */
4456 sscanf(str, "%29[A-Z]%n", func, &n);
4457 if (n == (int) strlen(str)) { /* matched */
4461 ("Unknown function string '%s' in VDEF '%s'",
4466 if (!strcmp("PERCENT", func))
4467 gdes->vf.op = VDEF_PERCENT;
4468 else if (!strcmp("MAXIMUM", func))
4469 gdes->vf.op = VDEF_MAXIMUM;
4470 else if (!strcmp("AVERAGE", func))
4471 gdes->vf.op = VDEF_AVERAGE;
4472 else if (!strcmp("STDEV", func))
4473 gdes->vf.op = VDEF_STDEV;
4474 else if (!strcmp("MINIMUM", func))
4475 gdes->vf.op = VDEF_MINIMUM;
4476 else if (!strcmp("TOTAL", func))
4477 gdes->vf.op = VDEF_TOTAL;
4478 else if (!strcmp("FIRST", func))
4479 gdes->vf.op = VDEF_FIRST;
4480 else if (!strcmp("LAST", func))
4481 gdes->vf.op = VDEF_LAST;
4482 else if (!strcmp("LSLSLOPE", func))
4483 gdes->vf.op = VDEF_LSLSLOPE;
4484 else if (!strcmp("LSLINT", func))
4485 gdes->vf.op = VDEF_LSLINT;
4486 else if (!strcmp("LSLCORREL", func))
4487 gdes->vf.op = VDEF_LSLCORREL;
4490 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4493 switch (gdes->vf.op) {
4495 if (isnan(param)) { /* no parameter given */
4497 ("Function '%s' needs parameter in VDEF '%s'\n",
4501 if (param >= 0.0 && param <= 100.0) {
4502 gdes->vf.param = param;
4503 gdes->vf.val = DNAN; /* undefined */
4504 gdes->vf.when = 0; /* undefined */
4507 ("Parameter '%f' out of range in VDEF '%s'\n",
4508 param, gdes->vname);
4521 case VDEF_LSLCORREL:
4523 gdes->vf.param = DNAN;
4524 gdes->vf.val = DNAN;
4528 ("Function '%s' needs no parameter in VDEF '%s'\n",
4542 graph_desc_t *src, *dst;
4547 dst = &im->gdes[gdi];
4548 src = &im->gdes[dst->vidx];
4549 data = src->data + src->ds;
4551 src->end_orig % (long) src->step ==
4552 0 ? src->end_orig : (src->end_orig + (long) src->step -
4553 src->end_orig % (long) src->step);
4555 steps = (end - src->start) / src->step;
4558 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4559 src->start, src->end_orig, steps);
4561 switch (dst->vf.op) {
4565 if ((array = malloc(steps * sizeof(double))) == NULL) {
4566 rrd_set_error("malloc VDEV_PERCENT");
4569 for (step = 0; step < steps; step++) {
4570 array[step] = data[step * src->ds_cnt];
4572 qsort(array, step, sizeof(double), vdef_percent_compar);
4573 field = (steps - 1) * dst->vf.param / 100;
4574 dst->vf.val = array[field];
4575 dst->vf.when = 0; /* no time component */
4578 for (step = 0; step < steps; step++)
4579 printf("DEBUG: %3li:%10.2f %c\n",
4580 step, array[step], step == field ? '*' : ' ');
4586 while (step != steps && isnan(data[step * src->ds_cnt]))
4588 if (step == steps) {
4592 dst->vf.val = data[step * src->ds_cnt];
4593 dst->vf.when = src->start + (step + 1) * src->step;
4595 while (step != steps) {
4596 if (finite(data[step * src->ds_cnt])) {
4597 if (data[step * src->ds_cnt] > dst->vf.val) {
4598 dst->vf.val = data[step * src->ds_cnt];
4599 dst->vf.when = src->start + (step + 1) * src->step;
4610 double average = 0.0;
4612 for (step = 0; step < steps; step++) {
4613 if (finite(data[step * src->ds_cnt])) {
4614 sum += data[step * src->ds_cnt];
4619 if (dst->vf.op == VDEF_TOTAL) {
4620 dst->vf.val = sum * src->step;
4621 dst->vf.when = 0; /* no time component */
4622 } else if (dst->vf.op == VDEF_AVERAGE) {
4623 dst->vf.val = sum / cnt;
4624 dst->vf.when = 0; /* no time component */
4626 average = sum / cnt;
4628 for (step = 0; step < steps; step++) {
4629 if (finite(data[step * src->ds_cnt])) {
4630 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4633 dst->vf.val = pow(sum / cnt, 0.5);
4634 dst->vf.when = 0; /* no time component */
4644 while (step != steps && isnan(data[step * src->ds_cnt]))
4646 if (step == steps) {
4650 dst->vf.val = data[step * src->ds_cnt];
4651 dst->vf.when = src->start + (step + 1) * src->step;
4653 while (step != steps) {
4654 if (finite(data[step * src->ds_cnt])) {
4655 if (data[step * src->ds_cnt] < dst->vf.val) {
4656 dst->vf.val = data[step * src->ds_cnt];
4657 dst->vf.when = src->start + (step + 1) * src->step;
4664 /* The time value returned here is one step before the
4665 * actual time value. This is the start of the first
4669 while (step != steps && isnan(data[step * src->ds_cnt]))
4671 if (step == steps) { /* all entries were NaN */
4675 dst->vf.val = data[step * src->ds_cnt];
4676 dst->vf.when = src->start + step * src->step;
4680 /* The time value returned here is the
4681 * actual time value. This is the end of the last
4685 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4687 if (step < 0) { /* all entries were NaN */
4691 dst->vf.val = data[step * src->ds_cnt];
4692 dst->vf.when = src->start + (step + 1) * src->step;
4697 case VDEF_LSLCORREL:{
4698 /* Bestfit line by linear least squares method */
4701 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4708 for (step = 0; step < steps; step++) {
4709 if (finite(data[step * src->ds_cnt])) {
4712 SUMxx += step * step;
4713 SUMxy += step * data[step * src->ds_cnt];
4714 SUMy += data[step * src->ds_cnt];
4715 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4719 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4720 y_intercept = (SUMy - slope * SUMx) / cnt;
4723 (SUMx * SUMy) / cnt) /
4725 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4727 if (dst->vf.op == VDEF_LSLSLOPE) {
4728 dst->vf.val = slope;
4730 } else if (dst->vf.op == VDEF_LSLINT) {
4731 dst->vf.val = y_intercept;
4733 } else if (dst->vf.op == VDEF_LSLCORREL) {
4734 dst->vf.val = correl;
4747 /* NaN < -INF < finite_values < INF */
4748 int vdef_percent_compar(
4754 /* Equality is not returned; this doesn't hurt except
4755 * (maybe) for a little performance.
4758 /* First catch NaN values. They are smallest */
4759 if (isnan(*(double *) a))
4761 if (isnan(*(double *) b))
4763 /* NaN doesn't reach this part so INF and -INF are extremes.
4764 * The sign from isinf() is compatible with the sign we return
4766 if (isinf(*(double *) a))
4767 return isinf(*(double *) a);
4768 if (isinf(*(double *) b))
4769 return isinf(*(double *) b);
4770 /* If we reach this, both values must be finite */
4771 if (*(double *) a < *(double *) b)
4780 rrd_info_type_t type,
4781 rrd_infoval_t value)
4783 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4784 if (im->grinfo == NULL) {
4785 im->grinfo = im->grinfo_current;