1 /****************************************************************************
2 * RRDtool 1.3.1 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
28 #include "rrd_graph.h"
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 ){
3719 static text_prop_t tp_cache[] = { {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}};
3721 if (tp_cache[prop].font_desc == NULL){
3722 if (prop > 0 && tp_cache[0].font_desc != NULL){
3723 tp_cache[prop].font_desc = pango_font_description_copy (tp_cache[0].font_desc);
3724 strcpy(tp_cache[prop].font,tp_cache[0].font);
3725 tp_cache[prop].size = tp_cache[0].size;
3728 tp_cache[prop].font_desc = pango_font_description_new();
3730 im->text_prop[prop].font_desc = pango_font_description_copy (tp_cache[prop].font_desc);
3733 if (font != NULL && strcmp(tp_cache[prop].font,font) != 0){
3734 pango_font_description_free(tp_cache[prop].font_desc);
3735 pango_font_description_free(im->text_prop[prop].font_desc);
3736 tp_cache[prop].font_desc = pango_font_description_from_string( font );
3737 im->text_prop[prop].font_desc = pango_font_description_copy( tp_cache[prop].font_desc );
3738 strncpy(tp_cache[prop].font, font, sizeof(text_prop[prop].font) - 1);
3739 tp_cache[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3740 strcpy(im->text_prop[prop].font,tp_cache[prop].font);
3742 if (size != 0 && size != (tp_cache[prop].size)){
3743 pango_font_description_set_size(tp_cache[prop].font_desc, size * PANGO_SCALE);
3744 pango_font_description_set_size(im->text_prop[prop].font_desc, size * PANGO_SCALE);
3745 im->text_prop[prop].size = size;
3746 tp_cache[prop].size = size;
3748 if (im->text_prop[prop].size < 0){
3749 im->text_prop[prop].size = tp_cache[prop].size;
3750 im->text_prop[prop].font_desc = pango_font_description_copy( tp_cache[prop].font_desc );
3751 strcpy(im->text_prop[prop].font,tp_cache[prop].font);
3753 // fprintf(stderr,"%d %s\n",prop,pango_font_description_to_string(im->text_prop[prop].font_desc));
3756 void rrd_graph_init(
3761 char *deffont = getenv("RRD_DEFAULT_FONT");
3766 #ifdef HAVE_SETLOCALE
3767 setlocale(LC_TIME, "");
3768 #ifdef HAVE_MBSTOWCS
3769 setlocale(LC_CTYPE, "");
3773 im->draw_x_grid = 1;
3774 im->draw_y_grid = 1;
3775 im->extra_flags = 0;
3776 im->font_options = cairo_font_options_create();
3777 im->forceleftspace = 0;
3780 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3781 im->grid_dash_off = 1;
3782 im->grid_dash_on = 1;
3784 im->grinfo = (rrd_info_t *) NULL;
3785 im->grinfo_current = (rrd_info_t *) NULL;
3786 im->imgformat = IF_PNG;
3788 im->use_rrdcached = 0;
3790 im->logarithmic = 0;
3796 im->rendered_image_size = 0;
3797 im->rendered_image = NULL;
3801 im->tabwidth = 40.0;
3802 im->title[0] = '\0';
3803 im->unitsexponent = 9999;
3804 im->unitslength = 6;
3805 im->viewfactor = 1.0;
3806 im->watermark[0] = '\0';
3807 im->with_markup = 0;
3809 im->xlab_user.minsec = -1;
3812 im->ygridstep = DNAN;
3814 im->ylegend[0] = '\0';
3819 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3820 im->cr = cairo_create(im->surface);
3822 for (i = 0; i < DIM(text_prop); i++) {
3823 im->text_prop[i].size = -1;
3824 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3827 im->layout = pango_cairo_create_layout(im->cr);
3828 pango_cairo_context_set_resolution(pango_layout_get_context(im->layout), 100);
3830 cairo_font_options_set_hint_style
3831 (im->font_options, CAIRO_HINT_STYLE_FULL);
3832 cairo_font_options_set_hint_metrics
3833 (im->font_options, CAIRO_HINT_METRICS_ON);
3834 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3838 for (i = 0; i < DIM(graph_col); i++)
3839 im->graph_col[i] = graph_col[i];
3845 void rrd_graph_options(
3852 char *parsetime_error = NULL;
3853 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3854 time_t start_tmp = 0, end_tmp = 0;
3856 rrd_time_value_t start_tv, end_tv;
3857 long unsigned int color;
3858 char *old_locale = "";
3860 /* defines for long options without a short equivalent. should be bytes,
3861 and may not collide with (the ASCII value of) short options */
3862 #define LONGOPT_UNITS_SI 255
3865 struct option long_options[] = {
3866 { "start", required_argument, 0, 's'},
3867 { "end", required_argument, 0, 'e'},
3868 { "x-grid", required_argument, 0, 'x'},
3869 { "y-grid", required_argument, 0, 'y'},
3870 { "vertical-label", required_argument, 0, 'v'},
3871 { "width", required_argument, 0, 'w'},
3872 { "height", required_argument, 0, 'h'},
3873 { "full-size-mode", no_argument, 0, 'D'},
3874 { "interlaced", no_argument, 0, 'i'},
3875 { "upper-limit", required_argument, 0, 'u'},
3876 { "lower-limit", required_argument, 0, 'l'},
3877 { "rigid", no_argument, 0, 'r'},
3878 { "base", required_argument, 0, 'b'},
3879 { "logarithmic", no_argument, 0, 'o'},
3880 { "color", required_argument, 0, 'c'},
3881 { "font", required_argument, 0, 'n'},
3882 { "title", required_argument, 0, 't'},
3883 { "imginfo", required_argument, 0, 'f'},
3884 { "imgformat", required_argument, 0, 'a'},
3885 { "lazy", no_argument, 0, 'z'},
3886 { "zoom", required_argument, 0, 'm'},
3887 { "no-legend", no_argument, 0, 'g'},
3888 { "force-rules-legend", no_argument, 0, 'F'},
3889 { "only-graph", no_argument, 0, 'j'},
3890 { "alt-y-grid", no_argument, 0, 'Y'},
3891 { "no-minor", no_argument, 0, 'I'},
3892 { "slope-mode", no_argument, 0, 'E'},
3893 { "alt-autoscale", no_argument, 0, 'A'},
3894 { "alt-autoscale-min", no_argument, 0, 'J'},
3895 { "alt-autoscale-max", no_argument, 0, 'M'},
3896 { "no-gridfit", no_argument, 0, 'N'},
3897 { "units-exponent", required_argument, 0, 'X'},
3898 { "units-length", required_argument, 0, 'L'},
3899 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3900 { "step", required_argument, 0, 'S'},
3901 { "tabwidth", required_argument, 0, 'T'},
3902 { "font-render-mode", required_argument, 0, 'R'},
3903 { "graph-render-mode", required_argument, 0, 'G'},
3904 { "font-smoothing-threshold", required_argument, 0, 'B'},
3905 { "watermark", required_argument, 0, 'W'},
3906 { "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 */
3907 { "pango-markup", no_argument, 0, 'P'},
3908 { "daemon", required_argument, 0, 'd'},
3914 opterr = 0; /* initialize getopt */
3915 rrd_parsetime("end-24h", &start_tv);
3916 rrd_parsetime("now", &end_tv);
3918 int option_index = 0;
3920 int col_start, col_end;
3922 opt = getopt_long(argc, argv,
3923 "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:",
3924 long_options, &option_index);
3929 im->extra_flags |= NOMINOR;
3932 im->extra_flags |= ALTYGRID;
3935 im->extra_flags |= ALTAUTOSCALE;
3938 im->extra_flags |= ALTAUTOSCALE_MIN;
3941 im->extra_flags |= ALTAUTOSCALE_MAX;
3944 im->extra_flags |= ONLY_GRAPH;
3947 im->extra_flags |= NOLEGEND;
3950 im->extra_flags |= FORCE_RULES_LEGEND;
3952 case LONGOPT_UNITS_SI:
3953 if (im->extra_flags & FORCE_UNITS) {
3954 rrd_set_error("--units can only be used once!");
3955 setlocale(LC_NUMERIC, old_locale);
3958 if (strcmp(optarg, "si") == 0)
3959 im->extra_flags |= FORCE_UNITS_SI;
3961 rrd_set_error("invalid argument for --units: %s", optarg);
3966 im->unitsexponent = atoi(optarg);
3969 im->unitslength = atoi(optarg);
3970 im->forceleftspace = 1;
3973 old_locale = setlocale(LC_NUMERIC, "C");
3974 im->tabwidth = atof(optarg);
3975 setlocale(LC_NUMERIC, old_locale);
3978 old_locale = setlocale(LC_NUMERIC, "C");
3979 im->step = atoi(optarg);
3980 setlocale(LC_NUMERIC, old_locale);
3986 im->with_markup = 1;
3989 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3990 rrd_set_error("start time: %s", parsetime_error);
3995 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3996 rrd_set_error("end time: %s", parsetime_error);
4001 if (strcmp(optarg, "none") == 0) {
4002 im->draw_x_grid = 0;
4006 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4008 &im->xlab_user.gridst,
4010 &im->xlab_user.mgridst,
4012 &im->xlab_user.labst,
4013 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4014 strncpy(im->xlab_form, optarg + stroff,
4015 sizeof(im->xlab_form) - 1);
4016 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4018 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4019 rrd_set_error("unknown keyword %s", scan_gtm);
4022 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4024 rrd_set_error("unknown keyword %s", scan_mtm);
4027 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4028 rrd_set_error("unknown keyword %s", scan_ltm);
4031 im->xlab_user.minsec = 1;
4032 im->xlab_user.stst = im->xlab_form;
4034 rrd_set_error("invalid x-grid format");
4040 if (strcmp(optarg, "none") == 0) {
4041 im->draw_y_grid = 0;
4044 old_locale = setlocale(LC_NUMERIC, "C");
4045 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4046 setlocale(LC_NUMERIC, old_locale);
4047 if (im->ygridstep <= 0) {
4048 rrd_set_error("grid step must be > 0");
4050 } else if (im->ylabfact < 1) {
4051 rrd_set_error("label factor must be > 0");
4055 setlocale(LC_NUMERIC, old_locale);
4056 rrd_set_error("invalid y-grid format");
4061 strncpy(im->ylegend, optarg, 150);
4062 im->ylegend[150] = '\0';
4065 old_locale = setlocale(LC_NUMERIC, "C");
4066 im->maxval = atof(optarg);
4067 setlocale(LC_NUMERIC, old_locale);
4070 old_locale = setlocale(LC_NUMERIC, "C");
4071 im->minval = atof(optarg);
4072 setlocale(LC_NUMERIC, old_locale);
4075 im->base = atol(optarg);
4076 if (im->base != 1024 && im->base != 1000) {
4078 ("the only sensible value for base apart from 1000 is 1024");
4083 long_tmp = atol(optarg);
4084 if (long_tmp < 10) {
4085 rrd_set_error("width below 10 pixels");
4088 im->xsize = long_tmp;
4091 long_tmp = atol(optarg);
4092 if (long_tmp < 10) {
4093 rrd_set_error("height below 10 pixels");
4096 im->ysize = long_tmp;
4099 im->extra_flags |= FULL_SIZE_MODE;
4102 /* interlaced png not supported at the moment */
4108 im->imginfo = optarg;
4112 (im->imgformat = if_conv(optarg)) == -1) {
4113 rrd_set_error("unsupported graphics format '%s'", optarg);
4124 im->logarithmic = 1;
4128 "%10[A-Z]#%n%8lx%n",
4129 col_nam, &col_start, &color, &col_end) == 2) {
4131 int col_len = col_end - col_start;
4136 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4144 (((color & 0xF000) *
4145 0x11000) | ((color & 0x0F00) *
4146 0x01100) | ((color &
4149 ((color & 0x000F) * 0x00011)
4153 color = (color << 8) + 0xff /* shift left by 8 */ ;
4158 rrd_set_error("the color format is #RRGGBB[AA]");
4161 if ((ci = grc_conv(col_nam)) != -1) {
4162 im->graph_col[ci] = gfx_hex_to_col(color);
4164 rrd_set_error("invalid color name '%s'", col_nam);
4168 rrd_set_error("invalid color def format");
4177 old_locale = setlocale(LC_NUMERIC, "C");
4178 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4179 int sindex, propidx;
4181 setlocale(LC_NUMERIC, old_locale);
4182 if ((sindex = text_prop_conv(prop)) != -1) {
4183 for (propidx = sindex;
4184 propidx < TEXT_PROP_LAST; propidx++) {
4186 rrd_set_font_desc(im,propidx,NULL,size);
4188 if ((int) strlen(optarg) > end) {
4189 if (optarg[end] == ':') {
4190 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4193 ("expected : after font size in '%s'",
4198 /* only run the for loop for DEFAULT (0) for
4199 all others, we break here. woodo programming */
4200 if (propidx == sindex && sindex != 0)
4204 rrd_set_error("invalid fonttag '%s'", prop);
4208 setlocale(LC_NUMERIC, old_locale);
4209 rrd_set_error("invalid text property format");
4215 old_locale = setlocale(LC_NUMERIC, "C");
4216 im->zoom = atof(optarg);
4217 setlocale(LC_NUMERIC, old_locale);
4218 if (im->zoom <= 0.0) {
4219 rrd_set_error("zoom factor must be > 0");
4224 strncpy(im->title, optarg, 150);
4225 im->title[150] = '\0';
4228 if (strcmp(optarg, "normal") == 0) {
4229 cairo_font_options_set_antialias
4230 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4231 cairo_font_options_set_hint_style
4232 (im->font_options, CAIRO_HINT_STYLE_FULL);
4233 } else if (strcmp(optarg, "light") == 0) {
4234 cairo_font_options_set_antialias
4235 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4236 cairo_font_options_set_hint_style
4237 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4238 } else if (strcmp(optarg, "mono") == 0) {
4239 cairo_font_options_set_antialias
4240 (im->font_options, CAIRO_ANTIALIAS_NONE);
4241 cairo_font_options_set_hint_style
4242 (im->font_options, CAIRO_HINT_STYLE_FULL);
4244 rrd_set_error("unknown font-render-mode '%s'", optarg);
4249 if (strcmp(optarg, "normal") == 0)
4250 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4251 else if (strcmp(optarg, "mono") == 0)
4252 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4254 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4259 /* not supported curently */
4262 strncpy(im->watermark, optarg, 100);
4263 im->watermark[99] = '\0';
4268 if (im->use_rrdcached)
4270 rrd_set_error ("You cannot specify --daemon "
4274 status = rrdc_connect (optarg);
4277 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4281 im->use_rrdcached = 1;
4286 rrd_set_error("unknown option '%c'", optopt);
4288 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4293 if (im->use_rrdcached == 0)
4297 temp = getenv (ENV_RRDCACHED_ADDRESS);
4302 status = rrdc_connect (temp);
4305 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4309 im->use_rrdcached = 1;
4313 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4314 pango_layout_context_changed(im->layout);
4318 if (im->logarithmic && im->minval <= 0) {
4320 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4324 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4325 /* error string is set in rrd_parsetime.c */
4329 if (start_tmp < 3600 * 24 * 365 * 10) {
4331 ("the first entry to fetch should be after 1980 (%ld)",
4336 if (end_tmp < start_tmp) {
4338 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4342 im->start = start_tmp;
4344 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4347 int rrd_graph_color(
4355 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4357 color = strstr(var, "#");
4358 if (color == NULL) {
4359 if (optional == 0) {
4360 rrd_set_error("Found no color in %s", err);
4367 long unsigned int col;
4369 rest = strstr(color, ":");
4376 sscanf(color, "#%6lx%n", &col, &n);
4377 col = (col << 8) + 0xff /* shift left by 8 */ ;
4379 rrd_set_error("Color problem in %s", err);
4382 sscanf(color, "#%8lx%n", &col, &n);
4386 rrd_set_error("Color problem in %s", err);
4388 if (rrd_test_error())
4390 gdp->col = gfx_hex_to_col(col);
4403 while (*ptr != '\0')
4404 if (*ptr++ == '%') {
4406 /* line cannot end with percent char */
4409 /* '%s', '%S' and '%%' are allowed */
4410 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4412 /* %c is allowed (but use only with vdef!) */
4413 else if (*ptr == 'c') {
4418 /* or else '% 6.2lf' and such are allowed */
4420 /* optional padding character */
4421 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4423 /* This should take care of 'm.n' with all three optional */
4424 while (*ptr >= '0' && *ptr <= '9')
4428 while (*ptr >= '0' && *ptr <= '9')
4430 /* Either 'le', 'lf' or 'lg' must follow here */
4433 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4448 const char *const str)
4450 /* A VDEF currently is either "func" or "param,func"
4451 * so the parsing is rather simple. Change if needed.
4459 old_locale = setlocale(LC_NUMERIC, "C");
4460 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4461 setlocale(LC_NUMERIC, old_locale);
4462 if (n == (int) strlen(str)) { /* matched */
4466 sscanf(str, "%29[A-Z]%n", func, &n);
4467 if (n == (int) strlen(str)) { /* matched */
4471 ("Unknown function string '%s' in VDEF '%s'",
4476 if (!strcmp("PERCENT", func))
4477 gdes->vf.op = VDEF_PERCENT;
4478 else if (!strcmp("MAXIMUM", func))
4479 gdes->vf.op = VDEF_MAXIMUM;
4480 else if (!strcmp("AVERAGE", func))
4481 gdes->vf.op = VDEF_AVERAGE;
4482 else if (!strcmp("STDEV", func))
4483 gdes->vf.op = VDEF_STDEV;
4484 else if (!strcmp("MINIMUM", func))
4485 gdes->vf.op = VDEF_MINIMUM;
4486 else if (!strcmp("TOTAL", func))
4487 gdes->vf.op = VDEF_TOTAL;
4488 else if (!strcmp("FIRST", func))
4489 gdes->vf.op = VDEF_FIRST;
4490 else if (!strcmp("LAST", func))
4491 gdes->vf.op = VDEF_LAST;
4492 else if (!strcmp("LSLSLOPE", func))
4493 gdes->vf.op = VDEF_LSLSLOPE;
4494 else if (!strcmp("LSLINT", func))
4495 gdes->vf.op = VDEF_LSLINT;
4496 else if (!strcmp("LSLCORREL", func))
4497 gdes->vf.op = VDEF_LSLCORREL;
4500 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4503 switch (gdes->vf.op) {
4505 if (isnan(param)) { /* no parameter given */
4507 ("Function '%s' needs parameter in VDEF '%s'\n",
4511 if (param >= 0.0 && param <= 100.0) {
4512 gdes->vf.param = param;
4513 gdes->vf.val = DNAN; /* undefined */
4514 gdes->vf.when = 0; /* undefined */
4517 ("Parameter '%f' out of range in VDEF '%s'\n",
4518 param, gdes->vname);
4531 case VDEF_LSLCORREL:
4533 gdes->vf.param = DNAN;
4534 gdes->vf.val = DNAN;
4538 ("Function '%s' needs no parameter in VDEF '%s'\n",
4552 graph_desc_t *src, *dst;
4557 dst = &im->gdes[gdi];
4558 src = &im->gdes[dst->vidx];
4559 data = src->data + src->ds;
4561 src->end_orig % (long) src->step ==
4562 0 ? src->end_orig : (src->end_orig + (long) src->step -
4563 src->end_orig % (long) src->step);
4565 steps = (end - src->start) / src->step;
4568 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4569 src->start, src->end_orig, steps);
4571 switch (dst->vf.op) {
4575 if ((array = malloc(steps * sizeof(double))) == NULL) {
4576 rrd_set_error("malloc VDEV_PERCENT");
4579 for (step = 0; step < steps; step++) {
4580 array[step] = data[step * src->ds_cnt];
4582 qsort(array, step, sizeof(double), vdef_percent_compar);
4583 field = (steps - 1) * dst->vf.param / 100;
4584 dst->vf.val = array[field];
4585 dst->vf.when = 0; /* no time component */
4588 for (step = 0; step < steps; step++)
4589 printf("DEBUG: %3li:%10.2f %c\n",
4590 step, array[step], step == field ? '*' : ' ');
4596 while (step != steps && isnan(data[step * src->ds_cnt]))
4598 if (step == steps) {
4602 dst->vf.val = data[step * src->ds_cnt];
4603 dst->vf.when = src->start + (step + 1) * src->step;
4605 while (step != steps) {
4606 if (finite(data[step * src->ds_cnt])) {
4607 if (data[step * src->ds_cnt] > dst->vf.val) {
4608 dst->vf.val = data[step * src->ds_cnt];
4609 dst->vf.when = src->start + (step + 1) * src->step;
4620 double average = 0.0;
4622 for (step = 0; step < steps; step++) {
4623 if (finite(data[step * src->ds_cnt])) {
4624 sum += data[step * src->ds_cnt];
4629 if (dst->vf.op == VDEF_TOTAL) {
4630 dst->vf.val = sum * src->step;
4631 dst->vf.when = 0; /* no time component */
4632 } else if (dst->vf.op == VDEF_AVERAGE) {
4633 dst->vf.val = sum / cnt;
4634 dst->vf.when = 0; /* no time component */
4636 average = sum / cnt;
4638 for (step = 0; step < steps; step++) {
4639 if (finite(data[step * src->ds_cnt])) {
4640 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4643 dst->vf.val = pow(sum / cnt, 0.5);
4644 dst->vf.when = 0; /* no time component */
4654 while (step != steps && isnan(data[step * src->ds_cnt]))
4656 if (step == steps) {
4660 dst->vf.val = data[step * src->ds_cnt];
4661 dst->vf.when = src->start + (step + 1) * src->step;
4663 while (step != steps) {
4664 if (finite(data[step * src->ds_cnt])) {
4665 if (data[step * src->ds_cnt] < dst->vf.val) {
4666 dst->vf.val = data[step * src->ds_cnt];
4667 dst->vf.when = src->start + (step + 1) * src->step;
4674 /* The time value returned here is one step before the
4675 * actual time value. This is the start of the first
4679 while (step != steps && isnan(data[step * src->ds_cnt]))
4681 if (step == steps) { /* all entries were NaN */
4685 dst->vf.val = data[step * src->ds_cnt];
4686 dst->vf.when = src->start + step * src->step;
4690 /* The time value returned here is the
4691 * actual time value. This is the end of the last
4695 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4697 if (step < 0) { /* all entries were NaN */
4701 dst->vf.val = data[step * src->ds_cnt];
4702 dst->vf.when = src->start + (step + 1) * src->step;
4707 case VDEF_LSLCORREL:{
4708 /* Bestfit line by linear least squares method */
4711 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4718 for (step = 0; step < steps; step++) {
4719 if (finite(data[step * src->ds_cnt])) {
4722 SUMxx += step * step;
4723 SUMxy += step * data[step * src->ds_cnt];
4724 SUMy += data[step * src->ds_cnt];
4725 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4729 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4730 y_intercept = (SUMy - slope * SUMx) / cnt;
4733 (SUMx * SUMy) / cnt) /
4735 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4737 if (dst->vf.op == VDEF_LSLSLOPE) {
4738 dst->vf.val = slope;
4740 } else if (dst->vf.op == VDEF_LSLINT) {
4741 dst->vf.val = y_intercept;
4743 } else if (dst->vf.op == VDEF_LSLCORREL) {
4744 dst->vf.val = correl;
4757 /* NaN < -INF < finite_values < INF */
4758 int vdef_percent_compar(
4764 /* Equality is not returned; this doesn't hurt except
4765 * (maybe) for a little performance.
4768 /* First catch NaN values. They are smallest */
4769 if (isnan(*(double *) a))
4771 if (isnan(*(double *) b))
4773 /* NaN doesn't reach this part so INF and -INF are extremes.
4774 * The sign from isinf() is compatible with the sign we return
4776 if (isinf(*(double *) a))
4777 return isinf(*(double *) a);
4778 if (isinf(*(double *) b))
4779 return isinf(*(double *) b);
4780 /* If we reach this, both values must be finite */
4781 if (*(double *) a < *(double *) b)
4790 rrd_info_type_t type,
4791 rrd_infoval_t value)
4793 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4794 if (im->grinfo == NULL) {
4795 im->grinfo = im->grinfo_current;