1 /****************************************************************************
2 * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
13 #include "plbasename.h"
18 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
31 #include "rrd_graph.h"
32 #include "rrd_client.h"
34 /* some constant definitions */
38 #ifndef RRD_DEFAULT_FONT
39 /* there is special code later to pick Cour.ttf when running on windows */
40 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
43 text_prop_t text_prop[] = {
44 {8.0, RRD_DEFAULT_FONT,NULL}
46 {9.0, RRD_DEFAULT_FONT,NULL}
48 {7.0, RRD_DEFAULT_FONT,NULL}
50 {8.0, RRD_DEFAULT_FONT,NULL}
52 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
54 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
58 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
60 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
62 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
64 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
66 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
68 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
70 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
72 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
74 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
76 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
77 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
79 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
81 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
83 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
85 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
87 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
90 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
93 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
96 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
98 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
99 365 * 24 * 3600, "%y"}
101 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
104 /* sensible y label intervals ...*/
128 {20.0, {1, 5, 10, 20}
134 {100.0, {1, 2, 5, 10}
137 {200.0, {1, 5, 10, 20}
140 {500.0, {1, 2, 4, 10}
148 gfx_color_t graph_col[] = /* default colors */
150 {1.00, 1.00, 1.00, 1.00}, /* canvas */
151 {0.95, 0.95, 0.95, 1.00}, /* background */
152 {0.81, 0.81, 0.81, 1.00}, /* shade A */
153 {0.62, 0.62, 0.62, 1.00}, /* shade B */
154 {0.56, 0.56, 0.56, 0.75}, /* grid */
155 {0.87, 0.31, 0.31, 0.60}, /* major grid */
156 {0.00, 0.00, 0.00, 1.00}, /* font */
157 {0.50, 0.12, 0.12, 1.00}, /* arrow */
158 {0.12, 0.12, 0.12, 1.00}, /* axis */
159 {0.00, 0.00, 0.00, 1.00} /* frame */
166 # define DPRINT(x) (void)(printf x, printf("\n"))
172 /* initialize with xtr(im,0); */
180 pixie = (double) im->xsize / (double) (im->end - im->start);
183 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
186 /* translate data values into y coordinates */
195 if (!im->logarithmic)
196 pixie = (double) im->ysize / (im->maxval - im->minval);
199 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
201 } else if (!im->logarithmic) {
202 yval = im->yorigin - pixie * (value - im->minval);
204 if (value < im->minval) {
207 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
215 /* conversion function for symbolic entry names */
218 #define conv_if(VV,VVV) \
219 if (strcmp(#VV, string) == 0) return VVV ;
225 conv_if(PRINT, GF_PRINT);
226 conv_if(GPRINT, GF_GPRINT);
227 conv_if(COMMENT, GF_COMMENT);
228 conv_if(HRULE, GF_HRULE);
229 conv_if(VRULE, GF_VRULE);
230 conv_if(LINE, GF_LINE);
231 conv_if(AREA, GF_AREA);
232 conv_if(STACK, GF_STACK);
233 conv_if(TICK, GF_TICK);
234 conv_if(TEXTALIGN, GF_TEXTALIGN);
235 conv_if(DEF, GF_DEF);
236 conv_if(CDEF, GF_CDEF);
237 conv_if(VDEF, GF_VDEF);
238 conv_if(XPORT, GF_XPORT);
239 conv_if(SHIFT, GF_SHIFT);
241 return (enum gf_en)(-1);
244 enum gfx_if_en if_conv(
248 conv_if(PNG, IF_PNG);
249 conv_if(SVG, IF_SVG);
250 conv_if(EPS, IF_EPS);
251 conv_if(PDF, IF_PDF);
253 return (enum gfx_if_en)(-1);
256 enum tmt_en tmt_conv(
260 conv_if(SECOND, TMT_SECOND);
261 conv_if(MINUTE, TMT_MINUTE);
262 conv_if(HOUR, TMT_HOUR);
263 conv_if(DAY, TMT_DAY);
264 conv_if(WEEK, TMT_WEEK);
265 conv_if(MONTH, TMT_MONTH);
266 conv_if(YEAR, TMT_YEAR);
267 return (enum tmt_en)(-1);
270 enum grc_en grc_conv(
274 conv_if(BACK, GRC_BACK);
275 conv_if(CANVAS, GRC_CANVAS);
276 conv_if(SHADEA, GRC_SHADEA);
277 conv_if(SHADEB, GRC_SHADEB);
278 conv_if(GRID, GRC_GRID);
279 conv_if(MGRID, GRC_MGRID);
280 conv_if(FONT, GRC_FONT);
281 conv_if(ARROW, GRC_ARROW);
282 conv_if(AXIS, GRC_AXIS);
283 conv_if(FRAME, GRC_FRAME);
285 return (enum grc_en)(-1);
288 enum text_prop_en text_prop_conv(
292 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
293 conv_if(TITLE, TEXT_PROP_TITLE);
294 conv_if(AXIS, TEXT_PROP_AXIS);
295 conv_if(UNIT, TEXT_PROP_UNIT);
296 conv_if(LEGEND, TEXT_PROP_LEGEND);
297 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
298 return (enum text_prop_en)(-1);
308 cairo_status_t status = (cairo_status_t) 0;
313 if (im->daemon_addr != NULL)
314 free(im->daemon_addr);
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((double)(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 (rrdc_is_connected(im->daemon_addr))
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 (rrdc_is_connected()) */
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 greatest common divisor 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 (long*)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 = (rrd_value_t*)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 = (rrd_value_t*)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 = (char*)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;
1662 double leg_x = border;
1663 int leg_y = im->yimg;
1664 int leg_y_prev = im->yimg;
1667 int i, ii, mark = 0;
1668 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1672 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1673 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1674 rrd_set_error("malloc for legspace");
1678 for (i = 0; i < im->gdes_c; i++) {
1679 char prt_fctn; /*special printfunctions */
1681 /* hide legends for rules which are not displayed */
1682 if (im->gdes[i].gf == GF_TEXTALIGN) {
1683 default_txtalign = im->gdes[i].txtalign;
1686 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1687 if (im->gdes[i].gf == GF_HRULE
1688 && (im->gdes[i].yrule <
1689 im->minval || im->gdes[i].yrule > im->maxval))
1690 im->gdes[i].legend[0] = '\0';
1691 if (im->gdes[i].gf == GF_VRULE
1692 && (im->gdes[i].xrule <
1693 im->start || im->gdes[i].xrule > im->end))
1694 im->gdes[i].legend[0] = '\0';
1697 /* turn \\t into tab */
1698 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1699 memmove(tab, tab + 1, strlen(tab));
1702 leg_cc = strlen(im->gdes[i].legend);
1703 /* is there a controle code at the end of the legend string ? */
1704 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1705 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1707 im->gdes[i].legend[leg_cc] = '\0';
1711 /* only valid control codes */
1712 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1716 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1719 ("Unknown control code at the end of '%s\\%c'",
1720 im->gdes[i].legend, prt_fctn);
1724 if (prt_fctn == 'n') {
1728 /* remove exess space from the end of the legend for \g */
1729 while (prt_fctn == 'g' &&
1730 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1732 im->gdes[i].legend[leg_cc] = '\0';
1737 /* no interleg space if string ends in \g */
1738 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1740 fill += legspace[i];
1743 gfx_get_text_width(im,
1749 im->tabwidth, im->gdes[i].legend);
1754 /* who said there was a special tag ... ? */
1755 if (prt_fctn == 'g') {
1759 if (prt_fctn == '\0') {
1760 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1761 /* just one legend item is left right or center */
1762 switch (default_txtalign) {
1777 /* is it time to place the legends ? */
1778 if (fill > im->ximg - 2 * border) {
1786 if (leg_c == 1 && prt_fctn == 'j') {
1792 if (prt_fctn != '\0') {
1794 if (leg_c >= 2 && prt_fctn == 'j') {
1795 glue = (double)(im->ximg - fill - 2 * border) / (double)(leg_c - 1);
1799 if (prt_fctn == 'c')
1800 leg_x = (double)(im->ximg - fill) / 2.0;
1801 if (prt_fctn == 'r')
1802 leg_x = im->ximg - fill - border;
1803 for (ii = mark; ii <= i; ii++) {
1804 if (im->gdes[ii].legend[0] == '\0')
1805 continue; /* skip empty legends */
1806 im->gdes[ii].leg_x = leg_x;
1807 im->gdes[ii].leg_y = leg_y;
1809 (double)gfx_get_text_width(im, leg_x,
1814 im->tabwidth, im->gdes[ii].legend)
1815 +(double)legspace[ii]
1819 if (leg_x > border || prt_fctn == 's')
1820 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1821 if (prt_fctn == 's')
1822 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1829 if (im->extra_flags & FULL_SIZE_MODE) {
1830 /* now for some backpaddeling. We have to shift up all the
1831 legend items into the graph and tell the caller about the
1832 space we used up. */
1833 long shift_up = leg_y - im->yimg - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 + border * 0.7;
1834 for (i = 0; i < im->gdes_c; i++) {
1835 im->gdes[i].leg_y -= shift_up;
1837 im->yorigin = im->yorigin - leg_y + im->yimg - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 - border;
1841 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1849 /* create a grid on the graph. it determines what to do
1850 from the values of xsize, start and end */
1852 /* the xaxis labels are determined from the number of seconds per pixel
1853 in the requested graph */
1855 int calc_horizontal_grid(
1863 int decimals, fractionals;
1865 im->ygrid_scale.labfact = 2;
1866 range = im->maxval - im->minval;
1867 scaledrange = range / im->magfact;
1868 /* does the scale of this graph make it impossible to put lines
1869 on it? If so, give up. */
1870 if (isnan(scaledrange)) {
1874 /* find grid spaceing */
1876 if (isnan(im->ygridstep)) {
1877 if (im->extra_flags & ALTYGRID) {
1878 /* find the value with max number of digits. Get number of digits */
1881 (max(fabs(im->maxval), fabs(im->minval)) *
1882 im->viewfactor / im->magfact));
1883 if (decimals <= 0) /* everything is small. make place for zero */
1885 im->ygrid_scale.gridstep =
1887 floor(log10(range * im->viewfactor / im->magfact))) /
1888 im->viewfactor * im->magfact;
1889 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1890 im->ygrid_scale.gridstep = 0.1;
1891 /* should have at least 5 lines but no more then 15 */
1892 if (range / im->ygrid_scale.gridstep < 5
1893 && im->ygrid_scale.gridstep >= 30)
1894 im->ygrid_scale.gridstep /= 10;
1895 if (range / im->ygrid_scale.gridstep > 15)
1896 im->ygrid_scale.gridstep *= 10;
1897 if (range / im->ygrid_scale.gridstep > 5) {
1898 im->ygrid_scale.labfact = 1;
1899 if (range / im->ygrid_scale.gridstep > 8
1900 || im->ygrid_scale.gridstep <
1901 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1902 im->ygrid_scale.labfact = 2;
1904 im->ygrid_scale.gridstep /= 5;
1905 im->ygrid_scale.labfact = 5;
1909 (im->ygrid_scale.gridstep *
1910 (double) im->ygrid_scale.labfact * im->viewfactor /
1912 if (fractionals < 0) { /* small amplitude. */
1913 int len = decimals - fractionals + 1;
1915 if (im->unitslength < len + 2)
1916 im->unitslength = len + 2;
1917 sprintf(im->ygrid_scale.labfmt,
1919 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1921 int len = decimals + 1;
1923 if (im->unitslength < len + 2)
1924 im->unitslength = len + 2;
1925 sprintf(im->ygrid_scale.labfmt,
1926 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1928 } else { /* classic rrd grid */
1929 for (i = 0; ylab[i].grid > 0; i++) {
1930 pixel = im->ysize / (scaledrange / ylab[i].grid);
1936 for (i = 0; i < 4; i++) {
1937 if (pixel * ylab[gridind].lfac[i] >=
1938 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1939 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1944 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1947 im->ygrid_scale.gridstep = im->ygridstep;
1948 im->ygrid_scale.labfact = im->ylabfact;
1953 int draw_horizontal_grid(
1959 char graph_label[100];
1961 double X0 = im->xorigin;
1962 double X1 = im->xorigin + im->xsize;
1963 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1964 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1966 double second_axis_magfact = 0;
1967 char *second_axis_symb = "";
1970 im->ygrid_scale.gridstep /
1971 (double) im->magfact * (double) im->viewfactor;
1972 MaxY = scaledstep * (double) egrid;
1973 for (i = sgrid; i <= egrid; i++) {
1975 im->ygrid_scale.gridstep * i);
1977 im->ygrid_scale.gridstep * (i + 1));
1979 if (floor(Y0 + 0.5) >=
1980 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1981 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1982 with the chosen settings. Add a label if required by settings, or if
1983 there is only one label so far and the next grid line is out of bounds. */
1984 if (i % im->ygrid_scale.labfact == 0
1986 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1987 if (im->symbol == ' ') {
1988 if (im->extra_flags & ALTYGRID) {
1989 sprintf(graph_label,
1990 im->ygrid_scale.labfmt,
1991 scaledstep * (double) i);
1994 sprintf(graph_label, "%4.1f",
1995 scaledstep * (double) i);
1997 sprintf(graph_label, "%4.0f",
1998 scaledstep * (double) i);
2002 char sisym = (i == 0 ? ' ' : im->symbol);
2004 if (im->extra_flags & ALTYGRID) {
2005 sprintf(graph_label,
2006 im->ygrid_scale.labfmt,
2007 scaledstep * (double) i, sisym);
2010 sprintf(graph_label, "%4.1f %c",
2011 scaledstep * (double) i, sisym);
2013 sprintf(graph_label, "%4.0f %c",
2014 scaledstep * (double) i, sisym);
2019 if (im->second_axis_scale != 0){
2020 char graph_label_right[100];
2021 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2022 if (im->second_axis_format[0] == '\0'){
2023 if (!second_axis_magfact){
2024 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2025 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2027 sval /= second_axis_magfact;
2030 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2032 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2036 sprintf(graph_label_right,im->second_axis_format,sval);
2040 im->graph_col[GRC_FONT],
2041 im->text_prop[TEXT_PROP_AXIS].font_desc,
2042 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2043 graph_label_right );
2049 text_prop[TEXT_PROP_AXIS].
2051 im->graph_col[GRC_FONT],
2053 text_prop[TEXT_PROP_AXIS].
2056 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2057 gfx_line(im, X0 - 2, Y0, X0, Y0,
2058 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2059 gfx_line(im, X1, Y0, X1 + 2, Y0,
2060 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2061 gfx_dashed_line(im, X0 - 2, Y0,
2067 im->grid_dash_on, im->grid_dash_off);
2068 } else if (!(im->extra_flags & NOMINOR)) {
2071 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2072 gfx_line(im, X1, Y0, X1 + 2, Y0,
2073 GRIDWIDTH, im->graph_col[GRC_GRID]);
2074 gfx_dashed_line(im, X0 - 1, Y0,
2078 graph_col[GRC_GRID],
2079 im->grid_dash_on, im->grid_dash_off);
2086 /* this is frexp for base 10 */
2097 iexp = floor(log((double)fabs(x)) / log((double)10));
2098 mnt = x / pow(10.0, iexp);
2101 mnt = x / pow(10.0, iexp);
2108 /* logaritmic horizontal grid */
2109 int horizontal_log_grid(
2113 double yloglab[][10] = {
2115 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2117 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2119 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2136 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2138 int i, j, val_exp, min_exp;
2139 double nex; /* number of decades in data */
2140 double logscale; /* scale in logarithmic space */
2141 int exfrac = 1; /* decade spacing */
2142 int mid = -1; /* row in yloglab for major grid */
2143 double mspac; /* smallest major grid spacing (pixels) */
2144 int flab; /* first value in yloglab to use */
2145 double value, tmp, pre_value;
2147 char graph_label[100];
2149 nex = log10(im->maxval / im->minval);
2150 logscale = im->ysize / nex;
2151 /* major spacing for data with high dynamic range */
2152 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2159 /* major spacing for less dynamic data */
2161 /* search best row in yloglab */
2163 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2164 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2167 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2170 /* find first value in yloglab */
2172 yloglab[mid][flab] < 10
2173 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2174 if (yloglab[mid][flab] == 10.0) {
2179 if (val_exp % exfrac)
2180 val_exp += abs(-val_exp % exfrac);
2182 X1 = im->xorigin + im->xsize;
2187 value = yloglab[mid][flab] * pow(10.0, val_exp);
2188 if (AlmostEqual2sComplement(value, pre_value, 4))
2189 break; /* it seems we are not converging */
2191 Y0 = ytr(im, value);
2192 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2194 /* major grid line */
2196 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2197 gfx_line(im, X1, Y0, X1 + 2, Y0,
2198 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2199 gfx_dashed_line(im, X0 - 2, Y0,
2204 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2206 if (im->extra_flags & FORCE_UNITS_SI) {
2211 scale = floor(val_exp / 3.0);
2213 pvalue = pow(10.0, val_exp % 3);
2215 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2216 pvalue *= yloglab[mid][flab];
2217 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2218 && ((scale + si_symbcenter) >= 0))
2219 symbol = si_symbol[scale + si_symbcenter];
2222 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2224 sprintf(graph_label, "%3.0e", value);
2226 if (im->second_axis_scale != 0){
2227 char graph_label_right[100];
2228 double sval = value*im->second_axis_scale+im->second_axis_shift;
2229 if (im->second_axis_format[0] == '\0'){
2230 if (im->extra_flags & FORCE_UNITS_SI) {
2233 auto_scale(im,&sval,&symb,&mfac);
2234 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2237 sprintf(graph_label_right,"%3.0e", sval);
2241 sprintf(graph_label_right,im->second_axis_format,sval);
2246 im->graph_col[GRC_FONT],
2247 im->text_prop[TEXT_PROP_AXIS].font_desc,
2248 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2249 graph_label_right );
2255 text_prop[TEXT_PROP_AXIS].
2257 im->graph_col[GRC_FONT],
2259 text_prop[TEXT_PROP_AXIS].
2262 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2264 if (mid < 4 && exfrac == 1) {
2265 /* find first and last minor line behind current major line
2266 * i is the first line and j tha last */
2268 min_exp = val_exp - 1;
2269 for (i = 1; yloglab[mid][i] < 10.0; i++);
2270 i = yloglab[mid][i - 1] + 1;
2274 i = yloglab[mid][flab - 1] + 1;
2275 j = yloglab[mid][flab];
2278 /* draw minor lines below current major line */
2279 for (; i < j; i++) {
2281 value = i * pow(10.0, min_exp);
2282 if (value < im->minval)
2284 Y0 = ytr(im, value);
2285 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2290 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2291 gfx_line(im, X1, Y0, X1 + 2, Y0,
2292 GRIDWIDTH, im->graph_col[GRC_GRID]);
2293 gfx_dashed_line(im, X0 - 1, Y0,
2297 graph_col[GRC_GRID],
2298 im->grid_dash_on, im->grid_dash_off);
2300 } else if (exfrac > 1) {
2301 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2302 value = pow(10.0, i);
2303 if (value < im->minval)
2305 Y0 = ytr(im, value);
2306 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2311 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2312 gfx_line(im, X1, Y0, X1 + 2, Y0,
2313 GRIDWIDTH, im->graph_col[GRC_GRID]);
2314 gfx_dashed_line(im, X0 - 1, Y0,
2318 graph_col[GRC_GRID],
2319 im->grid_dash_on, im->grid_dash_off);
2324 if (yloglab[mid][++flab] == 10.0) {
2330 /* draw minor lines after highest major line */
2331 if (mid < 4 && exfrac == 1) {
2332 /* find first and last minor line below current major line
2333 * i is the first line and j tha last */
2335 min_exp = val_exp - 1;
2336 for (i = 1; yloglab[mid][i] < 10.0; i++);
2337 i = yloglab[mid][i - 1] + 1;
2341 i = yloglab[mid][flab - 1] + 1;
2342 j = yloglab[mid][flab];
2345 /* draw minor lines below current major line */
2346 for (; i < j; i++) {
2348 value = i * pow(10.0, min_exp);
2349 if (value < im->minval)
2351 Y0 = ytr(im, value);
2352 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2356 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2357 gfx_line(im, X1, Y0, X1 + 2, Y0,
2358 GRIDWIDTH, im->graph_col[GRC_GRID]);
2359 gfx_dashed_line(im, X0 - 1, Y0,
2363 graph_col[GRC_GRID],
2364 im->grid_dash_on, im->grid_dash_off);
2367 /* fancy minor gridlines */
2368 else if (exfrac > 1) {
2369 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2370 value = pow(10.0, i);
2371 if (value < im->minval)
2373 Y0 = ytr(im, value);
2374 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2378 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2379 gfx_line(im, X1, Y0, X1 + 2, Y0,
2380 GRIDWIDTH, im->graph_col[GRC_GRID]);
2381 gfx_dashed_line(im, X0 - 1, Y0,
2385 graph_col[GRC_GRID],
2386 im->grid_dash_on, im->grid_dash_off);
2397 int xlab_sel; /* which sort of label and grid ? */
2398 time_t ti, tilab, timajor;
2400 char graph_label[100];
2401 double X0, Y0, Y1; /* points for filled graph and more */
2404 /* the type of time grid is determined by finding
2405 the number of seconds per pixel in the graph */
2406 if (im->xlab_user.minsec == -1) {
2407 factor = (im->end - im->start) / im->xsize;
2409 while (xlab[xlab_sel + 1].minsec !=
2410 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2412 } /* pick the last one */
2413 while (xlab[xlab_sel - 1].minsec ==
2414 xlab[xlab_sel].minsec
2415 && xlab[xlab_sel].length > (im->end - im->start)) {
2417 } /* go back to the smallest size */
2418 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2419 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2420 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2421 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2422 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2423 im->xlab_user.labst = xlab[xlab_sel].labst;
2424 im->xlab_user.precis = xlab[xlab_sel].precis;
2425 im->xlab_user.stst = xlab[xlab_sel].stst;
2428 /* y coords are the same for every line ... */
2430 Y1 = im->yorigin - im->ysize;
2431 /* paint the minor grid */
2432 if (!(im->extra_flags & NOMINOR)) {
2433 for (ti = find_first_time(im->start,
2441 find_first_time(im->start,
2448 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2450 /* are we inside the graph ? */
2451 if (ti < im->start || ti > im->end)
2453 while (timajor < ti) {
2454 timajor = find_next_time(timajor,
2457 mgridtm, im->xlab_user.mgridst);
2460 continue; /* skip as falls on major grid line */
2462 gfx_line(im, X0, Y1 - 2, X0, Y1,
2463 GRIDWIDTH, im->graph_col[GRC_GRID]);
2464 gfx_line(im, X0, Y0, X0, Y0 + 2,
2465 GRIDWIDTH, im->graph_col[GRC_GRID]);
2466 gfx_dashed_line(im, X0, Y0 + 1, X0,
2469 graph_col[GRC_GRID],
2470 im->grid_dash_on, im->grid_dash_off);
2474 /* paint the major grid */
2475 for (ti = find_first_time(im->start,
2483 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2485 /* are we inside the graph ? */
2486 if (ti < im->start || ti > im->end)
2489 gfx_line(im, X0, Y1 - 2, X0, Y1,
2490 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2491 gfx_line(im, X0, Y0, X0, Y0 + 3,
2492 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2493 gfx_dashed_line(im, X0, Y0 + 3, X0,
2497 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2499 /* paint the labels below the graph */
2501 find_first_time(im->start -
2510 im->xlab_user.precis / 2;
2511 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2513 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2514 /* are we inside the graph ? */
2515 if (tilab < im->start || tilab > im->end)
2518 localtime_r(&tilab, &tm);
2519 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2521 # error "your libc has no strftime I guess we'll abort the exercise here."
2526 im->graph_col[GRC_FONT],
2528 text_prop[TEXT_PROP_AXIS].
2531 GFX_H_CENTER, GFX_V_TOP, graph_label);
2540 /* draw x and y axis */
2541 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2542 im->xorigin+im->xsize,im->yorigin-im->ysize,
2543 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2545 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2546 im->xorigin+im->xsize,im->yorigin-im->ysize,
2547 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2549 gfx_line(im, im->xorigin - 4,
2551 im->xorigin + im->xsize +
2552 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2553 gfx_line(im, im->xorigin,
2556 im->yorigin - im->ysize -
2557 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2558 /* arrow for X and Y axis direction */
2559 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 */
2560 im->graph_col[GRC_ARROW]);
2562 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 */
2563 im->graph_col[GRC_ARROW]);
2565 if (im->second_axis_scale != 0){
2566 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2567 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2568 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2570 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2571 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2572 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2573 im->graph_col[GRC_ARROW]);
2584 double X0, Y0; /* points for filled graph and more */
2585 struct gfx_color_t water_color;
2587 /* draw 3d border */
2588 gfx_new_area(im, 0, im->yimg,
2589 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2590 gfx_add_point(im, im->ximg - 2, 2);
2591 gfx_add_point(im, im->ximg, 0);
2592 gfx_add_point(im, 0, 0);
2594 gfx_new_area(im, 2, im->yimg - 2,
2596 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2597 gfx_add_point(im, im->ximg, 0);
2598 gfx_add_point(im, im->ximg, im->yimg);
2599 gfx_add_point(im, 0, im->yimg);
2601 if (im->draw_x_grid == 1)
2603 if (im->draw_y_grid == 1) {
2604 if (im->logarithmic) {
2605 res = horizontal_log_grid(im);
2607 res = draw_horizontal_grid(im);
2610 /* dont draw horizontal grid if there is no min and max val */
2612 char *nodata = "No Data found";
2614 gfx_text(im, im->ximg / 2,
2617 im->graph_col[GRC_FONT],
2619 text_prop[TEXT_PROP_AXIS].
2622 GFX_H_CENTER, GFX_V_CENTER, nodata);
2626 /* yaxis unit description */
2627 if (im->ylegend[0] != '\0'){
2632 im->graph_col[GRC_FONT],
2634 text_prop[TEXT_PROP_UNIT].
2637 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2639 if (im->second_axis_legend[0] != '\0'){
2640 double Xylabel=gfx_get_text_width(im, 0,
2641 im->text_prop[TEXT_PROP_AXIS].font_desc,
2643 "0") * im->unitslength
2644 + im->text_prop[TEXT_PROP_UNIT].size *2;
2646 im->xorigin+im->xsize+Xylabel+8, (im->yorigin - im->ysize/2),
2647 im->graph_col[GRC_FONT],
2648 im->text_prop[TEXT_PROP_UNIT].font_desc,
2650 RRDGRAPH_YLEGEND_ANGLE,
2651 GFX_H_CENTER, GFX_V_CENTER,
2652 im->second_axis_legend);
2658 im->graph_col[GRC_FONT],
2660 text_prop[TEXT_PROP_TITLE].
2662 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2663 /* rrdtool 'logo' */
2664 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2665 water_color = im->graph_col[GRC_FONT];
2666 water_color.alpha = 0.3;
2667 gfx_text(im, im->ximg - 4, 5,
2670 text_prop[TEXT_PROP_WATERMARK].
2671 font_desc, im->tabwidth,
2672 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2674 /* graph watermark */
2675 if (im->watermark[0] != '\0') {
2677 im->ximg / 2, im->yimg - 6,
2680 text_prop[TEXT_PROP_WATERMARK].
2681 font_desc, im->tabwidth, 0,
2682 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2686 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2687 for (i = 0; i < im->gdes_c; i++) {
2688 if (im->gdes[i].legend[0] == '\0')
2690 /* im->gdes[i].leg_y is the bottom of the legend */
2691 X0 = im->gdes[i].leg_x;
2692 Y0 = im->gdes[i].leg_y;
2693 gfx_text(im, X0, Y0,
2694 im->graph_col[GRC_FONT],
2697 [TEXT_PROP_LEGEND].font_desc,
2699 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2700 /* The legend for GRAPH items starts with "M " to have
2701 enough space for the box */
2702 if (im->gdes[i].gf != GF_PRINT &&
2703 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2707 boxH = gfx_get_text_width(im, 0,
2712 im->tabwidth, "o") * 1.2;
2714 /* shift the box up a bit */
2716 /* make sure transparent colors show up the same way as in the graph */
2719 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2720 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2722 gfx_new_area(im, X0, Y0 - boxV, X0,
2723 Y0, X0 + boxH, Y0, im->gdes[i].col);
2724 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2727 cairo_new_path(im->cr);
2728 cairo_set_line_width(im->cr, 1.0);
2731 gfx_line_fit(im, &X0, &Y0);
2732 gfx_line_fit(im, &X1, &Y1);
2733 cairo_move_to(im->cr, X0, Y0);
2734 cairo_line_to(im->cr, X1, Y0);
2735 cairo_line_to(im->cr, X1, Y1);
2736 cairo_line_to(im->cr, X0, Y1);
2737 cairo_close_path(im->cr);
2738 cairo_set_source_rgba(im->cr,
2750 blue, im->graph_col[GRC_FRAME].alpha);
2751 if (im->gdes[i].dash) {
2752 /* make box borders in legend dashed if the graph is dashed */
2756 cairo_set_dash(im->cr, dashes, 1, 0.0);
2758 cairo_stroke(im->cr);
2759 cairo_restore(im->cr);
2766 /*****************************************************
2767 * lazy check make sure we rely need to create this graph
2768 *****************************************************/
2775 struct stat imgstat;
2778 return 0; /* no lazy option */
2779 if (strlen(im->graphfile) == 0)
2780 return 0; /* inmemory option */
2781 if (stat(im->graphfile, &imgstat) != 0)
2782 return 0; /* can't stat */
2783 /* one pixel in the existing graph is more then what we would
2785 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2787 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2788 return 0; /* the file does not exist */
2789 switch (im->imgformat) {
2791 size = PngSize(fd, &(im->ximg), &(im->yimg));
2801 int graph_size_location(
2806 /* The actual size of the image to draw is determined from
2807 ** several sources. The size given on the command line is
2808 ** the graph area but we need more as we have to draw labels
2809 ** and other things outside the graph area
2812 int Xvertical = 0, Ytitle =
2813 0, Xylabel = 0, Xmain = 0, Ymain =
2814 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2816 if (im->extra_flags & ONLY_GRAPH) {
2818 im->ximg = im->xsize;
2819 im->yimg = im->ysize;
2820 im->yorigin = im->ysize;
2825 /** +---+-----------------------------------+
2826 ** | y |...............graph title.........|
2827 ** | +---+-------------------------------+
2831 ** | s | x | main graph area |
2836 ** | l | b +-------------------------------+
2837 ** | e | l | x axis labels |
2838 ** +---+---+-------------------------------+
2839 ** |....................legends............|
2840 ** +---------------------------------------+
2842 ** +---------------------------------------+
2845 if (im->ylegend[0] != '\0') {
2846 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2849 if (im->title[0] != '\0') {
2850 /* The title is placed "inbetween" two text lines so it
2851 ** automatically has some vertical spacing. The horizontal
2852 ** spacing is added here, on each side.
2854 /* if necessary, reduce the font size of the title until it fits the image width */
2855 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2859 if (im->draw_x_grid) {
2860 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2862 if (im->draw_y_grid || im->forceleftspace) {
2864 gfx_get_text_width(im, 0,
2869 im->tabwidth, "0") * im->unitslength;
2873 if (im->extra_flags & FULL_SIZE_MODE) {
2874 /* The actual size of the image to draw has been determined by the user.
2875 ** The graph area is the space remaining after accounting for the legend,
2876 ** the watermark, the axis labels, and the title.
2879 im->ximg = im->xsize;
2880 im->yimg = im->ysize;
2881 im->yorigin = im->ysize;
2884 /* Now calculate the total size. Insert some spacing where
2885 desired. im->xorigin and im->yorigin need to correspond
2886 with the lower left corner of the main graph area or, if
2887 this one is not set, the imaginary box surrounding the
2889 /* Initial size calculation for the main graph area */
2890 Xmain = im->ximg - Xylabel - 3 * Xspacing;
2892 im->xorigin = Xspacing + Xylabel;
2894 if (Xvertical) { /* unit description */
2896 im->xorigin += Xvertical;
2899 /* adjust space for second axis */
2900 if (im->second_axis_scale != 0){
2901 Xmain -= Xylabel + Xspacing;
2903 if (im->extra_flags & NO_RRDTOOL_TAG){
2906 if (im->second_axis_legend[0] != '\0' ) {
2907 Xmain -= im->text_prop[TEXT_PROP_UNIT].size * 1.5;
2913 /* The vertical size of the image is known in advance. The main graph area
2914 ** (Ymain) and im->yorigin must be set according to the space requirements
2915 ** of the legend and the axis labels.
2917 if (im->extra_flags & NOLEGEND) {
2918 im->yorigin = im->yimg -
2919 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2920 Ymain = im->yorigin;
2923 /* Determine where to place the legends onto the image.
2924 ** Set Ymain and adjust im->yorigin to match the space requirements.
2926 if (leg_place(im, &Ymain) == -1)
2931 /* remove title space *or* some padding above the graph from the main graph area */
2935 Ymain -= 1.5 * Yspacing;
2938 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2939 if (im->watermark[0] != '\0') {
2940 Ymain -= Ywatermark;
2944 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2946 /* The actual size of the image to draw is determined from
2947 ** several sources. The size given on the command line is
2948 ** the graph area but we need more as we have to draw labels
2949 ** and other things outside the graph area.
2952 if (im->ylegend[0] != '\0') {
2953 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2957 if (im->title[0] != '\0') {
2958 /* The title is placed "inbetween" two text lines so it
2959 ** automatically has some vertical spacing. The horizontal
2960 ** spacing is added here, on each side.
2962 /* don't care for the with of the title
2963 Xtitle = gfx_get_text_width(im->canvas, 0,
2964 im->text_prop[TEXT_PROP_TITLE].font_desc,
2966 im->title, 0) + 2*Xspacing; */
2967 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2974 /* Now calculate the total size. Insert some spacing where
2975 desired. im->xorigin and im->yorigin need to correspond
2976 with the lower left corner of the main graph area or, if
2977 this one is not set, the imaginary box surrounding the
2980 /* The legend width cannot yet be determined, as a result we
2981 ** have problems adjusting the image to it. For now, we just
2982 ** forget about it at all; the legend will have to fit in the
2983 ** size already allocated.
2985 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2987 if (im->second_axis_scale != 0){
2988 im->ximg += Xylabel + Xspacing;
2990 if (im->extra_flags & NO_RRDTOOL_TAG){
2991 im->ximg -= Xspacing;
2995 im->ximg += Xspacing;
2996 im->xorigin = Xspacing + Xylabel;
2997 /* the length of the title should not influence with width of the graph
2998 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2999 if (Xvertical) { /* unit description */
3000 im->ximg += Xvertical;
3001 im->xorigin += Xvertical;
3003 if (im->second_axis_legend[0] != '\0' ) {
3004 im->ximg += Xvertical;
3008 /* The vertical size is interesting... we need to compare
3009 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
3010 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
3011 ** in order to start even thinking about Ylegend or Ywatermark.
3013 ** Do it in three portions: First calculate the inner part,
3014 ** then do the legend, then adjust the total height of the img,
3015 ** adding space for a watermark if one exists;
3017 /* reserve space for main and/or pie */
3018 im->yimg = Ymain + Yxlabel;
3019 im->yorigin = im->yimg - Yxlabel;
3020 /* reserve space for the title *or* some padding above the graph */
3023 im->yorigin += Ytitle;
3025 im->yimg += 1.5 * Yspacing;
3026 im->yorigin += 1.5 * Yspacing;
3028 /* reserve space for padding below the graph */
3029 im->yimg += Yspacing;
3030 /* Determine where to place the legends onto the image.
3031 ** Adjust im->yimg to match the space requirements.
3033 if (leg_place(im, 0) == -1)
3035 if (im->watermark[0] != '\0') {
3036 im->yimg += Ywatermark;
3044 static cairo_status_t cairo_output(
3048 unsigned int length)
3050 image_desc_t *im = (image_desc_t*)closure;
3052 im->rendered_image =
3053 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3054 if (im->rendered_image == NULL)
3055 return CAIRO_STATUS_WRITE_ERROR;
3056 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3057 im->rendered_image_size += length;
3058 return CAIRO_STATUS_SUCCESS;
3061 /* draw that picture thing ... */
3066 int lazy = lazy_check(im);
3067 double areazero = 0.0;
3068 graph_desc_t *lastgdes = NULL;
3071 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3073 /* if we want and can be lazy ... quit now */
3075 info.u_cnt = im->ximg;
3076 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3077 info.u_cnt = im->yimg;
3078 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3081 /* pull the data from the rrd files ... */
3082 if (data_fetch(im) == -1)
3084 /* evaluate VDEF and CDEF operations ... */
3085 if (data_calc(im) == -1)
3087 /* calculate and PRINT and GPRINT definitions. We have to do it at
3088 * this point because it will affect the length of the legends
3089 * if there are no graph elements (i==0) we stop here ...
3090 * if we are lazy, try to quit ...
3096 if ((i == 0) || lazy)
3099 /**************************************************************
3100 *** Calculating sizes and locations became a bit confusing ***
3101 *** so I moved this into a separate function. ***
3102 **************************************************************/
3103 if (graph_size_location(im, i) == -1)
3106 info.u_cnt = im->xorigin;
3107 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3108 info.u_cnt = im->yorigin - im->ysize;
3109 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3110 info.u_cnt = im->xsize;
3111 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3112 info.u_cnt = im->ysize;
3113 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3114 info.u_cnt = im->ximg;
3115 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3116 info.u_cnt = im->yimg;
3117 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3118 info.u_cnt = im->start;
3119 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3120 info.u_cnt = im->end;
3121 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3123 /* get actual drawing data and find min and max values */
3124 if (data_proc(im) == -1)
3126 if (!im->logarithmic) {
3130 /* identify si magnitude Kilo, Mega Giga ? */
3131 if (!im->rigid && !im->logarithmic)
3132 expand_range(im); /* make sure the upper and lower limit are
3135 info.u_val = im->minval;
3136 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3137 info.u_val = im->maxval;
3138 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3140 if (!calc_horizontal_grid(im))
3145 apply_gridfit(im); */
3146 /* the actual graph is created by going through the individual
3147 graph elements and then drawing them */
3148 cairo_surface_destroy(im->surface);
3149 switch (im->imgformat) {
3152 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3153 im->ximg * im->zoom,
3154 im->yimg * im->zoom);
3158 im->surface = strlen(im->graphfile)
3159 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3160 im->yimg * im->zoom)
3161 : cairo_pdf_surface_create_for_stream
3162 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3166 im->surface = strlen(im->graphfile)
3168 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3169 im->yimg * im->zoom)
3170 : cairo_ps_surface_create_for_stream
3171 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3175 im->surface = strlen(im->graphfile)
3177 cairo_svg_surface_create(im->
3179 im->ximg * im->zoom, im->yimg * im->zoom)
3180 : cairo_svg_surface_create_for_stream
3181 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3182 cairo_svg_surface_restrict_to_version
3183 (im->surface, CAIRO_SVG_VERSION_1_1);
3186 cairo_destroy(im->cr);
3187 im->cr = cairo_create(im->surface);
3188 cairo_set_antialias(im->cr, im->graph_antialias);
3189 cairo_scale(im->cr, im->zoom, im->zoom);
3190 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3191 gfx_new_area(im, 0, 0, 0, im->yimg,
3192 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3193 gfx_add_point(im, im->ximg, 0);
3195 gfx_new_area(im, im->xorigin,
3198 im->xsize, im->yorigin,
3201 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3202 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3204 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3205 im->xsize, im->ysize + 2.0);
3207 if (im->minval > 0.0)
3208 areazero = im->minval;
3209 if (im->maxval < 0.0)
3210 areazero = im->maxval;
3211 for (i = 0; i < im->gdes_c; i++) {
3212 switch (im->gdes[i].gf) {
3226 for (ii = 0; ii < im->xsize; ii++) {
3227 if (!isnan(im->gdes[i].p_data[ii])
3228 && im->gdes[i].p_data[ii] != 0.0) {
3229 if (im->gdes[i].yrule > 0) {
3236 im->ysize, 1.0, im->gdes[i].col);
3237 } else if (im->gdes[i].yrule < 0) {
3240 im->yorigin - im->ysize - 1.0,
3242 im->yorigin - im->ysize -
3245 im->ysize, 1.0, im->gdes[i].col);
3252 /* fix data points at oo and -oo */
3253 for (ii = 0; ii < im->xsize; ii++) {
3254 if (isinf(im->gdes[i].p_data[ii])) {
3255 if (im->gdes[i].p_data[ii] > 0) {
3256 im->gdes[i].p_data[ii] = im->maxval;
3258 im->gdes[i].p_data[ii] = im->minval;
3264 /* *******************************************************
3269 -------|--t-1--t--------------------------------
3271 if we know the value at time t was a then
3272 we draw a square from t-1 to t with the value a.
3274 ********************************************************* */
3275 if (im->gdes[i].col.alpha != 0.0) {
3276 /* GF_LINE and friend */
3277 if (im->gdes[i].gf == GF_LINE) {
3278 double last_y = 0.0;
3282 cairo_new_path(im->cr);
3283 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3284 if (im->gdes[i].dash) {
3285 cairo_set_dash(im->cr,
3286 im->gdes[i].p_dashes,
3287 im->gdes[i].ndash, im->gdes[i].offset);
3290 for (ii = 1; ii < im->xsize; ii++) {
3291 if (isnan(im->gdes[i].p_data[ii])
3292 || (im->slopemode == 1
3293 && isnan(im->gdes[i].p_data[ii - 1]))) {
3298 last_y = ytr(im, im->gdes[i].p_data[ii]);
3299 if (im->slopemode == 0) {
3300 double x = ii - 1 + im->xorigin;
3303 gfx_line_fit(im, &x, &y);
3304 cairo_move_to(im->cr, x, y);
3305 x = ii + im->xorigin;
3307 gfx_line_fit(im, &x, &y);
3308 cairo_line_to(im->cr, x, y);
3310 double x = ii - 1 + im->xorigin;
3312 ytr(im, im->gdes[i].p_data[ii - 1]);
3313 gfx_line_fit(im, &x, &y);
3314 cairo_move_to(im->cr, x, y);
3315 x = ii + im->xorigin;
3317 gfx_line_fit(im, &x, &y);
3318 cairo_line_to(im->cr, x, y);
3322 double x1 = ii + im->xorigin;
3323 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3325 if (im->slopemode == 0
3326 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3327 double x = ii - 1 + im->xorigin;
3330 gfx_line_fit(im, &x, &y);
3331 cairo_line_to(im->cr, x, y);
3334 gfx_line_fit(im, &x1, &y1);
3335 cairo_line_to(im->cr, x1, y1);
3338 cairo_set_source_rgba(im->cr,
3344 col.blue, im->gdes[i].col.alpha);
3345 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3346 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3347 cairo_stroke(im->cr);
3348 cairo_restore(im->cr);
3352 (double *) malloc(sizeof(double) * im->xsize * 2);
3354 (double *) malloc(sizeof(double) * im->xsize * 2);
3356 (double *) malloc(sizeof(double) * im->xsize * 2);
3358 (double *) malloc(sizeof(double) * im->xsize * 2);
3361 for (ii = 0; ii <= im->xsize; ii++) {
3364 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3370 AlmostEqual2sComplement(foreY
3374 AlmostEqual2sComplement(foreY
3384 foreY[cntI], im->gdes[i].col);
3385 while (cntI < idxI) {
3390 AlmostEqual2sComplement(foreY
3394 AlmostEqual2sComplement(foreY
3401 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3403 gfx_add_point(im, backX[idxI], backY[idxI]);
3409 AlmostEqual2sComplement(backY
3413 AlmostEqual2sComplement(backY
3420 gfx_add_point(im, backX[idxI], backY[idxI]);
3430 if (ii == im->xsize)
3432 if (im->slopemode == 0 && ii == 0) {
3435 if (isnan(im->gdes[i].p_data[ii])) {
3439 ytop = ytr(im, im->gdes[i].p_data[ii]);
3440 if (lastgdes && im->gdes[i].stack) {
3441 ybase = ytr(im, lastgdes->p_data[ii]);
3443 ybase = ytr(im, areazero);
3445 if (ybase == ytop) {
3451 double extra = ytop;
3456 if (im->slopemode == 0) {
3457 backY[++idxI] = ybase - 0.2;
3458 backX[idxI] = ii + im->xorigin - 1;
3459 foreY[idxI] = ytop + 0.2;
3460 foreX[idxI] = ii + im->xorigin - 1;
3462 backY[++idxI] = ybase - 0.2;
3463 backX[idxI] = ii + im->xorigin;
3464 foreY[idxI] = ytop + 0.2;
3465 foreX[idxI] = ii + im->xorigin;
3467 /* close up any remaining area */
3472 } /* else GF_LINE */
3474 /* if color != 0x0 */
3475 /* make sure we do not run into trouble when stacking on NaN */
3476 for (ii = 0; ii < im->xsize; ii++) {
3477 if (isnan(im->gdes[i].p_data[ii])) {
3478 if (lastgdes && (im->gdes[i].stack)) {
3479 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3481 im->gdes[i].p_data[ii] = areazero;
3485 lastgdes = &(im->gdes[i]);
3489 ("STACK should already be turned into LINE or AREA here");
3494 cairo_reset_clip(im->cr);
3496 /* grid_paint also does the text */
3497 if (!(im->extra_flags & ONLY_GRAPH))
3499 if (!(im->extra_flags & ONLY_GRAPH))
3501 /* the RULES are the last thing to paint ... */
3502 for (i = 0; i < im->gdes_c; i++) {
3504 switch (im->gdes[i].gf) {
3506 if (im->gdes[i].yrule >= im->minval
3507 && im->gdes[i].yrule <= im->maxval) {
3509 if (im->gdes[i].dash) {
3510 cairo_set_dash(im->cr,
3511 im->gdes[i].p_dashes,
3512 im->gdes[i].ndash, im->gdes[i].offset);
3514 gfx_line(im, im->xorigin,
3515 ytr(im, im->gdes[i].yrule),
3516 im->xorigin + im->xsize,
3517 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3518 cairo_stroke(im->cr);
3519 cairo_restore(im->cr);
3523 if (im->gdes[i].xrule >= im->start
3524 && im->gdes[i].xrule <= im->end) {
3526 if (im->gdes[i].dash) {
3527 cairo_set_dash(im->cr,
3528 im->gdes[i].p_dashes,
3529 im->gdes[i].ndash, im->gdes[i].offset);
3532 xtr(im, im->gdes[i].xrule),
3533 im->yorigin, xtr(im,
3537 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3538 cairo_stroke(im->cr);
3539 cairo_restore(im->cr);
3548 switch (im->imgformat) {
3551 cairo_status_t status;
3553 status = strlen(im->graphfile) ?
3554 cairo_surface_write_to_png(im->surface, im->graphfile)
3555 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3558 if (status != CAIRO_STATUS_SUCCESS) {
3559 rrd_set_error("Could not save png to '%s'", im->graphfile);
3565 if (strlen(im->graphfile)) {
3566 cairo_show_page(im->cr);
3568 cairo_surface_finish(im->surface);
3577 /*****************************************************
3579 *****************************************************/
3586 if ((im->gdes = (graph_desc_t *)
3587 rrd_realloc(im->gdes, (im->gdes_c)
3588 * sizeof(graph_desc_t))) == NULL) {
3589 rrd_set_error("realloc graph_descs");
3594 im->gdes[im->gdes_c - 1].step = im->step;
3595 im->gdes[im->gdes_c - 1].step_orig = im->step;
3596 im->gdes[im->gdes_c - 1].stack = 0;
3597 im->gdes[im->gdes_c - 1].linewidth = 0;
3598 im->gdes[im->gdes_c - 1].debug = 0;
3599 im->gdes[im->gdes_c - 1].start = im->start;
3600 im->gdes[im->gdes_c - 1].start_orig = im->start;
3601 im->gdes[im->gdes_c - 1].end = im->end;
3602 im->gdes[im->gdes_c - 1].end_orig = im->end;
3603 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3604 im->gdes[im->gdes_c - 1].data = NULL;
3605 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3606 im->gdes[im->gdes_c - 1].data_first = 0;
3607 im->gdes[im->gdes_c - 1].p_data = NULL;
3608 im->gdes[im->gdes_c - 1].rpnp = NULL;
3609 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3610 im->gdes[im->gdes_c - 1].shift = 0.0;
3611 im->gdes[im->gdes_c - 1].dash = 0;
3612 im->gdes[im->gdes_c - 1].ndash = 0;
3613 im->gdes[im->gdes_c - 1].offset = 0;
3614 im->gdes[im->gdes_c - 1].col.red = 0.0;
3615 im->gdes[im->gdes_c - 1].col.green = 0.0;
3616 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3617 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3618 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3619 im->gdes[im->gdes_c - 1].format[0] = '\0';
3620 im->gdes[im->gdes_c - 1].strftm = 0;
3621 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3622 im->gdes[im->gdes_c - 1].ds = -1;
3623 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3624 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3625 im->gdes[im->gdes_c - 1].yrule = DNAN;
3626 im->gdes[im->gdes_c - 1].xrule = 0;
3630 /* copies input untill the first unescaped colon is found
3631 or until input ends. backslashes have to be escaped as well */
3633 const char *const input,
3639 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3640 if (input[inp] == '\\'
3641 && input[inp + 1] != '\0'
3642 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3643 output[outp++] = input[++inp];
3645 output[outp++] = input[inp];
3648 output[outp] = '\0';
3652 /* Now just a wrapper around rrd_graph_v */
3664 rrd_info_t *grinfo = NULL;
3667 grinfo = rrd_graph_v(argc, argv);
3673 if (strcmp(walker->key, "image_info") == 0) {
3676 (char**)rrd_realloc((*prdata),
3677 (prlines + 1) * sizeof(char *))) == NULL) {
3678 rrd_set_error("realloc prdata");
3681 /* imginfo goes to position 0 in the prdata array */
3682 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3683 + 2) * sizeof(char));
3684 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3685 (*prdata)[prlines] = NULL;
3687 /* skip anything else */
3688 walker = walker->next;
3696 if (strcmp(walker->key, "image_width") == 0) {
3697 *xsize = walker->value.u_cnt;
3698 } else if (strcmp(walker->key, "image_height") == 0) {
3699 *ysize = walker->value.u_cnt;
3700 } else if (strcmp(walker->key, "value_min") == 0) {
3701 *ymin = walker->value.u_val;
3702 } else if (strcmp(walker->key, "value_max") == 0) {
3703 *ymax = walker->value.u_val;
3704 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3707 (char**)rrd_realloc((*prdata),
3708 (prlines + 1) * sizeof(char *))) == NULL) {
3709 rrd_set_error("realloc prdata");
3712 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3713 + 2) * sizeof(char));
3714 (*prdata)[prlines] = NULL;
3715 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3716 } else if (strcmp(walker->key, "image") == 0) {
3717 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3718 (stream ? stream : stdout));
3720 /* skip anything else */
3721 walker = walker->next;
3723 rrd_info_free(grinfo);
3728 /* Some surgery done on this function, it became ridiculously big.
3730 ** - initializing now in rrd_graph_init()
3731 ** - options parsing now in rrd_graph_options()
3732 ** - script parsing now in rrd_graph_script()
3734 rrd_info_t *rrd_graph_v(
3740 rrd_graph_init(&im);
3741 /* a dummy surface so that we can measure text sizes for placements */
3743 rrd_graph_options(argc, argv, &im);
3744 if (rrd_test_error()) {
3745 rrd_info_free(im.grinfo);
3750 if (optind >= argc) {
3751 rrd_info_free(im.grinfo);
3753 rrd_set_error("missing filename");
3757 if (strlen(argv[optind]) >= MAXPATH) {
3758 rrd_set_error("filename (including path) too long");
3759 rrd_info_free(im.grinfo);
3764 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3765 im.graphfile[MAXPATH - 1] = '\0';
3767 if (strcmp(im.graphfile, "-") == 0) {
3768 im.graphfile[0] = '\0';
3771 rrd_graph_script(argc, argv, &im, 1);
3772 if (rrd_test_error()) {
3773 rrd_info_free(im.grinfo);
3778 /* Everything is now read and the actual work can start */
3780 if (graph_paint(&im) == -1) {
3781 rrd_info_free(im.grinfo);
3787 /* The image is generated and needs to be output.
3788 ** Also, if needed, print a line with information about the image.
3796 path = strdup(im.graphfile);
3797 filename = basename(path);
3799 sprintf_alloc(im.imginfo,
3802 im.ximg), (long) (im.zoom * im.yimg));
3803 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3807 if (im.rendered_image) {
3810 img.u_blo.size = im.rendered_image_size;
3811 img.u_blo.ptr = im.rendered_image;
3812 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3821 image_desc_t *im,int prop,char *font, double size ){
3823 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3824 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3825 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3828 im->text_prop[prop].size = size;
3830 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3831 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3835 void rrd_graph_init(
3840 char *deffont = getenv("RRD_DEFAULT_FONT");
3841 static PangoFontMap *fontmap = NULL;
3842 PangoContext *context;
3847 #ifdef HAVE_SETLOCALE
3848 setlocale(LC_TIME, "");
3849 #ifdef HAVE_MBSTOWCS
3850 setlocale(LC_CTYPE, "");
3854 im->daemon_addr = NULL;
3855 im->draw_x_grid = 1;
3856 im->draw_y_grid = 1;
3857 im->extra_flags = 0;
3858 im->font_options = cairo_font_options_create();
3859 im->forceleftspace = 0;
3862 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3863 im->grid_dash_off = 1;
3864 im->grid_dash_on = 1;
3866 im->grinfo = (rrd_info_t *) NULL;
3867 im->grinfo_current = (rrd_info_t *) NULL;
3868 im->imgformat = IF_PNG;
3871 im->logarithmic = 0;
3877 im->rendered_image_size = 0;
3878 im->rendered_image = NULL;
3882 im->tabwidth = 40.0;
3883 im->title[0] = '\0';
3884 im->unitsexponent = 9999;
3885 im->unitslength = 6;
3886 im->viewfactor = 1.0;
3887 im->watermark[0] = '\0';
3888 im->with_markup = 0;
3890 im->xlab_user.minsec = -1;
3893 im->ygridstep = DNAN;
3895 im->ylegend[0] = '\0';
3896 im->second_axis_scale = 0; /* 0 disables it */
3897 im->second_axis_shift = 0; /* no shift by default */
3898 im->second_axis_legend[0] = '\0';
3899 im->second_axis_format[0] = '\0';
3904 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3905 im->cr = cairo_create(im->surface);
3907 for (i = 0; i < DIM(text_prop); i++) {
3908 im->text_prop[i].size = -1;
3909 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3912 if (fontmap == NULL){
3913 fontmap = pango_cairo_font_map_get_default();
3916 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3918 pango_cairo_context_set_resolution(context, 100);
3920 pango_cairo_update_context(im->cr,context);
3922 im->layout = pango_layout_new(context);
3924 // im->layout = pango_cairo_create_layout(im->cr);
3927 cairo_font_options_set_hint_style
3928 (im->font_options, CAIRO_HINT_STYLE_FULL);
3929 cairo_font_options_set_hint_metrics
3930 (im->font_options, CAIRO_HINT_METRICS_ON);
3931 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3935 for (i = 0; i < DIM(graph_col); i++)
3936 im->graph_col[i] = graph_col[i];
3942 void rrd_graph_options(
3949 char *parsetime_error = NULL;
3950 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3951 time_t start_tmp = 0, end_tmp = 0;
3953 rrd_time_value_t start_tv, end_tv;
3954 long unsigned int color;
3955 char *old_locale = "";
3957 /* defines for long options without a short equivalent. should be bytes,
3958 and may not collide with (the ASCII value of) short options */
3959 #define LONGOPT_UNITS_SI 255
3962 struct option long_options[] = {
3963 { "start", required_argument, 0, 's'},
3964 { "end", required_argument, 0, 'e'},
3965 { "x-grid", required_argument, 0, 'x'},
3966 { "y-grid", required_argument, 0, 'y'},
3967 { "vertical-label", required_argument, 0, 'v'},
3968 { "width", required_argument, 0, 'w'},
3969 { "height", required_argument, 0, 'h'},
3970 { "full-size-mode", no_argument, 0, 'D'},
3971 { "interlaced", no_argument, 0, 'i'},
3972 { "upper-limit", required_argument, 0, 'u'},
3973 { "lower-limit", required_argument, 0, 'l'},
3974 { "rigid", no_argument, 0, 'r'},
3975 { "base", required_argument, 0, 'b'},
3976 { "logarithmic", no_argument, 0, 'o'},
3977 { "color", required_argument, 0, 'c'},
3978 { "font", required_argument, 0, 'n'},
3979 { "title", required_argument, 0, 't'},
3980 { "imginfo", required_argument, 0, 'f'},
3981 { "imgformat", required_argument, 0, 'a'},
3982 { "lazy", no_argument, 0, 'z'},
3983 { "zoom", required_argument, 0, 'm'},
3984 { "no-legend", no_argument, 0, 'g'},
3985 { "force-rules-legend", no_argument, 0, 'F'},
3986 { "only-graph", no_argument, 0, 'j'},
3987 { "alt-y-grid", no_argument, 0, 'Y'},
3988 {"disable-rrdtool-tag", no_argument, 0, 1001},
3989 {"right-axis", required_argument, 0, 1002},
3990 {"right-axis-label", required_argument, 0, 1003},
3991 {"right-axis-format", required_argument, 0, 1004},
3992 { "no-minor", no_argument, 0, 'I'},
3993 { "slope-mode", no_argument, 0, 'E'},
3994 { "alt-autoscale", no_argument, 0, 'A'},
3995 { "alt-autoscale-min", no_argument, 0, 'J'},
3996 { "alt-autoscale-max", no_argument, 0, 'M'},
3997 { "no-gridfit", no_argument, 0, 'N'},
3998 { "units-exponent", required_argument, 0, 'X'},
3999 { "units-length", required_argument, 0, 'L'},
4000 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4001 { "step", required_argument, 0, 'S'},
4002 { "tabwidth", required_argument, 0, 'T'},
4003 { "font-render-mode", required_argument, 0, 'R'},
4004 { "graph-render-mode", required_argument, 0, 'G'},
4005 { "font-smoothing-threshold", required_argument, 0, 'B'},
4006 { "watermark", required_argument, 0, 'W'},
4007 { "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 */
4008 { "pango-markup", no_argument, 0, 'P'},
4009 { "daemon", required_argument, 0, 'd'},
4015 opterr = 0; /* initialize getopt */
4016 rrd_parsetime("end-24h", &start_tv);
4017 rrd_parsetime("now", &end_tv);
4019 int option_index = 0;
4021 int col_start, col_end;
4023 opt = getopt_long(argc, argv,
4024 "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:",
4025 long_options, &option_index);
4030 im->extra_flags |= NOMINOR;
4033 im->extra_flags |= ALTYGRID;
4036 im->extra_flags |= ALTAUTOSCALE;
4039 im->extra_flags |= ALTAUTOSCALE_MIN;
4042 im->extra_flags |= ALTAUTOSCALE_MAX;
4045 im->extra_flags |= ONLY_GRAPH;
4048 im->extra_flags |= NOLEGEND;
4051 im->extra_flags |= FORCE_RULES_LEGEND;
4054 im->extra_flags |= NO_RRDTOOL_TAG;
4056 case LONGOPT_UNITS_SI:
4057 if (im->extra_flags & FORCE_UNITS) {
4058 rrd_set_error("--units can only be used once!");
4059 setlocale(LC_NUMERIC, old_locale);
4062 if (strcmp(optarg, "si") == 0)
4063 im->extra_flags |= FORCE_UNITS_SI;
4065 rrd_set_error("invalid argument for --units: %s", optarg);
4070 im->unitsexponent = atoi(optarg);
4073 im->unitslength = atoi(optarg);
4074 im->forceleftspace = 1;
4077 old_locale = setlocale(LC_NUMERIC, "C");
4078 im->tabwidth = atof(optarg);
4079 setlocale(LC_NUMERIC, old_locale);
4082 old_locale = setlocale(LC_NUMERIC, "C");
4083 im->step = atoi(optarg);
4084 setlocale(LC_NUMERIC, old_locale);
4090 im->with_markup = 1;
4093 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4094 rrd_set_error("start time: %s", parsetime_error);
4099 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4100 rrd_set_error("end time: %s", parsetime_error);
4105 if (strcmp(optarg, "none") == 0) {
4106 im->draw_x_grid = 0;
4110 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4112 &im->xlab_user.gridst,
4114 &im->xlab_user.mgridst,
4116 &im->xlab_user.labst,
4117 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4118 strncpy(im->xlab_form, optarg + stroff,
4119 sizeof(im->xlab_form) - 1);
4120 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4122 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4123 rrd_set_error("unknown keyword %s", scan_gtm);
4126 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4128 rrd_set_error("unknown keyword %s", scan_mtm);
4131 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4132 rrd_set_error("unknown keyword %s", scan_ltm);
4135 im->xlab_user.minsec = 1;
4136 im->xlab_user.stst = im->xlab_form;
4138 rrd_set_error("invalid x-grid format");
4144 if (strcmp(optarg, "none") == 0) {
4145 im->draw_y_grid = 0;
4148 old_locale = setlocale(LC_NUMERIC, "C");
4149 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4150 setlocale(LC_NUMERIC, old_locale);
4151 if (im->ygridstep <= 0) {
4152 rrd_set_error("grid step must be > 0");
4154 } else if (im->ylabfact < 1) {
4155 rrd_set_error("label factor must be > 0");
4159 setlocale(LC_NUMERIC, old_locale);
4160 rrd_set_error("invalid y-grid format");
4164 case 1002: /* right y axis */
4168 &im->second_axis_scale,
4169 &im->second_axis_shift) == 2) {
4170 if(im->second_axis_scale==0){
4171 rrd_set_error("the second_axis_scale must not be 0");
4175 rrd_set_error("invalid right-axis format expected scale:shift");
4180 strncpy(im->second_axis_legend,optarg,150);
4181 im->second_axis_legend[150]='\0';
4184 if (bad_format(optarg)){
4185 rrd_set_error("use either %le or %lf formats");
4188 strncpy(im->second_axis_format,optarg,150);
4189 im->second_axis_format[150]='\0';
4192 strncpy(im->ylegend, optarg, 150);
4193 im->ylegend[150] = '\0';
4196 old_locale = setlocale(LC_NUMERIC, "C");
4197 im->maxval = atof(optarg);
4198 setlocale(LC_NUMERIC, old_locale);
4201 old_locale = setlocale(LC_NUMERIC, "C");
4202 im->minval = atof(optarg);
4203 setlocale(LC_NUMERIC, old_locale);
4206 im->base = atol(optarg);
4207 if (im->base != 1024 && im->base != 1000) {
4209 ("the only sensible value for base apart from 1000 is 1024");
4214 long_tmp = atol(optarg);
4215 if (long_tmp < 10) {
4216 rrd_set_error("width below 10 pixels");
4219 im->xsize = long_tmp;
4222 long_tmp = atol(optarg);
4223 if (long_tmp < 10) {
4224 rrd_set_error("height below 10 pixels");
4227 im->ysize = long_tmp;
4230 im->extra_flags |= FULL_SIZE_MODE;
4233 /* interlaced png not supported at the moment */
4239 im->imginfo = optarg;
4243 (im->imgformat = if_conv(optarg)) == -1) {
4244 rrd_set_error("unsupported graphics format '%s'", optarg);
4255 im->logarithmic = 1;
4259 "%10[A-Z]#%n%8lx%n",
4260 col_nam, &col_start, &color, &col_end) == 2) {
4262 int col_len = col_end - col_start;
4267 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4275 (((color & 0xF000) *
4276 0x11000) | ((color & 0x0F00) *
4277 0x01100) | ((color &
4280 ((color & 0x000F) * 0x00011)
4284 color = (color << 8) + 0xff /* shift left by 8 */ ;
4289 rrd_set_error("the color format is #RRGGBB[AA]");
4292 if ((ci = grc_conv(col_nam)) != -1) {
4293 im->graph_col[ci] = gfx_hex_to_col(color);
4295 rrd_set_error("invalid color name '%s'", col_nam);
4299 rrd_set_error("invalid color def format");
4308 old_locale = setlocale(LC_NUMERIC, "C");
4309 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4310 int sindex, propidx;
4312 setlocale(LC_NUMERIC, old_locale);
4313 if ((sindex = text_prop_conv(prop)) != -1) {
4314 for (propidx = sindex;
4315 propidx < TEXT_PROP_LAST; propidx++) {
4317 rrd_set_font_desc(im,propidx,NULL,size);
4319 if ((int) strlen(optarg) > end+2) {
4320 if (optarg[end] == ':') {
4321 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4324 ("expected : after font size in '%s'",
4329 /* only run the for loop for DEFAULT (0) for
4330 all others, we break here. woodo programming */
4331 if (propidx == sindex && sindex != 0)
4335 rrd_set_error("invalid fonttag '%s'", prop);
4339 setlocale(LC_NUMERIC, old_locale);
4340 rrd_set_error("invalid text property format");
4346 old_locale = setlocale(LC_NUMERIC, "C");
4347 im->zoom = atof(optarg);
4348 setlocale(LC_NUMERIC, old_locale);
4349 if (im->zoom <= 0.0) {
4350 rrd_set_error("zoom factor must be > 0");
4355 strncpy(im->title, optarg, 150);
4356 im->title[150] = '\0';
4359 if (strcmp(optarg, "normal") == 0) {
4360 cairo_font_options_set_antialias
4361 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4362 cairo_font_options_set_hint_style
4363 (im->font_options, CAIRO_HINT_STYLE_FULL);
4364 } else if (strcmp(optarg, "light") == 0) {
4365 cairo_font_options_set_antialias
4366 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4367 cairo_font_options_set_hint_style
4368 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4369 } else if (strcmp(optarg, "mono") == 0) {
4370 cairo_font_options_set_antialias
4371 (im->font_options, CAIRO_ANTIALIAS_NONE);
4372 cairo_font_options_set_hint_style
4373 (im->font_options, CAIRO_HINT_STYLE_FULL);
4375 rrd_set_error("unknown font-render-mode '%s'", optarg);
4380 if (strcmp(optarg, "normal") == 0)
4381 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4382 else if (strcmp(optarg, "mono") == 0)
4383 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4385 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4390 /* not supported curently */
4393 strncpy(im->watermark, optarg, 100);
4394 im->watermark[99] = '\0';
4398 if (im->daemon_addr != NULL)
4400 rrd_set_error ("You cannot specify --daemon "
4405 im->daemon_addr = strdup(optarg);
4406 if (im->daemon_addr == NULL)
4408 rrd_set_error("strdup failed");
4416 rrd_set_error("unknown option '%c'", optopt);
4418 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4423 { /* try to connect to rrdcached */
4424 int status = rrdc_connect(im->daemon_addr);
4425 if (status != 0) return;
4428 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4429 pango_layout_context_changed(im->layout);
4433 if (im->logarithmic && im->minval <= 0) {
4435 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4439 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4440 /* error string is set in rrd_parsetime.c */
4444 if (start_tmp < 3600 * 24 * 365 * 10) {
4446 ("the first entry to fetch should be after 1980 (%ld)",
4451 if (end_tmp < start_tmp) {
4453 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4457 im->start = start_tmp;
4459 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4462 int rrd_graph_color(
4470 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4472 color = strstr(var, "#");
4473 if (color == NULL) {
4474 if (optional == 0) {
4475 rrd_set_error("Found no color in %s", err);
4482 long unsigned int col;
4484 rest = strstr(color, ":");
4491 sscanf(color, "#%6lx%n", &col, &n);
4492 col = (col << 8) + 0xff /* shift left by 8 */ ;
4494 rrd_set_error("Color problem in %s", err);
4497 sscanf(color, "#%8lx%n", &col, &n);
4501 rrd_set_error("Color problem in %s", err);
4503 if (rrd_test_error())
4505 gdp->col = gfx_hex_to_col(col);
4518 while (*ptr != '\0')
4519 if (*ptr++ == '%') {
4521 /* line cannot end with percent char */
4524 /* '%s', '%S' and '%%' are allowed */
4525 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4527 /* %c is allowed (but use only with vdef!) */
4528 else if (*ptr == 'c') {
4533 /* or else '% 6.2lf' and such are allowed */
4535 /* optional padding character */
4536 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4538 /* This should take care of 'm.n' with all three optional */
4539 while (*ptr >= '0' && *ptr <= '9')
4543 while (*ptr >= '0' && *ptr <= '9')
4545 /* Either 'le', 'lf' or 'lg' must follow here */
4548 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4563 const char *const str)
4565 /* A VDEF currently is either "func" or "param,func"
4566 * so the parsing is rather simple. Change if needed.
4574 old_locale = setlocale(LC_NUMERIC, "C");
4575 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4576 setlocale(LC_NUMERIC, old_locale);
4577 if (n == (int) strlen(str)) { /* matched */
4581 sscanf(str, "%29[A-Z]%n", func, &n);
4582 if (n == (int) strlen(str)) { /* matched */
4586 ("Unknown function string '%s' in VDEF '%s'",
4591 if (!strcmp("PERCENT", func))
4592 gdes->vf.op = VDEF_PERCENT;
4593 else if (!strcmp("PERCENTNAN", func))
4594 gdes->vf.op = VDEF_PERCENTNAN;
4595 else if (!strcmp("MAXIMUM", func))
4596 gdes->vf.op = VDEF_MAXIMUM;
4597 else if (!strcmp("AVERAGE", func))
4598 gdes->vf.op = VDEF_AVERAGE;
4599 else if (!strcmp("STDEV", func))
4600 gdes->vf.op = VDEF_STDEV;
4601 else if (!strcmp("MINIMUM", func))
4602 gdes->vf.op = VDEF_MINIMUM;
4603 else if (!strcmp("TOTAL", func))
4604 gdes->vf.op = VDEF_TOTAL;
4605 else if (!strcmp("FIRST", func))
4606 gdes->vf.op = VDEF_FIRST;
4607 else if (!strcmp("LAST", func))
4608 gdes->vf.op = VDEF_LAST;
4609 else if (!strcmp("LSLSLOPE", func))
4610 gdes->vf.op = VDEF_LSLSLOPE;
4611 else if (!strcmp("LSLINT", func))
4612 gdes->vf.op = VDEF_LSLINT;
4613 else if (!strcmp("LSLCORREL", func))
4614 gdes->vf.op = VDEF_LSLCORREL;
4617 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4620 switch (gdes->vf.op) {
4622 case VDEF_PERCENTNAN:
4623 if (isnan(param)) { /* no parameter given */
4625 ("Function '%s' needs parameter in VDEF '%s'\n",
4629 if (param >= 0.0 && param <= 100.0) {
4630 gdes->vf.param = param;
4631 gdes->vf.val = DNAN; /* undefined */
4632 gdes->vf.when = 0; /* undefined */
4635 ("Parameter '%f' out of range in VDEF '%s'\n",
4636 param, gdes->vname);
4649 case VDEF_LSLCORREL:
4651 gdes->vf.param = DNAN;
4652 gdes->vf.val = DNAN;
4656 ("Function '%s' needs no parameter in VDEF '%s'\n",
4670 graph_desc_t *src, *dst;
4674 dst = &im->gdes[gdi];
4675 src = &im->gdes[dst->vidx];
4676 data = src->data + src->ds;
4678 steps = (src->end - src->start) / src->step;
4681 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4682 src->start, src->end, steps);
4684 switch (dst->vf.op) {
4688 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4689 rrd_set_error("malloc VDEV_PERCENT");
4692 for (step = 0; step < steps; step++) {
4693 array[step] = data[step * src->ds_cnt];
4695 qsort(array, step, sizeof(double), vdef_percent_compar);
4696 field = (steps - 1) * dst->vf.param / 100;
4697 dst->vf.val = array[field];
4698 dst->vf.when = 0; /* no time component */
4701 for (step = 0; step < steps; step++)
4702 printf("DEBUG: %3li:%10.2f %c\n",
4703 step, array[step], step == field ? '*' : ' ');
4707 case VDEF_PERCENTNAN:{
4710 /* count number of "valid" values */
4712 for (step = 0; step < steps; step++) {
4713 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4715 /* and allocate it */
4716 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4717 rrd_set_error("malloc VDEV_PERCENT");
4720 /* and fill it in */
4722 for (step = 0; step < steps; step++) {
4723 if (!isnan(data[step * src->ds_cnt])) {
4724 array[field] = data[step * src->ds_cnt];
4728 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4729 field = (nancount - 1) * dst->vf.param / 100;
4730 dst->vf.val = array[field];
4731 dst->vf.when = 0; /* no time component */
4737 while (step != steps && isnan(data[step * src->ds_cnt]))
4739 if (step == steps) {
4743 dst->vf.val = data[step * src->ds_cnt];
4744 dst->vf.when = src->start + (step + 1) * src->step;
4746 while (step != steps) {
4747 if (finite(data[step * src->ds_cnt])) {
4748 if (data[step * src->ds_cnt] > dst->vf.val) {
4749 dst->vf.val = data[step * src->ds_cnt];
4750 dst->vf.when = src->start + (step + 1) * src->step;
4761 double average = 0.0;
4763 for (step = 0; step < steps; step++) {
4764 if (finite(data[step * src->ds_cnt])) {
4765 sum += data[step * src->ds_cnt];
4770 if (dst->vf.op == VDEF_TOTAL) {
4771 dst->vf.val = sum * src->step;
4772 dst->vf.when = 0; /* no time component */
4773 } else if (dst->vf.op == VDEF_AVERAGE) {
4774 dst->vf.val = sum / cnt;
4775 dst->vf.when = 0; /* no time component */
4777 average = sum / cnt;
4779 for (step = 0; step < steps; step++) {
4780 if (finite(data[step * src->ds_cnt])) {
4781 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4784 dst->vf.val = pow(sum / cnt, 0.5);
4785 dst->vf.when = 0; /* no time component */
4795 while (step != steps && isnan(data[step * src->ds_cnt]))
4797 if (step == steps) {
4801 dst->vf.val = data[step * src->ds_cnt];
4802 dst->vf.when = src->start + (step + 1) * src->step;
4804 while (step != steps) {
4805 if (finite(data[step * src->ds_cnt])) {
4806 if (data[step * src->ds_cnt] < dst->vf.val) {
4807 dst->vf.val = data[step * src->ds_cnt];
4808 dst->vf.when = src->start + (step + 1) * src->step;
4815 /* The time value returned here is one step before the
4816 * actual time value. This is the start of the first
4820 while (step != steps && isnan(data[step * src->ds_cnt]))
4822 if (step == steps) { /* all entries were NaN */
4826 dst->vf.val = data[step * src->ds_cnt];
4827 dst->vf.when = src->start + step * src->step;
4831 /* The time value returned here is the
4832 * actual time value. This is the end of the last
4836 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4838 if (step < 0) { /* all entries were NaN */
4842 dst->vf.val = data[step * src->ds_cnt];
4843 dst->vf.when = src->start + (step + 1) * src->step;
4848 case VDEF_LSLCORREL:{
4849 /* Bestfit line by linear least squares method */
4852 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4859 for (step = 0; step < steps; step++) {
4860 if (finite(data[step * src->ds_cnt])) {
4863 SUMxx += step * step;
4864 SUMxy += step * data[step * src->ds_cnt];
4865 SUMy += data[step * src->ds_cnt];
4866 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4870 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4871 y_intercept = (SUMy - slope * SUMx) / cnt;
4874 (SUMx * SUMy) / cnt) /
4876 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4878 if (dst->vf.op == VDEF_LSLSLOPE) {
4879 dst->vf.val = slope;
4881 } else if (dst->vf.op == VDEF_LSLINT) {
4882 dst->vf.val = y_intercept;
4884 } else if (dst->vf.op == VDEF_LSLCORREL) {
4885 dst->vf.val = correl;
4898 /* NaN < -INF < finite_values < INF */
4899 int vdef_percent_compar(
4905 /* Equality is not returned; this doesn't hurt except
4906 * (maybe) for a little performance.
4909 /* First catch NaN values. They are smallest */
4910 if (isnan(*(double *) a))
4912 if (isnan(*(double *) b))
4914 /* NaN doesn't reach this part so INF and -INF are extremes.
4915 * The sign from isinf() is compatible with the sign we return
4917 if (isinf(*(double *) a))
4918 return isinf(*(double *) a);
4919 if (isinf(*(double *) b))
4920 return isinf(*(double *) b);
4921 /* If we reach this, both values must be finite */
4922 if (*(double *) a < *(double *) b)
4931 rrd_info_type_t type,
4932 rrd_infoval_t value)
4934 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4935 if (im->grinfo == NULL) {
4936 im->grinfo = im->grinfo_current;