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(TEXTALIGN, GF_TEXTALIGN);
239 conv_if(DEF, GF_DEF);
240 conv_if(CDEF, GF_CDEF);
241 conv_if(VDEF, GF_VDEF);
242 conv_if(XPORT, GF_XPORT);
243 conv_if(SHIFT, GF_SHIFT);
248 enum gfx_if_en if_conv(
252 conv_if(PNG, IF_PNG);
253 conv_if(SVG, IF_SVG);
254 conv_if(EPS, IF_EPS);
255 conv_if(PDF, IF_PDF);
260 enum tmt_en tmt_conv(
264 conv_if(SECOND, TMT_SECOND);
265 conv_if(MINUTE, TMT_MINUTE);
266 conv_if(HOUR, TMT_HOUR);
267 conv_if(DAY, TMT_DAY);
268 conv_if(WEEK, TMT_WEEK);
269 conv_if(MONTH, TMT_MONTH);
270 conv_if(YEAR, TMT_YEAR);
274 enum grc_en grc_conv(
278 conv_if(BACK, GRC_BACK);
279 conv_if(CANVAS, GRC_CANVAS);
280 conv_if(SHADEA, GRC_SHADEA);
281 conv_if(SHADEB, GRC_SHADEB);
282 conv_if(GRID, GRC_GRID);
283 conv_if(MGRID, GRC_MGRID);
284 conv_if(FONT, GRC_FONT);
285 conv_if(ARROW, GRC_ARROW);
286 conv_if(AXIS, GRC_AXIS);
287 conv_if(FRAME, GRC_FRAME);
292 enum text_prop_en text_prop_conv(
296 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
297 conv_if(TITLE, TEXT_PROP_TITLE);
298 conv_if(AXIS, TEXT_PROP_AXIS);
299 conv_if(UNIT, TEXT_PROP_UNIT);
300 conv_if(LEGEND, TEXT_PROP_LEGEND);
311 cairo_status_t status;
315 for (i = 0; i < (unsigned) im->gdes_c; i++) {
316 if (im->gdes[i].data_first) {
317 /* careful here, because a single pointer can occur several times */
318 free(im->gdes[i].data);
319 if (im->gdes[i].ds_namv) {
320 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
321 free(im->gdes[i].ds_namv[ii]);
322 free(im->gdes[i].ds_namv);
325 free(im->gdes[i].p_data);
326 free(im->gdes[i].rpnp);
329 if (im->font_options)
330 cairo_font_options_destroy(im->font_options);
332 status = cairo_status(im->cr);
335 cairo_destroy(im->cr);
337 cairo_surface_destroy(im->surface);
339 fprintf(stderr, "OOPS: Cairo has issuesm it can't even die: %s\n",
340 cairo_status_to_string(status));
345 /* find SI magnitude symbol for the given number*/
347 image_desc_t *im, /* image description */
353 char *symbol[] = { "a", /* 10e-18 Atto */
354 "f", /* 10e-15 Femto */
355 "p", /* 10e-12 Pico */
356 "n", /* 10e-9 Nano */
357 "u", /* 10e-6 Micro */
358 "m", /* 10e-3 Milli */
363 "T", /* 10e12 Tera */
364 "P", /* 10e15 Peta */
371 if (*value == 0.0 || isnan(*value)) {
375 sindex = floor(log(fabs(*value)) / log((double) im->base));
376 *magfact = pow((double) im->base, (double) sindex);
377 (*value) /= (*magfact);
379 if (sindex <= symbcenter && sindex >= -symbcenter) {
380 (*symb_ptr) = symbol[sindex + symbcenter];
387 static char si_symbol[] = {
388 'a', /* 10e-18 Atto */
389 'f', /* 10e-15 Femto */
390 'p', /* 10e-12 Pico */
391 'n', /* 10e-9 Nano */
392 'u', /* 10e-6 Micro */
393 'm', /* 10e-3 Milli */
398 'T', /* 10e12 Tera */
399 'P', /* 10e15 Peta */
402 static const int si_symbcenter = 6;
404 /* find SI magnitude symbol for the numbers on the y-axis*/
406 image_desc_t *im /* image description */
410 double digits, viewdigits = 0;
413 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
414 log((double) im->base));
416 if (im->unitsexponent != 9999) {
417 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
418 viewdigits = floor(im->unitsexponent / 3);
423 im->magfact = pow((double) im->base, digits);
426 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
429 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
431 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
432 ((viewdigits + si_symbcenter) >= 0))
433 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
438 /* move min and max values around to become sensible */
443 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
444 600.0, 500.0, 400.0, 300.0, 250.0,
445 200.0, 125.0, 100.0, 90.0, 80.0,
446 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
447 25.0, 20.0, 10.0, 9.0, 8.0,
448 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
449 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
450 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
453 double scaled_min, scaled_max;
460 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
461 im->minval, im->maxval, im->magfact);
464 if (isnan(im->ygridstep)) {
465 if (im->extra_flags & ALTAUTOSCALE) {
466 /* measure the amplitude of the function. Make sure that
467 graph boundaries are slightly higher then max/min vals
468 so we can see amplitude on the graph */
471 delt = im->maxval - im->minval;
473 fact = 2.0 * pow(10.0,
475 (max(fabs(im->minval), fabs(im->maxval)) /
478 adj = (fact - delt) * 0.55;
481 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
482 im->minval, im->maxval, delt, fact, adj);
487 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
488 /* measure the amplitude of the function. Make sure that
489 graph boundaries are slightly lower than min vals
490 so we can see amplitude on the graph */
491 adj = (im->maxval - im->minval) * 0.1;
493 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
494 /* measure the amplitude of the function. Make sure that
495 graph boundaries are slightly higher than max vals
496 so we can see amplitude on the graph */
497 adj = (im->maxval - im->minval) * 0.1;
500 scaled_min = im->minval / im->magfact;
501 scaled_max = im->maxval / im->magfact;
503 for (i = 1; sensiblevalues[i] > 0; i++) {
504 if (sensiblevalues[i - 1] >= scaled_min &&
505 sensiblevalues[i] <= scaled_min)
506 im->minval = sensiblevalues[i] * (im->magfact);
508 if (-sensiblevalues[i - 1] <= scaled_min &&
509 -sensiblevalues[i] >= scaled_min)
510 im->minval = -sensiblevalues[i - 1] * (im->magfact);
512 if (sensiblevalues[i - 1] >= scaled_max &&
513 sensiblevalues[i] <= scaled_max)
514 im->maxval = sensiblevalues[i - 1] * (im->magfact);
516 if (-sensiblevalues[i - 1] <= scaled_max &&
517 -sensiblevalues[i] >= scaled_max)
518 im->maxval = -sensiblevalues[i] * (im->magfact);
522 /* adjust min and max to the grid definition if there is one */
523 im->minval = (double) im->ylabfact * im->ygridstep *
524 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
525 im->maxval = (double) im->ylabfact * im->ygridstep *
526 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
530 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
531 im->minval, im->maxval, im->magfact);
539 if (isnan(im->minval) || isnan(im->maxval))
542 if (im->logarithmic) {
543 double ya, yb, ypix, ypixfrac;
544 double log10_range = log10(im->maxval) - log10(im->minval);
546 ya = pow((double) 10, floor(log10(im->minval)));
547 while (ya < im->minval)
550 return; /* don't have y=10^x gridline */
552 if (yb <= im->maxval) {
553 /* we have at least 2 y=10^x gridlines.
554 Make sure distance between them in pixels
555 are an integer by expanding im->maxval */
556 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
557 double factor = y_pixel_delta / floor(y_pixel_delta);
558 double new_log10_range = factor * log10_range;
559 double new_ymax_log10 = log10(im->minval) + new_log10_range;
561 im->maxval = pow(10, new_ymax_log10);
562 ytr(im, DNAN); /* reset precalc */
563 log10_range = log10(im->maxval) - log10(im->minval);
565 /* make sure first y=10^x gridline is located on
566 integer pixel position by moving scale slightly
567 downwards (sub-pixel movement) */
568 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
569 ypixfrac = ypix - floor(ypix);
570 if (ypixfrac > 0 && ypixfrac < 1) {
571 double yfrac = ypixfrac / im->ysize;
573 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
574 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
575 ytr(im, DNAN); /* reset precalc */
578 /* Make sure we have an integer pixel distance between
579 each minor gridline */
580 double ypos1 = ytr(im, im->minval);
581 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
582 double y_pixel_delta = ypos1 - ypos2;
583 double factor = y_pixel_delta / floor(y_pixel_delta);
584 double new_range = factor * (im->maxval - im->minval);
585 double gridstep = im->ygrid_scale.gridstep;
586 double minor_y, minor_y_px, minor_y_px_frac;
588 if (im->maxval > 0.0)
589 im->maxval = im->minval + new_range;
591 im->minval = im->maxval - new_range;
592 ytr(im, DNAN); /* reset precalc */
593 /* make sure first minor gridline is on integer pixel y coord */
594 minor_y = gridstep * floor(im->minval / gridstep);
595 while (minor_y < im->minval)
597 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
598 minor_y_px_frac = minor_y_px - floor(minor_y_px);
599 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
600 double yfrac = minor_y_px_frac / im->ysize;
601 double range = im->maxval - im->minval;
603 im->minval = im->minval - yfrac * range;
604 im->maxval = im->maxval - yfrac * range;
605 ytr(im, DNAN); /* reset precalc */
607 calc_horizontal_grid(im); /* recalc with changed im->maxval */
611 /* reduce data reimplementation by Alex */
614 enum cf_en cf, /* which consolidation function ? */
615 unsigned long cur_step, /* step the data currently is in */
616 time_t *start, /* start, end and step as requested ... */
617 time_t *end, /* ... by the application will be ... */
618 unsigned long *step, /* ... adjusted to represent reality */
619 unsigned long *ds_cnt, /* number of data sources in file */
621 { /* two dimensional array containing the data */
622 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
623 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
625 rrd_value_t *srcptr, *dstptr;
627 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
630 row_cnt = ((*end) - (*start)) / cur_step;
636 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
637 row_cnt, reduce_factor, *start, *end, cur_step);
638 for (col = 0; col < row_cnt; col++) {
639 printf("time %10lu: ", *start + (col + 1) * cur_step);
640 for (i = 0; i < *ds_cnt; i++)
641 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
646 /* We have to combine [reduce_factor] rows of the source
647 ** into one row for the destination. Doing this we also
648 ** need to take care to combine the correct rows. First
649 ** alter the start and end time so that they are multiples
650 ** of the new step time. We cannot reduce the amount of
651 ** time so we have to move the end towards the future and
652 ** the start towards the past.
654 end_offset = (*end) % (*step);
655 start_offset = (*start) % (*step);
657 /* If there is a start offset (which cannot be more than
658 ** one destination row), skip the appropriate number of
659 ** source rows and one destination row. The appropriate
660 ** number is what we do know (start_offset/cur_step) of
661 ** the new interval (*step/cur_step aka reduce_factor).
664 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
665 printf("row_cnt before: %lu\n", row_cnt);
668 (*start) = (*start) - start_offset;
669 skiprows = reduce_factor - start_offset / cur_step;
670 srcptr += skiprows * *ds_cnt;
671 for (col = 0; col < (*ds_cnt); col++)
676 printf("row_cnt between: %lu\n", row_cnt);
679 /* At the end we have some rows that are not going to be
680 ** used, the amount is end_offset/cur_step
683 (*end) = (*end) - end_offset + (*step);
684 skiprows = end_offset / cur_step;
688 printf("row_cnt after: %lu\n", row_cnt);
691 /* Sanity check: row_cnt should be multiple of reduce_factor */
692 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
694 if (row_cnt % reduce_factor) {
695 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
696 row_cnt, reduce_factor);
697 printf("BUG in reduce_data()\n");
701 /* Now combine reduce_factor intervals at a time
702 ** into one interval for the destination.
705 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
706 for (col = 0; col < (*ds_cnt); col++) {
707 rrd_value_t newval = DNAN;
708 unsigned long validval = 0;
710 for (i = 0; i < reduce_factor; i++) {
711 if (isnan(srcptr[i * (*ds_cnt) + col])) {
716 newval = srcptr[i * (*ds_cnt) + col];
724 newval += srcptr[i * (*ds_cnt) + col];
727 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
730 /* an interval contains a failure if any subintervals contained a failure */
732 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
735 newval = srcptr[i * (*ds_cnt) + col];
760 srcptr += (*ds_cnt) * reduce_factor;
761 row_cnt -= reduce_factor;
763 /* If we had to alter the endtime, we didn't have enough
764 ** source rows to fill the last row. Fill it with NaN.
767 for (col = 0; col < (*ds_cnt); col++)
770 row_cnt = ((*end) - (*start)) / *step;
772 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
773 row_cnt, *start, *end, *step);
774 for (col = 0; col < row_cnt; col++) {
775 printf("time %10lu: ", *start + (col + 1) * (*step));
776 for (i = 0; i < *ds_cnt; i++)
777 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
784 /* get the data required for the graphs from the
793 /* pull the data from the rrd files ... */
794 for (i = 0; i < (int) im->gdes_c; i++) {
795 /* only GF_DEF elements fetch data */
796 if (im->gdes[i].gf != GF_DEF)
800 /* do we have it already ? */
801 for (ii = 0; ii < i; ii++) {
802 if (im->gdes[ii].gf != GF_DEF)
804 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
805 && (im->gdes[i].cf == im->gdes[ii].cf)
806 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
807 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
808 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
809 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
810 /* OK, the data is already there.
811 ** Just copy the header portion
813 im->gdes[i].start = im->gdes[ii].start;
814 im->gdes[i].end = im->gdes[ii].end;
815 im->gdes[i].step = im->gdes[ii].step;
816 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
817 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
818 im->gdes[i].data = im->gdes[ii].data;
819 im->gdes[i].data_first = 0;
826 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
828 if ((rrd_fetch_fn(im->gdes[i].rrd,
834 &im->gdes[i].ds_namv,
835 &im->gdes[i].data)) == -1) {
838 im->gdes[i].data_first = 1;
840 if (ft_step < im->gdes[i].step) {
841 reduce_data(im->gdes[i].cf_reduce,
846 &im->gdes[i].ds_cnt, &im->gdes[i].data);
848 im->gdes[i].step = ft_step;
852 /* lets see if the required data source is really there */
853 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
854 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
858 if (im->gdes[i].ds == -1) {
859 rrd_set_error("No DS called '%s' in '%s'",
860 im->gdes[i].ds_nam, im->gdes[i].rrd);
868 /* evaluate the expressions in the CDEF functions */
870 /*************************************************************
872 *************************************************************/
874 long find_var_wrapper(
878 return find_var((image_desc_t *) arg1, key);
881 /* find gdes containing var*/
888 for (ii = 0; ii < im->gdes_c - 1; ii++) {
889 if ((im->gdes[ii].gf == GF_DEF
890 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
891 && (strcmp(im->gdes[ii].vname, key) == 0)) {
898 /* find the largest common denominator for all the numbers
899 in the 0 terminated num array */
906 for (i = 0; num[i + 1] != 0; i++) {
908 rest = num[i] % num[i + 1];
914 /* return i==0?num[i]:num[i-1]; */
918 /* run the rpn calculator on all the VDEF and CDEF arguments */
925 long *steparray, rpi;
930 rpnstack_init(&rpnstack);
932 for (gdi = 0; gdi < im->gdes_c; gdi++) {
933 /* Look for GF_VDEF and GF_CDEF in the same loop,
934 * so CDEFs can use VDEFs and vice versa
936 switch (im->gdes[gdi].gf) {
940 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
942 /* remove current shift */
943 vdp->start -= vdp->shift;
944 vdp->end -= vdp->shift;
947 if (im->gdes[gdi].shidx >= 0)
948 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
951 vdp->shift = im->gdes[gdi].shval;
953 /* normalize shift to multiple of consolidated step */
954 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
957 vdp->start += vdp->shift;
958 vdp->end += vdp->shift;
962 /* A VDEF has no DS. This also signals other parts
963 * of rrdtool that this is a VDEF value, not a CDEF.
965 im->gdes[gdi].ds_cnt = 0;
966 if (vdef_calc(im, gdi)) {
967 rrd_set_error("Error processing VDEF '%s'",
968 im->gdes[gdi].vname);
969 rpnstack_free(&rpnstack);
974 im->gdes[gdi].ds_cnt = 1;
975 im->gdes[gdi].ds = 0;
976 im->gdes[gdi].data_first = 1;
977 im->gdes[gdi].start = 0;
978 im->gdes[gdi].end = 0;
983 /* Find the variables in the expression.
984 * - VDEF variables are substituted by their values
985 * and the opcode is changed into OP_NUMBER.
986 * - CDEF variables are analized for their step size,
987 * the lowest common denominator of all the step
988 * sizes of the data sources involved is calculated
989 * and the resulting number is the step size for the
990 * resulting data source.
992 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
993 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
994 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
995 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
997 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1000 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1001 im->gdes[gdi].vname, im->gdes[ptr].vname);
1002 printf("DEBUG: value from vdef is %f\n",
1003 im->gdes[ptr].vf.val);
1005 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1006 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1007 } else { /* normal variables and PREF(variables) */
1009 /* add one entry to the array that keeps track of the step sizes of the
1010 * data sources going into the CDEF. */
1012 rrd_realloc(steparray,
1014 1) * sizeof(*steparray))) == NULL) {
1015 rrd_set_error("realloc steparray");
1016 rpnstack_free(&rpnstack);
1020 steparray[stepcnt - 1] = im->gdes[ptr].step;
1022 /* adjust start and end of cdef (gdi) so
1023 * that it runs from the latest start point
1024 * to the earliest endpoint of any of the
1025 * rras involved (ptr)
1028 if (im->gdes[gdi].start < im->gdes[ptr].start)
1029 im->gdes[gdi].start = im->gdes[ptr].start;
1031 if (im->gdes[gdi].end == 0 ||
1032 im->gdes[gdi].end > im->gdes[ptr].end)
1033 im->gdes[gdi].end = im->gdes[ptr].end;
1035 /* store pointer to the first element of
1036 * the rra providing data for variable,
1037 * further save step size and data source
1040 im->gdes[gdi].rpnp[rpi].data =
1041 im->gdes[ptr].data + im->gdes[ptr].ds;
1042 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1043 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1045 /* backoff the *.data ptr; this is done so
1046 * rpncalc() function doesn't have to treat
1047 * the first case differently
1049 } /* if ds_cnt != 0 */
1050 } /* if OP_VARIABLE */
1051 } /* loop through all rpi */
1053 /* move the data pointers to the correct period */
1054 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1055 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1056 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1057 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1059 im->gdes[gdi].start - im->gdes[ptr].start;
1062 im->gdes[gdi].rpnp[rpi].data +=
1063 (diff / im->gdes[ptr].step) *
1064 im->gdes[ptr].ds_cnt;
1068 if (steparray == NULL) {
1069 rrd_set_error("rpn expressions without DEF"
1070 " or CDEF variables are not supported");
1071 rpnstack_free(&rpnstack);
1074 steparray[stepcnt] = 0;
1075 /* Now find the resulting step. All steps in all
1076 * used RRAs have to be visited
1078 im->gdes[gdi].step = lcd(steparray);
1080 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1081 im->gdes[gdi].start)
1082 / im->gdes[gdi].step)
1083 * sizeof(double))) == NULL) {
1084 rrd_set_error("malloc im->gdes[gdi].data");
1085 rpnstack_free(&rpnstack);
1089 /* Step through the new cdef results array and
1090 * calculate the values
1092 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1093 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1094 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1096 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1097 * in this case we are advancing by timesteps;
1098 * we use the fact that time_t is a synonym for long
1100 if (rpn_calc(rpnp, &rpnstack, (long) now,
1101 im->gdes[gdi].data, ++dataidx) == -1) {
1102 /* rpn_calc sets the error string */
1103 rpnstack_free(&rpnstack);
1106 } /* enumerate over time steps within a CDEF */
1111 } /* enumerate over CDEFs */
1112 rpnstack_free(&rpnstack);
1116 /* massage data so, that we get one value for each x coordinate in the graph */
1121 double pixstep = (double) (im->end - im->start)
1122 / (double) im->xsize; /* how much time
1123 passes in one pixel */
1125 double minval = DNAN, maxval = DNAN;
1127 unsigned long gr_time;
1129 /* memory for the processed data */
1130 for (i = 0; i < im->gdes_c; i++) {
1131 if ((im->gdes[i].gf == GF_LINE) ||
1132 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1133 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1134 * sizeof(rrd_value_t))) == NULL) {
1135 rrd_set_error("malloc data_proc");
1141 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1144 gr_time = im->start + pixstep * i; /* time of the current step */
1147 for (ii = 0; ii < im->gdes_c; ii++) {
1150 switch (im->gdes[ii].gf) {
1154 if (!im->gdes[ii].stack)
1156 value = im->gdes[ii].yrule;
1157 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1158 /* The time of the data doesn't necessarily match
1159 ** the time of the graph. Beware.
1161 vidx = im->gdes[ii].vidx;
1162 if (im->gdes[vidx].gf == GF_VDEF) {
1163 value = im->gdes[vidx].vf.val;
1165 if (((long int) gr_time >=
1166 (long int) im->gdes[vidx].start)
1167 && ((long int) gr_time <=
1168 (long int) im->gdes[vidx].end)) {
1169 value = im->gdes[vidx].data[(unsigned long)
1175 im->gdes[vidx].step)
1176 * im->gdes[vidx].ds_cnt +
1183 if (!isnan(value)) {
1185 im->gdes[ii].p_data[i] = paintval;
1186 /* GF_TICK: the data values are not
1187 ** relevant for min and max
1189 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1190 if ((isnan(minval) || paintval < minval) &&
1191 !(im->logarithmic && paintval <= 0.0))
1193 if (isnan(maxval) || paintval > maxval)
1197 im->gdes[ii].p_data[i] = DNAN;
1202 ("STACK should already be turned into LINE or AREA here");
1211 /* if min or max have not been asigned a value this is because
1212 there was no data in the graph ... this is not good ...
1213 lets set these to dummy values then ... */
1215 if (im->logarithmic) {
1227 /* adjust min and max values */
1228 if (isnan(im->minval)
1229 /* don't adjust low-end with log scale *//* why not? */
1230 || ((!im->rigid) && im->minval > minval)
1232 if (im->logarithmic)
1233 im->minval = minval * 0.5;
1235 im->minval = minval;
1237 if (isnan(im->maxval)
1238 || (!im->rigid && im->maxval < maxval)
1240 if (im->logarithmic)
1241 im->maxval = maxval * 2.0;
1243 im->maxval = maxval;
1245 /* make sure min is smaller than max */
1246 if (im->minval > im->maxval) {
1247 im->minval = 0.99 * im->maxval;
1250 /* make sure min and max are not equal */
1251 if (im->minval == im->maxval) {
1253 if (!im->logarithmic) {
1256 /* make sure min and max are not both zero */
1257 if (im->maxval == 0.0) {
1266 /* identify the point where the first gridline, label ... gets placed */
1268 time_t find_first_time(
1269 time_t start, /* what is the initial time */
1270 enum tmt_en baseint, /* what is the basic interval */
1271 long basestep /* how many if these do we jump a time */
1276 localtime_r(&start, &tm);
1280 tm. tm_sec -= tm.tm_sec % basestep;
1285 tm. tm_min -= tm.tm_min % basestep;
1291 tm. tm_hour -= tm.tm_hour % basestep;
1295 /* we do NOT look at the basestep for this ... */
1302 /* we do NOT look at the basestep for this ... */
1306 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1308 if (tm.tm_wday == 0)
1309 tm. tm_mday -= 7; /* we want the *previous* monday */
1317 tm. tm_mon -= tm.tm_mon % basestep;
1328 tm.tm_year + 1900) %basestep;
1334 /* identify the point where the next gridline, label ... gets placed */
1335 time_t find_next_time(
1336 time_t current, /* what is the initial time */
1337 enum tmt_en baseint, /* what is the basic interval */
1338 long basestep /* how many if these do we jump a time */
1344 localtime_r(¤t, &tm);
1349 tm. tm_sec += basestep;
1353 tm. tm_min += basestep;
1357 tm. tm_hour += basestep;
1361 tm. tm_mday += basestep;
1365 tm. tm_mday += 7 * basestep;
1369 tm. tm_mon += basestep;
1373 tm. tm_year += basestep;
1375 madetime = mktime(&tm);
1376 } while (madetime == -1); /* this is necessary to skip impssible times
1377 like the daylight saving time skips */
1383 /* calculate values required for PRINT and GPRINT functions */
1389 long i, ii, validsteps;
1392 int graphelement = 0;
1395 double magfact = -1;
1400 /* wow initializing tmvdef is quite a task :-) */
1401 time_t now = time(NULL);
1403 localtime_r(&now, &tmvdef);
1406 for (i = 0; i < im->gdes_c; i++) {
1407 vidx = im->gdes[i].vidx;
1408 switch (im->gdes[i].gf) {
1412 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1413 rrd_set_error("realloc prdata");
1417 /* PRINT and GPRINT can now print VDEF generated values.
1418 * There's no need to do any calculations on them as these
1419 * calculations were already made.
1421 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1422 printval = im->gdes[vidx].vf.val;
1423 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1424 } else { /* need to calculate max,min,avg etcetera */
1425 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1426 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1429 for (ii = im->gdes[vidx].ds;
1430 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1431 if (!finite(im->gdes[vidx].data[ii]))
1433 if (isnan(printval)) {
1434 printval = im->gdes[vidx].data[ii];
1439 switch (im->gdes[i].cf) {
1442 case CF_DEVSEASONAL:
1446 printval += im->gdes[vidx].data[ii];
1449 printval = min(printval, im->gdes[vidx].data[ii]);
1453 printval = max(printval, im->gdes[vidx].data[ii]);
1456 printval = im->gdes[vidx].data[ii];
1459 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1460 if (validsteps > 1) {
1461 printval = (printval / validsteps);
1464 } /* prepare printval */
1466 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1467 /* Magfact is set to -1 upon entry to print_calc. If it
1468 * is still less than 0, then we need to run auto_scale.
1469 * Otherwise, put the value into the correct units. If
1470 * the value is 0, then do not set the symbol or magnification
1471 * so next the calculation will be performed again. */
1472 if (magfact < 0.0) {
1473 auto_scale(im, &printval, &si_symb, &magfact);
1474 if (printval == 0.0)
1477 printval /= magfact;
1479 *(++percent_s) = 's';
1480 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1481 auto_scale(im, &printval, &si_symb, &magfact);
1484 if (im->gdes[i].gf == GF_PRINT) {
1485 (*prdata)[prlines - 2] =
1486 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1487 (*prdata)[prlines - 1] = NULL;
1488 if (im->gdes[i].strftm) {
1489 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1490 im->gdes[i].format, &tmvdef);
1492 if (bad_format(im->gdes[i].format)) {
1493 rrd_set_error("bad format for PRINT in '%s'",
1494 im->gdes[i].format);
1497 #ifdef HAVE_SNPRINTF
1498 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1499 im->gdes[i].format, printval, si_symb);
1501 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1508 if (im->gdes[i].strftm) {
1509 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1510 im->gdes[i].format, &tmvdef);
1512 if (bad_format(im->gdes[i].format)) {
1513 rrd_set_error("bad format for GPRINT in '%s'",
1514 im->gdes[i].format);
1517 #ifdef HAVE_SNPRINTF
1518 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1519 im->gdes[i].format, printval, si_symb);
1521 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1534 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1535 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1540 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1541 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1550 #ifdef WITH_PIECHART
1558 ("STACK should already be turned into LINE or AREA here");
1563 return graphelement;
1567 /* place legends with color spots */
1573 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1574 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1575 int fill = 0, fill_last;
1577 int leg_x = border, leg_y = im->yimg;
1578 int leg_y_prev = im->yimg;
1581 int i, ii, mark = 0;
1582 char prt_fctn; /*special printfunctions */
1583 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1586 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1587 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1588 rrd_set_error("malloc for legspace");
1592 if (im->extra_flags & FULL_SIZE_MODE)
1593 leg_y = leg_y_prev =
1594 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1596 for (i = 0; i < im->gdes_c; i++) {
1599 /* hide legends for rules which are not displayed */
1601 if (im->gdes[i].gf == GF_TEXTALIGN) {
1602 default_txtalign = im->gdes[i].txtalign;
1605 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1606 if (im->gdes[i].gf == GF_HRULE &&
1607 (im->gdes[i].yrule < im->minval
1608 || im->gdes[i].yrule > im->maxval))
1609 im->gdes[i].legend[0] = '\0';
1611 if (im->gdes[i].gf == GF_VRULE &&
1612 (im->gdes[i].xrule < im->start
1613 || im->gdes[i].xrule > im->end))
1614 im->gdes[i].legend[0] = '\0';
1617 leg_cc = strlen(im->gdes[i].legend);
1619 /* is there a controle code ant the end of the legend string ? */
1620 /* and it is not a tab \\t */
1621 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1622 && im->gdes[i].legend[leg_cc - 1] != 't') {
1623 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1625 im->gdes[i].legend[leg_cc] = '\0';
1629 /* only valid control codes */
1630 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1635 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1637 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1638 im->gdes[i].legend, prt_fctn);
1643 if (prt_fctn == 'n') {
1647 /* remove exess space from the end of the legend for \g */
1648 while (prt_fctn == 'g' &&
1649 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1651 im->gdes[i].legend[leg_cc] = '\0';
1656 /* no interleg space if string ends in \g */
1657 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1660 fill += legspace[i];
1662 fill += gfx_get_text_width(im, fill + border,
1663 im->text_prop[TEXT_PROP_LEGEND].
1665 im->text_prop[TEXT_PROP_LEGEND].
1667 im->gdes[i].legend);
1672 /* who said there was a special tag ... ? */
1673 if (prt_fctn == 'g') {
1677 if (prt_fctn == '\0') {
1678 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1679 /* just one legend item is left right or center */
1680 switch (default_txtalign) {
1695 /* is it time to place the legends ? */
1696 if (fill > im->ximg - 2 * border) {
1704 if (leg_c == 1 && prt_fctn == 'j') {
1710 if (prt_fctn != '\0') {
1712 if (leg_c >= 2 && prt_fctn == 'j') {
1713 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1717 if (prt_fctn == 'c')
1718 leg_x = (im->ximg - fill) / 2.0;
1719 if (prt_fctn == 'r')
1720 leg_x = im->ximg - fill - border;
1722 for (ii = mark; ii <= i; ii++) {
1723 if (im->gdes[ii].legend[0] == '\0')
1724 continue; /* skip empty legends */
1725 im->gdes[ii].leg_x = leg_x;
1726 im->gdes[ii].leg_y = leg_y;
1728 gfx_get_text_width(im, leg_x,
1729 im->text_prop[TEXT_PROP_LEGEND].
1731 im->text_prop[TEXT_PROP_LEGEND].
1733 im->gdes[ii].legend)
1738 if (im->extra_flags & FULL_SIZE_MODE) {
1739 /* only add y space if there was text on the line */
1740 if (leg_x > border || prt_fctn == 's')
1741 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1742 if (prt_fctn == 's')
1743 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1745 if (leg_x > border || prt_fctn == 's')
1746 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1747 if (prt_fctn == 's')
1748 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1756 if (im->extra_flags & FULL_SIZE_MODE) {
1757 if (leg_y != leg_y_prev) {
1758 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1760 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1763 im->yimg = leg_y_prev;
1764 /* if we did place some legends we have to add vertical space */
1765 if (leg_y != im->yimg)
1766 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1773 /* create a grid on the graph. it determines what to do
1774 from the values of xsize, start and end */
1776 /* the xaxis labels are determined from the number of seconds per pixel
1777 in the requested graph */
1781 int calc_horizontal_grid(
1788 int decimals, fractionals;
1790 im->ygrid_scale.labfact = 2;
1791 range = im->maxval - im->minval;
1792 scaledrange = range / im->magfact;
1794 /* does the scale of this graph make it impossible to put lines
1795 on it? If so, give up. */
1796 if (isnan(scaledrange)) {
1800 /* find grid spaceing */
1802 if (isnan(im->ygridstep)) {
1803 if (im->extra_flags & ALTYGRID) {
1804 /* find the value with max number of digits. Get number of digits */
1807 (max(fabs(im->maxval), fabs(im->minval)) *
1808 im->viewfactor / im->magfact));
1809 if (decimals <= 0) /* everything is small. make place for zero */
1812 im->ygrid_scale.gridstep =
1814 floor(log10(range * im->viewfactor / im->magfact))) /
1815 im->viewfactor * im->magfact;
1817 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1818 im->ygrid_scale.gridstep = 0.1;
1819 /* should have at least 5 lines but no more then 15 */
1820 if (range / im->ygrid_scale.gridstep < 5)
1821 im->ygrid_scale.gridstep /= 10;
1822 if (range / im->ygrid_scale.gridstep > 15)
1823 im->ygrid_scale.gridstep *= 10;
1824 if (range / im->ygrid_scale.gridstep > 5) {
1825 im->ygrid_scale.labfact = 1;
1826 if (range / im->ygrid_scale.gridstep > 8)
1827 im->ygrid_scale.labfact = 2;
1829 im->ygrid_scale.gridstep /= 5;
1830 im->ygrid_scale.labfact = 5;
1834 (im->ygrid_scale.gridstep *
1835 (double) im->ygrid_scale.labfact * im->viewfactor /
1837 if (fractionals < 0) { /* small amplitude. */
1838 int len = decimals - fractionals + 1;
1840 if (im->unitslength < len + 2)
1841 im->unitslength = len + 2;
1842 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1843 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1845 int len = decimals + 1;
1847 if (im->unitslength < len + 2)
1848 im->unitslength = len + 2;
1849 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1850 (im->symbol != ' ' ? " %c" : ""));
1853 for (i = 0; ylab[i].grid > 0; i++) {
1854 pixel = im->ysize / (scaledrange / ylab[i].grid);
1860 for (i = 0; i < 4; i++) {
1861 if (pixel * ylab[gridind].lfac[i] >=
1862 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1863 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1868 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1871 im->ygrid_scale.gridstep = im->ygridstep;
1872 im->ygrid_scale.labfact = im->ylabfact;
1877 int draw_horizontal_grid(
1882 char graph_label[100];
1884 double X0 = im->xorigin;
1885 double X1 = im->xorigin + im->xsize;
1887 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1888 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1892 im->ygrid_scale.gridstep / (double) im->magfact *
1893 (double) im->viewfactor;
1894 MaxY = scaledstep * (double) egrid;
1895 for (i = sgrid; i <= egrid; i++) {
1896 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1897 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1899 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1900 && floor(Y0 + 0.5) <= im->yorigin) {
1901 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1902 with the chosen settings. Add a label if required by settings, or if
1903 there is only one label so far and the next grid line is out of bounds. */
1904 if (i % im->ygrid_scale.labfact == 0
1906 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1907 if (im->symbol == ' ') {
1908 if (im->extra_flags & ALTYGRID) {
1909 sprintf(graph_label, im->ygrid_scale.labfmt,
1910 scaledstep * (double) i);
1913 sprintf(graph_label, "%4.1f",
1914 scaledstep * (double) i);
1916 sprintf(graph_label, "%4.0f",
1917 scaledstep * (double) i);
1921 char sisym = (i == 0 ? ' ' : im->symbol);
1923 if (im->extra_flags & ALTYGRID) {
1924 sprintf(graph_label, im->ygrid_scale.labfmt,
1925 scaledstep * (double) i, sisym);
1928 sprintf(graph_label, "%4.1f %c",
1929 scaledstep * (double) i, sisym);
1931 sprintf(graph_label, "%4.0f %c",
1932 scaledstep * (double) i, sisym);
1939 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1940 im->graph_col[GRC_FONT],
1941 im->text_prop[TEXT_PROP_AXIS].font,
1942 im->text_prop[TEXT_PROP_AXIS].size,
1943 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1947 X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1950 X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1954 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1955 im->grid_dash_on, im->grid_dash_off);
1957 } else if (!(im->extra_flags & NOMINOR)) {
1960 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1963 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1967 GRIDWIDTH, im->graph_col[GRC_GRID],
1968 im->grid_dash_on, im->grid_dash_off);
1976 /* this is frexp for base 10 */
1987 iexp = floor(log(fabs(x)) / log(10));
1988 mnt = x / pow(10.0, iexp);
1991 mnt = x / pow(10.0, iexp);
1997 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1998 /* yes we are loosing precision by doing tos with floats instead of doubles
1999 but it seems more stable this way. */
2001 static int AlmostEqual2sComplement(
2007 int aInt = *(int *) &A;
2008 int bInt = *(int *) &B;
2011 /* Make sure maxUlps is non-negative and small enough that the
2012 default NAN won't compare as equal to anything. */
2014 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2016 /* Make aInt lexicographically ordered as a twos-complement int */
2019 aInt = 0x80000000l - aInt;
2021 /* Make bInt lexicographically ordered as a twos-complement int */
2024 bInt = 0x80000000l - bInt;
2026 intDiff = abs(aInt - bInt);
2028 if (intDiff <= maxUlps)
2034 /* logaritmic horizontal grid */
2035 int horizontal_log_grid(
2038 double yloglab[][10] = {
2039 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2040 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2041 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2042 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2043 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2044 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2047 int i, j, val_exp, min_exp;
2048 double nex; /* number of decades in data */
2049 double logscale; /* scale in logarithmic space */
2050 int exfrac = 1; /* decade spacing */
2051 int mid = -1; /* row in yloglab for major grid */
2052 double mspac; /* smallest major grid spacing (pixels) */
2053 int flab; /* first value in yloglab to use */
2054 double value, tmp, pre_value;
2056 char graph_label[100];
2058 nex = log10(im->maxval / im->minval);
2059 logscale = im->ysize / nex;
2061 /* major spacing for data with high dynamic range */
2062 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2069 /* major spacing for less dynamic data */
2071 /* search best row in yloglab */
2073 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2074 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2075 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2076 && yloglab[mid][0] > 0);
2080 /* find first value in yloglab */
2082 yloglab[mid][flab] < 10
2083 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2084 if (yloglab[mid][flab] == 10.0) {
2089 if (val_exp % exfrac)
2090 val_exp += abs(-val_exp % exfrac);
2093 X1 = im->xorigin + im->xsize;
2099 value = yloglab[mid][flab] * pow(10.0, val_exp);
2100 if (AlmostEqual2sComplement(value, pre_value, 4))
2101 break; /* it seems we are not converging */
2105 Y0 = ytr(im, value);
2106 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2109 /* major grid line */
2112 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2114 X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2120 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2121 im->grid_dash_on, im->grid_dash_off);
2124 if (im->extra_flags & FORCE_UNITS_SI) {
2129 scale = floor(val_exp / 3.0);
2131 pvalue = pow(10.0, val_exp % 3);
2133 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2134 pvalue *= yloglab[mid][flab];
2136 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2137 ((scale + si_symbcenter) >= 0))
2138 symbol = si_symbol[scale + si_symbcenter];
2142 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2144 sprintf(graph_label, "%3.0e", value);
2146 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2147 im->graph_col[GRC_FONT],
2148 im->text_prop[TEXT_PROP_AXIS].font,
2149 im->text_prop[TEXT_PROP_AXIS].size,
2150 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2153 if (mid < 4 && exfrac == 1) {
2154 /* find first and last minor line behind current major line
2155 * i is the first line and j tha last */
2157 min_exp = val_exp - 1;
2158 for (i = 1; yloglab[mid][i] < 10.0; i++);
2159 i = yloglab[mid][i - 1] + 1;
2163 i = yloglab[mid][flab - 1] + 1;
2164 j = yloglab[mid][flab];
2167 /* draw minor lines below current major line */
2168 for (; i < j; i++) {
2170 value = i * pow(10.0, min_exp);
2171 if (value < im->minval)
2174 Y0 = ytr(im, value);
2175 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2181 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2184 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2188 GRIDWIDTH, im->graph_col[GRC_GRID],
2189 im->grid_dash_on, im->grid_dash_off);
2191 } else if (exfrac > 1) {
2192 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2193 value = pow(10.0, i);
2194 if (value < im->minval)
2197 Y0 = ytr(im, value);
2198 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2204 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2207 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2211 GRIDWIDTH, im->graph_col[GRC_GRID],
2212 im->grid_dash_on, im->grid_dash_off);
2217 if (yloglab[mid][++flab] == 10.0) {
2223 /* draw minor lines after highest major line */
2224 if (mid < 4 && exfrac == 1) {
2225 /* find first and last minor line below current major line
2226 * i is the first line and j tha last */
2228 min_exp = val_exp - 1;
2229 for (i = 1; yloglab[mid][i] < 10.0; i++);
2230 i = yloglab[mid][i - 1] + 1;
2234 i = yloglab[mid][flab - 1] + 1;
2235 j = yloglab[mid][flab];
2238 /* draw minor lines below current major line */
2239 for (; i < j; i++) {
2241 value = i * pow(10.0, min_exp);
2242 if (value < im->minval)
2245 Y0 = ytr(im, value);
2246 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2251 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2253 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2257 GRIDWIDTH, im->graph_col[GRC_GRID],
2258 im->grid_dash_on, im->grid_dash_off);
2261 /* fancy minor gridlines */
2262 else if (exfrac > 1) {
2263 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2264 value = pow(10.0, i);
2265 if (value < im->minval)
2268 Y0 = ytr(im, value);
2269 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2274 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2276 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2280 GRIDWIDTH, im->graph_col[GRC_GRID],
2281 im->grid_dash_on, im->grid_dash_off);
2292 int xlab_sel; /* which sort of label and grid ? */
2293 time_t ti, tilab, timajor;
2295 char graph_label[100];
2296 double X0, Y0, Y1; /* points for filled graph and more */
2299 /* the type of time grid is determined by finding
2300 the number of seconds per pixel in the graph */
2303 if (im->xlab_user.minsec == -1) {
2304 factor = (im->end - im->start) / im->xsize;
2306 while (xlab[xlab_sel + 1].minsec != -1
2307 && xlab[xlab_sel + 1].minsec <= factor) {
2309 } /* pick the last one */
2310 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2311 && xlab[xlab_sel].length > (im->end - im->start)) {
2313 } /* go back to the smallest size */
2314 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2315 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2316 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2317 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2318 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2319 im->xlab_user.labst = xlab[xlab_sel].labst;
2320 im->xlab_user.precis = xlab[xlab_sel].precis;
2321 im->xlab_user.stst = xlab[xlab_sel].stst;
2324 /* y coords are the same for every line ... */
2326 Y1 = im->yorigin - im->ysize;
2329 /* paint the minor grid */
2330 if (!(im->extra_flags & NOMINOR)) {
2331 for (ti = find_first_time(im->start,
2332 im->xlab_user.gridtm,
2333 im->xlab_user.gridst),
2334 timajor = find_first_time(im->start,
2335 im->xlab_user.mgridtm,
2336 im->xlab_user.mgridst);
2339 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2341 /* are we inside the graph ? */
2342 if (ti < im->start || ti > im->end)
2344 while (timajor < ti) {
2345 timajor = find_next_time(timajor,
2346 im->xlab_user.mgridtm,
2347 im->xlab_user.mgridst);
2350 continue; /* skip as falls on major grid line */
2352 gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2353 im->graph_col[GRC_GRID]);
2354 gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2355 im->graph_col[GRC_GRID]);
2356 gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2357 im->graph_col[GRC_GRID],
2358 im->grid_dash_on, im->grid_dash_off);
2363 /* paint the major grid */
2364 for (ti = find_first_time(im->start,
2365 im->xlab_user.mgridtm,
2366 im->xlab_user.mgridst);
2368 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2370 /* are we inside the graph ? */
2371 if (ti < im->start || ti > im->end)
2374 gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2375 im->graph_col[GRC_MGRID]);
2376 gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2377 im->graph_col[GRC_MGRID]);
2378 gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2379 im->graph_col[GRC_MGRID],
2380 im->grid_dash_on, im->grid_dash_off);
2383 /* paint the labels below the graph */
2384 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2385 im->xlab_user.labtm,
2386 im->xlab_user.labst);
2387 ti <= im->end - im->xlab_user.precis / 2;
2388 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2390 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2391 /* are we inside the graph ? */
2392 if (tilab < im->start || tilab > im->end)
2396 localtime_r(&tilab, &tm);
2397 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2399 # error "your libc has no strftime I guess we'll abort the exercise here."
2404 im->graph_col[GRC_FONT],
2405 im->text_prop[TEXT_PROP_AXIS].font,
2406 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2407 GFX_H_CENTER, GFX_V_TOP, graph_label);
2417 /* draw x and y axis */
2418 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2419 im->xorigin+im->xsize,im->yorigin-im->ysize,
2420 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2422 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2423 im->xorigin+im->xsize,im->yorigin-im->ysize,
2424 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2426 gfx_line(im, im->xorigin - 4, im->yorigin,
2427 im->xorigin + im->xsize + 4, im->yorigin,
2428 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2430 gfx_line(im, im->xorigin, im->yorigin + 4,
2431 im->xorigin, im->yorigin - im->ysize - 4,
2432 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2435 /* arrow for X and Y axis direction */
2437 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 */
2438 im->graph_col[GRC_ARROW]);
2441 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 */
2442 im->graph_col[GRC_ARROW]);
2453 double X0, Y0; /* points for filled graph and more */
2454 struct gfx_color_t water_color;
2456 /* draw 3d border */
2457 gfx_new_area(im, 0, im->yimg,
2458 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2459 gfx_add_point(im, im->ximg - 2, 2);
2460 gfx_add_point(im, im->ximg, 0);
2461 gfx_add_point(im, 0, 0);
2464 gfx_new_area(im, 2, im->yimg - 2,
2465 im->ximg - 2, im->yimg - 2,
2466 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2467 gfx_add_point(im, im->ximg, 0);
2468 gfx_add_point(im, im->ximg, im->yimg);
2469 gfx_add_point(im, 0, im->yimg);
2473 if (im->draw_x_grid == 1)
2476 if (im->draw_y_grid == 1) {
2477 if (im->logarithmic) {
2478 res = horizontal_log_grid(im);
2480 res = draw_horizontal_grid(im);
2483 /* dont draw horizontal grid if there is no min and max val */
2485 char *nodata = "No Data found";
2487 gfx_text(im, im->ximg / 2,
2488 (2 * im->yorigin - im->ysize) / 2,
2489 im->graph_col[GRC_FONT],
2490 im->text_prop[TEXT_PROP_AXIS].font,
2491 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2492 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2496 /* yaxis unit description */
2498 10, (im->yorigin - im->ysize / 2),
2499 im->graph_col[GRC_FONT],
2500 im->text_prop[TEXT_PROP_UNIT].font,
2501 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2502 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2507 im->graph_col[GRC_FONT],
2508 im->text_prop[TEXT_PROP_TITLE].font,
2509 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2510 GFX_H_CENTER, GFX_V_TOP, im->title);
2511 /* rrdtool 'logo' */
2512 water_color = im->graph_col[GRC_FONT];
2513 water_color.alpha = 0.3;
2517 im->text_prop[TEXT_PROP_AXIS].font,
2518 5.5, im->tabwidth, -90,
2519 GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2521 /* graph watermark */
2522 if (im->watermark[0] != '\0') {
2524 im->ximg / 2, im->yimg - 6,
2526 im->text_prop[TEXT_PROP_AXIS].font,
2527 5.5, im->tabwidth, 0,
2528 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2532 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2533 for (i = 0; i < im->gdes_c; i++) {
2534 if (im->gdes[i].legend[0] == '\0')
2537 /* im->gdes[i].leg_y is the bottom of the legend */
2538 X0 = im->gdes[i].leg_x;
2539 Y0 = im->gdes[i].leg_y;
2540 gfx_text(im, X0, Y0,
2541 im->graph_col[GRC_FONT],
2542 im->text_prop[TEXT_PROP_LEGEND].font,
2543 im->text_prop[TEXT_PROP_LEGEND].size,
2544 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2545 im->gdes[i].legend);
2546 /* The legend for GRAPH items starts with "M " to have
2547 enough space for the box */
2548 if (im->gdes[i].gf != GF_PRINT &&
2549 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2554 boxH = gfx_get_text_width(im, 0,
2555 im->text_prop[TEXT_PROP_LEGEND].
2557 im->text_prop[TEXT_PROP_LEGEND].
2558 size, im->tabwidth, "o") * 1.2;
2561 /* shift the box up a bit */
2564 /* make sure transparent colors show up the same way as in the graph */
2568 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2569 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2574 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2575 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2579 cairo_new_path(im->cr);
2580 cairo_set_line_width(im->cr, 1.0);
2583 gfx_line_fit(im, &X0, &Y0);
2584 gfx_line_fit(im, &X1, &Y1);
2585 cairo_move_to(im->cr, X0, Y0);
2586 cairo_line_to(im->cr, X1, Y0);
2587 cairo_line_to(im->cr, X1, Y1);
2588 cairo_line_to(im->cr, X0, Y1);
2589 cairo_close_path(im->cr);
2590 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2591 im->graph_col[GRC_FRAME].green,
2592 im->graph_col[GRC_FRAME].blue,
2593 im->graph_col[GRC_FRAME].alpha);
2594 cairo_stroke(im->cr);
2595 cairo_restore(im->cr);
2602 /*****************************************************
2603 * lazy check make sure we rely need to create this graph
2604 *****************************************************/
2611 struct stat imgstat;
2614 return 0; /* no lazy option */
2615 if (stat(im->graphfile, &imgstat) != 0)
2616 return 0; /* can't stat */
2617 /* one pixel in the existing graph is more then what we would
2619 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2621 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2622 return 0; /* the file does not exist */
2623 switch (im->imgformat) {
2625 size = PngSize(fd, &(im->ximg), &(im->yimg));
2635 int graph_size_location(
2639 /* The actual size of the image to draw is determined from
2640 ** several sources. The size given on the command line is
2641 ** the graph area but we need more as we have to draw labels
2642 ** and other things outside the graph area
2645 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2646 Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2648 if (im->extra_flags & ONLY_GRAPH) {
2650 im->ximg = im->xsize;
2651 im->yimg = im->ysize;
2652 im->yorigin = im->ysize;
2657 /** +---+--------------------------------------------+
2658 ** | y |...............graph title..................|
2659 ** | +---+-------------------------------+--------+
2662 ** | i | a | | pie |
2663 ** | s | x | main graph area | chart |
2668 ** | l | b +-------------------------------+--------+
2669 ** | e | l | x axis labels | |
2670 ** +---+---+-------------------------------+--------+
2671 ** |....................legends.....................|
2672 ** +------------------------------------------------+
2674 ** +------------------------------------------------+
2677 if (im->ylegend[0] != '\0') {
2678 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2681 if (im->title[0] != '\0') {
2682 /* The title is placed "inbetween" two text lines so it
2683 ** automatically has some vertical spacing. The horizontal
2684 ** spacing is added here, on each side.
2686 /* if necessary, reduce the font size of the title until it fits the image width */
2687 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2691 if (im->draw_x_grid) {
2692 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2694 if (im->draw_y_grid || im->forceleftspace) {
2695 Xylabel = gfx_get_text_width(im, 0,
2696 im->text_prop[TEXT_PROP_AXIS].font,
2697 im->text_prop[TEXT_PROP_AXIS].size,
2698 im->tabwidth, "0") * im->unitslength;
2702 if (im->extra_flags & FULL_SIZE_MODE) {
2703 /* The actual size of the image to draw has been determined by the user.
2704 ** The graph area is the space remaining after accounting for the legend,
2705 ** the watermark, the pie chart, the axis labels, and the title.
2708 im->ximg = im->xsize;
2709 im->yimg = im->ysize;
2710 im->yorigin = im->ysize;
2714 im->yorigin += Ytitle;
2716 /* Now calculate the total size. Insert some spacing where
2717 desired. im->xorigin and im->yorigin need to correspond
2718 with the lower left corner of the main graph area or, if
2719 this one is not set, the imaginary box surrounding the
2722 /* Initial size calculation for the main graph area */
2723 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2725 Xmain -= Xspacing; /* put space between main graph area and right edge */
2727 im->xorigin = Xspacing + Xylabel;
2729 /* the length of the title should not influence with width of the graph
2730 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2732 if (Xvertical) { /* unit description */
2734 im->xorigin += Xvertical;
2739 /* The vertical size of the image is known in advance. The main graph area
2740 ** (Ymain) and im->yorigin must be set according to the space requirements
2741 ** of the legend and the axis labels.
2744 if (im->extra_flags & NOLEGEND) {
2745 /* set dimensions correctly if using full size mode with no legend */
2747 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2749 Ymain = im->yorigin;
2751 /* Determine where to place the legends onto the image.
2752 ** Set Ymain and adjust im->yorigin to match the space requirements.
2754 if (leg_place(im, &Ymain) == -1)
2759 /* remove title space *or* some padding above the graph from the main graph area */
2763 Ymain -= 1.5 * Yspacing;
2766 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2767 if (im->watermark[0] != '\0') {
2768 Ymain -= Ywatermark;
2773 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2775 /* The actual size of the image to draw is determined from
2776 ** several sources. The size given on the command line is
2777 ** the graph area but we need more as we have to draw labels
2778 ** and other things outside the graph area.
2781 if (im->ylegend[0] != '\0') {
2782 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2786 if (im->title[0] != '\0') {
2787 /* The title is placed "inbetween" two text lines so it
2788 ** automatically has some vertical spacing. The horizontal
2789 ** spacing is added here, on each side.
2791 /* don't care for the with of the title
2792 Xtitle = gfx_get_text_width(im->canvas, 0,
2793 im->text_prop[TEXT_PROP_TITLE].font,
2794 im->text_prop[TEXT_PROP_TITLE].size,
2796 im->title, 0) + 2*Xspacing; */
2797 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2804 /* Now calculate the total size. Insert some spacing where
2805 desired. im->xorigin and im->yorigin need to correspond
2806 with the lower left corner of the main graph area or, if
2807 this one is not set, the imaginary box surrounding the
2810 /* The legend width cannot yet be determined, as a result we
2811 ** have problems adjusting the image to it. For now, we just
2812 ** forget about it at all; the legend will have to fit in the
2813 ** size already allocated.
2815 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2818 im->ximg += Xspacing;
2820 im->xorigin = Xspacing + Xylabel;
2822 /* the length of the title should not influence with width of the graph
2823 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2825 if (Xvertical) { /* unit description */
2826 im->ximg += Xvertical;
2827 im->xorigin += Xvertical;
2831 /* The vertical size is interesting... we need to compare
2832 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2833 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2834 ** in order to start even thinking about Ylegend or Ywatermark.
2836 ** Do it in three portions: First calculate the inner part,
2837 ** then do the legend, then adjust the total height of the img,
2838 ** adding space for a watermark if one exists;
2841 /* reserve space for main and/or pie */
2843 im->yimg = Ymain + Yxlabel;
2846 im->yorigin = im->yimg - Yxlabel;
2848 /* reserve space for the title *or* some padding above the graph */
2851 im->yorigin += Ytitle;
2853 im->yimg += 1.5 * Yspacing;
2854 im->yorigin += 1.5 * Yspacing;
2856 /* reserve space for padding below the graph */
2857 im->yimg += Yspacing;
2859 /* Determine where to place the legends onto the image.
2860 ** Adjust im->yimg to match the space requirements.
2862 if (leg_place(im, 0) == -1)
2865 if (im->watermark[0] != '\0') {
2866 im->yimg += Ywatermark;
2876 static cairo_status_t cairo_write_func_filehandle(
2878 const unsigned char *data,
2879 unsigned int length)
2881 if (fwrite(data, length, 1, closure) != 1)
2882 return CAIRO_STATUS_WRITE_ERROR;
2883 return CAIRO_STATUS_SUCCESS;
2886 static cairo_status_t cairo_copy_to_buffer(
2888 const unsigned char *data,
2889 unsigned int length)
2891 image_desc_t *im = closure;
2893 im->rendered_image =
2894 realloc(im->rendered_image, im->rendered_image_size + length);
2895 if (im->rendered_image == NULL) {
2896 return CAIRO_STATUS_WRITE_ERROR;
2899 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2901 im->rendered_image_size += length;
2903 return CAIRO_STATUS_SUCCESS;
2906 /* draw that picture thing ... */
2912 int lazy = lazy_check(im);
2914 double areazero = 0.0;
2915 graph_desc_t *lastgdes = NULL;
2917 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2920 /* if we are lazy and there is nothing to PRINT ... quit now */
2921 if (lazy && im->prt_c == 0)
2924 /* pull the data from the rrd files ... */
2926 if (data_fetch(im) == -1)
2929 /* evaluate VDEF and CDEF operations ... */
2930 if (data_calc(im) == -1)
2934 /* calculate and PRINT and GPRINT definitions. We have to do it at
2935 * this point because it will affect the length of the legends
2936 * if there are no graph elements we stop here ...
2937 * if we are lazy, try to quit ...
2939 i = print_calc(im, calcpr);
2942 if ((i == 0) || lazy)
2945 /**************************************************************
2946 *** Calculating sizes and locations became a bit confusing ***
2947 *** so I moved this into a separate function. ***
2948 **************************************************************/
2949 if (graph_size_location(im, i) == -1)
2952 /* get actual drawing data and find min and max values */
2953 if (data_proc(im) == -1)
2956 if (!im->logarithmic) {
2959 /* identify si magnitude Kilo, Mega Giga ? */
2960 if (!im->rigid && !im->logarithmic)
2961 expand_range(im); /* make sure the upper and lower limit are
2964 if (!calc_horizontal_grid(im))
2971 apply_gridfit(im); */
2974 /* the actual graph is created by going through the individual
2975 graph elements and then drawing them */
2976 cairo_surface_destroy(im->surface);
2978 switch (im->imgformat) {
2981 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2982 im->ximg * im->zoom,
2983 im->yimg * im->zoom);
2987 im->surface = strlen(im->graphfile)
2988 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2989 im->yimg * im->zoom)
2990 : cairo_pdf_surface_create_for_stream(&cairo_copy_to_buffer, im,
2991 im->ximg * im->zoom,
2992 im->yimg * im->zoom);
2996 im->surface = strlen(im->graphfile)
2997 ? cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
2998 im->yimg * im->zoom)
2999 : cairo_ps_surface_create_for_stream(&cairo_copy_to_buffer, im,
3000 im->ximg * im->zoom,
3001 im->yimg * im->zoom);
3005 im->surface = strlen(im->graphfile)
3006 ? cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
3007 im->yimg * im->zoom)
3008 : cairo_svg_surface_create_for_stream(&cairo_copy_to_buffer, im,
3009 im->ximg * im->zoom,
3010 im->yimg * im->zoom);
3011 cairo_svg_surface_restrict_to_version(im->surface,
3012 CAIRO_SVG_VERSION_1_1);
3015 im->cr = cairo_create(im->surface);
3016 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3017 cairo_set_antialias(im->cr, im->graph_antialias);
3018 cairo_scale(im->cr, im->zoom, im->zoom);
3022 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3024 gfx_add_point(im, im->ximg, 0);
3028 im->xorigin, im->yorigin,
3029 im->xorigin + im->xsize, im->yorigin,
3030 im->xorigin + im->xsize, im->yorigin - im->ysize,
3031 im->graph_col[GRC_CANVAS]);
3033 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3036 if (im->minval > 0.0)
3037 areazero = im->minval;
3038 if (im->maxval < 0.0)
3039 areazero = im->maxval;
3041 for (i = 0; i < im->gdes_c; i++) {
3042 switch (im->gdes[i].gf) {
3056 for (ii = 0; ii < im->xsize; ii++) {
3057 if (!isnan(im->gdes[i].p_data[ii]) &&
3058 im->gdes[i].p_data[ii] != 0.0) {
3059 if (im->gdes[i].yrule > 0) {
3061 im->xorigin + ii, im->yorigin,
3064 im->gdes[i].yrule * im->ysize, 1.0,
3066 } else if (im->gdes[i].yrule < 0) {
3069 im->yorigin - im->ysize,
3072 im->gdes[i].yrule) *
3073 im->ysize, 1.0, im->gdes[i].col);
3081 /* fix data points at oo and -oo */
3082 for (ii = 0; ii < im->xsize; ii++) {
3083 if (isinf(im->gdes[i].p_data[ii])) {
3084 if (im->gdes[i].p_data[ii] > 0) {
3085 im->gdes[i].p_data[ii] = im->maxval;
3087 im->gdes[i].p_data[ii] = im->minval;
3093 /* *******************************************************
3098 -------|--t-1--t--------------------------------
3100 if we know the value at time t was a then
3101 we draw a square from t-1 to t with the value a.
3103 ********************************************************* */
3104 if (im->gdes[i].col.alpha != 0.0) {
3105 /* GF_LINE and friend */
3106 if (im->gdes[i].gf == GF_LINE) {
3107 double last_y = 0.0;
3111 cairo_new_path(im->cr);
3113 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3114 for (ii = 1; ii < im->xsize; ii++) {
3115 if (isnan(im->gdes[i].p_data[ii])
3116 || (im->slopemode == 1
3117 && isnan(im->gdes[i].p_data[ii - 1]))) {
3122 last_y = ytr(im, im->gdes[i].p_data[ii]);
3123 if (im->slopemode == 0) {
3124 double x = ii - 1 + im->xorigin;
3127 gfx_line_fit(im, &x, &y);
3128 cairo_move_to(im->cr, x, y);
3129 x = ii + im->xorigin;
3131 gfx_line_fit(im, &x, &y);
3132 cairo_line_to(im->cr, x, y);
3134 double x = ii - 1 + im->xorigin;
3136 im->gdes[i].p_data[ii - 1]);
3138 gfx_line_fit(im, &x, &y);
3139 cairo_move_to(im->cr, x, y);
3140 x = ii + im->xorigin;
3142 gfx_line_fit(im, &x, &y);
3143 cairo_line_to(im->cr, x, y);
3147 double x1 = ii + im->xorigin;
3148 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3150 if (im->slopemode == 0
3151 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3152 double x = ii - 1 + im->xorigin;
3155 gfx_line_fit(im, &x, &y);
3156 cairo_line_to(im->cr, x, y);
3159 gfx_line_fit(im, &x1, &y1);
3160 cairo_line_to(im->cr, x1, y1);
3164 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3165 im->gdes[i].col.green,
3166 im->gdes[i].col.blue,
3167 im->gdes[i].col.alpha);
3168 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3169 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3170 cairo_stroke(im->cr);
3171 cairo_restore(im->cr);
3174 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3175 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3176 double *backY = malloc(sizeof(double) * im->xsize * 2);
3177 double *backX = malloc(sizeof(double) * im->xsize * 2);
3180 for (ii = 0; ii <= im->xsize; ii++) {
3183 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3188 && AlmostEqual2sComplement(foreY[lastI],
3190 && AlmostEqual2sComplement(foreY[lastI],
3198 foreX[cntI], foreY[cntI],
3200 while (cntI < idxI) {
3205 AlmostEqual2sComplement(foreY[lastI],
3208 AlmostEqual2sComplement(foreY[lastI],
3213 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3215 gfx_add_point(im, backX[idxI], backY[idxI]);
3221 AlmostEqual2sComplement(backY[lastI],
3224 AlmostEqual2sComplement(backY[lastI],
3229 gfx_add_point(im, backX[idxI], backY[idxI]);
3239 if (ii == im->xsize)
3242 if (im->slopemode == 0 && ii == 0) {
3245 if (isnan(im->gdes[i].p_data[ii])) {
3249 ytop = ytr(im, im->gdes[i].p_data[ii]);
3250 if (lastgdes && im->gdes[i].stack) {
3251 ybase = ytr(im, lastgdes->p_data[ii]);
3253 ybase = ytr(im, areazero);
3255 if (ybase == ytop) {
3261 double extra = ytop;
3266 if (im->slopemode == 0) {
3267 backY[++idxI] = ybase - 0.2;
3268 backX[idxI] = ii + im->xorigin - 1;
3269 foreY[idxI] = ytop + 0.2;
3270 foreX[idxI] = ii + im->xorigin - 1;
3272 backY[++idxI] = ybase - 0.2;
3273 backX[idxI] = ii + im->xorigin;
3274 foreY[idxI] = ytop + 0.2;
3275 foreX[idxI] = ii + im->xorigin;
3277 /* close up any remaining area */
3282 } /* else GF_LINE */
3284 /* if color != 0x0 */
3285 /* make sure we do not run into trouble when stacking on NaN */
3286 for (ii = 0; ii < im->xsize; ii++) {
3287 if (isnan(im->gdes[i].p_data[ii])) {
3288 if (lastgdes && (im->gdes[i].stack)) {
3289 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3291 im->gdes[i].p_data[ii] = areazero;
3295 lastgdes = &(im->gdes[i]);
3299 ("STACK should already be turned into LINE or AREA here");
3306 /* grid_paint also does the text */
3307 if (!(im->extra_flags & ONLY_GRAPH))
3311 if (!(im->extra_flags & ONLY_GRAPH))
3314 /* the RULES are the last thing to paint ... */
3315 for (i = 0; i < im->gdes_c; i++) {
3317 switch (im->gdes[i].gf) {
3319 if (im->gdes[i].yrule >= im->minval
3320 && im->gdes[i].yrule <= im->maxval)
3322 im->xorigin, ytr(im, im->gdes[i].yrule),
3323 im->xorigin + im->xsize, ytr(im,
3325 1.0, im->gdes[i].col);
3328 if (im->gdes[i].xrule >= im->start
3329 && im->gdes[i].xrule <= im->end)
3331 xtr(im, im->gdes[i].xrule), im->yorigin,
3332 xtr(im, im->gdes[i].xrule),
3333 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3341 switch (im->imgformat) {
3344 cairo_status_t status;
3346 if (strlen(im->graphfile) == 0) {
3348 cairo_surface_write_to_png_stream(im->surface,
3349 &cairo_copy_to_buffer, im);
3350 } else if (strcmp(im->graphfile, "-") == 0) {
3352 cairo_surface_write_to_png_stream(im->surface,
3353 &cairo_write_func_filehandle,
3356 status = cairo_surface_write_to_png(im->surface, im->graphfile);
3359 if (status != CAIRO_STATUS_SUCCESS) {
3360 rrd_set_error("Could not save png to '%s'", im->graphfile);
3366 if (strlen(im->graphfile)) {
3367 cairo_show_page(im->cr);
3369 cairo_surface_finish(im->surface);
3377 /*****************************************************
3379 *****************************************************/
3386 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3387 * sizeof(graph_desc_t))) ==
3389 rrd_set_error("realloc graph_descs");
3394 im->gdes[im->gdes_c - 1].step = im->step;
3395 im->gdes[im->gdes_c - 1].step_orig = im->step;
3396 im->gdes[im->gdes_c - 1].stack = 0;
3397 im->gdes[im->gdes_c - 1].linewidth = 0;
3398 im->gdes[im->gdes_c - 1].debug = 0;
3399 im->gdes[im->gdes_c - 1].start = im->start;
3400 im->gdes[im->gdes_c - 1].start_orig = im->start;
3401 im->gdes[im->gdes_c - 1].end = im->end;
3402 im->gdes[im->gdes_c - 1].end_orig = im->end;
3403 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3404 im->gdes[im->gdes_c - 1].data = NULL;
3405 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3406 im->gdes[im->gdes_c - 1].data_first = 0;
3407 im->gdes[im->gdes_c - 1].p_data = NULL;
3408 im->gdes[im->gdes_c - 1].rpnp = NULL;
3409 im->gdes[im->gdes_c - 1].shift = 0.0;
3410 im->gdes[im->gdes_c - 1].col.red = 0.0;
3411 im->gdes[im->gdes_c - 1].col.green = 0.0;
3412 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3413 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3414 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3415 im->gdes[im->gdes_c - 1].format[0] = '\0';
3416 im->gdes[im->gdes_c - 1].strftm = 0;
3417 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3418 im->gdes[im->gdes_c - 1].ds = -1;
3419 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3420 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3421 im->gdes[im->gdes_c - 1].p_data = NULL;
3422 im->gdes[im->gdes_c - 1].yrule = DNAN;
3423 im->gdes[im->gdes_c - 1].xrule = 0;
3427 /* copies input untill the first unescaped colon is found
3428 or until input ends. backslashes have to be escaped as well */
3430 const char *const input,
3436 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3437 if (input[inp] == '\\' &&
3438 input[inp + 1] != '\0' &&
3439 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3440 output[outp++] = input[++inp];
3442 output[outp++] = input[inp];
3445 output[outp] = '\0';
3449 /* Some surgery done on this function, it became ridiculously big.
3451 ** - initializing now in rrd_graph_init()
3452 ** - options parsing now in rrd_graph_options()
3453 ** - script parsing now in rrd_graph_script()
3467 rrd_graph_init(&im);
3469 /* a dummy surface so that we can measure text sizes for placements */
3470 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3471 im.cr = cairo_create(im.surface);
3472 im.graphhandle = stream;
3474 rrd_graph_options(argc, argv, &im);
3475 if (rrd_test_error()) {
3480 if (optind >= argc) {
3481 rrd_set_error("missing filename");
3485 if (strlen(argv[optind]) >= MAXPATH) {
3486 rrd_set_error("filename (including path) too long");
3491 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3492 im.graphfile[MAXPATH - 1] = '\0';
3494 rrd_graph_script(argc, argv, &im, 1);
3495 if (rrd_test_error()) {
3500 /* Everything is now read and the actual work can start */
3503 if (graph_paint(&im, prdata) == -1) {
3508 /* The image is generated and needs to be output.
3509 ** Also, if needed, print a line with information about the image.
3520 /* maybe prdata is not allocated yet ... lets do it now */
3521 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3522 rrd_set_error("malloc imginfo");
3527 malloc((strlen(im.imginfo) + 200 +
3528 strlen(im.graphfile)) * sizeof(char)))
3530 rrd_set_error("malloc imginfo");
3533 filename = im.graphfile + strlen(im.graphfile);
3534 while (filename > im.graphfile) {
3535 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3540 sprintf((*prdata)[0], im.imginfo, filename,
3541 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3547 /* a simplified version of the above that just creates the graph in memory
3548 and returns a pointer to it. */
3550 unsigned char *rrd_graph_in_memory(
3562 rrd_graph_init(&im);
3564 /* a dummy surface so that we can measure text sizes for placements */
3565 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3566 im.cr = cairo_create(im.surface);
3568 rrd_graph_options(argc, argv, &im);
3569 if (rrd_test_error()) {
3574 rrd_graph_script(argc, argv, &im, 1);
3575 if (rrd_test_error()) {
3580 /* Everything is now read and the actual work can start */
3582 /* by not assigning a name to im.graphfile data will be written to
3583 newly allocated memory on im.rendered_image ... */
3586 if (graph_paint(&im, prdata) == -1) {
3595 *img_size = im.rendered_image_size;
3598 return im.rendered_image;
3601 void rrd_graph_init(
3609 #ifdef HAVE_SETLOCALE
3610 setlocale(LC_TIME, "");
3611 #ifdef HAVE_MBSTOWCS
3612 setlocale(LC_CTYPE, "");
3618 im->xlab_user.minsec = -1;
3623 im->rendered_image_size = 0;
3624 im->rendered_image = NULL;
3626 im->ylegend[0] = '\0';
3627 im->title[0] = '\0';
3628 im->watermark[0] = '\0';
3631 im->unitsexponent = 9999;
3632 im->unitslength = 6;
3633 im->forceleftspace = 0;
3635 im->viewfactor = 1.0;
3636 im->imgformat = IF_PNG;
3637 im->graphfile[0] = '\0';
3640 im->extra_flags = 0;
3646 im->logarithmic = 0;
3647 im->ygridstep = DNAN;
3648 im->draw_x_grid = 1;
3649 im->draw_y_grid = 1;
3654 im->grid_dash_on = 1;
3655 im->grid_dash_off = 1;
3656 im->tabwidth = 40.0;
3658 im->font_options = cairo_font_options_create();
3659 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3661 cairo_font_options_set_hint_style(im->font_options,
3662 CAIRO_HINT_STYLE_FULL);
3663 cairo_font_options_set_hint_metrics(im->font_options,
3664 CAIRO_HINT_METRICS_ON);
3665 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3668 for (i = 0; i < DIM(graph_col); i++)
3669 im->graph_col[i] = graph_col[i];
3671 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3674 char rrd_win_default_font[1000];
3676 windir = getenv("windir");
3677 /* %windir% is something like D:\windows or C:\winnt */
3678 if (windir != NULL) {
3679 strncpy(rrd_win_default_font, windir, 500);
3680 rrd_win_default_font[500] = '\0';
3681 strcat(rrd_win_default_font, "\\fonts\\");
3682 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3683 for (i = 0; i < DIM(text_prop); i++) {
3684 strncpy(text_prop[i].font, rrd_win_default_font,
3685 sizeof(text_prop[i].font) - 1);
3686 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3694 deffont = getenv("RRD_DEFAULT_FONT");
3695 if (deffont != NULL) {
3696 for (i = 0; i < DIM(text_prop); i++) {
3697 strncpy(text_prop[i].font, deffont,
3698 sizeof(text_prop[i].font) - 1);
3699 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3703 for (i = 0; i < DIM(text_prop); i++) {
3704 im->text_prop[i].size = text_prop[i].size;
3705 strcpy(im->text_prop[i].font, text_prop[i].font);
3709 void rrd_graph_options(
3715 char *parsetime_error = NULL;
3716 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3717 time_t start_tmp = 0, end_tmp = 0;
3719 struct rrd_time_value start_tv, end_tv;
3720 long unsigned int color;
3723 opterr = 0; /* initialize getopt */
3725 parsetime("end-24h", &start_tv);
3726 parsetime("now", &end_tv);
3728 /* defines for long options without a short equivalent. should be bytes,
3729 and may not collide with (the ASCII value of) short options */
3730 #define LONGOPT_UNITS_SI 255
3733 static struct option long_options[] = {
3734 {"start", required_argument, 0, 's'},
3735 {"end", required_argument, 0, 'e'},
3736 {"x-grid", required_argument, 0, 'x'},
3737 {"y-grid", required_argument, 0, 'y'},
3738 {"vertical-label", required_argument, 0, 'v'},
3739 {"width", required_argument, 0, 'w'},
3740 {"height", required_argument, 0, 'h'},
3741 {"full-size-mode", no_argument, 0, 'D'},
3742 {"interlaced", no_argument, 0, 'i'},
3743 {"upper-limit", required_argument, 0, 'u'},
3744 {"lower-limit", required_argument, 0, 'l'},
3745 {"rigid", no_argument, 0, 'r'},
3746 {"base", required_argument, 0, 'b'},
3747 {"logarithmic", no_argument, 0, 'o'},
3748 {"color", required_argument, 0, 'c'},
3749 {"font", required_argument, 0, 'n'},
3750 {"title", required_argument, 0, 't'},
3751 {"imginfo", required_argument, 0, 'f'},
3752 {"imgformat", required_argument, 0, 'a'},
3753 {"lazy", no_argument, 0, 'z'},
3754 {"zoom", required_argument, 0, 'm'},
3755 {"no-legend", no_argument, 0, 'g'},
3756 {"force-rules-legend", no_argument, 0, 'F'},
3757 {"only-graph", no_argument, 0, 'j'},
3758 {"alt-y-grid", no_argument, 0, 'Y'},
3759 {"no-minor", no_argument, 0, 'I'},
3760 {"slope-mode", no_argument, 0, 'E'},
3761 {"alt-autoscale", no_argument, 0, 'A'},
3762 {"alt-autoscale-min", no_argument, 0, 'J'},
3763 {"alt-autoscale-max", no_argument, 0, 'M'},
3764 {"no-gridfit", no_argument, 0, 'N'},
3765 {"units-exponent", required_argument, 0, 'X'},
3766 {"units-length", required_argument, 0, 'L'},
3767 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3768 {"step", required_argument, 0, 'S'},
3769 {"tabwidth", required_argument, 0, 'T'},
3770 {"font-render-mode", required_argument, 0, 'R'},
3771 {"graph-render-mode", required_argument, 0, 'G'},
3772 {"font-smoothing-threshold", required_argument, 0, 'B'},
3773 {"watermark", required_argument, 0, 'W'},
3774 {"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 */
3777 int option_index = 0;
3779 int col_start, col_end;
3781 opt = getopt_long(argc, argv,
3782 "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:",
3783 long_options, &option_index);
3790 im->extra_flags |= NOMINOR;
3793 im->extra_flags |= ALTYGRID;
3796 im->extra_flags |= ALTAUTOSCALE;
3799 im->extra_flags |= ALTAUTOSCALE_MIN;
3802 im->extra_flags |= ALTAUTOSCALE_MAX;
3805 im->extra_flags |= ONLY_GRAPH;
3808 im->extra_flags |= NOLEGEND;
3811 im->extra_flags |= FORCE_RULES_LEGEND;
3813 case LONGOPT_UNITS_SI:
3814 if (im->extra_flags & FORCE_UNITS) {
3815 rrd_set_error("--units can only be used once!");
3818 if (strcmp(optarg, "si") == 0)
3819 im->extra_flags |= FORCE_UNITS_SI;
3821 rrd_set_error("invalid argument for --units: %s", optarg);
3826 im->unitsexponent = atoi(optarg);
3829 im->unitslength = atoi(optarg);
3830 im->forceleftspace = 1;
3833 im->tabwidth = atof(optarg);
3836 im->step = atoi(optarg);
3842 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3843 rrd_set_error("start time: %s", parsetime_error);
3848 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3849 rrd_set_error("end time: %s", parsetime_error);
3854 if (strcmp(optarg, "none") == 0) {
3855 im->draw_x_grid = 0;
3860 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3862 &im->xlab_user.gridst,
3864 &im->xlab_user.mgridst,
3866 &im->xlab_user.labst,
3867 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3868 strncpy(im->xlab_form, optarg + stroff,
3869 sizeof(im->xlab_form) - 1);
3870 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3871 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3872 rrd_set_error("unknown keyword %s", scan_gtm);
3874 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3876 rrd_set_error("unknown keyword %s", scan_mtm);
3878 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3880 rrd_set_error("unknown keyword %s", scan_ltm);
3883 im->xlab_user.minsec = 1;
3884 im->xlab_user.stst = im->xlab_form;
3886 rrd_set_error("invalid x-grid format");
3892 if (strcmp(optarg, "none") == 0) {
3893 im->draw_y_grid = 0;
3897 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3898 if (im->ygridstep <= 0) {
3899 rrd_set_error("grid step must be > 0");
3901 } else if (im->ylabfact < 1) {
3902 rrd_set_error("label factor must be > 0");
3906 rrd_set_error("invalid y-grid format");
3911 strncpy(im->ylegend, optarg, 150);
3912 im->ylegend[150] = '\0';
3915 im->maxval = atof(optarg);
3918 im->minval = atof(optarg);
3921 im->base = atol(optarg);
3922 if (im->base != 1024 && im->base != 1000) {
3924 ("the only sensible value for base apart from 1000 is 1024");
3929 long_tmp = atol(optarg);
3930 if (long_tmp < 10) {
3931 rrd_set_error("width below 10 pixels");
3934 im->xsize = long_tmp;
3937 long_tmp = atol(optarg);
3938 if (long_tmp < 10) {
3939 rrd_set_error("height below 10 pixels");
3942 im->ysize = long_tmp;
3945 im->extra_flags |= FULL_SIZE_MODE;
3948 /* interlaced png not supported at the moment */
3954 im->imginfo = optarg;
3957 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3958 rrd_set_error("unsupported graphics format '%s'", optarg);
3970 im->logarithmic = 1;
3974 "%10[A-Z]#%n%8lx%n",
3975 col_nam, &col_start, &color, &col_end) == 2) {
3977 int col_len = col_end - col_start;
3981 color = (((color & 0xF00) * 0x110000) |
3982 ((color & 0x0F0) * 0x011000) |
3983 ((color & 0x00F) * 0x001100) | 0x000000FF);
3986 color = (((color & 0xF000) * 0x11000) |
3987 ((color & 0x0F00) * 0x01100) |
3988 ((color & 0x00F0) * 0x00110) |
3989 ((color & 0x000F) * 0x00011)
3993 color = (color << 8) + 0xff /* shift left by 8 */ ;
3998 rrd_set_error("the color format is #RRGGBB[AA]");
4001 if ((ci = grc_conv(col_nam)) != -1) {
4002 im->graph_col[ci] = gfx_hex_to_col(color);
4004 rrd_set_error("invalid color name '%s'", col_nam);
4008 rrd_set_error("invalid color def format");
4015 char font[1024] = "";
4017 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
4018 int sindex, propidx;
4020 if ((sindex = text_prop_conv(prop)) != -1) {
4021 for (propidx = sindex; propidx < TEXT_PROP_LAST;
4024 im->text_prop[propidx].size = size;
4026 if (strlen(font) > 0) {
4027 strcpy(im->text_prop[propidx].font, font);
4029 if (propidx == sindex && sindex != 0)
4033 rrd_set_error("invalid fonttag '%s'", prop);
4037 rrd_set_error("invalid text property format");
4043 im->zoom = atof(optarg);
4044 if (im->zoom <= 0.0) {
4045 rrd_set_error("zoom factor must be > 0");
4050 strncpy(im->title, optarg, 150);
4051 im->title[150] = '\0';
4055 if (strcmp(optarg, "normal") == 0) {
4056 cairo_font_options_set_antialias(im->font_options,
4057 CAIRO_ANTIALIAS_GRAY);
4058 cairo_font_options_set_hint_style(im->font_options,
4059 CAIRO_HINT_STYLE_FULL);
4060 } else if (strcmp(optarg, "light") == 0) {
4061 cairo_font_options_set_antialias(im->font_options,
4062 CAIRO_ANTIALIAS_GRAY);
4063 cairo_font_options_set_hint_style(im->font_options,
4064 CAIRO_HINT_STYLE_SLIGHT);
4065 } else if (strcmp(optarg, "mono") == 0) {
4066 cairo_font_options_set_antialias(im->font_options,
4067 CAIRO_ANTIALIAS_NONE);
4068 cairo_font_options_set_hint_style(im->font_options,
4069 CAIRO_HINT_STYLE_FULL);
4071 rrd_set_error("unknown font-render-mode '%s'", optarg);
4076 if (strcmp(optarg, "normal") == 0)
4077 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4078 else if (strcmp(optarg, "mono") == 0)
4079 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4081 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4086 /* not supported curently */
4090 strncpy(im->watermark, optarg, 100);
4091 im->watermark[99] = '\0';
4096 rrd_set_error("unknown option '%c'", optopt);
4098 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4103 if (im->logarithmic == 1 && im->minval <= 0) {
4105 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4109 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4110 /* error string is set in parsetime.c */
4114 if (start_tmp < 3600 * 24 * 365 * 10) {
4115 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4120 if (end_tmp < start_tmp) {
4121 rrd_set_error("start (%ld) should be less than end (%ld)",
4122 start_tmp, end_tmp);
4126 im->start = start_tmp;
4128 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4131 int rrd_graph_color(
4138 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4140 color = strstr(var, "#");
4141 if (color == NULL) {
4142 if (optional == 0) {
4143 rrd_set_error("Found no color in %s", err);
4150 long unsigned int col;
4152 rest = strstr(color, ":");
4160 sscanf(color, "#%6lx%n", &col, &n);
4161 col = (col << 8) + 0xff /* shift left by 8 */ ;
4163 rrd_set_error("Color problem in %s", err);
4166 sscanf(color, "#%8lx%n", &col, &n);
4170 rrd_set_error("Color problem in %s", err);
4172 if (rrd_test_error())
4174 gdp->col = gfx_hex_to_col(col);
4187 while (*ptr != '\0')
4188 if (*ptr++ == '%') {
4190 /* line cannot end with percent char */
4194 /* '%s', '%S' and '%%' are allowed */
4195 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4198 /* %c is allowed (but use only with vdef!) */
4199 else if (*ptr == 'c') {
4204 /* or else '% 6.2lf' and such are allowed */
4206 /* optional padding character */
4207 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4210 /* This should take care of 'm.n' with all three optional */
4211 while (*ptr >= '0' && *ptr <= '9')
4215 while (*ptr >= '0' && *ptr <= '9')
4218 /* Either 'le', 'lf' or 'lg' must follow here */
4221 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4236 struct graph_desc_t *gdes;
4237 const char *const str;
4239 /* A VDEF currently is either "func" or "param,func"
4240 * so the parsing is rather simple. Change if needed.
4247 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4248 if (n == (int) strlen(str)) { /* matched */
4252 sscanf(str, "%29[A-Z]%n", func, &n);
4253 if (n == (int) strlen(str)) { /* matched */
4256 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4261 if (!strcmp("PERCENT", func))
4262 gdes->vf.op = VDEF_PERCENT;
4263 else if (!strcmp("MAXIMUM", func))
4264 gdes->vf.op = VDEF_MAXIMUM;
4265 else if (!strcmp("AVERAGE", func))
4266 gdes->vf.op = VDEF_AVERAGE;
4267 else if (!strcmp("MINIMUM", func))
4268 gdes->vf.op = VDEF_MINIMUM;
4269 else if (!strcmp("TOTAL", func))
4270 gdes->vf.op = VDEF_TOTAL;
4271 else if (!strcmp("FIRST", func))
4272 gdes->vf.op = VDEF_FIRST;
4273 else if (!strcmp("LAST", func))
4274 gdes->vf.op = VDEF_LAST;
4275 else if (!strcmp("LSLSLOPE", func))
4276 gdes->vf.op = VDEF_LSLSLOPE;
4277 else if (!strcmp("LSLINT", func))
4278 gdes->vf.op = VDEF_LSLINT;
4279 else if (!strcmp("LSLCORREL", func))
4280 gdes->vf.op = VDEF_LSLCORREL;
4282 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4287 switch (gdes->vf.op) {
4289 if (isnan(param)) { /* no parameter given */
4290 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4294 if (param >= 0.0 && param <= 100.0) {
4295 gdes->vf.param = param;
4296 gdes->vf.val = DNAN; /* undefined */
4297 gdes->vf.when = 0; /* undefined */
4299 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4312 case VDEF_LSLCORREL:
4314 gdes->vf.param = DNAN;
4315 gdes->vf.val = DNAN;
4318 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4334 graph_desc_t *src, *dst;
4338 dst = &im->gdes[gdi];
4339 src = &im->gdes[dst->vidx];
4340 data = src->data + src->ds;
4341 steps = (src->end - src->start) / src->step;
4344 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4348 switch (dst->vf.op) {
4354 if ((array = malloc(steps * sizeof(double))) == NULL) {
4355 rrd_set_error("malloc VDEV_PERCENT");
4358 for (step = 0; step < steps; step++) {
4359 array[step] = data[step * src->ds_cnt];
4361 qsort(array, step, sizeof(double), vdef_percent_compar);
4363 field = (steps - 1) * dst->vf.param / 100;
4364 dst->vf.val = array[field];
4365 dst->vf.when = 0; /* no time component */
4368 for (step = 0; step < steps; step++)
4369 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4370 step == field ? '*' : ' ');
4376 while (step != steps && isnan(data[step * src->ds_cnt]))
4378 if (step == steps) {
4382 dst->vf.val = data[step * src->ds_cnt];
4383 dst->vf.when = src->start + (step + 1) * src->step;
4385 while (step != steps) {
4386 if (finite(data[step * src->ds_cnt])) {
4387 if (data[step * src->ds_cnt] > dst->vf.val) {
4388 dst->vf.val = data[step * src->ds_cnt];
4389 dst->vf.when = src->start + (step + 1) * src->step;
4400 for (step = 0; step < steps; step++) {
4401 if (finite(data[step * src->ds_cnt])) {
4402 sum += data[step * src->ds_cnt];
4407 if (dst->vf.op == VDEF_TOTAL) {
4408 dst->vf.val = sum * src->step;
4409 dst->vf.when = 0; /* no time component */
4411 dst->vf.val = sum / cnt;
4412 dst->vf.when = 0; /* no time component */
4422 while (step != steps && isnan(data[step * src->ds_cnt]))
4424 if (step == steps) {
4428 dst->vf.val = data[step * src->ds_cnt];
4429 dst->vf.when = src->start + (step + 1) * src->step;
4431 while (step != steps) {
4432 if (finite(data[step * src->ds_cnt])) {
4433 if (data[step * src->ds_cnt] < dst->vf.val) {
4434 dst->vf.val = data[step * src->ds_cnt];
4435 dst->vf.when = src->start + (step + 1) * src->step;
4442 /* The time value returned here is one step before the
4443 * actual time value. This is the start of the first
4447 while (step != steps && isnan(data[step * src->ds_cnt]))
4449 if (step == steps) { /* all entries were NaN */
4453 dst->vf.val = data[step * src->ds_cnt];
4454 dst->vf.when = src->start + step * src->step;
4458 /* The time value returned here is the
4459 * actual time value. This is the end of the last
4463 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4465 if (step < 0) { /* all entries were NaN */
4469 dst->vf.val = data[step * src->ds_cnt];
4470 dst->vf.when = src->start + (step + 1) * src->step;
4475 case VDEF_LSLCORREL:{
4476 /* Bestfit line by linear least squares method */
4479 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4487 for (step = 0; step < steps; step++) {
4488 if (finite(data[step * src->ds_cnt])) {
4491 SUMxx += step * step;
4492 SUMxy += step * data[step * src->ds_cnt];
4493 SUMy += data[step * src->ds_cnt];
4494 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4498 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4499 y_intercept = (SUMy - slope * SUMx) / cnt;
4502 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4503 (SUMx * SUMx) / cnt) * (SUMyy -
4509 if (dst->vf.op == VDEF_LSLSLOPE) {
4510 dst->vf.val = slope;
4512 } else if (dst->vf.op == VDEF_LSLINT) {
4513 dst->vf.val = y_intercept;
4515 } else if (dst->vf.op == VDEF_LSLCORREL) {
4516 dst->vf.val = correl;
4530 /* NaN < -INF < finite_values < INF */
4531 int vdef_percent_compar(
4536 /* Equality is not returned; this doesn't hurt except
4537 * (maybe) for a little performance.
4540 /* First catch NaN values. They are smallest */
4541 if (isnan(*(double *) a))
4543 if (isnan(*(double *) b))
4546 /* NaN doesn't reach this part so INF and -INF are extremes.
4547 * The sign from isinf() is compatible with the sign we return
4549 if (isinf(*(double *) a))
4550 return isinf(*(double *) a);
4551 if (isinf(*(double *) b))
4552 return isinf(*(double *) b);
4554 /* If we reach this, both values must be finite */
4555 if (*(double *) a < *(double *) b)