1 /****************************************************************************
2 * RRDtool 1.2.23 Copyright by Tobi Oetiker, 1997-2007
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
39 text_prop_t text_prop[] = {
40 {8.0, RRD_DEFAULT_FONT}
42 {9.0, RRD_DEFAULT_FONT}
44 {7.0, RRD_DEFAULT_FONT}
46 {8.0, RRD_DEFAULT_FONT}
48 {8.0, RRD_DEFAULT_FONT} /* legend */
52 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
54 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
56 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
58 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
60 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
62 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
64 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 4, 0, "%a %H:%M"}
66 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
68 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
70 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
71 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
73 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
75 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
77 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
79 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
81 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
84 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
87 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
90 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
92 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
93 365 * 24 * 3600, "%y"}
95 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
98 /* sensible y label intervals ...*/
122 {20.0, {1, 5, 10, 20}
128 {100.0, {1, 2, 5, 10}
131 {200.0, {1, 5, 10, 20}
134 {500.0, {1, 2, 4, 10}
142 gfx_color_t graph_col[] = /* default colors */
144 {1.00, 1.00, 1.00, 1.00}, /* canvas */
145 {0.95, 0.95, 0.95, 1.00}, /* background */
146 {0.81, 0.81, 0.81, 1.00}, /* shade A */
147 {0.62, 0.62, 0.62, 1.00}, /* shade B */
148 {0.56, 0.56, 0.56, 0.75}, /* grid */
149 {0.87, 0.31, 0.31, 0.60}, /* major grid */
150 {0.00, 0.00, 0.00, 1.00}, /* font */
151 {0.50, 0.12, 0.12, 1.00}, /* arrow */
152 {0.12, 0.12, 0.12, 1.00}, /* axis */
153 {0.00, 0.00, 0.00, 1.00} /* frame */
160 # define DPRINT(x) (void)(printf x, printf("\n"))
166 /* initialize with xtr(im,0); */
174 pixie = (double) im->xsize / (double) (im->end - im->start);
177 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
180 /* translate data values into y coordinates */
189 if (!im->logarithmic)
190 pixie = (double) im->ysize / (im->maxval - im->minval);
193 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
195 } else if (!im->logarithmic) {
196 yval = im->yorigin - pixie * (value - im->minval);
198 if (value < im->minval) {
201 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
204 /* make sure we don't return anything too unreasonable. GD lib can
205 get terribly slow when drawing lines outside its scope. This is
206 especially problematic in connection with the rigid option */
208 /* keep yval as-is */
209 } else if (yval > im->yorigin) {
210 yval = im->yorigin + 0.00001;
211 } else if (yval < im->yorigin - im->ysize) {
212 yval = im->yorigin - im->ysize - 0.00001;
219 /* conversion function for symbolic entry names */
222 #define conv_if(VV,VVV) \
223 if (strcmp(#VV, string) == 0) return VVV ;
229 conv_if(PRINT, GF_PRINT);
230 conv_if(GPRINT, GF_GPRINT);
231 conv_if(COMMENT, GF_COMMENT);
232 conv_if(HRULE, GF_HRULE);
233 conv_if(VRULE, GF_VRULE);
234 conv_if(LINE, GF_LINE);
235 conv_if(AREA, GF_AREA);
236 conv_if(STACK, GF_STACK);
237 conv_if(TICK, GF_TICK);
238 conv_if(DEF, GF_DEF);
239 conv_if(CDEF, GF_CDEF);
240 conv_if(VDEF, GF_VDEF);
241 conv_if(XPORT, GF_XPORT);
242 conv_if(SHIFT, GF_SHIFT);
247 enum gfx_if_en if_conv(
251 conv_if(PNG, IF_PNG);
252 conv_if(SVG, IF_SVG);
253 conv_if(EPS, IF_EPS);
254 conv_if(PDF, IF_PDF);
259 enum tmt_en tmt_conv(
263 conv_if(SECOND, TMT_SECOND);
264 conv_if(MINUTE, TMT_MINUTE);
265 conv_if(HOUR, TMT_HOUR);
266 conv_if(DAY, TMT_DAY);
267 conv_if(WEEK, TMT_WEEK);
268 conv_if(MONTH, TMT_MONTH);
269 conv_if(YEAR, TMT_YEAR);
273 enum grc_en grc_conv(
277 conv_if(BACK, GRC_BACK);
278 conv_if(CANVAS, GRC_CANVAS);
279 conv_if(SHADEA, GRC_SHADEA);
280 conv_if(SHADEB, GRC_SHADEB);
281 conv_if(GRID, GRC_GRID);
282 conv_if(MGRID, GRC_MGRID);
283 conv_if(FONT, GRC_FONT);
284 conv_if(ARROW, GRC_ARROW);
285 conv_if(AXIS, GRC_AXIS);
286 conv_if(FRAME, GRC_FRAME);
291 enum text_prop_en text_prop_conv(
295 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
296 conv_if(TITLE, TEXT_PROP_TITLE);
297 conv_if(AXIS, TEXT_PROP_AXIS);
298 conv_if(UNIT, TEXT_PROP_UNIT);
299 conv_if(LEGEND, TEXT_PROP_LEGEND);
313 for (i = 0; i < (unsigned) im->gdes_c; i++) {
314 if (im->gdes[i].data_first) {
315 /* careful here, because a single pointer can occur several times */
316 free(im->gdes[i].data);
317 if (im->gdes[i].ds_namv) {
318 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
319 free(im->gdes[i].ds_namv[ii]);
320 free(im->gdes[i].ds_namv);
323 free(im->gdes[i].p_data);
324 free(im->gdes[i].rpnp);
328 cairo_surface_destroy(im->surface);
329 if (im->font_options)
330 cairo_font_options_destroy(im->font_options);
334 /* find SI magnitude symbol for the given number*/
336 image_desc_t *im, /* image description */
342 char *symbol[] = { "a", /* 10e-18 Atto */
343 "f", /* 10e-15 Femto */
344 "p", /* 10e-12 Pico */
345 "n", /* 10e-9 Nano */
346 "u", /* 10e-6 Micro */
347 "m", /* 10e-3 Milli */
352 "T", /* 10e12 Tera */
353 "P", /* 10e15 Peta */
360 if (*value == 0.0 || isnan(*value)) {
364 sindex = floor(log(fabs(*value)) / log((double) im->base));
365 *magfact = pow((double) im->base, (double) sindex);
366 (*value) /= (*magfact);
368 if (sindex <= symbcenter && sindex >= -symbcenter) {
369 (*symb_ptr) = symbol[sindex + symbcenter];
376 static char si_symbol[] = {
377 'a', /* 10e-18 Atto */
378 'f', /* 10e-15 Femto */
379 'p', /* 10e-12 Pico */
380 'n', /* 10e-9 Nano */
381 'u', /* 10e-6 Micro */
382 'm', /* 10e-3 Milli */
387 'T', /* 10e12 Tera */
388 'P', /* 10e15 Peta */
391 static const int si_symbcenter = 6;
393 /* find SI magnitude symbol for the numbers on the y-axis*/
395 image_desc_t *im /* image description */
399 double digits, viewdigits = 0;
402 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
403 log((double) im->base));
405 if (im->unitsexponent != 9999) {
406 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
407 viewdigits = floor(im->unitsexponent / 3);
412 im->magfact = pow((double) im->base, digits);
415 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
418 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
420 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
421 ((viewdigits + si_symbcenter) >= 0))
422 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
427 /* move min and max values around to become sensible */
432 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
433 600.0, 500.0, 400.0, 300.0, 250.0,
434 200.0, 125.0, 100.0, 90.0, 80.0,
435 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
436 25.0, 20.0, 10.0, 9.0, 8.0,
437 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
438 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
439 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
442 double scaled_min, scaled_max;
449 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
450 im->minval, im->maxval, im->magfact);
453 if (isnan(im->ygridstep)) {
454 if (im->extra_flags & ALTAUTOSCALE) {
455 /* measure the amplitude of the function. Make sure that
456 graph boundaries are slightly higher then max/min vals
457 so we can see amplitude on the graph */
460 delt = im->maxval - im->minval;
462 fact = 2.0 * pow(10.0,
464 (max(fabs(im->minval), fabs(im->maxval)) /
467 adj = (fact - delt) * 0.55;
470 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
471 im->minval, im->maxval, delt, fact, adj);
476 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
477 /* measure the amplitude of the function. Make sure that
478 graph boundaries are slightly lower than min vals
479 so we can see amplitude on the graph */
480 adj = (im->maxval - im->minval) * 0.1;
482 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
483 /* measure the amplitude of the function. Make sure that
484 graph boundaries are slightly higher than max vals
485 so we can see amplitude on the graph */
486 adj = (im->maxval - im->minval) * 0.1;
489 scaled_min = im->minval / im->magfact;
490 scaled_max = im->maxval / im->magfact;
492 for (i = 1; sensiblevalues[i] > 0; i++) {
493 if (sensiblevalues[i - 1] >= scaled_min &&
494 sensiblevalues[i] <= scaled_min)
495 im->minval = sensiblevalues[i] * (im->magfact);
497 if (-sensiblevalues[i - 1] <= scaled_min &&
498 -sensiblevalues[i] >= scaled_min)
499 im->minval = -sensiblevalues[i - 1] * (im->magfact);
501 if (sensiblevalues[i - 1] >= scaled_max &&
502 sensiblevalues[i] <= scaled_max)
503 im->maxval = sensiblevalues[i - 1] * (im->magfact);
505 if (-sensiblevalues[i - 1] <= scaled_max &&
506 -sensiblevalues[i] >= scaled_max)
507 im->maxval = -sensiblevalues[i] * (im->magfact);
511 /* adjust min and max to the grid definition if there is one */
512 im->minval = (double) im->ylabfact * im->ygridstep *
513 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
514 im->maxval = (double) im->ylabfact * im->ygridstep *
515 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
519 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
520 im->minval, im->maxval, im->magfact);
528 if (isnan(im->minval) || isnan(im->maxval))
531 if (im->logarithmic) {
532 double ya, yb, ypix, ypixfrac;
533 double log10_range = log10(im->maxval) - log10(im->minval);
535 ya = pow((double) 10, floor(log10(im->minval)));
536 while (ya < im->minval)
539 return; /* don't have y=10^x gridline */
541 if (yb <= im->maxval) {
542 /* we have at least 2 y=10^x gridlines.
543 Make sure distance between them in pixels
544 are an integer by expanding im->maxval */
545 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
546 double factor = y_pixel_delta / floor(y_pixel_delta);
547 double new_log10_range = factor * log10_range;
548 double new_ymax_log10 = log10(im->minval) + new_log10_range;
550 im->maxval = pow(10, new_ymax_log10);
551 ytr(im, DNAN); /* reset precalc */
552 log10_range = log10(im->maxval) - log10(im->minval);
554 /* make sure first y=10^x gridline is located on
555 integer pixel position by moving scale slightly
556 downwards (sub-pixel movement) */
557 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
558 ypixfrac = ypix - floor(ypix);
559 if (ypixfrac > 0 && ypixfrac < 1) {
560 double yfrac = ypixfrac / im->ysize;
562 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
563 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
564 ytr(im, DNAN); /* reset precalc */
567 /* Make sure we have an integer pixel distance between
568 each minor gridline */
569 double ypos1 = ytr(im, im->minval);
570 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
571 double y_pixel_delta = ypos1 - ypos2;
572 double factor = y_pixel_delta / floor(y_pixel_delta);
573 double new_range = factor * (im->maxval - im->minval);
574 double gridstep = im->ygrid_scale.gridstep;
575 double minor_y, minor_y_px, minor_y_px_frac;
577 if (im->maxval > 0.0)
578 im->maxval = im->minval + new_range;
580 im->minval = im->maxval - new_range;
581 ytr(im, DNAN); /* reset precalc */
582 /* make sure first minor gridline is on integer pixel y coord */
583 minor_y = gridstep * floor(im->minval / gridstep);
584 while (minor_y < im->minval)
586 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
587 minor_y_px_frac = minor_y_px - floor(minor_y_px);
588 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
589 double yfrac = minor_y_px_frac / im->ysize;
590 double range = im->maxval - im->minval;
592 im->minval = im->minval - yfrac * range;
593 im->maxval = im->maxval - yfrac * range;
594 ytr(im, DNAN); /* reset precalc */
596 calc_horizontal_grid(im); /* recalc with changed im->maxval */
600 /* reduce data reimplementation by Alex */
603 enum cf_en cf, /* which consolidation function ? */
604 unsigned long cur_step, /* step the data currently is in */
605 time_t *start, /* start, end and step as requested ... */
606 time_t *end, /* ... by the application will be ... */
607 unsigned long *step, /* ... adjusted to represent reality */
608 unsigned long *ds_cnt, /* number of data sources in file */
610 { /* two dimensional array containing the data */
611 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
612 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
614 rrd_value_t *srcptr, *dstptr;
616 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
619 row_cnt = ((*end) - (*start)) / cur_step;
625 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
626 row_cnt, reduce_factor, *start, *end, cur_step);
627 for (col = 0; col < row_cnt; col++) {
628 printf("time %10lu: ", *start + (col + 1) * cur_step);
629 for (i = 0; i < *ds_cnt; i++)
630 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
635 /* We have to combine [reduce_factor] rows of the source
636 ** into one row for the destination. Doing this we also
637 ** need to take care to combine the correct rows. First
638 ** alter the start and end time so that they are multiples
639 ** of the new step time. We cannot reduce the amount of
640 ** time so we have to move the end towards the future and
641 ** the start towards the past.
643 end_offset = (*end) % (*step);
644 start_offset = (*start) % (*step);
646 /* If there is a start offset (which cannot be more than
647 ** one destination row), skip the appropriate number of
648 ** source rows and one destination row. The appropriate
649 ** number is what we do know (start_offset/cur_step) of
650 ** the new interval (*step/cur_step aka reduce_factor).
653 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
654 printf("row_cnt before: %lu\n", row_cnt);
657 (*start) = (*start) - start_offset;
658 skiprows = reduce_factor - start_offset / cur_step;
659 srcptr += skiprows * *ds_cnt;
660 for (col = 0; col < (*ds_cnt); col++)
665 printf("row_cnt between: %lu\n", row_cnt);
668 /* At the end we have some rows that are not going to be
669 ** used, the amount is end_offset/cur_step
672 (*end) = (*end) - end_offset + (*step);
673 skiprows = end_offset / cur_step;
677 printf("row_cnt after: %lu\n", row_cnt);
680 /* Sanity check: row_cnt should be multiple of reduce_factor */
681 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
683 if (row_cnt % reduce_factor) {
684 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
685 row_cnt, reduce_factor);
686 printf("BUG in reduce_data()\n");
690 /* Now combine reduce_factor intervals at a time
691 ** into one interval for the destination.
694 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
695 for (col = 0; col < (*ds_cnt); col++) {
696 rrd_value_t newval = DNAN;
697 unsigned long validval = 0;
699 for (i = 0; i < reduce_factor; i++) {
700 if (isnan(srcptr[i * (*ds_cnt) + col])) {
705 newval = srcptr[i * (*ds_cnt) + col];
713 newval += srcptr[i * (*ds_cnt) + col];
716 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
719 /* an interval contains a failure if any subintervals contained a failure */
721 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
724 newval = srcptr[i * (*ds_cnt) + col];
749 srcptr += (*ds_cnt) * reduce_factor;
750 row_cnt -= reduce_factor;
752 /* If we had to alter the endtime, we didn't have enough
753 ** source rows to fill the last row. Fill it with NaN.
756 for (col = 0; col < (*ds_cnt); col++)
759 row_cnt = ((*end) - (*start)) / *step;
761 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
762 row_cnt, *start, *end, *step);
763 for (col = 0; col < row_cnt; col++) {
764 printf("time %10lu: ", *start + (col + 1) * (*step));
765 for (i = 0; i < *ds_cnt; i++)
766 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
773 /* get the data required for the graphs from the
782 /* pull the data from the rrd files ... */
783 for (i = 0; i < (int) im->gdes_c; i++) {
784 /* only GF_DEF elements fetch data */
785 if (im->gdes[i].gf != GF_DEF)
789 /* do we have it already ? */
790 for (ii = 0; ii < i; ii++) {
791 if (im->gdes[ii].gf != GF_DEF)
793 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
794 && (im->gdes[i].cf == im->gdes[ii].cf)
795 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
796 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
797 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
798 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
799 /* OK, the data is already there.
800 ** Just copy the header portion
802 im->gdes[i].start = im->gdes[ii].start;
803 im->gdes[i].end = im->gdes[ii].end;
804 im->gdes[i].step = im->gdes[ii].step;
805 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
806 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
807 im->gdes[i].data = im->gdes[ii].data;
808 im->gdes[i].data_first = 0;
815 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
817 if ((rrd_fetch_fn(im->gdes[i].rrd,
823 &im->gdes[i].ds_namv,
824 &im->gdes[i].data)) == -1) {
827 im->gdes[i].data_first = 1;
829 if (ft_step < im->gdes[i].step) {
830 reduce_data(im->gdes[i].cf_reduce,
835 &im->gdes[i].ds_cnt, &im->gdes[i].data);
837 im->gdes[i].step = ft_step;
841 /* lets see if the required data source is really there */
842 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
843 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
847 if (im->gdes[i].ds == -1) {
848 rrd_set_error("No DS called '%s' in '%s'",
849 im->gdes[i].ds_nam, im->gdes[i].rrd);
857 /* evaluate the expressions in the CDEF functions */
859 /*************************************************************
861 *************************************************************/
863 long find_var_wrapper(
867 return find_var((image_desc_t *) arg1, key);
870 /* find gdes containing var*/
877 for (ii = 0; ii < im->gdes_c - 1; ii++) {
878 if ((im->gdes[ii].gf == GF_DEF
879 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
880 && (strcmp(im->gdes[ii].vname, key) == 0)) {
887 /* find the largest common denominator for all the numbers
888 in the 0 terminated num array */
895 for (i = 0; num[i + 1] != 0; i++) {
897 rest = num[i] % num[i + 1];
903 /* return i==0?num[i]:num[i-1]; */
907 /* run the rpn calculator on all the VDEF and CDEF arguments */
914 long *steparray, rpi;
919 rpnstack_init(&rpnstack);
921 for (gdi = 0; gdi < im->gdes_c; gdi++) {
922 /* Look for GF_VDEF and GF_CDEF in the same loop,
923 * so CDEFs can use VDEFs and vice versa
925 switch (im->gdes[gdi].gf) {
929 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
931 /* remove current shift */
932 vdp->start -= vdp->shift;
933 vdp->end -= vdp->shift;
936 if (im->gdes[gdi].shidx >= 0)
937 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
940 vdp->shift = im->gdes[gdi].shval;
942 /* normalize shift to multiple of consolidated step */
943 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
946 vdp->start += vdp->shift;
947 vdp->end += vdp->shift;
951 /* A VDEF has no DS. This also signals other parts
952 * of rrdtool that this is a VDEF value, not a CDEF.
954 im->gdes[gdi].ds_cnt = 0;
955 if (vdef_calc(im, gdi)) {
956 rrd_set_error("Error processing VDEF '%s'",
957 im->gdes[gdi].vname);
958 rpnstack_free(&rpnstack);
963 im->gdes[gdi].ds_cnt = 1;
964 im->gdes[gdi].ds = 0;
965 im->gdes[gdi].data_first = 1;
966 im->gdes[gdi].start = 0;
967 im->gdes[gdi].end = 0;
972 /* Find the variables in the expression.
973 * - VDEF variables are substituted by their values
974 * and the opcode is changed into OP_NUMBER.
975 * - CDEF variables are analized for their step size,
976 * the lowest common denominator of all the step
977 * sizes of the data sources involved is calculated
978 * and the resulting number is the step size for the
979 * resulting data source.
981 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
982 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
983 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
984 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
986 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
989 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
990 im->gdes[gdi].vname, im->gdes[ptr].vname);
991 printf("DEBUG: value from vdef is %f\n",
992 im->gdes[ptr].vf.val);
994 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
995 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
996 } else { /* normal variables and PREF(variables) */
998 /* add one entry to the array that keeps track of the step sizes of the
999 * data sources going into the CDEF. */
1001 rrd_realloc(steparray,
1003 1) * sizeof(*steparray))) == NULL) {
1004 rrd_set_error("realloc steparray");
1005 rpnstack_free(&rpnstack);
1009 steparray[stepcnt - 1] = im->gdes[ptr].step;
1011 /* adjust start and end of cdef (gdi) so
1012 * that it runs from the latest start point
1013 * to the earliest endpoint of any of the
1014 * rras involved (ptr)
1017 if (im->gdes[gdi].start < im->gdes[ptr].start)
1018 im->gdes[gdi].start = im->gdes[ptr].start;
1020 if (im->gdes[gdi].end == 0 ||
1021 im->gdes[gdi].end > im->gdes[ptr].end)
1022 im->gdes[gdi].end = im->gdes[ptr].end;
1024 /* store pointer to the first element of
1025 * the rra providing data for variable,
1026 * further save step size and data source
1029 im->gdes[gdi].rpnp[rpi].data =
1030 im->gdes[ptr].data + im->gdes[ptr].ds;
1031 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1032 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1034 /* backoff the *.data ptr; this is done so
1035 * rpncalc() function doesn't have to treat
1036 * the first case differently
1038 } /* if ds_cnt != 0 */
1039 } /* if OP_VARIABLE */
1040 } /* loop through all rpi */
1042 /* move the data pointers to the correct period */
1043 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1044 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1045 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1046 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1048 im->gdes[gdi].start - im->gdes[ptr].start;
1051 im->gdes[gdi].rpnp[rpi].data +=
1052 (diff / im->gdes[ptr].step) *
1053 im->gdes[ptr].ds_cnt;
1057 if (steparray == NULL) {
1058 rrd_set_error("rpn expressions without DEF"
1059 " or CDEF variables are not supported");
1060 rpnstack_free(&rpnstack);
1063 steparray[stepcnt] = 0;
1064 /* Now find the resulting step. All steps in all
1065 * used RRAs have to be visited
1067 im->gdes[gdi].step = lcd(steparray);
1069 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1070 im->gdes[gdi].start)
1071 / im->gdes[gdi].step)
1072 * sizeof(double))) == NULL) {
1073 rrd_set_error("malloc im->gdes[gdi].data");
1074 rpnstack_free(&rpnstack);
1078 /* Step through the new cdef results array and
1079 * calculate the values
1081 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1082 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1083 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1085 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1086 * in this case we are advancing by timesteps;
1087 * we use the fact that time_t is a synonym for long
1089 if (rpn_calc(rpnp, &rpnstack, (long) now,
1090 im->gdes[gdi].data, ++dataidx) == -1) {
1091 /* rpn_calc sets the error string */
1092 rpnstack_free(&rpnstack);
1095 } /* enumerate over time steps within a CDEF */
1100 } /* enumerate over CDEFs */
1101 rpnstack_free(&rpnstack);
1105 /* massage data so, that we get one value for each x coordinate in the graph */
1110 double pixstep = (double) (im->end - im->start)
1111 / (double) im->xsize; /* how much time
1112 passes in one pixel */
1114 double minval = DNAN, maxval = DNAN;
1116 unsigned long gr_time;
1118 /* memory for the processed data */
1119 for (i = 0; i < im->gdes_c; i++) {
1120 if ((im->gdes[i].gf == GF_LINE) ||
1121 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1122 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1123 * sizeof(rrd_value_t))) == NULL) {
1124 rrd_set_error("malloc data_proc");
1130 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1133 gr_time = im->start + pixstep * i; /* time of the current step */
1136 for (ii = 0; ii < im->gdes_c; ii++) {
1139 switch (im->gdes[ii].gf) {
1143 if (!im->gdes[ii].stack)
1145 value = im->gdes[ii].yrule;
1146 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1147 /* The time of the data doesn't necessarily match
1148 ** the time of the graph. Beware.
1150 vidx = im->gdes[ii].vidx;
1151 if (im->gdes[vidx].gf == GF_VDEF) {
1152 value = im->gdes[vidx].vf.val;
1154 if (((long int) gr_time >=
1155 (long int) im->gdes[vidx].start)
1156 && ((long int) gr_time <=
1157 (long int) im->gdes[vidx].end)) {
1158 value = im->gdes[vidx].data[(unsigned long)
1164 im->gdes[vidx].step)
1165 * im->gdes[vidx].ds_cnt +
1172 if (!isnan(value)) {
1174 im->gdes[ii].p_data[i] = paintval;
1175 /* GF_TICK: the data values are not
1176 ** relevant for min and max
1178 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1179 if ((isnan(minval) || paintval < minval) &&
1180 !(im->logarithmic && paintval <= 0.0))
1182 if (isnan(maxval) || paintval > maxval)
1186 im->gdes[ii].p_data[i] = DNAN;
1191 ("STACK should already be turned into LINE or AREA here");
1200 /* if min or max have not been asigned a value this is because
1201 there was no data in the graph ... this is not good ...
1202 lets set these to dummy values then ... */
1204 if (im->logarithmic) {
1216 /* adjust min and max values */
1217 if (isnan(im->minval)
1218 /* don't adjust low-end with log scale *//* why not? */
1219 || ((!im->rigid) && im->minval > minval)
1221 if (im->logarithmic)
1222 im->minval = minval * 0.5;
1224 im->minval = minval;
1226 if (isnan(im->maxval)
1227 || (!im->rigid && im->maxval < maxval)
1229 if (im->logarithmic)
1230 im->maxval = maxval * 2.0;
1232 im->maxval = maxval;
1234 /* make sure min is smaller than max */
1235 if (im->minval > im->maxval) {
1236 im->minval = 0.99 * im->maxval;
1239 /* make sure min and max are not equal */
1240 if (im->minval == im->maxval) {
1242 if (!im->logarithmic) {
1245 /* make sure min and max are not both zero */
1246 if (im->maxval == 0.0) {
1255 /* identify the point where the first gridline, label ... gets placed */
1257 time_t find_first_time(
1258 time_t start, /* what is the initial time */
1259 enum tmt_en baseint, /* what is the basic interval */
1260 long basestep /* how many if these do we jump a time */
1265 localtime_r(&start, &tm);
1269 tm. tm_sec -= tm.tm_sec % basestep;
1274 tm. tm_min -= tm.tm_min % basestep;
1280 tm. tm_hour -= tm.tm_hour % basestep;
1284 /* we do NOT look at the basestep for this ... */
1291 /* we do NOT look at the basestep for this ... */
1295 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1297 if (tm.tm_wday == 0)
1298 tm. tm_mday -= 7; /* we want the *previous* monday */
1306 tm. tm_mon -= tm.tm_mon % basestep;
1317 tm.tm_year + 1900) %basestep;
1323 /* identify the point where the next gridline, label ... gets placed */
1324 time_t find_next_time(
1325 time_t current, /* what is the initial time */
1326 enum tmt_en baseint, /* what is the basic interval */
1327 long basestep /* how many if these do we jump a time */
1333 localtime_r(¤t, &tm);
1338 tm. tm_sec += basestep;
1342 tm. tm_min += basestep;
1346 tm. tm_hour += basestep;
1350 tm. tm_mday += basestep;
1354 tm. tm_mday += 7 * basestep;
1358 tm. tm_mon += basestep;
1362 tm. tm_year += basestep;
1364 madetime = mktime(&tm);
1365 } while (madetime == -1); /* this is necessary to skip impssible times
1366 like the daylight saving time skips */
1372 /* calculate values required for PRINT and GPRINT functions */
1378 long i, ii, validsteps;
1381 int graphelement = 0;
1384 double magfact = -1;
1389 /* wow initializing tmvdef is quite a task :-) */
1390 time_t now = time(NULL);
1392 localtime_r(&now, &tmvdef);
1395 for (i = 0; i < im->gdes_c; i++) {
1396 vidx = im->gdes[i].vidx;
1397 switch (im->gdes[i].gf) {
1401 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1402 rrd_set_error("realloc prdata");
1406 /* PRINT and GPRINT can now print VDEF generated values.
1407 * There's no need to do any calculations on them as these
1408 * calculations were already made.
1410 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1411 printval = im->gdes[vidx].vf.val;
1412 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1413 } else { /* need to calculate max,min,avg etcetera */
1414 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1415 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1418 for (ii = im->gdes[vidx].ds;
1419 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1420 if (!finite(im->gdes[vidx].data[ii]))
1422 if (isnan(printval)) {
1423 printval = im->gdes[vidx].data[ii];
1428 switch (im->gdes[i].cf) {
1431 case CF_DEVSEASONAL:
1435 printval += im->gdes[vidx].data[ii];
1438 printval = min(printval, im->gdes[vidx].data[ii]);
1442 printval = max(printval, im->gdes[vidx].data[ii]);
1445 printval = im->gdes[vidx].data[ii];
1448 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1449 if (validsteps > 1) {
1450 printval = (printval / validsteps);
1453 } /* prepare printval */
1455 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1456 /* Magfact is set to -1 upon entry to print_calc. If it
1457 * is still less than 0, then we need to run auto_scale.
1458 * Otherwise, put the value into the correct units. If
1459 * the value is 0, then do not set the symbol or magnification
1460 * so next the calculation will be performed again. */
1461 if (magfact < 0.0) {
1462 auto_scale(im, &printval, &si_symb, &magfact);
1463 if (printval == 0.0)
1466 printval /= magfact;
1468 *(++percent_s) = 's';
1469 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1470 auto_scale(im, &printval, &si_symb, &magfact);
1473 if (im->gdes[i].gf == GF_PRINT) {
1474 (*prdata)[prlines - 2] =
1475 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1476 (*prdata)[prlines - 1] = NULL;
1477 if (im->gdes[i].strftm) {
1478 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1479 im->gdes[i].format, &tmvdef);
1481 if (bad_format(im->gdes[i].format)) {
1482 rrd_set_error("bad format for PRINT in '%s'",
1483 im->gdes[i].format);
1486 #ifdef HAVE_SNPRINTF
1487 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1488 im->gdes[i].format, printval, si_symb);
1490 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1497 if (im->gdes[i].strftm) {
1498 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1499 im->gdes[i].format, &tmvdef);
1501 if (bad_format(im->gdes[i].format)) {
1502 rrd_set_error("bad format for GPRINT in '%s'",
1503 im->gdes[i].format);
1506 #ifdef HAVE_SNPRINTF
1507 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1508 im->gdes[i].format, printval, si_symb);
1510 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1523 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1524 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1529 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1530 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1538 #ifdef WITH_PIECHART
1546 ("STACK should already be turned into LINE or AREA here");
1551 return graphelement;
1555 /* place legends with color spots */
1561 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1562 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1563 int fill = 0, fill_last;
1565 int leg_x = border, leg_y = im->yimg;
1566 int leg_y_prev = im->yimg;
1569 int i, ii, mark = 0;
1570 char prt_fctn; /*special printfunctions */
1573 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1574 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1575 rrd_set_error("malloc for legspace");
1579 if (im->extra_flags & FULL_SIZE_MODE)
1580 leg_y = leg_y_prev =
1581 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1583 for (i = 0; i < im->gdes_c; i++) {
1586 /* hide legends for rules which are not displayed */
1588 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1589 if (im->gdes[i].gf == GF_HRULE &&
1590 (im->gdes[i].yrule < im->minval
1591 || im->gdes[i].yrule > im->maxval))
1592 im->gdes[i].legend[0] = '\0';
1594 if (im->gdes[i].gf == GF_VRULE &&
1595 (im->gdes[i].xrule < im->start
1596 || im->gdes[i].xrule > im->end))
1597 im->gdes[i].legend[0] = '\0';
1600 leg_cc = strlen(im->gdes[i].legend);
1602 /* is there a controle code ant the end of the legend string ? */
1603 /* and it is not a tab \\t */
1604 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1605 && im->gdes[i].legend[leg_cc - 1] != 't') {
1606 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1608 im->gdes[i].legend[leg_cc] = '\0';
1612 /* only valid control codes */
1613 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1618 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1620 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1621 im->gdes[i].legend, prt_fctn);
1626 /* remove exess space */
1627 if (prt_fctn == 'n') {
1631 while (prt_fctn == 'g' &&
1632 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1634 im->gdes[i].legend[leg_cc] = '\0';
1637 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1640 /* no interleg space if string ends in \g */
1641 fill += legspace[i];
1643 fill += gfx_get_text_width(im, fill + border,
1644 im->text_prop[TEXT_PROP_LEGEND].
1646 im->text_prop[TEXT_PROP_LEGEND].
1648 im->gdes[i].legend);
1653 /* who said there was a special tag ... ? */
1654 if (prt_fctn == 'g') {
1657 if (prt_fctn == '\0') {
1658 if (i == im->gdes_c - 1)
1661 /* is it time to place the legends ? */
1662 if (fill > im->ximg - 2 * border) {
1677 if (prt_fctn != '\0') {
1679 if (leg_c >= 2 && prt_fctn == 'j') {
1680 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1684 if (prt_fctn == 'c')
1685 leg_x = (im->ximg - fill) / 2.0;
1686 if (prt_fctn == 'r')
1687 leg_x = im->ximg - fill - border;
1689 for (ii = mark; ii <= i; ii++) {
1690 if (im->gdes[ii].legend[0] == '\0')
1691 continue; /* skip empty legends */
1692 im->gdes[ii].leg_x = leg_x;
1693 im->gdes[ii].leg_y = leg_y;
1695 gfx_get_text_width(im, leg_x,
1696 im->text_prop[TEXT_PROP_LEGEND].
1698 im->text_prop[TEXT_PROP_LEGEND].
1700 im->gdes[ii].legend)
1705 if (im->extra_flags & FULL_SIZE_MODE) {
1706 /* only add y space if there was text on the line */
1707 if (leg_x > border || prt_fctn == 's')
1708 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1709 if (prt_fctn == 's')
1710 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1712 if (leg_x > border || prt_fctn == 's')
1713 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1714 if (prt_fctn == 's')
1715 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1723 if (im->extra_flags & FULL_SIZE_MODE) {
1724 if (leg_y != leg_y_prev) {
1725 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1727 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1730 im->yimg = leg_y_prev;
1731 /* if we did place some legends we have to add vertical space */
1732 if (leg_y != im->yimg)
1733 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1740 /* create a grid on the graph. it determines what to do
1741 from the values of xsize, start and end */
1743 /* the xaxis labels are determined from the number of seconds per pixel
1744 in the requested graph */
1748 int calc_horizontal_grid(
1755 int decimals, fractionals;
1757 im->ygrid_scale.labfact = 2;
1758 range = im->maxval - im->minval;
1759 scaledrange = range / im->magfact;
1761 /* does the scale of this graph make it impossible to put lines
1762 on it? If so, give up. */
1763 if (isnan(scaledrange)) {
1767 /* find grid spaceing */
1769 if (isnan(im->ygridstep)) {
1770 if (im->extra_flags & ALTYGRID) {
1771 /* find the value with max number of digits. Get number of digits */
1774 (max(fabs(im->maxval), fabs(im->minval)) *
1775 im->viewfactor / im->magfact));
1776 if (decimals <= 0) /* everything is small. make place for zero */
1779 im->ygrid_scale.gridstep =
1781 floor(log10(range * im->viewfactor / im->magfact))) /
1782 im->viewfactor * im->magfact;
1784 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1785 im->ygrid_scale.gridstep = 0.1;
1786 /* should have at least 5 lines but no more then 15 */
1787 if (range / im->ygrid_scale.gridstep < 5)
1788 im->ygrid_scale.gridstep /= 10;
1789 if (range / im->ygrid_scale.gridstep > 15)
1790 im->ygrid_scale.gridstep *= 10;
1791 if (range / im->ygrid_scale.gridstep > 5) {
1792 im->ygrid_scale.labfact = 1;
1793 if (range / im->ygrid_scale.gridstep > 8)
1794 im->ygrid_scale.labfact = 2;
1796 im->ygrid_scale.gridstep /= 5;
1797 im->ygrid_scale.labfact = 5;
1801 (im->ygrid_scale.gridstep *
1802 (double) im->ygrid_scale.labfact * im->viewfactor /
1804 if (fractionals < 0) { /* small amplitude. */
1805 int len = decimals - fractionals + 1;
1807 if (im->unitslength < len + 2)
1808 im->unitslength = len + 2;
1809 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1810 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1812 int len = decimals + 1;
1814 if (im->unitslength < len + 2)
1815 im->unitslength = len + 2;
1816 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1817 (im->symbol != ' ' ? " %c" : ""));
1820 for (i = 0; ylab[i].grid > 0; i++) {
1821 pixel = im->ysize / (scaledrange / ylab[i].grid);
1827 for (i = 0; i < 4; i++) {
1828 if (pixel * ylab[gridind].lfac[i] >=
1829 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1830 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1835 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1838 im->ygrid_scale.gridstep = im->ygridstep;
1839 im->ygrid_scale.labfact = im->ylabfact;
1844 int draw_horizontal_grid(
1849 char graph_label[100];
1851 double X0 = im->xorigin;
1852 double X1 = im->xorigin + im->xsize;
1854 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1855 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1859 im->ygrid_scale.gridstep / (double) im->magfact *
1860 (double) im->viewfactor;
1861 MaxY = scaledstep * (double) egrid;
1862 for (i = sgrid; i <= egrid; i++) {
1863 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1864 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1866 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1867 && floor(Y0 + 0.5) <= im->yorigin) {
1868 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1869 with the chosen settings. Add a label if required by settings, or if
1870 there is only one label so far and the next grid line is out of bounds. */
1871 if (i % im->ygrid_scale.labfact == 0
1873 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1874 if (im->symbol == ' ') {
1875 if (im->extra_flags & ALTYGRID) {
1876 sprintf(graph_label, im->ygrid_scale.labfmt,
1877 scaledstep * (double) i);
1880 sprintf(graph_label, "%4.1f",
1881 scaledstep * (double) i);
1883 sprintf(graph_label, "%4.0f",
1884 scaledstep * (double) i);
1888 char sisym = (i == 0 ? ' ' : im->symbol);
1890 if (im->extra_flags & ALTYGRID) {
1891 sprintf(graph_label, im->ygrid_scale.labfmt,
1892 scaledstep * (double) i, sisym);
1895 sprintf(graph_label, "%4.1f %c",
1896 scaledstep * (double) i, sisym);
1898 sprintf(graph_label, "%4.0f %c",
1899 scaledstep * (double) i, sisym);
1906 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1907 im->graph_col[GRC_FONT],
1908 im->text_prop[TEXT_PROP_AXIS].font,
1909 im->text_prop[TEXT_PROP_AXIS].size,
1910 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1914 X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1917 X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1921 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1922 im->grid_dash_on, im->grid_dash_off);
1924 } else if (!(im->extra_flags & NOMINOR)) {
1927 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1930 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1934 GRIDWIDTH, im->graph_col[GRC_GRID],
1935 im->grid_dash_on, im->grid_dash_off);
1943 /* this is frexp for base 10 */
1954 iexp = floor(log(fabs(x)) / log(10));
1955 mnt = x / pow(10.0, iexp);
1958 mnt = x / pow(10.0, iexp);
1964 static int AlmostEqual2sComplement(
1970 int aInt = *(int *) &A;
1971 int bInt = *(int *) &B;
1974 /* Make sure maxUlps is non-negative and small enough that the
1975 default NAN won't compare as equal to anything. */
1977 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1979 /* Make aInt lexicographically ordered as a twos-complement int */
1982 aInt = 0x80000000l - aInt;
1984 /* Make bInt lexicographically ordered as a twos-complement int */
1987 bInt = 0x80000000l - bInt;
1989 intDiff = abs(aInt - bInt);
1991 if (intDiff <= maxUlps)
1997 /* logaritmic horizontal grid */
1998 int horizontal_log_grid(
2001 double yloglab[][10] = {
2002 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2003 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2004 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2005 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2006 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2007 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2010 int i, j, val_exp, min_exp;
2011 double nex; /* number of decades in data */
2012 double logscale; /* scale in logarithmic space */
2013 int exfrac = 1; /* decade spacing */
2014 int mid = -1; /* row in yloglab for major grid */
2015 double mspac; /* smallest major grid spacing (pixels) */
2016 int flab; /* first value in yloglab to use */
2017 double value, tmp, pre_value;
2019 char graph_label[100];
2021 nex = log10(im->maxval / im->minval);
2022 logscale = im->ysize / nex;
2024 /* major spacing for data with high dynamic range */
2025 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2032 /* major spacing for less dynamic data */
2034 /* search best row in yloglab */
2036 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2037 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2038 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2039 && yloglab[mid][0] > 0);
2043 /* find first value in yloglab */
2045 yloglab[mid][flab] < 10
2046 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2047 if (yloglab[mid][flab] == 10.0) {
2052 if (val_exp % exfrac)
2053 val_exp += abs(-val_exp % exfrac);
2056 X1 = im->xorigin + im->xsize;
2062 value = yloglab[mid][flab] * pow(10.0, val_exp);
2063 if (AlmostEqual2sComplement(value, pre_value, 4))
2064 break; /* it seems we are not converging */
2068 Y0 = ytr(im, value);
2069 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2072 /* major grid line */
2075 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2077 X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2083 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2084 im->grid_dash_on, im->grid_dash_off);
2087 if (im->extra_flags & FORCE_UNITS_SI) {
2092 scale = floor(val_exp / 3.0);
2094 pvalue = pow(10.0, val_exp % 3);
2096 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2097 pvalue *= yloglab[mid][flab];
2099 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2100 ((scale + si_symbcenter) >= 0))
2101 symbol = si_symbol[scale + si_symbcenter];
2105 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2107 sprintf(graph_label, "%3.0e", value);
2109 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2110 im->graph_col[GRC_FONT],
2111 im->text_prop[TEXT_PROP_AXIS].font,
2112 im->text_prop[TEXT_PROP_AXIS].size,
2113 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2116 if (mid < 4 && exfrac == 1) {
2117 /* find first and last minor line behind current major line
2118 * i is the first line and j tha last */
2120 min_exp = val_exp - 1;
2121 for (i = 1; yloglab[mid][i] < 10.0; i++);
2122 i = yloglab[mid][i - 1] + 1;
2126 i = yloglab[mid][flab - 1] + 1;
2127 j = yloglab[mid][flab];
2130 /* draw minor lines below current major line */
2131 for (; i < j; i++) {
2133 value = i * pow(10.0, min_exp);
2134 if (value < im->minval)
2137 Y0 = ytr(im, value);
2138 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2144 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2147 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2151 GRIDWIDTH, im->graph_col[GRC_GRID],
2152 im->grid_dash_on, im->grid_dash_off);
2154 } else if (exfrac > 1) {
2155 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2156 value = pow(10.0, i);
2157 if (value < im->minval)
2160 Y0 = ytr(im, value);
2161 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2167 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2170 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2174 GRIDWIDTH, im->graph_col[GRC_GRID],
2175 im->grid_dash_on, im->grid_dash_off);
2180 if (yloglab[mid][++flab] == 10.0) {
2186 /* draw minor lines after highest major line */
2187 if (mid < 4 && exfrac == 1) {
2188 /* find first and last minor line below current major line
2189 * i is the first line and j tha last */
2191 min_exp = val_exp - 1;
2192 for (i = 1; yloglab[mid][i] < 10.0; i++);
2193 i = yloglab[mid][i - 1] + 1;
2197 i = yloglab[mid][flab - 1] + 1;
2198 j = yloglab[mid][flab];
2201 /* draw minor lines below current major line */
2202 for (; i < j; i++) {
2204 value = i * pow(10.0, min_exp);
2205 if (value < im->minval)
2208 Y0 = ytr(im, value);
2209 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2214 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2216 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2220 GRIDWIDTH, im->graph_col[GRC_GRID],
2221 im->grid_dash_on, im->grid_dash_off);
2224 /* fancy minor gridlines */
2225 else if (exfrac > 1) {
2226 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2227 value = pow(10.0, i);
2228 if (value < im->minval)
2231 Y0 = ytr(im, value);
2232 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2237 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2239 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2243 GRIDWIDTH, im->graph_col[GRC_GRID],
2244 im->grid_dash_on, im->grid_dash_off);
2255 int xlab_sel; /* which sort of label and grid ? */
2256 time_t ti, tilab, timajor;
2258 char graph_label[100];
2259 double X0, Y0, Y1; /* points for filled graph and more */
2262 /* the type of time grid is determined by finding
2263 the number of seconds per pixel in the graph */
2266 if (im->xlab_user.minsec == -1) {
2267 factor = (im->end - im->start) / im->xsize;
2269 while (xlab[xlab_sel + 1].minsec != -1
2270 && xlab[xlab_sel + 1].minsec <= factor) {
2272 } /* pick the last one */
2273 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2274 && xlab[xlab_sel].length > (im->end - im->start)) {
2276 } /* go back to the smallest size */
2277 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2278 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2279 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2280 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2281 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2282 im->xlab_user.labst = xlab[xlab_sel].labst;
2283 im->xlab_user.precis = xlab[xlab_sel].precis;
2284 im->xlab_user.stst = xlab[xlab_sel].stst;
2287 /* y coords are the same for every line ... */
2289 Y1 = im->yorigin - im->ysize;
2292 /* paint the minor grid */
2293 if (!(im->extra_flags & NOMINOR)) {
2294 for (ti = find_first_time(im->start,
2295 im->xlab_user.gridtm,
2296 im->xlab_user.gridst),
2297 timajor = find_first_time(im->start,
2298 im->xlab_user.mgridtm,
2299 im->xlab_user.mgridst);
2302 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2304 /* are we inside the graph ? */
2305 if (ti < im->start || ti > im->end)
2307 while (timajor < ti) {
2308 timajor = find_next_time(timajor,
2309 im->xlab_user.mgridtm,
2310 im->xlab_user.mgridst);
2313 continue; /* skip as falls on major grid line */
2315 gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2316 im->graph_col[GRC_GRID]);
2317 gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2318 im->graph_col[GRC_GRID]);
2319 gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2320 im->graph_col[GRC_GRID],
2321 im->grid_dash_on, im->grid_dash_off);
2326 /* paint the major grid */
2327 for (ti = find_first_time(im->start,
2328 im->xlab_user.mgridtm,
2329 im->xlab_user.mgridst);
2331 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2333 /* are we inside the graph ? */
2334 if (ti < im->start || ti > im->end)
2337 gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2338 im->graph_col[GRC_MGRID]);
2339 gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2340 im->graph_col[GRC_MGRID]);
2341 gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2342 im->graph_col[GRC_MGRID],
2343 im->grid_dash_on, im->grid_dash_off);
2346 /* paint the labels below the graph */
2347 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2348 im->xlab_user.labtm,
2349 im->xlab_user.labst);
2350 ti <= im->end - im->xlab_user.precis / 2;
2351 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2353 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2354 /* are we inside the graph ? */
2355 if (tilab < im->start || tilab > im->end)
2359 localtime_r(&tilab, &tm);
2360 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2362 # error "your libc has no strftime I guess we'll abort the exercise here."
2367 im->graph_col[GRC_FONT],
2368 im->text_prop[TEXT_PROP_AXIS].font,
2369 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2370 GFX_H_CENTER, GFX_V_TOP, graph_label);
2380 /* draw x and y axis */
2381 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2382 im->xorigin+im->xsize,im->yorigin-im->ysize,
2383 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2385 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2386 im->xorigin+im->xsize,im->yorigin-im->ysize,
2387 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2389 gfx_line(im, im->xorigin - 4, im->yorigin,
2390 im->xorigin + im->xsize + 4, im->yorigin,
2391 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2393 gfx_line(im, im->xorigin, im->yorigin + 4,
2394 im->xorigin, im->yorigin - im->ysize - 4,
2395 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2398 /* arrow for X and Y axis direction */
2399 gfx_new_area(im, im->xorigin + im->xsize + 2, im->yorigin - 2, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin + 0.5, /* LINEOFFSET */
2400 im->graph_col[GRC_ARROW]);
2403 gfx_new_area(im, im->xorigin - 2, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin + 0.5, im->yorigin - im->ysize - 7, /* LINEOFFSET */
2404 im->graph_col[GRC_ARROW]);
2415 double X0, Y0; /* points for filled graph and more */
2416 struct gfx_color_t water_color;
2418 /* draw 3d border */
2419 gfx_new_area(im, 0, im->yimg,
2420 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2421 gfx_add_point(im, im->ximg - 2, 2);
2422 gfx_add_point(im, im->ximg, 0);
2423 gfx_add_point(im, 0, 0);
2426 gfx_new_area(im, 2, im->yimg - 2,
2427 im->ximg - 2, im->yimg - 2,
2428 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2429 gfx_add_point(im, im->ximg, 0);
2430 gfx_add_point(im, im->ximg, im->yimg);
2431 gfx_add_point(im, 0, im->yimg);
2435 if (im->draw_x_grid == 1)
2438 if (im->draw_y_grid == 1) {
2439 if (im->logarithmic) {
2440 res = horizontal_log_grid(im);
2442 res = draw_horizontal_grid(im);
2445 /* dont draw horizontal grid if there is no min and max val */
2447 char *nodata = "No Data found";
2449 gfx_text(im, im->ximg / 2,
2450 (2 * im->yorigin - im->ysize) / 2,
2451 im->graph_col[GRC_FONT],
2452 im->text_prop[TEXT_PROP_AXIS].font,
2453 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2454 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2458 /* yaxis unit description */
2460 10, (im->yorigin - im->ysize / 2),
2461 im->graph_col[GRC_FONT],
2462 im->text_prop[TEXT_PROP_UNIT].font,
2463 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2464 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2469 im->graph_col[GRC_FONT],
2470 im->text_prop[TEXT_PROP_TITLE].font,
2471 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2472 GFX_H_CENTER, GFX_V_TOP, im->title);
2473 /* rrdtool 'logo' */
2474 water_color = im->graph_col[GRC_FONT];
2475 water_color.alpha = 0.3;
2479 im->text_prop[TEXT_PROP_AXIS].font,
2480 5.5, im->tabwidth, -90,
2481 GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2483 /* graph watermark */
2484 if (im->watermark[0] != '\0') {
2486 im->ximg / 2, im->yimg - 6,
2488 im->text_prop[TEXT_PROP_AXIS].font,
2489 5.5, im->tabwidth, 0,
2490 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2494 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2495 for (i = 0; i < im->gdes_c; i++) {
2496 if (im->gdes[i].legend[0] == '\0')
2499 /* im->gdes[i].leg_y is the bottom of the legend */
2500 X0 = im->gdes[i].leg_x;
2501 Y0 = im->gdes[i].leg_y;
2502 gfx_text(im, X0, Y0,
2503 im->graph_col[GRC_FONT],
2504 im->text_prop[TEXT_PROP_LEGEND].font,
2505 im->text_prop[TEXT_PROP_LEGEND].size,
2506 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2507 im->gdes[i].legend);
2508 /* The legend for GRAPH items starts with "M " to have
2509 enough space for the box */
2510 if (im->gdes[i].gf != GF_PRINT &&
2511 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2516 boxH = gfx_get_text_width(im, 0,
2517 im->text_prop[TEXT_PROP_LEGEND].
2519 im->text_prop[TEXT_PROP_LEGEND].
2520 size, im->tabwidth, "o") * 1.2;
2523 /* shift the box up a bit */
2526 /* make sure transparent colors show up the same way as in the graph */
2530 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2531 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2536 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2537 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2541 cairo_new_path(im->cr);
2542 cairo_set_line_width(im->cr, 1.0);
2545 gfx_line_fit(im, &X0, &Y0);
2546 gfx_line_fit(im, &X1, &Y1);
2547 cairo_move_to(im->cr, X0, Y0);
2548 cairo_line_to(im->cr, X1, Y0);
2549 cairo_line_to(im->cr, X1, Y1);
2550 cairo_line_to(im->cr, X0, Y1);
2551 cairo_close_path(im->cr);
2552 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2553 im->graph_col[GRC_FRAME].green,
2554 im->graph_col[GRC_FRAME].blue,
2555 im->graph_col[GRC_FRAME].alpha);
2556 cairo_stroke(im->cr);
2557 cairo_restore(im->cr);
2564 /*****************************************************
2565 * lazy check make sure we rely need to create this graph
2566 *****************************************************/
2573 struct stat imgstat;
2576 return 0; /* no lazy option */
2577 if (stat(im->graphfile, &imgstat) != 0)
2578 return 0; /* can't stat */
2579 /* one pixel in the existing graph is more then what we would
2581 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2583 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2584 return 0; /* the file does not exist */
2585 switch (im->imgformat) {
2587 size = PngSize(fd, &(im->ximg), &(im->yimg));
2597 int graph_size_location(
2601 /* The actual size of the image to draw is determined from
2602 ** several sources. The size given on the command line is
2603 ** the graph area but we need more as we have to draw labels
2604 ** and other things outside the graph area
2607 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2608 Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2610 if (im->extra_flags & ONLY_GRAPH) {
2612 im->ximg = im->xsize;
2613 im->yimg = im->ysize;
2614 im->yorigin = im->ysize;
2619 /** +---+--------------------------------------------+
2620 ** | y |...............graph title..................|
2621 ** | +---+-------------------------------+--------+
2624 ** | i | a | | pie |
2625 ** | s | x | main graph area | chart |
2630 ** | l | b +-------------------------------+--------+
2631 ** | e | l | x axis labels | |
2632 ** +---+---+-------------------------------+--------+
2633 ** |....................legends.....................|
2634 ** +------------------------------------------------+
2636 ** +------------------------------------------------+
2639 if (im->ylegend[0] != '\0') {
2640 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2643 if (im->title[0] != '\0') {
2644 /* The title is placed "inbetween" two text lines so it
2645 ** automatically has some vertical spacing. The horizontal
2646 ** spacing is added here, on each side.
2648 /* if necessary, reduce the font size of the title until it fits the image width */
2649 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2653 if (im->draw_x_grid) {
2654 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2656 if (im->draw_y_grid || im->forceleftspace) {
2657 Xylabel = gfx_get_text_width(im, 0,
2658 im->text_prop[TEXT_PROP_AXIS].font,
2659 im->text_prop[TEXT_PROP_AXIS].size,
2660 im->tabwidth, "0") * im->unitslength;
2664 if (im->extra_flags & FULL_SIZE_MODE) {
2665 /* The actual size of the image to draw has been determined by the user.
2666 ** The graph area is the space remaining after accounting for the legend,
2667 ** the watermark, the pie chart, the axis labels, and the title.
2670 im->ximg = im->xsize;
2671 im->yimg = im->ysize;
2672 im->yorigin = im->ysize;
2676 im->yorigin += Ytitle;
2678 /* Now calculate the total size. Insert some spacing where
2679 desired. im->xorigin and im->yorigin need to correspond
2680 with the lower left corner of the main graph area or, if
2681 this one is not set, the imaginary box surrounding the
2684 /* Initial size calculation for the main graph area */
2685 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2687 Xmain -= Xspacing; /* put space between main graph area and right edge */
2689 im->xorigin = Xspacing + Xylabel;
2691 /* the length of the title should not influence with width of the graph
2692 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2694 if (Xvertical) { /* unit description */
2696 im->xorigin += Xvertical;
2701 /* The vertical size of the image is known in advance. The main graph area
2702 ** (Ymain) and im->yorigin must be set according to the space requirements
2703 ** of the legend and the axis labels.
2706 if (im->extra_flags & NOLEGEND) {
2707 /* set dimensions correctly if using full size mode with no legend */
2709 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2711 Ymain = im->yorigin;
2713 /* Determine where to place the legends onto the image.
2714 ** Set Ymain and adjust im->yorigin to match the space requirements.
2716 if (leg_place(im, &Ymain) == -1)
2721 /* remove title space *or* some padding above the graph from the main graph area */
2725 Ymain -= 1.5 * Yspacing;
2728 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2729 if (im->watermark[0] != '\0') {
2730 Ymain -= Ywatermark;
2735 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2737 /* The actual size of the image to draw is determined from
2738 ** several sources. The size given on the command line is
2739 ** the graph area but we need more as we have to draw labels
2740 ** and other things outside the graph area.
2743 if (im->ylegend[0] != '\0') {
2744 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2748 if (im->title[0] != '\0') {
2749 /* The title is placed "inbetween" two text lines so it
2750 ** automatically has some vertical spacing. The horizontal
2751 ** spacing is added here, on each side.
2753 /* don't care for the with of the title
2754 Xtitle = gfx_get_text_width(im->canvas, 0,
2755 im->text_prop[TEXT_PROP_TITLE].font,
2756 im->text_prop[TEXT_PROP_TITLE].size,
2758 im->title, 0) + 2*Xspacing; */
2759 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2766 /* Now calculate the total size. Insert some spacing where
2767 desired. im->xorigin and im->yorigin need to correspond
2768 with the lower left corner of the main graph area or, if
2769 this one is not set, the imaginary box surrounding the
2772 /* The legend width cannot yet be determined, as a result we
2773 ** have problems adjusting the image to it. For now, we just
2774 ** forget about it at all; the legend will have to fit in the
2775 ** size already allocated.
2777 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2780 im->ximg += Xspacing;
2782 im->xorigin = Xspacing + Xylabel;
2784 /* the length of the title should not influence with width of the graph
2785 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2787 if (Xvertical) { /* unit description */
2788 im->ximg += Xvertical;
2789 im->xorigin += Xvertical;
2793 /* The vertical size is interesting... we need to compare
2794 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2795 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2796 ** in order to start even thinking about Ylegend or Ywatermark.
2798 ** Do it in three portions: First calculate the inner part,
2799 ** then do the legend, then adjust the total height of the img,
2800 ** adding space for a watermark if one exists;
2803 /* reserve space for main and/or pie */
2805 im->yimg = Ymain + Yxlabel;
2808 im->yorigin = im->yimg - Yxlabel;
2810 /* reserve space for the title *or* some padding above the graph */
2813 im->yorigin += Ytitle;
2815 im->yimg += 1.5 * Yspacing;
2816 im->yorigin += 1.5 * Yspacing;
2818 /* reserve space for padding below the graph */
2819 im->yimg += Yspacing;
2821 /* Determine where to place the legends onto the image.
2822 ** Adjust im->yimg to match the space requirements.
2824 if (leg_place(im, 0) == -1)
2827 if (im->watermark[0] != '\0') {
2828 im->yimg += Ywatermark;
2836 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2837 /* yes we are loosing precision by doing tos with floats instead of doubles
2838 but it seems more stable this way. */
2841 /* draw that picture thing ... */
2847 int lazy = lazy_check(im);
2849 double areazero = 0.0;
2850 graph_desc_t *lastgdes = NULL;
2852 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2855 /* if we are lazy and there is nothing to PRINT ... quit now */
2856 if (lazy && im->prt_c == 0)
2859 /* pull the data from the rrd files ... */
2861 if (data_fetch(im) == -1)
2864 /* evaluate VDEF and CDEF operations ... */
2865 if (data_calc(im) == -1)
2869 /* calculate and PRINT and GPRINT definitions. We have to do it at
2870 * this point because it will affect the length of the legends
2871 * if there are no graph elements we stop here ...
2872 * if we are lazy, try to quit ...
2874 i = print_calc(im, calcpr);
2877 if ((i == 0) || lazy)
2880 /**************************************************************
2881 *** Calculating sizes and locations became a bit confusing ***
2882 *** so I moved this into a separate function. ***
2883 **************************************************************/
2884 if (graph_size_location(im, i) == -1)
2887 /* get actual drawing data and find min and max values */
2888 if (data_proc(im) == -1)
2891 if (!im->logarithmic) {
2894 /* identify si magnitude Kilo, Mega Giga ? */
2895 if (!im->rigid && !im->logarithmic)
2896 expand_range(im); /* make sure the upper and lower limit are
2899 if (!calc_horizontal_grid(im))
2906 apply_gridfit(im); */
2909 /* the actual graph is created by going through the individual
2910 graph elements and then drawing them */
2911 cairo_surface_destroy(im->surface);
2913 switch (im->imgformat) {
2916 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2917 im->ximg * im->zoom,
2918 im->yimg * im->zoom);
2922 cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2923 im->yimg * im->zoom);
2927 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
2928 im->yimg * im->zoom);
2932 cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
2933 im->yimg * im->zoom);
2934 cairo_svg_surface_restrict_to_version(im->surface,
2935 CAIRO_SVG_VERSION_1_1);
2938 im->cr = cairo_create(im->surface);
2939 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
2940 cairo_set_antialias(im->cr, im->graph_antialias);
2941 cairo_scale(im->cr, im->zoom, im->zoom);
2945 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2947 gfx_add_point(im, im->ximg, 0);
2951 im->xorigin, im->yorigin,
2952 im->xorigin + im->xsize, im->yorigin,
2953 im->xorigin + im->xsize, im->yorigin - im->ysize,
2954 im->graph_col[GRC_CANVAS]);
2956 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
2959 if (im->minval > 0.0)
2960 areazero = im->minval;
2961 if (im->maxval < 0.0)
2962 areazero = im->maxval;
2964 for (i = 0; i < im->gdes_c; i++) {
2965 switch (im->gdes[i].gf) {
2978 for (ii = 0; ii < im->xsize; ii++) {
2979 if (!isnan(im->gdes[i].p_data[ii]) &&
2980 im->gdes[i].p_data[ii] != 0.0) {
2981 if (im->gdes[i].yrule > 0) {
2983 im->xorigin + ii, im->yorigin,
2986 im->gdes[i].yrule * im->ysize, 1.0,
2988 } else if (im->gdes[i].yrule < 0) {
2991 im->yorigin - im->ysize,
2994 im->gdes[i].yrule) *
2995 im->ysize, 1.0, im->gdes[i].col);
3003 /* fix data points at oo and -oo */
3004 for (ii = 0; ii < im->xsize; ii++) {
3005 if (isinf(im->gdes[i].p_data[ii])) {
3006 if (im->gdes[i].p_data[ii] > 0) {
3007 im->gdes[i].p_data[ii] = im->maxval;
3009 im->gdes[i].p_data[ii] = im->minval;
3015 /* *******************************************************
3020 -------|--t-1--t--------------------------------
3022 if we know the value at time t was a then
3023 we draw a square from t-1 to t with the value a.
3025 ********************************************************* */
3026 if (im->gdes[i].col.alpha != 0.0) {
3027 /* GF_LINE and friend */
3028 if (im->gdes[i].gf == GF_LINE) {
3029 double last_y = 0.0;
3033 cairo_new_path(im->cr);
3035 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3036 for (ii = 1; ii < im->xsize; ii++) {
3037 if (isnan(im->gdes[i].p_data[ii])
3038 || (im->slopemode == 1
3039 && isnan(im->gdes[i].p_data[ii - 1]))) {
3044 last_y = ytr(im, im->gdes[i].p_data[ii]);
3045 if (im->slopemode == 0) {
3046 double x = ii - 1 + im->xorigin;
3049 gfx_line_fit(im, &x, &y);
3050 cairo_move_to(im->cr, x, y);
3051 x = ii + im->xorigin;
3053 gfx_line_fit(im, &x, &y);
3054 cairo_line_to(im->cr, x, y);
3056 double x = ii - 1 + im->xorigin;
3058 im->gdes[i].p_data[ii - 1]);
3060 gfx_line_fit(im, &x, &y);
3061 cairo_move_to(im->cr, x, y);
3062 x = ii + im->xorigin;
3064 gfx_line_fit(im, &x, &y);
3065 cairo_line_to(im->cr, x, y);
3069 double x1 = ii + im->xorigin;
3070 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3072 if (im->slopemode == 0
3073 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3074 double x = ii - 1 + im->xorigin;
3077 gfx_line_fit(im, &x, &y);
3078 cairo_line_to(im->cr, x, y);
3081 gfx_line_fit(im, &x1, &y1);
3082 cairo_line_to(im->cr, x1, y1);
3086 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3087 im->gdes[i].col.green,
3088 im->gdes[i].col.blue,
3089 im->gdes[i].col.alpha);
3090 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3091 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3092 cairo_stroke(im->cr);
3093 cairo_restore(im->cr);
3096 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3097 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3098 double *backY = malloc(sizeof(double) * im->xsize * 2);
3099 double *backX = malloc(sizeof(double) * im->xsize * 2);
3102 for (ii = 0; ii <= im->xsize; ii++) {
3105 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3110 && AlmostEqual2sComplement(foreY[lastI],
3112 && AlmostEqual2sComplement(foreY[lastI],
3120 foreX[cntI], foreY[cntI],
3122 while (cntI < idxI) {
3127 AlmostEqual2sComplement(foreY[lastI],
3130 AlmostEqual2sComplement(foreY[lastI],
3135 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3137 gfx_add_point(im, backX[idxI], backY[idxI]);
3143 AlmostEqual2sComplement(backY[lastI],
3146 AlmostEqual2sComplement(backY[lastI],
3151 gfx_add_point(im, backX[idxI], backY[idxI]);
3161 if (ii == im->xsize)
3164 if (im->slopemode == 0 && ii == 0) {
3167 if (isnan(im->gdes[i].p_data[ii])) {
3171 ytop = ytr(im, im->gdes[i].p_data[ii]);
3172 if (lastgdes && im->gdes[i].stack) {
3173 ybase = ytr(im, lastgdes->p_data[ii]);
3175 ybase = ytr(im, areazero);
3177 if (ybase == ytop) {
3183 double extra = ytop;
3188 if (im->slopemode == 0) {
3189 backY[++idxI] = ybase - 0.2;
3190 backX[idxI] = ii + im->xorigin - 1;
3191 foreY[idxI] = ytop + 0.2;
3192 foreX[idxI] = ii + im->xorigin - 1;
3194 backY[++idxI] = ybase - 0.2;
3195 backX[idxI] = ii + im->xorigin;
3196 foreY[idxI] = ytop + 0.2;
3197 foreX[idxI] = ii + im->xorigin;
3199 /* close up any remaining area */
3204 } /* else GF_LINE */
3206 /* if color != 0x0 */
3207 /* make sure we do not run into trouble when stacking on NaN */
3208 for (ii = 0; ii < im->xsize; ii++) {
3209 if (isnan(im->gdes[i].p_data[ii])) {
3210 if (lastgdes && (im->gdes[i].stack)) {
3211 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3213 im->gdes[i].p_data[ii] = areazero;
3217 lastgdes = &(im->gdes[i]);
3221 ("STACK should already be turned into LINE or AREA here");
3228 /* grid_paint also does the text */
3229 if (!(im->extra_flags & ONLY_GRAPH))
3233 if (!(im->extra_flags & ONLY_GRAPH))
3236 /* the RULES are the last thing to paint ... */
3237 for (i = 0; i < im->gdes_c; i++) {
3239 switch (im->gdes[i].gf) {
3241 if (im->gdes[i].yrule >= im->minval
3242 && im->gdes[i].yrule <= im->maxval)
3244 im->xorigin, ytr(im, im->gdes[i].yrule),
3245 im->xorigin + im->xsize, ytr(im,
3247 1.0, im->gdes[i].col);
3250 if (im->gdes[i].xrule >= im->start
3251 && im->gdes[i].xrule <= im->end)
3253 xtr(im, im->gdes[i].xrule), im->yorigin,
3254 xtr(im, im->gdes[i].xrule),
3255 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3263 switch (im->imgformat) {
3265 if (cairo_surface_write_to_png(im->surface, im->graphfile) !=
3266 CAIRO_STATUS_SUCCESS) {
3267 rrd_set_error("Could not save png to '%s'", im->graphfile);
3272 cairo_show_page(im->cr);
3279 /*****************************************************
3281 *****************************************************/
3288 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3289 * sizeof(graph_desc_t))) ==
3291 rrd_set_error("realloc graph_descs");
3296 im->gdes[im->gdes_c - 1].step = im->step;
3297 im->gdes[im->gdes_c - 1].step_orig = im->step;
3298 im->gdes[im->gdes_c - 1].stack = 0;
3299 im->gdes[im->gdes_c - 1].linewidth = 0;
3300 im->gdes[im->gdes_c - 1].debug = 0;
3301 im->gdes[im->gdes_c - 1].start = im->start;
3302 im->gdes[im->gdes_c - 1].start_orig = im->start;
3303 im->gdes[im->gdes_c - 1].end = im->end;
3304 im->gdes[im->gdes_c - 1].end_orig = im->end;
3305 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3306 im->gdes[im->gdes_c - 1].data = NULL;
3307 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3308 im->gdes[im->gdes_c - 1].data_first = 0;
3309 im->gdes[im->gdes_c - 1].p_data = NULL;
3310 im->gdes[im->gdes_c - 1].rpnp = NULL;
3311 im->gdes[im->gdes_c - 1].shift = 0.0;
3312 im->gdes[im->gdes_c - 1].col.red = 0.0;
3313 im->gdes[im->gdes_c - 1].col.green = 0.0;
3314 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3315 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3316 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3317 im->gdes[im->gdes_c - 1].format[0] = '\0';
3318 im->gdes[im->gdes_c - 1].strftm = 0;
3319 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3320 im->gdes[im->gdes_c - 1].ds = -1;
3321 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3322 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3323 im->gdes[im->gdes_c - 1].p_data = NULL;
3324 im->gdes[im->gdes_c - 1].yrule = DNAN;
3325 im->gdes[im->gdes_c - 1].xrule = 0;
3329 /* copies input untill the first unescaped colon is found
3330 or until input ends. backslashes have to be escaped as well */
3332 const char *const input,
3338 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3339 if (input[inp] == '\\' &&
3340 input[inp + 1] != '\0' &&
3341 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3342 output[outp++] = input[++inp];
3344 output[outp++] = input[inp];
3347 output[outp] = '\0';
3351 /* Some surgery done on this function, it became ridiculously big.
3353 ** - initializing now in rrd_graph_init()
3354 ** - options parsing now in rrd_graph_options()
3355 ** - script parsing now in rrd_graph_script()
3369 rrd_graph_init(&im);
3371 /* a dummy surface so that we can measure text sizes for placements */
3372 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3373 im.cr = cairo_create(im.surface);
3376 /* not currently using this ... */
3377 im.graphhandle = stream;
3379 rrd_graph_options(argc, argv, &im);
3380 if (rrd_test_error()) {
3385 if (strlen(argv[optind]) >= MAXPATH) {
3386 rrd_set_error("filename (including path) too long");
3390 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3391 im.graphfile[MAXPATH - 1] = '\0';
3393 rrd_graph_script(argc, argv, &im, 1);
3394 if (rrd_test_error()) {
3399 /* Everything is now read and the actual work can start */
3402 if (graph_paint(&im, prdata) == -1) {
3407 /* The image is generated and needs to be output.
3408 ** Also, if needed, print a line with information about the image.
3419 /* maybe prdata is not allocated yet ... lets do it now */
3420 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3421 rrd_set_error("malloc imginfo");
3426 malloc((strlen(im.imginfo) + 200 +
3427 strlen(im.graphfile)) * sizeof(char)))
3429 rrd_set_error("malloc imginfo");
3432 filename = im.graphfile + strlen(im.graphfile);
3433 while (filename > im.graphfile) {
3434 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3439 sprintf((*prdata)[0], im.imginfo, filename,
3440 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3446 void rrd_graph_init(
3454 #ifdef HAVE_SETLOCALE
3455 setlocale(LC_TIME, "");
3456 #ifdef HAVE_MBSTOWCS
3457 setlocale(LC_CTYPE, "");
3463 im->xlab_user.minsec = -1;
3469 im->ylegend[0] = '\0';
3470 im->title[0] = '\0';
3471 im->watermark[0] = '\0';
3474 im->unitsexponent = 9999;
3475 im->unitslength = 6;
3476 im->forceleftspace = 0;
3478 im->viewfactor = 1.0;
3479 im->imgformat = IF_PNG;
3482 im->extra_flags = 0;
3488 im->logarithmic = 0;
3489 im->ygridstep = DNAN;
3490 im->draw_x_grid = 1;
3491 im->draw_y_grid = 1;
3496 im->grid_dash_on = 1;
3497 im->grid_dash_off = 1;
3498 im->tabwidth = 40.0;
3500 im->font_options = cairo_font_options_create();
3501 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3503 cairo_font_options_set_hint_style(im->font_options,
3504 CAIRO_HINT_STYLE_FULL);
3505 cairo_font_options_set_hint_metrics(im->font_options,
3506 CAIRO_HINT_METRICS_ON);
3507 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3510 for (i = 0; i < DIM(graph_col); i++)
3511 im->graph_col[i] = graph_col[i];
3513 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3516 char rrd_win_default_font[1000];
3518 windir = getenv("windir");
3519 /* %windir% is something like D:\windows or C:\winnt */
3520 if (windir != NULL) {
3521 strncpy(rrd_win_default_font, windir, 500);
3522 rrd_win_default_font[500] = '\0';
3523 strcat(rrd_win_default_font, "\\fonts\\");
3524 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3525 for (i = 0; i < DIM(text_prop); i++) {
3526 strncpy(text_prop[i].font, rrd_win_default_font,
3527 sizeof(text_prop[i].font) - 1);
3528 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3536 deffont = getenv("RRD_DEFAULT_FONT");
3537 if (deffont != NULL) {
3538 for (i = 0; i < DIM(text_prop); i++) {
3539 strncpy(text_prop[i].font, deffont,
3540 sizeof(text_prop[i].font) - 1);
3541 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3545 for (i = 0; i < DIM(text_prop); i++) {
3546 im->text_prop[i].size = text_prop[i].size;
3547 strcpy(im->text_prop[i].font, text_prop[i].font);
3551 void rrd_graph_options(
3557 char *parsetime_error = NULL;
3558 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3559 time_t start_tmp = 0, end_tmp = 0;
3561 struct rrd_time_value start_tv, end_tv;
3562 long unsigned int color;
3565 opterr = 0; /* initialize getopt */
3567 parsetime("end-24h", &start_tv);
3568 parsetime("now", &end_tv);
3570 /* defines for long options without a short equivalent. should be bytes,
3571 and may not collide with (the ASCII value of) short options */
3572 #define LONGOPT_UNITS_SI 255
3575 static struct option long_options[] = {
3576 {"start", required_argument, 0, 's'},
3577 {"end", required_argument, 0, 'e'},
3578 {"x-grid", required_argument, 0, 'x'},
3579 {"y-grid", required_argument, 0, 'y'},
3580 {"vertical-label", required_argument, 0, 'v'},
3581 {"width", required_argument, 0, 'w'},
3582 {"height", required_argument, 0, 'h'},
3583 {"full-size-mode", no_argument, 0, 'D'},
3584 {"interlaced", no_argument, 0, 'i'},
3585 {"upper-limit", required_argument, 0, 'u'},
3586 {"lower-limit", required_argument, 0, 'l'},
3587 {"rigid", no_argument, 0, 'r'},
3588 {"base", required_argument, 0, 'b'},
3589 {"logarithmic", no_argument, 0, 'o'},
3590 {"color", required_argument, 0, 'c'},
3591 {"font", required_argument, 0, 'n'},
3592 {"title", required_argument, 0, 't'},
3593 {"imginfo", required_argument, 0, 'f'},
3594 {"imgformat", required_argument, 0, 'a'},
3595 {"lazy", no_argument, 0, 'z'},
3596 {"zoom", required_argument, 0, 'm'},
3597 {"no-legend", no_argument, 0, 'g'},
3598 {"force-rules-legend", no_argument, 0, 'F'},
3599 {"only-graph", no_argument, 0, 'j'},
3600 {"alt-y-grid", no_argument, 0, 'Y'},
3601 {"no-minor", no_argument, 0, 'I'},
3602 {"slope-mode", no_argument, 0, 'E'},
3603 {"alt-autoscale", no_argument, 0, 'A'},
3604 {"alt-autoscale-min", no_argument, 0, 'J'},
3605 {"alt-autoscale-max", no_argument, 0, 'M'},
3606 {"no-gridfit", no_argument, 0, 'N'},
3607 {"units-exponent", required_argument, 0, 'X'},
3608 {"units-length", required_argument, 0, 'L'},
3609 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3610 {"step", required_argument, 0, 'S'},
3611 {"tabwidth", required_argument, 0, 'T'},
3612 {"font-render-mode", required_argument, 0, 'R'},
3613 {"graph-render-mode", required_argument, 0, 'G'},
3614 {"font-smoothing-threshold", required_argument, 0, 'B'},
3615 {"watermark", required_argument, 0, 'W'},
3616 {"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 */
3619 int option_index = 0;
3621 int col_start, col_end;
3623 opt = getopt_long(argc, argv,
3624 "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:",
3625 long_options, &option_index);
3632 im->extra_flags |= NOMINOR;
3635 im->extra_flags |= ALTYGRID;
3638 im->extra_flags |= ALTAUTOSCALE;
3641 im->extra_flags |= ALTAUTOSCALE_MIN;
3644 im->extra_flags |= ALTAUTOSCALE_MAX;
3647 im->extra_flags |= ONLY_GRAPH;
3650 im->extra_flags |= NOLEGEND;
3653 im->extra_flags |= FORCE_RULES_LEGEND;
3655 case LONGOPT_UNITS_SI:
3656 if (im->extra_flags & FORCE_UNITS) {
3657 rrd_set_error("--units can only be used once!");
3660 if (strcmp(optarg, "si") == 0)
3661 im->extra_flags |= FORCE_UNITS_SI;
3663 rrd_set_error("invalid argument for --units: %s", optarg);
3668 im->unitsexponent = atoi(optarg);
3671 im->unitslength = atoi(optarg);
3672 im->forceleftspace = 1;
3675 im->tabwidth = atof(optarg);
3678 im->step = atoi(optarg);
3684 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3685 rrd_set_error("start time: %s", parsetime_error);
3690 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3691 rrd_set_error("end time: %s", parsetime_error);
3696 if (strcmp(optarg, "none") == 0) {
3697 im->draw_x_grid = 0;
3702 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3704 &im->xlab_user.gridst,
3706 &im->xlab_user.mgridst,
3708 &im->xlab_user.labst,
3709 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3710 strncpy(im->xlab_form, optarg + stroff,
3711 sizeof(im->xlab_form) - 1);
3712 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3713 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3714 rrd_set_error("unknown keyword %s", scan_gtm);
3716 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3718 rrd_set_error("unknown keyword %s", scan_mtm);
3720 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3722 rrd_set_error("unknown keyword %s", scan_ltm);
3725 im->xlab_user.minsec = 1;
3726 im->xlab_user.stst = im->xlab_form;
3728 rrd_set_error("invalid x-grid format");
3734 if (strcmp(optarg, "none") == 0) {
3735 im->draw_y_grid = 0;
3739 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3740 if (im->ygridstep <= 0) {
3741 rrd_set_error("grid step must be > 0");
3743 } else if (im->ylabfact < 1) {
3744 rrd_set_error("label factor must be > 0");
3748 rrd_set_error("invalid y-grid format");
3753 strncpy(im->ylegend, optarg, 150);
3754 im->ylegend[150] = '\0';
3757 im->maxval = atof(optarg);
3760 im->minval = atof(optarg);
3763 im->base = atol(optarg);
3764 if (im->base != 1024 && im->base != 1000) {
3766 ("the only sensible value for base apart from 1000 is 1024");
3771 long_tmp = atol(optarg);
3772 if (long_tmp < 10) {
3773 rrd_set_error("width below 10 pixels");
3776 im->xsize = long_tmp;
3779 long_tmp = atol(optarg);
3780 if (long_tmp < 10) {
3781 rrd_set_error("height below 10 pixels");
3784 im->ysize = long_tmp;
3787 im->extra_flags |= FULL_SIZE_MODE;
3790 /* interlaced png not supported at the moment */
3796 im->imginfo = optarg;
3799 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3800 rrd_set_error("unsupported graphics format '%s'", optarg);
3812 im->logarithmic = 1;
3816 "%10[A-Z]#%n%8lx%n",
3817 col_nam, &col_start, &color, &col_end) == 2) {
3819 int col_len = col_end - col_start;
3823 color = (((color & 0xF00) * 0x110000) |
3824 ((color & 0x0F0) * 0x011000) |
3825 ((color & 0x00F) * 0x001100) | 0x000000FF);
3828 color = (((color & 0xF000) * 0x11000) |
3829 ((color & 0x0F00) * 0x01100) |
3830 ((color & 0x00F0) * 0x00110) |
3831 ((color & 0x000F) * 0x00011)
3835 color = (color << 8) + 0xff /* shift left by 8 */ ;
3840 rrd_set_error("the color format is #RRGGBB[AA]");
3843 if ((ci = grc_conv(col_nam)) != -1) {
3844 im->graph_col[ci] = gfx_hex_to_col(color);
3846 rrd_set_error("invalid color name '%s'", col_nam);
3850 rrd_set_error("invalid color def format");
3857 char font[1024] = "";
3859 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3860 int sindex, propidx;
3862 if ((sindex = text_prop_conv(prop)) != -1) {
3863 for (propidx = sindex; propidx < TEXT_PROP_LAST;
3866 im->text_prop[propidx].size = size;
3868 if (strlen(font) > 0) {
3869 strcpy(im->text_prop[propidx].font, font);
3871 if (propidx == sindex && sindex != 0)
3875 rrd_set_error("invalid fonttag '%s'", prop);
3879 rrd_set_error("invalid text property format");
3885 im->zoom = atof(optarg);
3886 if (im->zoom <= 0.0) {
3887 rrd_set_error("zoom factor must be > 0");
3892 strncpy(im->title, optarg, 150);
3893 im->title[150] = '\0';
3897 if (strcmp(optarg, "normal") == 0) {
3898 cairo_font_options_set_antialias(im->font_options,
3899 CAIRO_ANTIALIAS_GRAY);
3900 cairo_font_options_set_hint_style(im->font_options,
3901 CAIRO_HINT_STYLE_FULL);
3902 } else if (strcmp(optarg, "light") == 0) {
3903 cairo_font_options_set_antialias(im->font_options,
3904 CAIRO_ANTIALIAS_GRAY);
3905 cairo_font_options_set_hint_style(im->font_options,
3906 CAIRO_HINT_STYLE_SLIGHT);
3907 } else if (strcmp(optarg, "mono") == 0) {
3908 cairo_font_options_set_antialias(im->font_options,
3909 CAIRO_ANTIALIAS_NONE);
3910 cairo_font_options_set_hint_style(im->font_options,
3911 CAIRO_HINT_STYLE_FULL);
3913 rrd_set_error("unknown font-render-mode '%s'", optarg);
3918 if (strcmp(optarg, "normal") == 0)
3919 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3920 else if (strcmp(optarg, "mono") == 0)
3921 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
3923 rrd_set_error("unknown graph-render-mode '%s'", optarg);
3928 /* not supported curently */
3932 strncpy(im->watermark, optarg, 100);
3933 im->watermark[99] = '\0';
3938 rrd_set_error("unknown option '%c'", optopt);
3940 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3945 if (optind >= argc) {
3946 rrd_set_error("missing filename");
3950 if (im->logarithmic == 1 && im->minval <= 0) {
3952 ("for a logarithmic yaxis you must specify a lower-limit > 0");
3956 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3957 /* error string is set in parsetime.c */
3961 if (start_tmp < 3600 * 24 * 365 * 10) {
3962 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3967 if (end_tmp < start_tmp) {
3968 rrd_set_error("start (%ld) should be less than end (%ld)",
3969 start_tmp, end_tmp);
3973 im->start = start_tmp;
3975 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3978 int rrd_graph_color(
3985 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
3987 color = strstr(var, "#");
3988 if (color == NULL) {
3989 if (optional == 0) {
3990 rrd_set_error("Found no color in %s", err);
3997 long unsigned int col;
3999 rest = strstr(color, ":");
4007 sscanf(color, "#%6lx%n", &col, &n);
4008 col = (col << 8) + 0xff /* shift left by 8 */ ;
4010 rrd_set_error("Color problem in %s", err);
4013 sscanf(color, "#%8lx%n", &col, &n);
4017 rrd_set_error("Color problem in %s", err);
4019 if (rrd_test_error())
4021 gdp->col = gfx_hex_to_col(col);
4034 while (*ptr != '\0')
4035 if (*ptr++ == '%') {
4037 /* line cannot end with percent char */
4041 /* '%s', '%S' and '%%' are allowed */
4042 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4045 /* %c is allowed (but use only with vdef!) */
4046 else if (*ptr == 'c') {
4051 /* or else '% 6.2lf' and such are allowed */
4053 /* optional padding character */
4054 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4057 /* This should take care of 'm.n' with all three optional */
4058 while (*ptr >= '0' && *ptr <= '9')
4062 while (*ptr >= '0' && *ptr <= '9')
4065 /* Either 'le', 'lf' or 'lg' must follow here */
4068 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4083 struct graph_desc_t *gdes;
4084 const char *const str;
4086 /* A VDEF currently is either "func" or "param,func"
4087 * so the parsing is rather simple. Change if needed.
4094 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4095 if (n == (int) strlen(str)) { /* matched */
4099 sscanf(str, "%29[A-Z]%n", func, &n);
4100 if (n == (int) strlen(str)) { /* matched */
4103 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4108 if (!strcmp("PERCENT", func))
4109 gdes->vf.op = VDEF_PERCENT;
4110 else if (!strcmp("MAXIMUM", func))
4111 gdes->vf.op = VDEF_MAXIMUM;
4112 else if (!strcmp("AVERAGE", func))
4113 gdes->vf.op = VDEF_AVERAGE;
4114 else if (!strcmp("MINIMUM", func))
4115 gdes->vf.op = VDEF_MINIMUM;
4116 else if (!strcmp("TOTAL", func))
4117 gdes->vf.op = VDEF_TOTAL;
4118 else if (!strcmp("FIRST", func))
4119 gdes->vf.op = VDEF_FIRST;
4120 else if (!strcmp("LAST", func))
4121 gdes->vf.op = VDEF_LAST;
4122 else if (!strcmp("LSLSLOPE", func))
4123 gdes->vf.op = VDEF_LSLSLOPE;
4124 else if (!strcmp("LSLINT", func))
4125 gdes->vf.op = VDEF_LSLINT;
4126 else if (!strcmp("LSLCORREL", func))
4127 gdes->vf.op = VDEF_LSLCORREL;
4129 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4134 switch (gdes->vf.op) {
4136 if (isnan(param)) { /* no parameter given */
4137 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4141 if (param >= 0.0 && param <= 100.0) {
4142 gdes->vf.param = param;
4143 gdes->vf.val = DNAN; /* undefined */
4144 gdes->vf.when = 0; /* undefined */
4146 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4159 case VDEF_LSLCORREL:
4161 gdes->vf.param = DNAN;
4162 gdes->vf.val = DNAN;
4165 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4181 graph_desc_t *src, *dst;
4185 dst = &im->gdes[gdi];
4186 src = &im->gdes[dst->vidx];
4187 data = src->data + src->ds;
4188 steps = (src->end - src->start) / src->step;
4191 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4195 switch (dst->vf.op) {
4201 if ((array = malloc(steps * sizeof(double))) == NULL) {
4202 rrd_set_error("malloc VDEV_PERCENT");
4205 for (step = 0; step < steps; step++) {
4206 array[step] = data[step * src->ds_cnt];
4208 qsort(array, step, sizeof(double), vdef_percent_compar);
4210 field = (steps - 1) * dst->vf.param / 100;
4211 dst->vf.val = array[field];
4212 dst->vf.when = 0; /* no time component */
4215 for (step = 0; step < steps; step++)
4216 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4217 step == field ? '*' : ' ');
4223 while (step != steps && isnan(data[step * src->ds_cnt]))
4225 if (step == steps) {
4229 dst->vf.val = data[step * src->ds_cnt];
4230 dst->vf.when = src->start + (step + 1) * src->step;
4232 while (step != steps) {
4233 if (finite(data[step * src->ds_cnt])) {
4234 if (data[step * src->ds_cnt] > dst->vf.val) {
4235 dst->vf.val = data[step * src->ds_cnt];
4236 dst->vf.when = src->start + (step + 1) * src->step;
4247 for (step = 0; step < steps; step++) {
4248 if (finite(data[step * src->ds_cnt])) {
4249 sum += data[step * src->ds_cnt];
4254 if (dst->vf.op == VDEF_TOTAL) {
4255 dst->vf.val = sum * src->step;
4256 dst->vf.when = 0; /* no time component */
4258 dst->vf.val = sum / cnt;
4259 dst->vf.when = 0; /* no time component */
4269 while (step != steps && isnan(data[step * src->ds_cnt]))
4271 if (step == steps) {
4275 dst->vf.val = data[step * src->ds_cnt];
4276 dst->vf.when = src->start + (step + 1) * src->step;
4278 while (step != steps) {
4279 if (finite(data[step * src->ds_cnt])) {
4280 if (data[step * src->ds_cnt] < dst->vf.val) {
4281 dst->vf.val = data[step * src->ds_cnt];
4282 dst->vf.when = src->start + (step + 1) * src->step;
4289 /* The time value returned here is one step before the
4290 * actual time value. This is the start of the first
4294 while (step != steps && isnan(data[step * src->ds_cnt]))
4296 if (step == steps) { /* all entries were NaN */
4300 dst->vf.val = data[step * src->ds_cnt];
4301 dst->vf.when = src->start + step * src->step;
4305 /* The time value returned here is the
4306 * actual time value. This is the end of the last
4310 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4312 if (step < 0) { /* all entries were NaN */
4316 dst->vf.val = data[step * src->ds_cnt];
4317 dst->vf.when = src->start + (step + 1) * src->step;
4322 case VDEF_LSLCORREL:{
4323 /* Bestfit line by linear least squares method */
4326 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4334 for (step = 0; step < steps; step++) {
4335 if (finite(data[step * src->ds_cnt])) {
4338 SUMxx += step * step;
4339 SUMxy += step * data[step * src->ds_cnt];
4340 SUMy += data[step * src->ds_cnt];
4341 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4345 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4346 y_intercept = (SUMy - slope * SUMx) / cnt;
4349 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4350 (SUMx * SUMx) / cnt) * (SUMyy -
4356 if (dst->vf.op == VDEF_LSLSLOPE) {
4357 dst->vf.val = slope;
4359 } else if (dst->vf.op == VDEF_LSLINT) {
4360 dst->vf.val = y_intercept;
4362 } else if (dst->vf.op == VDEF_LSLCORREL) {
4363 dst->vf.val = correl;
4377 /* NaN < -INF < finite_values < INF */
4378 int vdef_percent_compar(
4383 /* Equality is not returned; this doesn't hurt except
4384 * (maybe) for a little performance.
4387 /* First catch NaN values. They are smallest */
4388 if (isnan(*(double *) a))
4390 if (isnan(*(double *) b))
4393 /* NaN doesn't reach this part so INF and -INF are extremes.
4394 * The sign from isinf() is compatible with the sign we return
4396 if (isinf(*(double *) a))
4397 return isinf(*(double *) a);
4398 if (isinf(*(double *) b))
4399 return isinf(*(double *) b);
4401 /* If we reach this, both values must be finite */
4402 if (*(double *) a < *(double *) b)