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_file(
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;
2887 /* draw that picture thing ... */
2893 int lazy = lazy_check(im);
2895 double areazero = 0.0;
2896 graph_desc_t *lastgdes = NULL;
2898 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2901 /* if we are lazy and there is nothing to PRINT ... quit now */
2902 if (lazy && im->prt_c == 0)
2905 /* pull the data from the rrd files ... */
2907 if (data_fetch(im) == -1)
2910 /* evaluate VDEF and CDEF operations ... */
2911 if (data_calc(im) == -1)
2915 /* calculate and PRINT and GPRINT definitions. We have to do it at
2916 * this point because it will affect the length of the legends
2917 * if there are no graph elements we stop here ...
2918 * if we are lazy, try to quit ...
2920 i = print_calc(im, calcpr);
2923 if ((i == 0) || lazy)
2926 /**************************************************************
2927 *** Calculating sizes and locations became a bit confusing ***
2928 *** so I moved this into a separate function. ***
2929 **************************************************************/
2930 if (graph_size_location(im, i) == -1)
2933 /* get actual drawing data and find min and max values */
2934 if (data_proc(im) == -1)
2937 if (!im->logarithmic) {
2940 /* identify si magnitude Kilo, Mega Giga ? */
2941 if (!im->rigid && !im->logarithmic)
2942 expand_range(im); /* make sure the upper and lower limit are
2945 if (!calc_horizontal_grid(im))
2952 apply_gridfit(im); */
2955 /* the actual graph is created by going through the individual
2956 graph elements and then drawing them */
2957 cairo_surface_destroy(im->surface);
2959 switch (im->imgformat) {
2962 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2963 im->ximg * im->zoom,
2964 im->yimg * im->zoom);
2969 cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2970 im->yimg * im->zoom);
2975 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
2976 im->yimg * im->zoom);
2981 cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
2982 im->yimg * im->zoom);
2983 cairo_svg_surface_restrict_to_version(im->surface,
2984 CAIRO_SVG_VERSION_1_1);
2987 im->cr = cairo_create(im->surface);
2988 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
2989 cairo_set_antialias(im->cr, im->graph_antialias);
2990 cairo_scale(im->cr, im->zoom, im->zoom);
2994 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2996 gfx_add_point(im, im->ximg, 0);
3000 im->xorigin, im->yorigin,
3001 im->xorigin + im->xsize, im->yorigin,
3002 im->xorigin + im->xsize, im->yorigin - im->ysize,
3003 im->graph_col[GRC_CANVAS]);
3005 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3008 if (im->minval > 0.0)
3009 areazero = im->minval;
3010 if (im->maxval < 0.0)
3011 areazero = im->maxval;
3013 for (i = 0; i < im->gdes_c; i++) {
3014 switch (im->gdes[i].gf) {
3028 for (ii = 0; ii < im->xsize; ii++) {
3029 if (!isnan(im->gdes[i].p_data[ii]) &&
3030 im->gdes[i].p_data[ii] != 0.0) {
3031 if (im->gdes[i].yrule > 0) {
3033 im->xorigin + ii, im->yorigin,
3036 im->gdes[i].yrule * im->ysize, 1.0,
3038 } else if (im->gdes[i].yrule < 0) {
3041 im->yorigin - im->ysize,
3044 im->gdes[i].yrule) *
3045 im->ysize, 1.0, im->gdes[i].col);
3053 /* fix data points at oo and -oo */
3054 for (ii = 0; ii < im->xsize; ii++) {
3055 if (isinf(im->gdes[i].p_data[ii])) {
3056 if (im->gdes[i].p_data[ii] > 0) {
3057 im->gdes[i].p_data[ii] = im->maxval;
3059 im->gdes[i].p_data[ii] = im->minval;
3065 /* *******************************************************
3070 -------|--t-1--t--------------------------------
3072 if we know the value at time t was a then
3073 we draw a square from t-1 to t with the value a.
3075 ********************************************************* */
3076 if (im->gdes[i].col.alpha != 0.0) {
3077 /* GF_LINE and friend */
3078 if (im->gdes[i].gf == GF_LINE) {
3079 double last_y = 0.0;
3083 cairo_new_path(im->cr);
3085 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3086 for (ii = 1; ii < im->xsize; ii++) {
3087 if (isnan(im->gdes[i].p_data[ii])
3088 || (im->slopemode == 1
3089 && isnan(im->gdes[i].p_data[ii - 1]))) {
3094 last_y = ytr(im, im->gdes[i].p_data[ii]);
3095 if (im->slopemode == 0) {
3096 double x = ii - 1 + im->xorigin;
3099 gfx_line_fit(im, &x, &y);
3100 cairo_move_to(im->cr, x, y);
3101 x = ii + im->xorigin;
3103 gfx_line_fit(im, &x, &y);
3104 cairo_line_to(im->cr, x, y);
3106 double x = ii - 1 + im->xorigin;
3108 im->gdes[i].p_data[ii - 1]);
3110 gfx_line_fit(im, &x, &y);
3111 cairo_move_to(im->cr, x, y);
3112 x = ii + im->xorigin;
3114 gfx_line_fit(im, &x, &y);
3115 cairo_line_to(im->cr, x, y);
3119 double x1 = ii + im->xorigin;
3120 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3122 if (im->slopemode == 0
3123 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3124 double x = ii - 1 + im->xorigin;
3127 gfx_line_fit(im, &x, &y);
3128 cairo_line_to(im->cr, x, y);
3131 gfx_line_fit(im, &x1, &y1);
3132 cairo_line_to(im->cr, x1, y1);
3136 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3137 im->gdes[i].col.green,
3138 im->gdes[i].col.blue,
3139 im->gdes[i].col.alpha);
3140 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3141 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3142 cairo_stroke(im->cr);
3143 cairo_restore(im->cr);
3146 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3147 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3148 double *backY = malloc(sizeof(double) * im->xsize * 2);
3149 double *backX = malloc(sizeof(double) * im->xsize * 2);
3152 for (ii = 0; ii <= im->xsize; ii++) {
3155 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3160 && AlmostEqual2sComplement(foreY[lastI],
3162 && AlmostEqual2sComplement(foreY[lastI],
3170 foreX[cntI], foreY[cntI],
3172 while (cntI < idxI) {
3177 AlmostEqual2sComplement(foreY[lastI],
3180 AlmostEqual2sComplement(foreY[lastI],
3185 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3187 gfx_add_point(im, backX[idxI], backY[idxI]);
3193 AlmostEqual2sComplement(backY[lastI],
3196 AlmostEqual2sComplement(backY[lastI],
3201 gfx_add_point(im, backX[idxI], backY[idxI]);
3211 if (ii == im->xsize)
3214 if (im->slopemode == 0 && ii == 0) {
3217 if (isnan(im->gdes[i].p_data[ii])) {
3221 ytop = ytr(im, im->gdes[i].p_data[ii]);
3222 if (lastgdes && im->gdes[i].stack) {
3223 ybase = ytr(im, lastgdes->p_data[ii]);
3225 ybase = ytr(im, areazero);
3227 if (ybase == ytop) {
3233 double extra = ytop;
3238 if (im->slopemode == 0) {
3239 backY[++idxI] = ybase - 0.2;
3240 backX[idxI] = ii + im->xorigin - 1;
3241 foreY[idxI] = ytop + 0.2;
3242 foreX[idxI] = ii + im->xorigin - 1;
3244 backY[++idxI] = ybase - 0.2;
3245 backX[idxI] = ii + im->xorigin;
3246 foreY[idxI] = ytop + 0.2;
3247 foreX[idxI] = ii + im->xorigin;
3249 /* close up any remaining area */
3254 } /* else GF_LINE */
3256 /* if color != 0x0 */
3257 /* make sure we do not run into trouble when stacking on NaN */
3258 for (ii = 0; ii < im->xsize; ii++) {
3259 if (isnan(im->gdes[i].p_data[ii])) {
3260 if (lastgdes && (im->gdes[i].stack)) {
3261 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3263 im->gdes[i].p_data[ii] = areazero;
3267 lastgdes = &(im->gdes[i]);
3271 ("STACK should already be turned into LINE or AREA here");
3278 /* grid_paint also does the text */
3279 if (!(im->extra_flags & ONLY_GRAPH))
3283 if (!(im->extra_flags & ONLY_GRAPH))
3286 /* the RULES are the last thing to paint ... */
3287 for (i = 0; i < im->gdes_c; i++) {
3289 switch (im->gdes[i].gf) {
3291 if (im->gdes[i].yrule >= im->minval
3292 && im->gdes[i].yrule <= im->maxval)
3294 im->xorigin, ytr(im, im->gdes[i].yrule),
3295 im->xorigin + im->xsize, ytr(im,
3297 1.0, im->gdes[i].col);
3300 if (im->gdes[i].xrule >= im->start
3301 && im->gdes[i].xrule <= im->end)
3303 xtr(im, im->gdes[i].xrule), im->yorigin,
3304 xtr(im, im->gdes[i].xrule),
3305 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3313 switch (im->imgformat) {
3316 cairo_status_t status;
3318 if (strcmp(im->graphfile, "-") == 0) {
3320 cairo_surface_write_to_png_stream(im->surface,
3321 &cairo_write_func_file,
3324 status = cairo_surface_write_to_png(im->surface, im->graphfile);
3327 if (status != CAIRO_STATUS_SUCCESS) {
3328 rrd_set_error("Could not save png to '%s'", im->graphfile);
3334 cairo_show_page(im->cr);
3341 /*****************************************************
3343 *****************************************************/
3350 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3351 * sizeof(graph_desc_t))) ==
3353 rrd_set_error("realloc graph_descs");
3358 im->gdes[im->gdes_c - 1].step = im->step;
3359 im->gdes[im->gdes_c - 1].step_orig = im->step;
3360 im->gdes[im->gdes_c - 1].stack = 0;
3361 im->gdes[im->gdes_c - 1].linewidth = 0;
3362 im->gdes[im->gdes_c - 1].debug = 0;
3363 im->gdes[im->gdes_c - 1].start = im->start;
3364 im->gdes[im->gdes_c - 1].start_orig = im->start;
3365 im->gdes[im->gdes_c - 1].end = im->end;
3366 im->gdes[im->gdes_c - 1].end_orig = im->end;
3367 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3368 im->gdes[im->gdes_c - 1].data = NULL;
3369 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3370 im->gdes[im->gdes_c - 1].data_first = 0;
3371 im->gdes[im->gdes_c - 1].p_data = NULL;
3372 im->gdes[im->gdes_c - 1].rpnp = NULL;
3373 im->gdes[im->gdes_c - 1].shift = 0.0;
3374 im->gdes[im->gdes_c - 1].col.red = 0.0;
3375 im->gdes[im->gdes_c - 1].col.green = 0.0;
3376 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3377 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3378 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3379 im->gdes[im->gdes_c - 1].format[0] = '\0';
3380 im->gdes[im->gdes_c - 1].strftm = 0;
3381 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3382 im->gdes[im->gdes_c - 1].ds = -1;
3383 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3384 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3385 im->gdes[im->gdes_c - 1].p_data = NULL;
3386 im->gdes[im->gdes_c - 1].yrule = DNAN;
3387 im->gdes[im->gdes_c - 1].xrule = 0;
3391 /* copies input untill the first unescaped colon is found
3392 or until input ends. backslashes have to be escaped as well */
3394 const char *const input,
3400 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3401 if (input[inp] == '\\' &&
3402 input[inp + 1] != '\0' &&
3403 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3404 output[outp++] = input[++inp];
3406 output[outp++] = input[inp];
3409 output[outp] = '\0';
3413 /* Some surgery done on this function, it became ridiculously big.
3415 ** - initializing now in rrd_graph_init()
3416 ** - options parsing now in rrd_graph_options()
3417 ** - script parsing now in rrd_graph_script()
3431 rrd_graph_init(&im);
3433 /* a dummy surface so that we can measure text sizes for placements */
3434 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3435 im.cr = cairo_create(im.surface);
3438 /* not currently using this ... */
3439 im.graphhandle = stream;
3441 rrd_graph_options(argc, argv, &im);
3442 if (rrd_test_error()) {
3447 if (strlen(argv[optind]) >= MAXPATH) {
3448 rrd_set_error("filename (including path) too long");
3452 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3453 im.graphfile[MAXPATH - 1] = '\0';
3455 rrd_graph_script(argc, argv, &im, 1);
3456 if (rrd_test_error()) {
3461 /* Everything is now read and the actual work can start */
3464 if (graph_paint(&im, prdata) == -1) {
3469 /* The image is generated and needs to be output.
3470 ** Also, if needed, print a line with information about the image.
3481 /* maybe prdata is not allocated yet ... lets do it now */
3482 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3483 rrd_set_error("malloc imginfo");
3488 malloc((strlen(im.imginfo) + 200 +
3489 strlen(im.graphfile)) * sizeof(char)))
3491 rrd_set_error("malloc imginfo");
3494 filename = im.graphfile + strlen(im.graphfile);
3495 while (filename > im.graphfile) {
3496 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3501 sprintf((*prdata)[0], im.imginfo, filename,
3502 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3508 void rrd_graph_init(
3516 #ifdef HAVE_SETLOCALE
3517 setlocale(LC_TIME, "");
3518 #ifdef HAVE_MBSTOWCS
3519 setlocale(LC_CTYPE, "");
3525 im->xlab_user.minsec = -1;
3531 im->ylegend[0] = '\0';
3532 im->title[0] = '\0';
3533 im->watermark[0] = '\0';
3536 im->unitsexponent = 9999;
3537 im->unitslength = 6;
3538 im->forceleftspace = 0;
3540 im->viewfactor = 1.0;
3541 im->imgformat = IF_PNG;
3544 im->extra_flags = 0;
3550 im->logarithmic = 0;
3551 im->ygridstep = DNAN;
3552 im->draw_x_grid = 1;
3553 im->draw_y_grid = 1;
3558 im->grid_dash_on = 1;
3559 im->grid_dash_off = 1;
3560 im->tabwidth = 40.0;
3562 im->font_options = cairo_font_options_create();
3563 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3565 cairo_font_options_set_hint_style(im->font_options,
3566 CAIRO_HINT_STYLE_FULL);
3567 cairo_font_options_set_hint_metrics(im->font_options,
3568 CAIRO_HINT_METRICS_ON);
3569 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3572 for (i = 0; i < DIM(graph_col); i++)
3573 im->graph_col[i] = graph_col[i];
3575 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3578 char rrd_win_default_font[1000];
3580 windir = getenv("windir");
3581 /* %windir% is something like D:\windows or C:\winnt */
3582 if (windir != NULL) {
3583 strncpy(rrd_win_default_font, windir, 500);
3584 rrd_win_default_font[500] = '\0';
3585 strcat(rrd_win_default_font, "\\fonts\\");
3586 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3587 for (i = 0; i < DIM(text_prop); i++) {
3588 strncpy(text_prop[i].font, rrd_win_default_font,
3589 sizeof(text_prop[i].font) - 1);
3590 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3598 deffont = getenv("RRD_DEFAULT_FONT");
3599 if (deffont != NULL) {
3600 for (i = 0; i < DIM(text_prop); i++) {
3601 strncpy(text_prop[i].font, deffont,
3602 sizeof(text_prop[i].font) - 1);
3603 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3607 for (i = 0; i < DIM(text_prop); i++) {
3608 im->text_prop[i].size = text_prop[i].size;
3609 strcpy(im->text_prop[i].font, text_prop[i].font);
3613 void rrd_graph_options(
3619 char *parsetime_error = NULL;
3620 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3621 time_t start_tmp = 0, end_tmp = 0;
3623 struct rrd_time_value start_tv, end_tv;
3624 long unsigned int color;
3627 opterr = 0; /* initialize getopt */
3629 parsetime("end-24h", &start_tv);
3630 parsetime("now", &end_tv);
3632 /* defines for long options without a short equivalent. should be bytes,
3633 and may not collide with (the ASCII value of) short options */
3634 #define LONGOPT_UNITS_SI 255
3637 static struct option long_options[] = {
3638 {"start", required_argument, 0, 's'},
3639 {"end", required_argument, 0, 'e'},
3640 {"x-grid", required_argument, 0, 'x'},
3641 {"y-grid", required_argument, 0, 'y'},
3642 {"vertical-label", required_argument, 0, 'v'},
3643 {"width", required_argument, 0, 'w'},
3644 {"height", required_argument, 0, 'h'},
3645 {"full-size-mode", no_argument, 0, 'D'},
3646 {"interlaced", no_argument, 0, 'i'},
3647 {"upper-limit", required_argument, 0, 'u'},
3648 {"lower-limit", required_argument, 0, 'l'},
3649 {"rigid", no_argument, 0, 'r'},
3650 {"base", required_argument, 0, 'b'},
3651 {"logarithmic", no_argument, 0, 'o'},
3652 {"color", required_argument, 0, 'c'},
3653 {"font", required_argument, 0, 'n'},
3654 {"title", required_argument, 0, 't'},
3655 {"imginfo", required_argument, 0, 'f'},
3656 {"imgformat", required_argument, 0, 'a'},
3657 {"lazy", no_argument, 0, 'z'},
3658 {"zoom", required_argument, 0, 'm'},
3659 {"no-legend", no_argument, 0, 'g'},
3660 {"force-rules-legend", no_argument, 0, 'F'},
3661 {"only-graph", no_argument, 0, 'j'},
3662 {"alt-y-grid", no_argument, 0, 'Y'},
3663 {"no-minor", no_argument, 0, 'I'},
3664 {"slope-mode", no_argument, 0, 'E'},
3665 {"alt-autoscale", no_argument, 0, 'A'},
3666 {"alt-autoscale-min", no_argument, 0, 'J'},
3667 {"alt-autoscale-max", no_argument, 0, 'M'},
3668 {"no-gridfit", no_argument, 0, 'N'},
3669 {"units-exponent", required_argument, 0, 'X'},
3670 {"units-length", required_argument, 0, 'L'},
3671 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3672 {"step", required_argument, 0, 'S'},
3673 {"tabwidth", required_argument, 0, 'T'},
3674 {"font-render-mode", required_argument, 0, 'R'},
3675 {"graph-render-mode", required_argument, 0, 'G'},
3676 {"font-smoothing-threshold", required_argument, 0, 'B'},
3677 {"watermark", required_argument, 0, 'W'},
3678 {"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 */
3681 int option_index = 0;
3683 int col_start, col_end;
3685 opt = getopt_long(argc, argv,
3686 "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:",
3687 long_options, &option_index);
3694 im->extra_flags |= NOMINOR;
3697 im->extra_flags |= ALTYGRID;
3700 im->extra_flags |= ALTAUTOSCALE;
3703 im->extra_flags |= ALTAUTOSCALE_MIN;
3706 im->extra_flags |= ALTAUTOSCALE_MAX;
3709 im->extra_flags |= ONLY_GRAPH;
3712 im->extra_flags |= NOLEGEND;
3715 im->extra_flags |= FORCE_RULES_LEGEND;
3717 case LONGOPT_UNITS_SI:
3718 if (im->extra_flags & FORCE_UNITS) {
3719 rrd_set_error("--units can only be used once!");
3722 if (strcmp(optarg, "si") == 0)
3723 im->extra_flags |= FORCE_UNITS_SI;
3725 rrd_set_error("invalid argument for --units: %s", optarg);
3730 im->unitsexponent = atoi(optarg);
3733 im->unitslength = atoi(optarg);
3734 im->forceleftspace = 1;
3737 im->tabwidth = atof(optarg);
3740 im->step = atoi(optarg);
3746 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3747 rrd_set_error("start time: %s", parsetime_error);
3752 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3753 rrd_set_error("end time: %s", parsetime_error);
3758 if (strcmp(optarg, "none") == 0) {
3759 im->draw_x_grid = 0;
3764 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3766 &im->xlab_user.gridst,
3768 &im->xlab_user.mgridst,
3770 &im->xlab_user.labst,
3771 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3772 strncpy(im->xlab_form, optarg + stroff,
3773 sizeof(im->xlab_form) - 1);
3774 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3775 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3776 rrd_set_error("unknown keyword %s", scan_gtm);
3778 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3780 rrd_set_error("unknown keyword %s", scan_mtm);
3782 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3784 rrd_set_error("unknown keyword %s", scan_ltm);
3787 im->xlab_user.minsec = 1;
3788 im->xlab_user.stst = im->xlab_form;
3790 rrd_set_error("invalid x-grid format");
3796 if (strcmp(optarg, "none") == 0) {
3797 im->draw_y_grid = 0;
3801 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3802 if (im->ygridstep <= 0) {
3803 rrd_set_error("grid step must be > 0");
3805 } else if (im->ylabfact < 1) {
3806 rrd_set_error("label factor must be > 0");
3810 rrd_set_error("invalid y-grid format");
3815 strncpy(im->ylegend, optarg, 150);
3816 im->ylegend[150] = '\0';
3819 im->maxval = atof(optarg);
3822 im->minval = atof(optarg);
3825 im->base = atol(optarg);
3826 if (im->base != 1024 && im->base != 1000) {
3828 ("the only sensible value for base apart from 1000 is 1024");
3833 long_tmp = atol(optarg);
3834 if (long_tmp < 10) {
3835 rrd_set_error("width below 10 pixels");
3838 im->xsize = long_tmp;
3841 long_tmp = atol(optarg);
3842 if (long_tmp < 10) {
3843 rrd_set_error("height below 10 pixels");
3846 im->ysize = long_tmp;
3849 im->extra_flags |= FULL_SIZE_MODE;
3852 /* interlaced png not supported at the moment */
3858 im->imginfo = optarg;
3861 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3862 rrd_set_error("unsupported graphics format '%s'", optarg);
3874 im->logarithmic = 1;
3878 "%10[A-Z]#%n%8lx%n",
3879 col_nam, &col_start, &color, &col_end) == 2) {
3881 int col_len = col_end - col_start;
3885 color = (((color & 0xF00) * 0x110000) |
3886 ((color & 0x0F0) * 0x011000) |
3887 ((color & 0x00F) * 0x001100) | 0x000000FF);
3890 color = (((color & 0xF000) * 0x11000) |
3891 ((color & 0x0F00) * 0x01100) |
3892 ((color & 0x00F0) * 0x00110) |
3893 ((color & 0x000F) * 0x00011)
3897 color = (color << 8) + 0xff /* shift left by 8 */ ;
3902 rrd_set_error("the color format is #RRGGBB[AA]");
3905 if ((ci = grc_conv(col_nam)) != -1) {
3906 im->graph_col[ci] = gfx_hex_to_col(color);
3908 rrd_set_error("invalid color name '%s'", col_nam);
3912 rrd_set_error("invalid color def format");
3919 char font[1024] = "";
3921 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3922 int sindex, propidx;
3924 if ((sindex = text_prop_conv(prop)) != -1) {
3925 for (propidx = sindex; propidx < TEXT_PROP_LAST;
3928 im->text_prop[propidx].size = size;
3930 if (strlen(font) > 0) {
3931 strcpy(im->text_prop[propidx].font, font);
3933 if (propidx == sindex && sindex != 0)
3937 rrd_set_error("invalid fonttag '%s'", prop);
3941 rrd_set_error("invalid text property format");
3947 im->zoom = atof(optarg);
3948 if (im->zoom <= 0.0) {
3949 rrd_set_error("zoom factor must be > 0");
3954 strncpy(im->title, optarg, 150);
3955 im->title[150] = '\0';
3959 if (strcmp(optarg, "normal") == 0) {
3960 cairo_font_options_set_antialias(im->font_options,
3961 CAIRO_ANTIALIAS_GRAY);
3962 cairo_font_options_set_hint_style(im->font_options,
3963 CAIRO_HINT_STYLE_FULL);
3964 } else if (strcmp(optarg, "light") == 0) {
3965 cairo_font_options_set_antialias(im->font_options,
3966 CAIRO_ANTIALIAS_GRAY);
3967 cairo_font_options_set_hint_style(im->font_options,
3968 CAIRO_HINT_STYLE_SLIGHT);
3969 } else if (strcmp(optarg, "mono") == 0) {
3970 cairo_font_options_set_antialias(im->font_options,
3971 CAIRO_ANTIALIAS_NONE);
3972 cairo_font_options_set_hint_style(im->font_options,
3973 CAIRO_HINT_STYLE_FULL);
3975 rrd_set_error("unknown font-render-mode '%s'", optarg);
3980 if (strcmp(optarg, "normal") == 0)
3981 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3982 else if (strcmp(optarg, "mono") == 0)
3983 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
3985 rrd_set_error("unknown graph-render-mode '%s'", optarg);
3990 /* not supported curently */
3994 strncpy(im->watermark, optarg, 100);
3995 im->watermark[99] = '\0';
4000 rrd_set_error("unknown option '%c'", optopt);
4002 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4007 if (optind >= argc) {
4008 rrd_set_error("missing filename");
4012 if (im->logarithmic == 1 && im->minval <= 0) {
4014 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4018 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4019 /* error string is set in parsetime.c */
4023 if (start_tmp < 3600 * 24 * 365 * 10) {
4024 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4029 if (end_tmp < start_tmp) {
4030 rrd_set_error("start (%ld) should be less than end (%ld)",
4031 start_tmp, end_tmp);
4035 im->start = start_tmp;
4037 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4040 int rrd_graph_color(
4047 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4049 color = strstr(var, "#");
4050 if (color == NULL) {
4051 if (optional == 0) {
4052 rrd_set_error("Found no color in %s", err);
4059 long unsigned int col;
4061 rest = strstr(color, ":");
4069 sscanf(color, "#%6lx%n", &col, &n);
4070 col = (col << 8) + 0xff /* shift left by 8 */ ;
4072 rrd_set_error("Color problem in %s", err);
4075 sscanf(color, "#%8lx%n", &col, &n);
4079 rrd_set_error("Color problem in %s", err);
4081 if (rrd_test_error())
4083 gdp->col = gfx_hex_to_col(col);
4096 while (*ptr != '\0')
4097 if (*ptr++ == '%') {
4099 /* line cannot end with percent char */
4103 /* '%s', '%S' and '%%' are allowed */
4104 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4107 /* %c is allowed (but use only with vdef!) */
4108 else if (*ptr == 'c') {
4113 /* or else '% 6.2lf' and such are allowed */
4115 /* optional padding character */
4116 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4119 /* This should take care of 'm.n' with all three optional */
4120 while (*ptr >= '0' && *ptr <= '9')
4124 while (*ptr >= '0' && *ptr <= '9')
4127 /* Either 'le', 'lf' or 'lg' must follow here */
4130 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4145 struct graph_desc_t *gdes;
4146 const char *const str;
4148 /* A VDEF currently is either "func" or "param,func"
4149 * so the parsing is rather simple. Change if needed.
4156 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4157 if (n == (int) strlen(str)) { /* matched */
4161 sscanf(str, "%29[A-Z]%n", func, &n);
4162 if (n == (int) strlen(str)) { /* matched */
4165 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4170 if (!strcmp("PERCENT", func))
4171 gdes->vf.op = VDEF_PERCENT;
4172 else if (!strcmp("MAXIMUM", func))
4173 gdes->vf.op = VDEF_MAXIMUM;
4174 else if (!strcmp("AVERAGE", func))
4175 gdes->vf.op = VDEF_AVERAGE;
4176 else if (!strcmp("MINIMUM", func))
4177 gdes->vf.op = VDEF_MINIMUM;
4178 else if (!strcmp("TOTAL", func))
4179 gdes->vf.op = VDEF_TOTAL;
4180 else if (!strcmp("FIRST", func))
4181 gdes->vf.op = VDEF_FIRST;
4182 else if (!strcmp("LAST", func))
4183 gdes->vf.op = VDEF_LAST;
4184 else if (!strcmp("LSLSLOPE", func))
4185 gdes->vf.op = VDEF_LSLSLOPE;
4186 else if (!strcmp("LSLINT", func))
4187 gdes->vf.op = VDEF_LSLINT;
4188 else if (!strcmp("LSLCORREL", func))
4189 gdes->vf.op = VDEF_LSLCORREL;
4191 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4196 switch (gdes->vf.op) {
4198 if (isnan(param)) { /* no parameter given */
4199 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4203 if (param >= 0.0 && param <= 100.0) {
4204 gdes->vf.param = param;
4205 gdes->vf.val = DNAN; /* undefined */
4206 gdes->vf.when = 0; /* undefined */
4208 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4221 case VDEF_LSLCORREL:
4223 gdes->vf.param = DNAN;
4224 gdes->vf.val = DNAN;
4227 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4243 graph_desc_t *src, *dst;
4247 dst = &im->gdes[gdi];
4248 src = &im->gdes[dst->vidx];
4249 data = src->data + src->ds;
4250 steps = (src->end - src->start) / src->step;
4253 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4257 switch (dst->vf.op) {
4263 if ((array = malloc(steps * sizeof(double))) == NULL) {
4264 rrd_set_error("malloc VDEV_PERCENT");
4267 for (step = 0; step < steps; step++) {
4268 array[step] = data[step * src->ds_cnt];
4270 qsort(array, step, sizeof(double), vdef_percent_compar);
4272 field = (steps - 1) * dst->vf.param / 100;
4273 dst->vf.val = array[field];
4274 dst->vf.when = 0; /* no time component */
4277 for (step = 0; step < steps; step++)
4278 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4279 step == field ? '*' : ' ');
4285 while (step != steps && isnan(data[step * src->ds_cnt]))
4287 if (step == steps) {
4291 dst->vf.val = data[step * src->ds_cnt];
4292 dst->vf.when = src->start + (step + 1) * src->step;
4294 while (step != steps) {
4295 if (finite(data[step * src->ds_cnt])) {
4296 if (data[step * src->ds_cnt] > dst->vf.val) {
4297 dst->vf.val = data[step * src->ds_cnt];
4298 dst->vf.when = src->start + (step + 1) * src->step;
4309 for (step = 0; step < steps; step++) {
4310 if (finite(data[step * src->ds_cnt])) {
4311 sum += data[step * src->ds_cnt];
4316 if (dst->vf.op == VDEF_TOTAL) {
4317 dst->vf.val = sum * src->step;
4318 dst->vf.when = 0; /* no time component */
4320 dst->vf.val = sum / cnt;
4321 dst->vf.when = 0; /* no time component */
4331 while (step != steps && isnan(data[step * src->ds_cnt]))
4333 if (step == steps) {
4337 dst->vf.val = data[step * src->ds_cnt];
4338 dst->vf.when = src->start + (step + 1) * src->step;
4340 while (step != steps) {
4341 if (finite(data[step * src->ds_cnt])) {
4342 if (data[step * src->ds_cnt] < dst->vf.val) {
4343 dst->vf.val = data[step * src->ds_cnt];
4344 dst->vf.when = src->start + (step + 1) * src->step;
4351 /* The time value returned here is one step before the
4352 * actual time value. This is the start of the first
4356 while (step != steps && isnan(data[step * src->ds_cnt]))
4358 if (step == steps) { /* all entries were NaN */
4362 dst->vf.val = data[step * src->ds_cnt];
4363 dst->vf.when = src->start + step * src->step;
4367 /* The time value returned here is the
4368 * actual time value. This is the end of the last
4372 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4374 if (step < 0) { /* all entries were NaN */
4378 dst->vf.val = data[step * src->ds_cnt];
4379 dst->vf.when = src->start + (step + 1) * src->step;
4384 case VDEF_LSLCORREL:{
4385 /* Bestfit line by linear least squares method */
4388 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4396 for (step = 0; step < steps; step++) {
4397 if (finite(data[step * src->ds_cnt])) {
4400 SUMxx += step * step;
4401 SUMxy += step * data[step * src->ds_cnt];
4402 SUMy += data[step * src->ds_cnt];
4403 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4407 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4408 y_intercept = (SUMy - slope * SUMx) / cnt;
4411 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4412 (SUMx * SUMx) / cnt) * (SUMyy -
4418 if (dst->vf.op == VDEF_LSLSLOPE) {
4419 dst->vf.val = slope;
4421 } else if (dst->vf.op == VDEF_LSLINT) {
4422 dst->vf.val = y_intercept;
4424 } else if (dst->vf.op == VDEF_LSLCORREL) {
4425 dst->vf.val = correl;
4439 /* NaN < -INF < finite_values < INF */
4440 int vdef_percent_compar(
4445 /* Equality is not returned; this doesn't hurt except
4446 * (maybe) for a little performance.
4449 /* First catch NaN values. They are smallest */
4450 if (isnan(*(double *) a))
4452 if (isnan(*(double *) b))
4455 /* NaN doesn't reach this part so INF and -INF are extremes.
4456 * The sign from isinf() is compatible with the sign we return
4458 if (isinf(*(double *) a))
4459 return isinf(*(double *) a);
4460 if (isinf(*(double *) b))
4461 return isinf(*(double *) b);
4463 /* If we reach this, both values must be finite */
4464 if (*(double *) a < *(double *) b)