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 */
143 { 0xFFFFFFFF, /* canvas */
144 0xF0F0F0FF, /* background */
145 0xD0D0D0FF, /* shade A */
146 0xA0A0A0FF, /* shade B */
147 0x90909080, /* grid */
148 0xE0505080, /* major grid */
149 0x000000FF, /* font */
150 0x802020FF, /* arrow */
151 0x202020FF, /* axis */
152 0x000000FF /* frame */
159 # define DPRINT(x) (void)(printf x, printf("\n"))
165 /* initialize with xtr(im,0); */
173 pixie = (double) im->xsize / (double) (im->end - im->start);
176 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
179 /* translate data values into y coordinates */
188 if (!im->logarithmic)
189 pixie = (double) im->ysize / (im->maxval - im->minval);
192 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
194 } else if (!im->logarithmic) {
195 yval = im->yorigin - pixie * (value - im->minval);
197 if (value < im->minval) {
200 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
203 /* make sure we don't return anything too unreasonable. GD lib can
204 get terribly slow when drawing lines outside its scope. This is
205 especially problematic in connection with the rigid option */
207 /* keep yval as-is */
208 } else if (yval > im->yorigin) {
209 yval = im->yorigin + 0.00001;
210 } else if (yval < im->yorigin - im->ysize) {
211 yval = im->yorigin - im->ysize - 0.00001;
218 /* conversion function for symbolic entry names */
221 #define conv_if(VV,VVV) \
222 if (strcmp(#VV, string) == 0) return VVV ;
228 conv_if(PRINT, GF_PRINT)
229 conv_if(GPRINT, GF_GPRINT)
230 conv_if(COMMENT, GF_COMMENT)
231 conv_if(HRULE, GF_HRULE)
232 conv_if(VRULE, GF_VRULE)
233 conv_if(LINE, GF_LINE)
234 conv_if(AREA, GF_AREA)
235 conv_if(STACK, GF_STACK)
236 conv_if(TICK, GF_TICK)
238 conv_if(CDEF, GF_CDEF)
239 conv_if(VDEF, GF_VDEF)
241 conv_if(PART, GF_PART)
243 conv_if(XPORT, GF_XPORT)
244 conv_if(SHIFT, GF_SHIFT)
249 enum gfx_if_en if_conv(
261 enum tmt_en tmt_conv(
265 conv_if(SECOND, TMT_SECOND)
266 conv_if(MINUTE, TMT_MINUTE)
267 conv_if(HOUR, TMT_HOUR)
268 conv_if(DAY, TMT_DAY)
269 conv_if(WEEK, TMT_WEEK)
270 conv_if(MONTH, TMT_MONTH)
271 conv_if(YEAR, TMT_YEAR)
275 enum grc_en grc_conv(
279 conv_if(BACK, GRC_BACK)
280 conv_if(CANVAS, GRC_CANVAS)
281 conv_if(SHADEA, GRC_SHADEA)
282 conv_if(SHADEB, GRC_SHADEB)
283 conv_if(GRID, GRC_GRID)
284 conv_if(MGRID, GRC_MGRID)
285 conv_if(FONT, GRC_FONT)
286 conv_if(ARROW, GRC_ARROW)
287 conv_if(AXIS, GRC_AXIS)
288 conv_if(FRAME, GRC_FRAME)
293 enum text_prop_en text_prop_conv(
297 conv_if(DEFAULT, TEXT_PROP_DEFAULT)
298 conv_if(TITLE, TEXT_PROP_TITLE)
299 conv_if(AXIS, TEXT_PROP_AXIS)
300 conv_if(UNIT, TEXT_PROP_UNIT)
301 conv_if(LEGEND, TEXT_PROP_LEGEND)
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 gfx_destroy(im->canvas);
333 /* find SI magnitude symbol for the given number*/
335 image_desc_t *im, /* image description */
341 char *symbol[] = { "a", /* 10e-18 Atto */
342 "f", /* 10e-15 Femto */
343 "p", /* 10e-12 Pico */
344 "n", /* 10e-9 Nano */
345 "u", /* 10e-6 Micro */
346 "m", /* 10e-3 Milli */
351 "T", /* 10e12 Tera */
352 "P", /* 10e15 Peta */
359 if (*value == 0.0 || isnan(*value)) {
363 sindex = floor(log(fabs(*value)) / log((double) im->base));
364 *magfact = pow((double) im->base, (double) sindex);
365 (*value) /= (*magfact);
367 if (sindex <= symbcenter && sindex >= -symbcenter) {
368 (*symb_ptr) = symbol[sindex + symbcenter];
375 static char si_symbol[] = {
376 'a', /* 10e-18 Atto */
377 'f', /* 10e-15 Femto */
378 'p', /* 10e-12 Pico */
379 'n', /* 10e-9 Nano */
380 'u', /* 10e-6 Micro */
381 'm', /* 10e-3 Milli */
386 'T', /* 10e12 Tera */
387 'P', /* 10e15 Peta */
390 static const int si_symbcenter = 6;
392 /* find SI magnitude symbol for the numbers on the y-axis*/
394 image_desc_t *im /* image description */
398 double digits, viewdigits = 0;
401 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
402 log((double) im->base));
404 if (im->unitsexponent != 9999) {
405 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
406 viewdigits = floor(im->unitsexponent / 3);
411 im->magfact = pow((double) im->base, digits);
414 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
417 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
419 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
420 ((viewdigits + si_symbcenter) >= 0))
421 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
426 /* move min and max values around to become sensible */
431 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
432 600.0, 500.0, 400.0, 300.0, 250.0,
433 200.0, 125.0, 100.0, 90.0, 80.0,
434 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
435 25.0, 20.0, 10.0, 9.0, 8.0,
436 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
437 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
438 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
441 double scaled_min, scaled_max;
448 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
449 im->minval, im->maxval, im->magfact);
452 if (isnan(im->ygridstep)) {
453 if (im->extra_flags & ALTAUTOSCALE) {
454 /* measure the amplitude of the function. Make sure that
455 graph boundaries are slightly higher then max/min vals
456 so we can see amplitude on the graph */
459 delt = im->maxval - im->minval;
461 fact = 2.0 * pow(10.0,
463 (max(fabs(im->minval), fabs(im->maxval)) /
466 adj = (fact - delt) * 0.55;
469 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
470 im->minval, im->maxval, delt, fact, adj);
475 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
476 /* measure the amplitude of the function. Make sure that
477 graph boundaries are slightly lower than min vals
478 so we can see amplitude on the graph */
479 adj = (im->maxval - im->minval) * 0.1;
481 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
482 /* measure the amplitude of the function. Make sure that
483 graph boundaries are slightly higher than max vals
484 so we can see amplitude on the graph */
485 adj = (im->maxval - im->minval) * 0.1;
488 scaled_min = im->minval / im->magfact;
489 scaled_max = im->maxval / im->magfact;
491 for (i = 1; sensiblevalues[i] > 0; i++) {
492 if (sensiblevalues[i - 1] >= scaled_min &&
493 sensiblevalues[i] <= scaled_min)
494 im->minval = sensiblevalues[i] * (im->magfact);
496 if (-sensiblevalues[i - 1] <= scaled_min &&
497 -sensiblevalues[i] >= scaled_min)
498 im->minval = -sensiblevalues[i - 1] * (im->magfact);
500 if (sensiblevalues[i - 1] >= scaled_max &&
501 sensiblevalues[i] <= scaled_max)
502 im->maxval = sensiblevalues[i - 1] * (im->magfact);
504 if (-sensiblevalues[i - 1] <= scaled_max &&
505 -sensiblevalues[i] >= scaled_max)
506 im->maxval = -sensiblevalues[i] * (im->magfact);
510 /* adjust min and max to the grid definition if there is one */
511 im->minval = (double) im->ylabfact * im->ygridstep *
512 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
513 im->maxval = (double) im->ylabfact * im->ygridstep *
514 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
518 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
519 im->minval, im->maxval, im->magfact);
526 if (isnan(im->minval) || isnan(im->maxval))
529 if (im->logarithmic) {
530 double ya, yb, ypix, ypixfrac;
531 double log10_range = log10(im->maxval) - log10(im->minval);
533 ya = pow((double) 10, floor(log10(im->minval)));
534 while (ya < im->minval)
537 return; /* don't have y=10^x gridline */
539 if (yb <= im->maxval) {
540 /* we have at least 2 y=10^x gridlines.
541 Make sure distance between them in pixels
542 are an integer by expanding im->maxval */
543 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
544 double factor = y_pixel_delta / floor(y_pixel_delta);
545 double new_log10_range = factor * log10_range;
546 double new_ymax_log10 = log10(im->minval) + new_log10_range;
548 im->maxval = pow(10, new_ymax_log10);
549 ytr(im, DNAN); /* reset precalc */
550 log10_range = log10(im->maxval) - log10(im->minval);
552 /* make sure first y=10^x gridline is located on
553 integer pixel position by moving scale slightly
554 downwards (sub-pixel movement) */
555 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
556 ypixfrac = ypix - floor(ypix);
557 if (ypixfrac > 0 && ypixfrac < 1) {
558 double yfrac = ypixfrac / im->ysize;
560 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
561 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
562 ytr(im, DNAN); /* reset precalc */
565 /* Make sure we have an integer pixel distance between
566 each minor gridline */
567 double ypos1 = ytr(im, im->minval);
568 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
569 double y_pixel_delta = ypos1 - ypos2;
570 double factor = y_pixel_delta / floor(y_pixel_delta);
571 double new_range = factor * (im->maxval - im->minval);
572 double gridstep = im->ygrid_scale.gridstep;
573 double minor_y, minor_y_px, minor_y_px_frac;
575 if (im->maxval > 0.0)
576 im->maxval = im->minval + new_range;
578 im->minval = im->maxval - new_range;
579 ytr(im, DNAN); /* reset precalc */
580 /* make sure first minor gridline is on integer pixel y coord */
581 minor_y = gridstep * floor(im->minval / gridstep);
582 while (minor_y < im->minval)
584 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
585 minor_y_px_frac = minor_y_px - floor(minor_y_px);
586 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
587 double yfrac = minor_y_px_frac / im->ysize;
588 double range = im->maxval - im->minval;
590 im->minval = im->minval - yfrac * range;
591 im->maxval = im->maxval - yfrac * range;
592 ytr(im, DNAN); /* reset precalc */
594 calc_horizontal_grid(im); /* recalc with changed im->maxval */
598 /* reduce data reimplementation by Alex */
601 enum cf_en cf, /* which consolidation function ? */
602 unsigned long cur_step, /* step the data currently is in */
603 time_t *start, /* start, end and step as requested ... */
604 time_t *end, /* ... by the application will be ... */
605 unsigned long *step, /* ... adjusted to represent reality */
606 unsigned long *ds_cnt, /* number of data sources in file */
608 { /* two dimensional array containing the data */
609 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
610 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
612 rrd_value_t *srcptr, *dstptr;
614 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
617 row_cnt = ((*end) - (*start)) / cur_step;
623 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
624 row_cnt, reduce_factor, *start, *end, cur_step);
625 for (col = 0; col < row_cnt; col++) {
626 printf("time %10lu: ", *start + (col + 1) * cur_step);
627 for (i = 0; i < *ds_cnt; i++)
628 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
633 /* We have to combine [reduce_factor] rows of the source
634 ** into one row for the destination. Doing this we also
635 ** need to take care to combine the correct rows. First
636 ** alter the start and end time so that they are multiples
637 ** of the new step time. We cannot reduce the amount of
638 ** time so we have to move the end towards the future and
639 ** the start towards the past.
641 end_offset = (*end) % (*step);
642 start_offset = (*start) % (*step);
644 /* If there is a start offset (which cannot be more than
645 ** one destination row), skip the appropriate number of
646 ** source rows and one destination row. The appropriate
647 ** number is what we do know (start_offset/cur_step) of
648 ** the new interval (*step/cur_step aka reduce_factor).
651 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
652 printf("row_cnt before: %lu\n", row_cnt);
655 (*start) = (*start) - start_offset;
656 skiprows = reduce_factor - start_offset / cur_step;
657 srcptr += skiprows * *ds_cnt;
658 for (col = 0; col < (*ds_cnt); col++)
663 printf("row_cnt between: %lu\n", row_cnt);
666 /* At the end we have some rows that are not going to be
667 ** used, the amount is end_offset/cur_step
670 (*end) = (*end) - end_offset + (*step);
671 skiprows = end_offset / cur_step;
675 printf("row_cnt after: %lu\n", row_cnt);
678 /* Sanity check: row_cnt should be multiple of reduce_factor */
679 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
681 if (row_cnt % reduce_factor) {
682 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
683 row_cnt, reduce_factor);
684 printf("BUG in reduce_data()\n");
688 /* Now combine reduce_factor intervals at a time
689 ** into one interval for the destination.
692 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
693 for (col = 0; col < (*ds_cnt); col++) {
694 rrd_value_t newval = DNAN;
695 unsigned long validval = 0;
697 for (i = 0; i < reduce_factor; i++) {
698 if (isnan(srcptr[i * (*ds_cnt) + col])) {
703 newval = srcptr[i * (*ds_cnt) + col];
711 newval += srcptr[i * (*ds_cnt) + col];
714 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
717 /* an interval contains a failure if any subintervals contained a failure */
719 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
722 newval = srcptr[i * (*ds_cnt) + col];
747 srcptr += (*ds_cnt) * reduce_factor;
748 row_cnt -= reduce_factor;
750 /* If we had to alter the endtime, we didn't have enough
751 ** source rows to fill the last row. Fill it with NaN.
754 for (col = 0; col < (*ds_cnt); col++)
757 row_cnt = ((*end) - (*start)) / *step;
759 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
760 row_cnt, *start, *end, *step);
761 for (col = 0; col < row_cnt; col++) {
762 printf("time %10lu: ", *start + (col + 1) * (*step));
763 for (i = 0; i < *ds_cnt; i++)
764 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
771 /* get the data required for the graphs from the
780 /* pull the data from the rrd files ... */
781 for (i = 0; i < (int) im->gdes_c; i++) {
782 /* only GF_DEF elements fetch data */
783 if (im->gdes[i].gf != GF_DEF)
787 /* do we have it already ? */
788 for (ii = 0; ii < i; ii++) {
789 if (im->gdes[ii].gf != GF_DEF)
791 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
792 && (im->gdes[i].cf == im->gdes[ii].cf)
793 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
794 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
795 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
796 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
797 /* OK, the data is already there.
798 ** Just copy the header portion
800 im->gdes[i].start = im->gdes[ii].start;
801 im->gdes[i].end = im->gdes[ii].end;
802 im->gdes[i].step = im->gdes[ii].step;
803 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
804 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
805 im->gdes[i].data = im->gdes[ii].data;
806 im->gdes[i].data_first = 0;
813 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
815 if ((rrd_fetch_fn(im->gdes[i].rrd,
821 &im->gdes[i].ds_namv,
822 &im->gdes[i].data)) == -1) {
825 im->gdes[i].data_first = 1;
827 if (ft_step < im->gdes[i].step) {
828 reduce_data(im->gdes[i].cf_reduce,
833 &im->gdes[i].ds_cnt, &im->gdes[i].data);
835 im->gdes[i].step = ft_step;
839 /* lets see if the required data source is really there */
840 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
841 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
845 if (im->gdes[i].ds == -1) {
846 rrd_set_error("No DS called '%s' in '%s'",
847 im->gdes[i].ds_nam, im->gdes[i].rrd);
855 /* evaluate the expressions in the CDEF functions */
857 /*************************************************************
859 *************************************************************/
861 long find_var_wrapper(
865 return find_var((image_desc_t *) arg1, key);
868 /* find gdes containing var*/
875 for (ii = 0; ii < im->gdes_c - 1; ii++) {
876 if ((im->gdes[ii].gf == GF_DEF
877 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
878 && (strcmp(im->gdes[ii].vname, key) == 0)) {
885 /* find the largest common denominator for all the numbers
886 in the 0 terminated num array */
893 for (i = 0; num[i + 1] != 0; i++) {
895 rest = num[i] % num[i + 1];
901 /* return i==0?num[i]:num[i-1]; */
905 /* run the rpn calculator on all the VDEF and CDEF arguments */
912 long *steparray, rpi;
917 rpnstack_init(&rpnstack);
919 for (gdi = 0; gdi < im->gdes_c; gdi++) {
920 /* Look for GF_VDEF and GF_CDEF in the same loop,
921 * so CDEFs can use VDEFs and vice versa
923 switch (im->gdes[gdi].gf) {
927 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
929 /* remove current shift */
930 vdp->start -= vdp->shift;
931 vdp->end -= vdp->shift;
934 if (im->gdes[gdi].shidx >= 0)
935 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
938 vdp->shift = im->gdes[gdi].shval;
940 /* normalize shift to multiple of consolidated step */
941 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
944 vdp->start += vdp->shift;
945 vdp->end += vdp->shift;
949 /* A VDEF has no DS. This also signals other parts
950 * of rrdtool that this is a VDEF value, not a CDEF.
952 im->gdes[gdi].ds_cnt = 0;
953 if (vdef_calc(im, gdi)) {
954 rrd_set_error("Error processing VDEF '%s'",
955 im->gdes[gdi].vname);
956 rpnstack_free(&rpnstack);
961 im->gdes[gdi].ds_cnt = 1;
962 im->gdes[gdi].ds = 0;
963 im->gdes[gdi].data_first = 1;
964 im->gdes[gdi].start = 0;
965 im->gdes[gdi].end = 0;
970 /* Find the variables in the expression.
971 * - VDEF variables are substituted by their values
972 * and the opcode is changed into OP_NUMBER.
973 * - CDEF variables are analized for their step size,
974 * the lowest common denominator of all the step
975 * sizes of the data sources involved is calculated
976 * and the resulting number is the step size for the
977 * resulting data source.
979 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
980 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
981 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
982 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
984 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
987 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
988 im->gdes[gdi].vname, im->gdes[ptr].vname);
989 printf("DEBUG: value from vdef is %f\n",
990 im->gdes[ptr].vf.val);
992 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
993 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
994 } else { /* normal variables and PREF(variables) */
996 /* add one entry to the array that keeps track of the step sizes of the
997 * data sources going into the CDEF. */
999 rrd_realloc(steparray,
1001 1) * sizeof(*steparray))) == NULL) {
1002 rrd_set_error("realloc steparray");
1003 rpnstack_free(&rpnstack);
1007 steparray[stepcnt - 1] = im->gdes[ptr].step;
1009 /* adjust start and end of cdef (gdi) so
1010 * that it runs from the latest start point
1011 * to the earliest endpoint of any of the
1012 * rras involved (ptr)
1015 if (im->gdes[gdi].start < im->gdes[ptr].start)
1016 im->gdes[gdi].start = im->gdes[ptr].start;
1018 if (im->gdes[gdi].end == 0 ||
1019 im->gdes[gdi].end > im->gdes[ptr].end)
1020 im->gdes[gdi].end = im->gdes[ptr].end;
1022 /* store pointer to the first element of
1023 * the rra providing data for variable,
1024 * further save step size and data source
1027 im->gdes[gdi].rpnp[rpi].data =
1028 im->gdes[ptr].data + im->gdes[ptr].ds;
1029 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1030 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1032 /* backoff the *.data ptr; this is done so
1033 * rpncalc() function doesn't have to treat
1034 * the first case differently
1036 } /* if ds_cnt != 0 */
1037 } /* if OP_VARIABLE */
1038 } /* loop through all rpi */
1040 /* move the data pointers to the correct period */
1041 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1042 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1043 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1044 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1046 im->gdes[gdi].start - im->gdes[ptr].start;
1049 im->gdes[gdi].rpnp[rpi].data +=
1050 (diff / im->gdes[ptr].step) *
1051 im->gdes[ptr].ds_cnt;
1055 if (steparray == NULL) {
1056 rrd_set_error("rpn expressions without DEF"
1057 " or CDEF variables are not supported");
1058 rpnstack_free(&rpnstack);
1061 steparray[stepcnt] = 0;
1062 /* Now find the resulting step. All steps in all
1063 * used RRAs have to be visited
1065 im->gdes[gdi].step = lcd(steparray);
1067 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1068 im->gdes[gdi].start)
1069 / im->gdes[gdi].step)
1070 * sizeof(double))) == NULL) {
1071 rrd_set_error("malloc im->gdes[gdi].data");
1072 rpnstack_free(&rpnstack);
1076 /* Step through the new cdef results array and
1077 * calculate the values
1079 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1080 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1081 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1083 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1084 * in this case we are advancing by timesteps;
1085 * we use the fact that time_t is a synonym for long
1087 if (rpn_calc(rpnp, &rpnstack, (long) now,
1088 im->gdes[gdi].data, ++dataidx) == -1) {
1089 /* rpn_calc sets the error string */
1090 rpnstack_free(&rpnstack);
1093 } /* enumerate over time steps within a CDEF */
1098 } /* enumerate over CDEFs */
1099 rpnstack_free(&rpnstack);
1103 /* massage data so, that we get one value for each x coordinate in the graph */
1108 double pixstep = (double) (im->end - im->start)
1109 / (double) im->xsize; /* how much time
1110 passes in one pixel */
1112 double minval = DNAN, maxval = DNAN;
1114 unsigned long gr_time;
1116 /* memory for the processed data */
1117 for (i = 0; i < im->gdes_c; i++) {
1118 if ((im->gdes[i].gf == GF_LINE) ||
1119 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1120 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1121 * sizeof(rrd_value_t))) == NULL) {
1122 rrd_set_error("malloc data_proc");
1128 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1131 gr_time = im->start + pixstep * i; /* time of the current step */
1134 for (ii = 0; ii < im->gdes_c; ii++) {
1137 switch (im->gdes[ii].gf) {
1141 if (!im->gdes[ii].stack)
1143 value = im->gdes[ii].yrule;
1144 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1145 /* The time of the data doesn't necessarily match
1146 ** the time of the graph. Beware.
1148 vidx = im->gdes[ii].vidx;
1149 if (im->gdes[vidx].gf == GF_VDEF) {
1150 value = im->gdes[vidx].vf.val;
1152 if (((long int) gr_time >=
1153 (long int) im->gdes[vidx].start)
1154 && ((long int) gr_time <=
1155 (long int) im->gdes[vidx].end)) {
1156 value = im->gdes[vidx].data[(unsigned long)
1162 im->gdes[vidx].step)
1163 * im->gdes[vidx].ds_cnt +
1170 if (!isnan(value)) {
1172 im->gdes[ii].p_data[i] = paintval;
1173 /* GF_TICK: the data values are not
1174 ** relevant for min and max
1176 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1177 if ((isnan(minval) || paintval < minval) &&
1178 !(im->logarithmic && paintval <= 0.0))
1180 if (isnan(maxval) || paintval > maxval)
1184 im->gdes[ii].p_data[i] = DNAN;
1189 ("STACK should already be turned into LINE or AREA here");
1198 /* if min or max have not been asigned a value this is because
1199 there was no data in the graph ... this is not good ...
1200 lets set these to dummy values then ... */
1202 if (im->logarithmic) {
1214 /* adjust min and max values */
1215 if (isnan(im->minval)
1216 /* don't adjust low-end with log scale *//* why not? */
1217 || ((!im->rigid) && im->minval > minval)
1219 if (im->logarithmic)
1220 im->minval = minval * 0.5;
1222 im->minval = minval;
1224 if (isnan(im->maxval)
1225 || (!im->rigid && im->maxval < maxval)
1227 if (im->logarithmic)
1228 im->maxval = maxval * 2.0;
1230 im->maxval = maxval;
1232 /* make sure min is smaller than max */
1233 if (im->minval > im->maxval) {
1234 im->minval = 0.99 * im->maxval;
1237 /* make sure min and max are not equal */
1238 if (im->minval == im->maxval) {
1240 if (!im->logarithmic) {
1243 /* make sure min and max are not both zero */
1244 if (im->maxval == 0.0) {
1253 /* identify the point where the first gridline, label ... gets placed */
1255 time_t find_first_time(
1256 time_t start, /* what is the initial time */
1257 enum tmt_en baseint, /* what is the basic interval */
1258 long basestep /* how many if these do we jump a time */
1263 localtime_r(&start, &tm);
1267 tm. tm_sec -= tm.tm_sec % basestep;
1272 tm. tm_min -= tm.tm_min % basestep;
1278 tm. tm_hour -= tm.tm_hour % basestep;
1282 /* we do NOT look at the basestep for this ... */
1289 /* we do NOT look at the basestep for this ... */
1293 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1295 if (tm.tm_wday == 0)
1296 tm. tm_mday -= 7; /* we want the *previous* monday */
1304 tm. tm_mon -= tm.tm_mon % basestep;
1315 tm.tm_year + 1900) %basestep;
1321 /* identify the point where the next gridline, label ... gets placed */
1322 time_t find_next_time(
1323 time_t current, /* what is the initial time */
1324 enum tmt_en baseint, /* what is the basic interval */
1325 long basestep /* how many if these do we jump a time */
1331 localtime_r(¤t, &tm);
1336 tm. tm_sec += basestep;
1340 tm. tm_min += basestep;
1344 tm. tm_hour += basestep;
1348 tm. tm_mday += basestep;
1352 tm. tm_mday += 7 * basestep;
1356 tm. tm_mon += basestep;
1360 tm. tm_year += basestep;
1362 madetime = mktime(&tm);
1363 } while (madetime == -1); /* this is necessary to skip impssible times
1364 like the daylight saving time skips */
1370 /* calculate values required for PRINT and GPRINT functions */
1376 long i, ii, validsteps;
1379 int graphelement = 0;
1382 double magfact = -1;
1387 /* wow initializing tmvdef is quite a task :-) */
1388 time_t now = time(NULL);
1390 localtime_r(&now, &tmvdef);
1393 for (i = 0; i < im->gdes_c; i++) {
1394 vidx = im->gdes[i].vidx;
1395 switch (im->gdes[i].gf) {
1399 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1400 rrd_set_error("realloc prdata");
1404 /* PRINT and GPRINT can now print VDEF generated values.
1405 * There's no need to do any calculations on them as these
1406 * calculations were already made.
1408 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1409 printval = im->gdes[vidx].vf.val;
1410 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1411 } else { /* need to calculate max,min,avg etcetera */
1412 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1413 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1416 for (ii = im->gdes[vidx].ds;
1417 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1418 if (!finite(im->gdes[vidx].data[ii]))
1420 if (isnan(printval)) {
1421 printval = im->gdes[vidx].data[ii];
1426 switch (im->gdes[i].cf) {
1429 case CF_DEVSEASONAL:
1433 printval += im->gdes[vidx].data[ii];
1436 printval = min(printval, im->gdes[vidx].data[ii]);
1440 printval = max(printval, im->gdes[vidx].data[ii]);
1443 printval = im->gdes[vidx].data[ii];
1446 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1447 if (validsteps > 1) {
1448 printval = (printval / validsteps);
1451 } /* prepare printval */
1453 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1454 /* Magfact is set to -1 upon entry to print_calc. If it
1455 * is still less than 0, then we need to run auto_scale.
1456 * Otherwise, put the value into the correct units. If
1457 * the value is 0, then do not set the symbol or magnification
1458 * so next the calculation will be performed again. */
1459 if (magfact < 0.0) {
1460 auto_scale(im, &printval, &si_symb, &magfact);
1461 if (printval == 0.0)
1464 printval /= magfact;
1466 *(++percent_s) = 's';
1467 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1468 auto_scale(im, &printval, &si_symb, &magfact);
1471 if (im->gdes[i].gf == GF_PRINT) {
1472 (*prdata)[prlines - 2] =
1473 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1474 (*prdata)[prlines - 1] = NULL;
1475 if (im->gdes[i].strftm) {
1476 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1477 im->gdes[i].format, &tmvdef);
1479 if (bad_format(im->gdes[i].format)) {
1480 rrd_set_error("bad format for PRINT in '%s'",
1481 im->gdes[i].format);
1484 #ifdef HAVE_SNPRINTF
1485 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1486 im->gdes[i].format, printval, si_symb);
1488 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1495 if (im->gdes[i].strftm) {
1496 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1497 im->gdes[i].format, &tmvdef);
1499 if (bad_format(im->gdes[i].format)) {
1500 rrd_set_error("bad format for GPRINT in '%s'",
1501 im->gdes[i].format);
1504 #ifdef HAVE_SNPRINTF
1505 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1506 im->gdes[i].format, printval, si_symb);
1508 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1521 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1522 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1527 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1528 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1536 #ifdef WITH_PIECHART
1544 ("STACK should already be turned into LINE or AREA here");
1549 return graphelement;
1553 /* place legends with color spots */
1559 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1560 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1561 int fill = 0, fill_last;
1563 int leg_x = border, leg_y = im->yimg;
1564 int leg_y_prev = im->yimg;
1567 int i, ii, mark = 0;
1568 char prt_fctn; /*special printfunctions */
1571 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1572 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1573 rrd_set_error("malloc for legspace");
1577 if (im->extra_flags & FULL_SIZE_MODE)
1578 leg_y = leg_y_prev =
1579 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1581 for (i = 0; i < im->gdes_c; i++) {
1584 /* hide legends for rules which are not displayed */
1586 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1587 if (im->gdes[i].gf == GF_HRULE &&
1588 (im->gdes[i].yrule < im->minval
1589 || im->gdes[i].yrule > im->maxval))
1590 im->gdes[i].legend[0] = '\0';
1592 if (im->gdes[i].gf == GF_VRULE &&
1593 (im->gdes[i].xrule < im->start
1594 || im->gdes[i].xrule > im->end))
1595 im->gdes[i].legend[0] = '\0';
1598 leg_cc = strlen(im->gdes[i].legend);
1600 /* is there a controle code ant the end of the legend string ? */
1601 /* and it is not a tab \\t */
1602 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1603 && im->gdes[i].legend[leg_cc - 1] != 't') {
1604 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1606 im->gdes[i].legend[leg_cc] = '\0';
1610 /* only valid control codes */
1611 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1616 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1618 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1619 im->gdes[i].legend, prt_fctn);
1624 /* remove exess space */
1625 if (prt_fctn == 'n') {
1629 while (prt_fctn == 'g' &&
1630 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1632 im->gdes[i].legend[leg_cc] = '\0';
1635 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1638 /* no interleg space if string ends in \g */
1639 fill += legspace[i];
1641 fill += gfx_get_text_width(im->canvas, fill + border,
1642 im->text_prop[TEXT_PROP_LEGEND].
1644 im->text_prop[TEXT_PROP_LEGEND].
1646 im->gdes[i].legend, 0);
1651 /* who said there was a special tag ... ? */
1652 if (prt_fctn == 'g') {
1655 if (prt_fctn == '\0') {
1656 if (i == im->gdes_c - 1)
1659 /* is it time to place the legends ? */
1660 if (fill > im->ximg - 2 * border) {
1675 if (prt_fctn != '\0') {
1677 if (leg_c >= 2 && prt_fctn == 'j') {
1678 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1682 if (prt_fctn == 'c')
1683 leg_x = (im->ximg - fill) / 2.0;
1684 if (prt_fctn == 'r')
1685 leg_x = im->ximg - fill - border;
1687 for (ii = mark; ii <= i; ii++) {
1688 if (im->gdes[ii].legend[0] == '\0')
1689 continue; /* skip empty legends */
1690 im->gdes[ii].leg_x = leg_x;
1691 im->gdes[ii].leg_y = leg_y;
1693 gfx_get_text_width(im->canvas, leg_x,
1694 im->text_prop[TEXT_PROP_LEGEND].
1696 im->text_prop[TEXT_PROP_LEGEND].
1698 im->gdes[ii].legend, 0)
1703 if (im->extra_flags & FULL_SIZE_MODE) {
1704 /* only add y space if there was text on the line */
1705 if (leg_x > border || prt_fctn == 's')
1706 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1707 if (prt_fctn == 's')
1708 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1710 if (leg_x > border || prt_fctn == 's')
1711 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1712 if (prt_fctn == 's')
1713 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1721 if (im->extra_flags & FULL_SIZE_MODE) {
1722 if (leg_y != leg_y_prev) {
1723 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1725 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1728 im->yimg = leg_y_prev;
1729 /* if we did place some legends we have to add vertical space */
1730 if (leg_y != im->yimg)
1731 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1738 /* create a grid on the graph. it determines what to do
1739 from the values of xsize, start and end */
1741 /* the xaxis labels are determined from the number of seconds per pixel
1742 in the requested graph */
1746 int calc_horizontal_grid(
1753 int decimals, fractionals;
1755 im->ygrid_scale.labfact = 2;
1756 range = im->maxval - im->minval;
1757 scaledrange = range / im->magfact;
1759 /* does the scale of this graph make it impossible to put lines
1760 on it? If so, give up. */
1761 if (isnan(scaledrange)) {
1765 /* find grid spaceing */
1767 if (isnan(im->ygridstep)) {
1768 if (im->extra_flags & ALTYGRID) {
1769 /* find the value with max number of digits. Get number of digits */
1772 (max(fabs(im->maxval), fabs(im->minval)) *
1773 im->viewfactor / im->magfact));
1774 if (decimals <= 0) /* everything is small. make place for zero */
1777 im->ygrid_scale.gridstep =
1779 floor(log10(range * im->viewfactor / im->magfact))) /
1780 im->viewfactor * im->magfact;
1782 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1783 im->ygrid_scale.gridstep = 0.1;
1784 /* should have at least 5 lines but no more then 15 */
1785 if (range / im->ygrid_scale.gridstep < 5)
1786 im->ygrid_scale.gridstep /= 10;
1787 if (range / im->ygrid_scale.gridstep > 15)
1788 im->ygrid_scale.gridstep *= 10;
1789 if (range / im->ygrid_scale.gridstep > 5) {
1790 im->ygrid_scale.labfact = 1;
1791 if (range / im->ygrid_scale.gridstep > 8)
1792 im->ygrid_scale.labfact = 2;
1794 im->ygrid_scale.gridstep /= 5;
1795 im->ygrid_scale.labfact = 5;
1799 (im->ygrid_scale.gridstep *
1800 (double) im->ygrid_scale.labfact * im->viewfactor /
1802 if (fractionals < 0) { /* small amplitude. */
1803 int len = decimals - fractionals + 1;
1805 if (im->unitslength < len + 2)
1806 im->unitslength = len + 2;
1807 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1808 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1810 int len = decimals + 1;
1812 if (im->unitslength < len + 2)
1813 im->unitslength = len + 2;
1814 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1815 (im->symbol != ' ' ? " %c" : ""));
1818 for (i = 0; ylab[i].grid > 0; i++) {
1819 pixel = im->ysize / (scaledrange / ylab[i].grid);
1825 for (i = 0; i < 4; i++) {
1826 if (pixel * ylab[gridind].lfac[i] >=
1827 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1828 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1833 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1836 im->ygrid_scale.gridstep = im->ygridstep;
1837 im->ygrid_scale.labfact = im->ylabfact;
1842 int draw_horizontal_grid(
1847 char graph_label[100];
1849 double X0 = im->xorigin;
1850 double X1 = im->xorigin + im->xsize;
1852 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1853 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1857 im->ygrid_scale.gridstep / (double) im->magfact *
1858 (double) im->viewfactor;
1859 MaxY = scaledstep * (double) egrid;
1860 for (i = sgrid; i <= egrid; i++) {
1861 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1862 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1864 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1865 && floor(Y0 + 0.5) <= im->yorigin) {
1866 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1867 with the chosen settings. Add a label if required by settings, or if
1868 there is only one label so far and the next grid line is out of bounds. */
1869 if (i % im->ygrid_scale.labfact == 0
1871 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1872 if (im->symbol == ' ') {
1873 if (im->extra_flags & ALTYGRID) {
1874 sprintf(graph_label, im->ygrid_scale.labfmt,
1875 scaledstep * (double) i);
1878 sprintf(graph_label, "%4.1f",
1879 scaledstep * (double) i);
1881 sprintf(graph_label, "%4.0f",
1882 scaledstep * (double) i);
1886 char sisym = (i == 0 ? ' ' : im->symbol);
1888 if (im->extra_flags & ALTYGRID) {
1889 sprintf(graph_label, im->ygrid_scale.labfmt,
1890 scaledstep * (double) i, sisym);
1893 sprintf(graph_label, "%4.1f %c",
1894 scaledstep * (double) i, sisym);
1896 sprintf(graph_label, "%4.0f %c",
1897 scaledstep * (double) i, sisym);
1903 gfx_new_text(im->canvas,
1904 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1905 im->graph_col[GRC_FONT],
1906 im->text_prop[TEXT_PROP_AXIS].font,
1907 im->text_prop[TEXT_PROP_AXIS].size,
1908 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1910 gfx_new_dashed_line(im->canvas,
1913 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1914 im->grid_dash_on, im->grid_dash_off);
1916 } else if (!(im->extra_flags & NOMINOR)) {
1917 gfx_new_dashed_line(im->canvas,
1920 GRIDWIDTH, im->graph_col[GRC_GRID],
1921 im->grid_dash_on, im->grid_dash_off);
1929 /* this is frexp for base 10 */
1940 iexp = floor(log(fabs(x)) / log(10));
1941 mnt = x / pow(10.0, iexp);
1944 mnt = x / pow(10.0, iexp);
1950 static int AlmostEqual2sComplement(
1956 int aInt = *(int *) &A;
1957 int bInt = *(int *) &B;
1960 /* Make sure maxUlps is non-negative and small enough that the
1961 default NAN won't compare as equal to anything. */
1963 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1965 /* Make aInt lexicographically ordered as a twos-complement int */
1968 aInt = 0x80000000l - aInt;
1970 /* Make bInt lexicographically ordered as a twos-complement int */
1973 bInt = 0x80000000l - bInt;
1975 intDiff = abs(aInt - bInt);
1977 if (intDiff <= maxUlps)
1983 /* logaritmic horizontal grid */
1984 int horizontal_log_grid(
1987 double yloglab[][10] = {
1988 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1989 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1990 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1991 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1992 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1993 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
1996 int i, j, val_exp, min_exp;
1997 double nex; /* number of decades in data */
1998 double logscale; /* scale in logarithmic space */
1999 int exfrac = 1; /* decade spacing */
2000 int mid = -1; /* row in yloglab for major grid */
2001 double mspac; /* smallest major grid spacing (pixels) */
2002 int flab; /* first value in yloglab to use */
2003 double value, tmp, pre_value;
2005 char graph_label[100];
2007 nex = log10(im->maxval / im->minval);
2008 logscale = im->ysize / nex;
2010 /* major spacing for data with high dynamic range */
2011 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2018 /* major spacing for less dynamic data */
2020 /* search best row in yloglab */
2022 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2023 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2024 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2025 && yloglab[mid][0] > 0);
2029 /* find first value in yloglab */
2031 yloglab[mid][flab] < 10
2032 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2033 if (yloglab[mid][flab] == 10.0) {
2038 if (val_exp % exfrac)
2039 val_exp += abs(-val_exp % exfrac);
2042 X1 = im->xorigin + im->xsize;
2048 value = yloglab[mid][flab] * pow(10.0, val_exp);
2049 if (AlmostEqual2sComplement(value, pre_value, 4))
2050 break; /* it seems we are not converging */
2054 Y0 = ytr(im, value);
2055 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2058 /* major grid line */
2059 gfx_new_dashed_line(im->canvas,
2062 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2063 im->grid_dash_on, im->grid_dash_off);
2066 if (im->extra_flags & FORCE_UNITS_SI) {
2071 scale = floor(val_exp / 3.0);
2073 pvalue = pow(10.0, val_exp % 3);
2075 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2076 pvalue *= yloglab[mid][flab];
2078 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2079 ((scale + si_symbcenter) >= 0))
2080 symbol = si_symbol[scale + si_symbcenter];
2084 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2086 sprintf(graph_label, "%3.0e", value);
2087 gfx_new_text(im->canvas,
2088 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2089 im->graph_col[GRC_FONT],
2090 im->text_prop[TEXT_PROP_AXIS].font,
2091 im->text_prop[TEXT_PROP_AXIS].size,
2092 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
2096 if (mid < 4 && exfrac == 1) {
2097 /* find first and last minor line behind current major line
2098 * i is the first line and j tha last */
2100 min_exp = val_exp - 1;
2101 for (i = 1; yloglab[mid][i] < 10.0; i++);
2102 i = yloglab[mid][i - 1] + 1;
2106 i = yloglab[mid][flab - 1] + 1;
2107 j = yloglab[mid][flab];
2110 /* draw minor lines below current major line */
2111 for (; i < j; i++) {
2113 value = i * pow(10.0, min_exp);
2114 if (value < im->minval)
2117 Y0 = ytr(im, value);
2118 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2122 gfx_new_dashed_line(im->canvas,
2125 GRIDWIDTH, im->graph_col[GRC_GRID],
2126 im->grid_dash_on, im->grid_dash_off);
2128 } else if (exfrac > 1) {
2129 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2130 value = pow(10.0, i);
2131 if (value < im->minval)
2134 Y0 = ytr(im, value);
2135 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2139 gfx_new_dashed_line(im->canvas,
2142 GRIDWIDTH, im->graph_col[GRC_GRID],
2143 im->grid_dash_on, im->grid_dash_off);
2148 if (yloglab[mid][++flab] == 10.0) {
2154 /* draw minor lines after highest major line */
2155 if (mid < 4 && exfrac == 1) {
2156 /* find first and last minor line below current major line
2157 * i is the first line and j tha last */
2159 min_exp = val_exp - 1;
2160 for (i = 1; yloglab[mid][i] < 10.0; i++);
2161 i = yloglab[mid][i - 1] + 1;
2165 i = yloglab[mid][flab - 1] + 1;
2166 j = yloglab[mid][flab];
2169 /* draw minor lines below current major line */
2170 for (; i < j; i++) {
2172 value = i * pow(10.0, min_exp);
2173 if (value < im->minval)
2176 Y0 = ytr(im, value);
2177 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2181 gfx_new_dashed_line(im->canvas,
2184 GRIDWIDTH, im->graph_col[GRC_GRID],
2185 im->grid_dash_on, im->grid_dash_off);
2188 /* fancy minor gridlines */
2189 else if (exfrac > 1) {
2190 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2191 value = pow(10.0, i);
2192 if (value < im->minval)
2195 Y0 = ytr(im, value);
2196 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2200 gfx_new_dashed_line(im->canvas,
2203 GRIDWIDTH, im->graph_col[GRC_GRID],
2204 im->grid_dash_on, im->grid_dash_off);
2215 int xlab_sel; /* which sort of label and grid ? */
2216 time_t ti, tilab, timajor;
2218 char graph_label[100];
2219 double X0, Y0, Y1; /* points for filled graph and more */
2222 /* the type of time grid is determined by finding
2223 the number of seconds per pixel in the graph */
2226 if (im->xlab_user.minsec == -1) {
2227 factor = (im->end - im->start) / im->xsize;
2229 while (xlab[xlab_sel + 1].minsec != -1
2230 && xlab[xlab_sel + 1].minsec <= factor) {
2232 } /* pick the last one */
2233 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2234 && xlab[xlab_sel].length > (im->end - im->start)) {
2236 } /* go back to the smallest size */
2237 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2238 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2239 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2240 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2241 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2242 im->xlab_user.labst = xlab[xlab_sel].labst;
2243 im->xlab_user.precis = xlab[xlab_sel].precis;
2244 im->xlab_user.stst = xlab[xlab_sel].stst;
2247 /* y coords are the same for every line ... */
2249 Y1 = im->yorigin - im->ysize;
2252 /* paint the minor grid */
2253 if (!(im->extra_flags & NOMINOR)) {
2254 for (ti = find_first_time(im->start,
2255 im->xlab_user.gridtm,
2256 im->xlab_user.gridst),
2257 timajor = find_first_time(im->start,
2258 im->xlab_user.mgridtm,
2259 im->xlab_user.mgridst);
2262 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2264 /* are we inside the graph ? */
2265 if (ti < im->start || ti > im->end)
2267 while (timajor < ti) {
2268 timajor = find_next_time(timajor,
2269 im->xlab_user.mgridtm,
2270 im->xlab_user.mgridst);
2273 continue; /* skip as falls on major grid line */
2275 gfx_new_dashed_line(im->canvas, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2276 im->graph_col[GRC_GRID],
2277 im->grid_dash_on, im->grid_dash_off);
2282 /* paint the major grid */
2283 for (ti = find_first_time(im->start,
2284 im->xlab_user.mgridtm,
2285 im->xlab_user.mgridst);
2287 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2289 /* are we inside the graph ? */
2290 if (ti < im->start || ti > im->end)
2293 gfx_new_dashed_line(im->canvas, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2294 im->graph_col[GRC_MGRID],
2295 im->grid_dash_on, im->grid_dash_off);
2298 /* paint the labels below the graph */
2299 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2300 im->xlab_user.labtm,
2301 im->xlab_user.labst);
2302 ti <= im->end - im->xlab_user.precis / 2;
2303 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2305 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2306 /* are we inside the graph ? */
2307 if (tilab < im->start || tilab > im->end)
2311 localtime_r(&tilab, &tm);
2312 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2314 # error "your libc has no strftime I guess we'll abort the exercise here."
2316 gfx_new_text(im->canvas,
2318 Y0 + im->text_prop[TEXT_PROP_AXIS].size * 1.4 + 5,
2319 im->graph_col[GRC_FONT],
2320 im->text_prop[TEXT_PROP_AXIS].font,
2321 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2322 GFX_H_CENTER, GFX_V_BOTTOM, graph_label);
2332 /* draw x and y axis */
2333 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2334 im->xorigin+im->xsize,im->yorigin-im->ysize,
2335 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2337 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2338 im->xorigin+im->xsize,im->yorigin-im->ysize,
2339 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2341 gfx_new_line(im->canvas, im->xorigin - 4, im->yorigin,
2342 im->xorigin + im->xsize + 4, im->yorigin,
2343 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2345 gfx_new_line(im->canvas, im->xorigin, im->yorigin + 4,
2346 im->xorigin, im->yorigin - im->ysize - 4,
2347 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2350 /* arrow for X and Y axis direction */
2351 gfx_new_area(im->canvas, im->xorigin + im->xsize + 2, im->yorigin - 2, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin + 0.5, /* LINEOFFSET */
2352 im->graph_col[GRC_ARROW]);
2354 gfx_new_area(im->canvas, im->xorigin - 2, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin + 0.5, im->yorigin - im->ysize - 7, /* LINEOFFSET */
2355 im->graph_col[GRC_ARROW]);
2364 double X0, Y0; /* points for filled graph and more */
2367 /* draw 3d border */
2368 node = gfx_new_area(im->canvas, 0, im->yimg,
2369 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2370 gfx_add_point(node, im->ximg - 2, 2);
2371 gfx_add_point(node, im->ximg, 0);
2372 gfx_add_point(node, 0, 0);
2373 /* gfx_add_point( node , 0,im->yimg ); */
2375 node = gfx_new_area(im->canvas, 2, im->yimg - 2,
2376 im->ximg - 2, im->yimg - 2,
2377 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2378 gfx_add_point(node, im->ximg, 0);
2379 gfx_add_point(node, im->ximg, im->yimg);
2380 gfx_add_point(node, 0, im->yimg);
2381 /* gfx_add_point( node , 0,im->yimg ); */
2384 if (im->draw_x_grid == 1)
2387 if (im->draw_y_grid == 1) {
2388 if (im->logarithmic) {
2389 res = horizontal_log_grid(im);
2391 res = draw_horizontal_grid(im);
2394 /* dont draw horizontal grid if there is no min and max val */
2396 char *nodata = "No Data found";
2398 gfx_new_text(im->canvas, im->ximg / 2,
2399 (2 * im->yorigin - im->ysize) / 2,
2400 im->graph_col[GRC_FONT],
2401 im->text_prop[TEXT_PROP_AXIS].font,
2402 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2403 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2407 /* yaxis unit description */
2408 gfx_new_text(im->canvas,
2409 10, (im->yorigin - im->ysize / 2),
2410 im->graph_col[GRC_FONT],
2411 im->text_prop[TEXT_PROP_UNIT].font,
2412 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2413 RRDGRAPH_YLEGEND_ANGLE,
2414 GFX_H_LEFT, GFX_V_CENTER, im->ylegend);
2417 gfx_new_text(im->canvas,
2418 im->ximg / 2, im->text_prop[TEXT_PROP_TITLE].size * 1.3 + 4,
2419 im->graph_col[GRC_FONT],
2420 im->text_prop[TEXT_PROP_TITLE].font,
2421 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2422 GFX_H_CENTER, GFX_V_CENTER, im->title);
2423 /* rrdtool 'logo' */
2424 gfx_new_text(im->canvas,
2426 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2427 im->text_prop[TEXT_PROP_AXIS].font,
2428 5.5, im->tabwidth, 270,
2429 GFX_H_RIGHT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2431 /* graph watermark */
2432 if (im->watermark[0] != '\0') {
2433 gfx_new_text(im->canvas,
2434 im->ximg / 2, im->yimg - 6,
2435 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2436 im->text_prop[TEXT_PROP_AXIS].font,
2437 5.5, im->tabwidth, 0,
2438 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2442 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2443 for (i = 0; i < im->gdes_c; i++) {
2444 if (im->gdes[i].legend[0] == '\0')
2447 /* im->gdes[i].leg_y is the bottom of the legend */
2448 X0 = im->gdes[i].leg_x;
2449 Y0 = im->gdes[i].leg_y;
2450 gfx_new_text(im->canvas, X0, Y0,
2451 im->graph_col[GRC_FONT],
2452 im->text_prop[TEXT_PROP_LEGEND].font,
2453 im->text_prop[TEXT_PROP_LEGEND].size,
2454 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2455 im->gdes[i].legend);
2456 /* The legend for GRAPH items starts with "M " to have
2457 enough space for the box */
2458 if (im->gdes[i].gf != GF_PRINT &&
2459 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2462 boxH = gfx_get_text_width(im->canvas, 0,
2463 im->text_prop[TEXT_PROP_LEGEND].
2465 im->text_prop[TEXT_PROP_LEGEND].
2466 size, im->tabwidth, "o", 0) * 1.2;
2469 /* make sure transparent colors show up the same way as in the graph */
2470 node = gfx_new_area(im->canvas,
2473 X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2474 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2476 node = gfx_new_area(im->canvas,
2478 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2479 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2480 node = gfx_new_line(im->canvas,
2482 X0, Y0, 1.0, im->graph_col[GRC_FRAME]);
2483 gfx_add_point(node, X0 + boxH, Y0);
2484 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2485 gfx_close_path(node);
2492 /*****************************************************
2493 * lazy check make sure we rely need to create this graph
2494 *****************************************************/
2501 struct stat imgstat;
2504 return 0; /* no lazy option */
2505 if (stat(im->graphfile, &imgstat) != 0)
2506 return 0; /* can't stat */
2507 /* one pixel in the existing graph is more then what we would
2509 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2511 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2512 return 0; /* the file does not exist */
2513 switch (im->canvas->imgformat) {
2515 size = PngSize(fd, &(im->ximg), &(im->yimg));
2524 #ifdef WITH_PIECHART
2536 double step = M_PI / 50; /* Number of iterations for the circle;
2537 ** 10 is definitely too low, more than
2538 ** 50 seems to be overkill
2541 /* Strange but true: we have to work clockwise or else
2542 ** anti aliasing nor transparency don't work.
2544 ** This test is here to make sure we do it right, also
2545 ** this makes the for...next loop more easy to implement.
2546 ** The return will occur if the user enters a negative number
2547 ** (which shouldn't be done according to the specs) or if the
2548 ** programmers do something wrong (which, as we all know, never
2549 ** happens anyway :)
2551 if (endangle < startangle)
2554 /* Hidden feature: Radius decreases each full circle */
2556 while (angle >= 2 * M_PI) {
2561 node = gfx_new_area(im->canvas,
2562 PieCenterX + sin(startangle) * Radius,
2563 PieCenterY - cos(startangle) * Radius,
2566 PieCenterX + sin(endangle) * Radius,
2567 PieCenterY - cos(endangle) * Radius, color);
2568 for (angle = endangle; angle - startangle >= step; angle -= step) {
2570 PieCenterX + sin(angle) * Radius,
2571 PieCenterY - cos(angle) * Radius);
2577 int graph_size_location(
2580 #ifdef WITH_PIECHART
2586 /* The actual size of the image to draw is determined from
2587 ** several sources. The size given on the command line is
2588 ** the graph area but we need more as we have to draw labels
2589 ** and other things outside the graph area
2592 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2593 #ifdef WITH_PIECHART
2598 Xlegend = 0, Ylegend = 0,
2600 Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2602 if (im->extra_flags & ONLY_GRAPH) {
2604 im->ximg = im->xsize;
2605 im->yimg = im->ysize;
2606 im->yorigin = im->ysize;
2611 /** +---+--------------------------------------------+
2612 ** | y |...............graph title..................|
2613 ** | +---+-------------------------------+--------+
2616 ** | i | a | | pie |
2617 ** | s | x | main graph area | chart |
2622 ** | l | b +-------------------------------+--------+
2623 ** | e | l | x axis labels | |
2624 ** +---+---+-------------------------------+--------+
2625 ** |....................legends.....................|
2626 ** +------------------------------------------------+
2628 ** +------------------------------------------------+
2631 if (im->ylegend[0] != '\0') {
2632 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2635 if (im->title[0] != '\0') {
2636 /* The title is placed "inbetween" two text lines so it
2637 ** automatically has some vertical spacing. The horizontal
2638 ** spacing is added here, on each side.
2640 /* if necessary, reduce the font size of the title until it fits the image width */
2641 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2645 if (im->draw_x_grid) {
2646 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2648 if (im->draw_y_grid || im->forceleftspace) {
2649 Xylabel = gfx_get_text_width(im->canvas, 0,
2650 im->text_prop[TEXT_PROP_AXIS].font,
2651 im->text_prop[TEXT_PROP_AXIS].size,
2653 "0", 0) * im->unitslength;
2657 if (im->extra_flags & FULL_SIZE_MODE) {
2658 /* The actual size of the image to draw has been determined by the user.
2659 ** The graph area is the space remaining after accounting for the legend,
2660 ** the watermark, the pie chart, the axis labels, and the title.
2663 im->ximg = im->xsize;
2664 im->yimg = im->ysize;
2665 im->yorigin = im->ysize;
2669 im->yorigin += Ytitle;
2671 #ifdef WITH_PIECHART
2673 im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2679 /* Now calculate the total size. Insert some spacing where
2680 desired. im->xorigin and im->yorigin need to correspond
2681 with the lower left corner of the main graph area or, if
2682 this one is not set, the imaginary box surrounding the
2685 /* Initial size calculation for the main graph area */
2686 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2688 Xmain -= Xspacing; /* put space between main graph area and right edge */
2690 #ifdef WITH_PIECHART
2691 Xmain -= Xpie; /* remove pie width from main graph area */
2693 Xmain -= Xspacing; /* put space between pie and main graph area */
2696 im->xorigin = Xspacing + Xylabel;
2698 /* the length of the title should not influence with width of the graph
2699 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2701 if (Xvertical) { /* unit description */
2703 im->xorigin += Xvertical;
2708 /* The vertical size of the image is known in advance. The main graph area
2709 ** (Ymain) and im->yorigin must be set according to the space requirements
2710 ** of the legend and the axis labels.
2713 /* Determine where to place the legends onto the image.
2714 ** Set Ymain and adjust im->yorigin to match the space requirements.
2716 if (leg_place(im, &Ymain) == -1)
2719 #ifdef WITH_PIECHART
2720 /* if (im->yimg < Ypie) im->yimg = Ypie; * not sure what do about this */
2723 /* remove title space *or* some padding above the graph from the main graph area */
2727 Ymain -= 1.5 * Yspacing;
2730 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2731 if (im->watermark[0] != '\0') {
2732 Ymain -= Ywatermark;
2737 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2739 /* The actual size of the image to draw is determined from
2740 ** several sources. The size given on the command line is
2741 ** the graph area but we need more as we have to draw labels
2742 ** and other things outside the graph area.
2745 if (im->ylegend[0] != '\0') {
2746 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2750 if (im->title[0] != '\0') {
2751 /* The title is placed "inbetween" two text lines so it
2752 ** automatically has some vertical spacing. The horizontal
2753 ** spacing is added here, on each side.
2755 /* don't care for the with of the title
2756 Xtitle = gfx_get_text_width(im->canvas, 0,
2757 im->text_prop[TEXT_PROP_TITLE].font,
2758 im->text_prop[TEXT_PROP_TITLE].size,
2760 im->title, 0) + 2*Xspacing; */
2761 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2768 #ifdef WITH_PIECHART
2770 im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2776 /* Now calculate the total size. Insert some spacing where
2777 desired. im->xorigin and im->yorigin need to correspond
2778 with the lower left corner of the main graph area or, if
2779 this one is not set, the imaginary box surrounding the
2782 /* The legend width cannot yet be determined, as a result we
2783 ** have problems adjusting the image to it. For now, we just
2784 ** forget about it at all; the legend will have to fit in the
2785 ** size already allocated.
2787 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2789 #ifdef WITH_PIECHART
2794 im->ximg += Xspacing;
2795 #ifdef WITH_PIECHART
2797 im->ximg += Xspacing;
2800 im->xorigin = Xspacing + Xylabel;
2802 /* the length of the title should not influence with width of the graph
2803 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2805 if (Xvertical) { /* unit description */
2806 im->ximg += Xvertical;
2807 im->xorigin += Xvertical;
2811 /* The vertical size is interesting... we need to compare
2812 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2813 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2814 ** in order to start even thinking about Ylegend or Ywatermark.
2816 ** Do it in three portions: First calculate the inner part,
2817 ** then do the legend, then adjust the total height of the img,
2818 ** adding space for a watermark if one exists;
2821 /* reserve space for main and/or pie */
2823 im->yimg = Ymain + Yxlabel;
2825 #ifdef WITH_PIECHART
2826 if (im->yimg < Ypie)
2830 im->yorigin = im->yimg - Yxlabel;
2832 /* reserve space for the title *or* some padding above the graph */
2835 im->yorigin += Ytitle;
2837 im->yimg += 1.5 * Yspacing;
2838 im->yorigin += 1.5 * Yspacing;
2840 /* reserve space for padding below the graph */
2841 im->yimg += Yspacing;
2843 /* Determine where to place the legends onto the image.
2844 ** Adjust im->yimg to match the space requirements.
2846 if (leg_place(im, 0) == -1)
2849 if (im->watermark[0] != '\0') {
2850 im->yimg += Ywatermark;
2855 if (Xlegend > im->ximg) {
2857 /* reposition Pie */
2861 #ifdef WITH_PIECHART
2862 /* The pie is placed in the upper right hand corner,
2863 ** just below the title (if any) and with sufficient
2867 im->pie_x = im->ximg - Xspacing - Xpie / 2;
2868 im->pie_y = im->yorigin - Ymain + Ypie / 2;
2870 im->pie_x = im->ximg / 2;
2871 im->pie_y = im->yorigin - Ypie / 2;
2879 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2880 /* yes we are loosing precision by doing tos with floats instead of doubles
2881 but it seems more stable this way. */
2884 /* draw that picture thing ... */
2890 int lazy = lazy_check(im);
2892 #ifdef WITH_PIECHART
2894 double PieStart = 0.0;
2899 double areazero = 0.0;
2900 graph_desc_t *lastgdes = NULL;
2902 /* if we are lazy and there is nothing to PRINT ... quit now */
2903 if (lazy && im->prt_c == 0)
2906 /* pull the data from the rrd files ... */
2908 if (data_fetch(im) == -1)
2911 /* evaluate VDEF and CDEF operations ... */
2912 if (data_calc(im) == -1)
2915 #ifdef WITH_PIECHART
2916 /* check if we need to draw a piechart */
2917 for (i = 0; i < im->gdes_c; i++) {
2918 if (im->gdes[i].gf == GF_PART) {
2925 /* calculate and PRINT and GPRINT definitions. We have to do it at
2926 * this point because it will affect the length of the legends
2927 * if there are no graph elements we stop here ...
2928 * if we are lazy, try to quit ...
2930 i = print_calc(im, calcpr);
2934 #ifdef WITH_PIECHART
2940 #ifdef WITH_PIECHART
2941 /* If there's only the pie chart to draw, signal this */
2946 /**************************************************************
2947 *** Calculating sizes and locations became a bit confusing ***
2948 *** so I moved this into a separate function. ***
2949 **************************************************************/
2950 if (graph_size_location(im, i
2951 #ifdef WITH_PIECHART
2957 /* get actual drawing data and find min and max values */
2958 if (data_proc(im) == -1)
2961 if (!im->logarithmic) {
2964 /* identify si magnitude Kilo, Mega Giga ? */
2965 if (!im->rigid && !im->logarithmic)
2966 expand_range(im); /* make sure the upper and lower limit are
2969 if (!calc_horizontal_grid(im))
2975 /* the actual graph is created by going through the individual
2976 graph elements and then drawing them */
2978 node = gfx_new_area(im->canvas,
2981 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2983 gfx_add_point(node, im->ximg, 0);
2985 #ifdef WITH_PIECHART
2986 if (piechart != 2) {
2988 node = gfx_new_area(im->canvas,
2989 im->xorigin, im->yorigin,
2990 im->xorigin + im->xsize, im->yorigin,
2991 im->xorigin + im->xsize, im->yorigin - im->ysize,
2992 im->graph_col[GRC_CANVAS]);
2994 gfx_add_point(node, im->xorigin, im->yorigin - im->ysize);
2996 if (im->minval > 0.0)
2997 areazero = im->minval;
2998 if (im->maxval < 0.0)
2999 areazero = im->maxval;
3000 #ifdef WITH_PIECHART
3004 #ifdef WITH_PIECHART
3006 pie_part(im, im->graph_col[GRC_CANVAS], im->pie_x, im->pie_y,
3007 im->piesize * 0.5, 0, 2 * M_PI);
3011 for (i = 0; i < im->gdes_c; i++) {
3012 switch (im->gdes[i].gf) {
3025 for (ii = 0; ii < im->xsize; ii++) {
3026 if (!isnan(im->gdes[i].p_data[ii]) &&
3027 im->gdes[i].p_data[ii] != 0.0) {
3028 if (im->gdes[i].yrule > 0) {
3029 gfx_new_line(im->canvas,
3030 im->xorigin + ii, im->yorigin,
3033 im->gdes[i].yrule * im->ysize, 1.0,
3035 } else if (im->gdes[i].yrule < 0) {
3036 gfx_new_line(im->canvas,
3038 im->yorigin - im->ysize,
3041 im->gdes[i].yrule) *
3042 im->ysize, 1.0, im->gdes[i].col);
3050 /* fix data points at oo and -oo */
3051 for (ii = 0; ii < im->xsize; ii++) {
3052 if (isinf(im->gdes[i].p_data[ii])) {
3053 if (im->gdes[i].p_data[ii] > 0) {
3054 im->gdes[i].p_data[ii] = im->maxval;
3056 im->gdes[i].p_data[ii] = im->minval;
3062 /* *******************************************************
3067 -------|--t-1--t--------------------------------
3069 if we know the value at time t was a then
3070 we draw a square from t-1 to t with the value a.
3072 ********************************************************* */
3073 if (im->gdes[i].col != 0x0) {
3074 /* GF_LINE and friend */
3075 if (im->gdes[i].gf == GF_LINE) {
3076 double last_y = 0.0;
3079 for (ii = 1; ii < im->xsize; ii++) {
3080 if (isnan(im->gdes[i].p_data[ii])
3081 || (im->slopemode == 1
3082 && isnan(im->gdes[i].p_data[ii - 1]))) {
3087 last_y = ytr(im, im->gdes[i].p_data[ii]);
3088 if (im->slopemode == 0) {
3089 node = gfx_new_line(im->canvas,
3090 ii - 1 + im->xorigin,
3091 last_y, ii + im->xorigin,
3093 im->gdes[i].linewidth,
3096 node = gfx_new_line(im->canvas,
3097 ii - 1 + im->xorigin,
3101 ii + im->xorigin, last_y,
3102 im->gdes[i].linewidth,
3106 double new_y = ytr(im, im->gdes[i].p_data[ii]);
3108 if (im->slopemode == 0
3109 && !AlmostEqual2sComplement(new_y, last_y,
3111 gfx_add_point(node, ii - 1 + im->xorigin,
3115 gfx_add_point(node, ii + im->xorigin, new_y);
3121 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3122 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3123 double *backY = malloc(sizeof(double) * im->xsize * 2);
3124 double *backX = malloc(sizeof(double) * im->xsize * 2);
3127 for (ii = 0; ii <= im->xsize; ii++) {
3130 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3135 && AlmostEqual2sComplement(foreY[lastI],
3137 && AlmostEqual2sComplement(foreY[lastI],
3142 node = gfx_new_area(im->canvas,
3145 foreX[cntI], foreY[cntI],
3147 while (cntI < idxI) {
3152 AlmostEqual2sComplement(foreY[lastI],
3155 AlmostEqual2sComplement(foreY[lastI],
3160 gfx_add_point(node, foreX[cntI], foreY[cntI]);
3162 gfx_add_point(node, backX[idxI], backY[idxI]);
3168 AlmostEqual2sComplement(backY[lastI],
3171 AlmostEqual2sComplement(backY[lastI],
3176 gfx_add_point(node, backX[idxI], backY[idxI]);
3185 if (ii == im->xsize)
3188 /* keep things simple for now, just draw these bars
3189 do not try to build a big and complex area */
3192 if (im->slopemode == 0 && ii == 0) {
3195 if (isnan(im->gdes[i].p_data[ii])) {
3199 ytop = ytr(im, im->gdes[i].p_data[ii]);
3200 if (lastgdes && im->gdes[i].stack) {
3201 ybase = ytr(im, lastgdes->p_data[ii]);
3203 ybase = ytr(im, areazero);
3205 if (ybase == ytop) {
3209 /* every area has to be wound clock-wise,
3210 so we have to make sur base remains base */
3212 double extra = ytop;
3217 if (im->slopemode == 0) {
3218 backY[++idxI] = ybase - 0.2;
3219 backX[idxI] = ii + im->xorigin - 1;
3220 foreY[idxI] = ytop + 0.2;
3221 foreX[idxI] = ii + im->xorigin - 1;
3223 backY[++idxI] = ybase - 0.2;
3224 backX[idxI] = ii + im->xorigin;
3225 foreY[idxI] = ytop + 0.2;
3226 foreX[idxI] = ii + im->xorigin;
3228 /* close up any remaining area */
3233 } /* else GF_LINE */
3235 /* if color != 0x0 */
3236 /* make sure we do not run into trouble when stacking on NaN */
3237 for (ii = 0; ii < im->xsize; ii++) {
3238 if (isnan(im->gdes[i].p_data[ii])) {
3239 if (lastgdes && (im->gdes[i].stack)) {
3240 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3242 im->gdes[i].p_data[ii] = areazero;
3246 lastgdes = &(im->gdes[i]);
3248 #ifdef WITH_PIECHART
3250 if (isnan(im->gdes[i].yrule)) /* fetch variable */
3251 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
3253 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
3254 pie_part(im, im->gdes[i].col,
3255 im->pie_x, im->pie_y, im->piesize * 0.4,
3256 M_PI * 2.0 * PieStart / 100.0,
3257 M_PI * 2.0 * (PieStart + im->gdes[i].yrule) / 100.0);
3258 PieStart += im->gdes[i].yrule;
3264 ("STACK should already be turned into LINE or AREA here");
3270 #ifdef WITH_PIECHART
3271 if (piechart == 2) {
3272 im->draw_x_grid = 0;
3273 im->draw_y_grid = 0;
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)
3293 gfx_new_line(im->canvas,
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)
3302 gfx_new_line(im->canvas,
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 if (strcmp(im->graphfile, "-") == 0) {
3314 fo = im->graphhandle ? im->graphhandle : stdout;
3315 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3316 /* Change translation mode for stdout to BINARY */
3317 _setmode(_fileno(fo), O_BINARY);
3320 if ((fo = fopen(im->graphfile, "wb")) == NULL) {
3321 rrd_set_error("Opening '%s' for write: %s", im->graphfile,
3322 rrd_strerror(errno));
3326 gfx_render(im->canvas, im->ximg, im->yimg, 0x00000000, fo);
3327 if (strcmp(im->graphfile, "-") != 0)
3333 /*****************************************************
3335 *****************************************************/
3342 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3343 * sizeof(graph_desc_t))) ==
3345 rrd_set_error("realloc graph_descs");
3350 im->gdes[im->gdes_c - 1].step = im->step;
3351 im->gdes[im->gdes_c - 1].step_orig = im->step;
3352 im->gdes[im->gdes_c - 1].stack = 0;
3353 im->gdes[im->gdes_c - 1].linewidth = 0;
3354 im->gdes[im->gdes_c - 1].debug = 0;
3355 im->gdes[im->gdes_c - 1].start = im->start;
3356 im->gdes[im->gdes_c - 1].start_orig = im->start;
3357 im->gdes[im->gdes_c - 1].end = im->end;
3358 im->gdes[im->gdes_c - 1].end_orig = im->end;
3359 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3360 im->gdes[im->gdes_c - 1].data = NULL;
3361 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3362 im->gdes[im->gdes_c - 1].data_first = 0;
3363 im->gdes[im->gdes_c - 1].p_data = NULL;
3364 im->gdes[im->gdes_c - 1].rpnp = NULL;
3365 im->gdes[im->gdes_c - 1].shift = 0;
3366 im->gdes[im->gdes_c - 1].col = 0x0;
3367 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3368 im->gdes[im->gdes_c - 1].format[0] = '\0';
3369 im->gdes[im->gdes_c - 1].strftm = 0;
3370 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3371 im->gdes[im->gdes_c - 1].ds = -1;
3372 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3373 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3374 im->gdes[im->gdes_c - 1].p_data = NULL;
3375 im->gdes[im->gdes_c - 1].yrule = DNAN;
3376 im->gdes[im->gdes_c - 1].xrule = 0;
3380 /* copies input untill the first unescaped colon is found
3381 or until input ends. backslashes have to be escaped as well */
3383 const char *const input,
3389 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3390 if (input[inp] == '\\' &&
3391 input[inp + 1] != '\0' &&
3392 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3393 output[outp++] = input[++inp];
3395 output[outp++] = input[inp];
3398 output[outp] = '\0';
3402 /* Some surgery done on this function, it became ridiculously big.
3404 ** - initializing now in rrd_graph_init()
3405 ** - options parsing now in rrd_graph_options()
3406 ** - script parsing now in rrd_graph_script()
3420 rrd_graph_init(&im);
3421 im.graphhandle = stream;
3423 rrd_graph_options(argc, argv, &im);
3424 if (rrd_test_error()) {
3429 if (strlen(argv[optind]) >= MAXPATH) {
3430 rrd_set_error("filename (including path) too long");
3434 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3435 im.graphfile[MAXPATH - 1] = '\0';
3437 rrd_graph_script(argc, argv, &im, 1);
3438 if (rrd_test_error()) {
3443 /* Everything is now read and the actual work can start */
3446 if (graph_paint(&im, prdata) == -1) {
3451 /* The image is generated and needs to be output.
3452 ** Also, if needed, print a line with information about the image.
3463 /* maybe prdata is not allocated yet ... lets do it now */
3464 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3465 rrd_set_error("malloc imginfo");
3470 malloc((strlen(im.imginfo) + 200 +
3471 strlen(im.graphfile)) * sizeof(char)))
3473 rrd_set_error("malloc imginfo");
3476 filename = im.graphfile + strlen(im.graphfile);
3477 while (filename > im.graphfile) {
3478 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3483 sprintf((*prdata)[0], im.imginfo, filename,
3484 (long) (im.canvas->zoom * im.ximg),
3485 (long) (im.canvas->zoom * im.yimg));
3491 void rrd_graph_init(
3499 #ifdef HAVE_SETLOCALE
3500 setlocale(LC_TIME, "");
3501 #ifdef HAVE_MBSTOWCS
3502 setlocale(LC_CTYPE, "");
3508 im->xlab_user.minsec = -1;
3514 im->ylegend[0] = '\0';
3515 im->title[0] = '\0';
3516 im->watermark[0] = '\0';
3519 im->unitsexponent = 9999;
3520 im->unitslength = 6;
3521 im->forceleftspace = 0;
3523 im->viewfactor = 1.0;
3524 im->extra_flags = 0;
3530 im->logarithmic = 0;
3531 im->ygridstep = DNAN;
3532 im->draw_x_grid = 1;
3533 im->draw_y_grid = 1;
3538 im->canvas = gfx_new_canvas();
3539 im->grid_dash_on = 1;
3540 im->grid_dash_off = 1;
3541 im->tabwidth = 40.0;
3543 for (i = 0; i < DIM(graph_col); i++)
3544 im->graph_col[i] = graph_col[i];
3546 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3549 char rrd_win_default_font[1000];
3551 windir = getenv("windir");
3552 /* %windir% is something like D:\windows or C:\winnt */
3553 if (windir != NULL) {
3554 strncpy(rrd_win_default_font, windir, 500);
3555 rrd_win_default_font[500] = '\0';
3556 strcat(rrd_win_default_font, "\\fonts\\");
3557 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3558 for (i = 0; i < DIM(text_prop); i++) {
3559 strncpy(text_prop[i].font, rrd_win_default_font,
3560 sizeof(text_prop[i].font) - 1);
3561 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3569 deffont = getenv("RRD_DEFAULT_FONT");
3570 if (deffont != NULL) {
3571 for (i = 0; i < DIM(text_prop); i++) {
3572 strncpy(text_prop[i].font, deffont,
3573 sizeof(text_prop[i].font) - 1);
3574 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3578 for (i = 0; i < DIM(text_prop); i++) {
3579 im->text_prop[i].size = text_prop[i].size;
3580 strcpy(im->text_prop[i].font, text_prop[i].font);
3584 void rrd_graph_options(
3590 char *parsetime_error = NULL;
3591 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3592 time_t start_tmp = 0, end_tmp = 0;
3594 struct rrd_time_value start_tv, end_tv;
3598 opterr = 0; /* initialize getopt */
3600 parsetime("end-24h", &start_tv);
3601 parsetime("now", &end_tv);
3603 /* defines for long options without a short equivalent. should be bytes,
3604 and may not collide with (the ASCII value of) short options */
3605 #define LONGOPT_UNITS_SI 255
3608 static struct option long_options[] = {
3609 {"start", required_argument, 0, 's'},
3610 {"end", required_argument, 0, 'e'},
3611 {"x-grid", required_argument, 0, 'x'},
3612 {"y-grid", required_argument, 0, 'y'},
3613 {"vertical-label", required_argument, 0, 'v'},
3614 {"width", required_argument, 0, 'w'},
3615 {"height", required_argument, 0, 'h'},
3616 {"full-size-mode", no_argument, 0, 'D'},
3617 {"interlaced", no_argument, 0, 'i'},
3618 {"upper-limit", required_argument, 0, 'u'},
3619 {"lower-limit", required_argument, 0, 'l'},
3620 {"rigid", no_argument, 0, 'r'},
3621 {"base", required_argument, 0, 'b'},
3622 {"logarithmic", no_argument, 0, 'o'},
3623 {"color", required_argument, 0, 'c'},
3624 {"font", required_argument, 0, 'n'},
3625 {"title", required_argument, 0, 't'},
3626 {"imginfo", required_argument, 0, 'f'},
3627 {"imgformat", required_argument, 0, 'a'},
3628 {"lazy", no_argument, 0, 'z'},
3629 {"zoom", required_argument, 0, 'm'},
3630 {"no-legend", no_argument, 0, 'g'},
3631 {"force-rules-legend", no_argument, 0, 'F'},
3632 {"only-graph", no_argument, 0, 'j'},
3633 {"alt-y-grid", no_argument, 0, 'Y'},
3634 {"no-minor", no_argument, 0, 'I'},
3635 {"slope-mode", no_argument, 0, 'E'},
3636 {"alt-autoscale", no_argument, 0, 'A'},
3637 {"alt-autoscale-min", no_argument, 0, 'J'},
3638 {"alt-autoscale-max", no_argument, 0, 'M'},
3639 {"no-gridfit", no_argument, 0, 'N'},
3640 {"units-exponent", required_argument, 0, 'X'},
3641 {"units-length", required_argument, 0, 'L'},
3642 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3643 {"step", required_argument, 0, 'S'},
3644 {"tabwidth", required_argument, 0, 'T'},
3645 {"font-render-mode", required_argument, 0, 'R'},
3646 {"font-smoothing-threshold", required_argument, 0, 'B'},
3647 {"watermark", required_argument, 0, 'W'},
3648 {"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 */
3651 int option_index = 0;
3653 int col_start, col_end;
3655 opt = getopt_long(argc, argv,
3656 "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:",
3657 long_options, &option_index);
3664 im->extra_flags |= NOMINOR;
3667 im->extra_flags |= ALTYGRID;
3670 im->extra_flags |= ALTAUTOSCALE;
3673 im->extra_flags |= ALTAUTOSCALE_MIN;
3676 im->extra_flags |= ALTAUTOSCALE_MAX;
3679 im->extra_flags |= ONLY_GRAPH;
3682 im->extra_flags |= NOLEGEND;
3685 im->extra_flags |= FORCE_RULES_LEGEND;
3687 case LONGOPT_UNITS_SI:
3688 if (im->extra_flags & FORCE_UNITS) {
3689 rrd_set_error("--units can only be used once!");
3692 if (strcmp(optarg, "si") == 0)
3693 im->extra_flags |= FORCE_UNITS_SI;
3695 rrd_set_error("invalid argument for --units: %s", optarg);
3700 im->unitsexponent = atoi(optarg);
3703 im->unitslength = atoi(optarg);
3704 im->forceleftspace = 1;
3707 im->tabwidth = atof(optarg);
3710 im->step = atoi(optarg);
3716 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3717 rrd_set_error("start time: %s", parsetime_error);
3722 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3723 rrd_set_error("end time: %s", parsetime_error);
3728 if (strcmp(optarg, "none") == 0) {
3729 im->draw_x_grid = 0;
3734 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3736 &im->xlab_user.gridst,
3738 &im->xlab_user.mgridst,
3740 &im->xlab_user.labst,
3741 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3742 strncpy(im->xlab_form, optarg + stroff,
3743 sizeof(im->xlab_form) - 1);
3744 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3745 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3746 rrd_set_error("unknown keyword %s", scan_gtm);
3748 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3750 rrd_set_error("unknown keyword %s", scan_mtm);
3752 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3754 rrd_set_error("unknown keyword %s", scan_ltm);
3757 im->xlab_user.minsec = 1;
3758 im->xlab_user.stst = im->xlab_form;
3760 rrd_set_error("invalid x-grid format");
3766 if (strcmp(optarg, "none") == 0) {
3767 im->draw_y_grid = 0;
3771 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3772 if (im->ygridstep <= 0) {
3773 rrd_set_error("grid step must be > 0");
3775 } else if (im->ylabfact < 1) {
3776 rrd_set_error("label factor must be > 0");
3780 rrd_set_error("invalid y-grid format");
3785 strncpy(im->ylegend, optarg, 150);
3786 im->ylegend[150] = '\0';
3789 im->maxval = atof(optarg);
3792 im->minval = atof(optarg);
3795 im->base = atol(optarg);
3796 if (im->base != 1024 && im->base != 1000) {
3798 ("the only sensible value for base apart from 1000 is 1024");
3803 long_tmp = atol(optarg);
3804 if (long_tmp < 10) {
3805 rrd_set_error("width below 10 pixels");
3808 im->xsize = long_tmp;
3811 long_tmp = atol(optarg);
3812 if (long_tmp < 10) {
3813 rrd_set_error("height below 10 pixels");
3816 im->ysize = long_tmp;
3819 im->extra_flags |= FULL_SIZE_MODE;
3822 im->canvas->interlaced = 1;
3828 im->imginfo = optarg;
3831 if ((int) (im->canvas->imgformat = if_conv(optarg)) == -1) {
3832 rrd_set_error("unsupported graphics format '%s'", optarg);
3844 im->logarithmic = 1;
3848 "%10[A-Z]#%n%8lx%n",
3849 col_nam, &col_start, &color, &col_end) == 2) {
3851 int col_len = col_end - col_start;
3855 color = (((color & 0xF00) * 0x110000) |
3856 ((color & 0x0F0) * 0x011000) |
3857 ((color & 0x00F) * 0x001100) | 0x000000FF);
3860 color = (((color & 0xF000) * 0x11000) |
3861 ((color & 0x0F00) * 0x01100) |
3862 ((color & 0x00F0) * 0x00110) |
3863 ((color & 0x000F) * 0x00011)
3867 color = (color << 8) + 0xff /* shift left by 8 */ ;
3872 rrd_set_error("the color format is #RRGGBB[AA]");
3875 if ((ci = grc_conv(col_nam)) != -1) {
3876 im->graph_col[ci] = color;
3878 rrd_set_error("invalid color name '%s'", col_nam);
3882 rrd_set_error("invalid color def format");
3889 char font[1024] = "";
3891 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3892 int sindex, propidx;
3894 if ((sindex = text_prop_conv(prop)) != -1) {
3895 for (propidx = sindex; propidx < TEXT_PROP_LAST;
3898 im->text_prop[propidx].size = size;
3900 if (strlen(font) > 0) {
3901 strcpy(im->text_prop[propidx].font, font);
3903 if (propidx == sindex && sindex != 0)
3907 rrd_set_error("invalid fonttag '%s'", prop);
3911 rrd_set_error("invalid text property format");
3917 im->canvas->zoom = atof(optarg);
3918 if (im->canvas->zoom <= 0.0) {
3919 rrd_set_error("zoom factor must be > 0");
3924 strncpy(im->title, optarg, 150);
3925 im->title[150] = '\0';
3929 if (strcmp(optarg, "normal") == 0)
3930 im->canvas->aa_type = AA_NORMAL;
3931 else if (strcmp(optarg, "light") == 0)
3932 im->canvas->aa_type = AA_LIGHT;
3933 else if (strcmp(optarg, "mono") == 0)
3934 im->canvas->aa_type = AA_NONE;
3936 rrd_set_error("unknown font-render-mode '%s'", optarg);
3942 im->canvas->font_aa_threshold = atof(optarg);
3946 strncpy(im->watermark, optarg, 100);
3947 im->watermark[99] = '\0';
3952 rrd_set_error("unknown option '%c'", optopt);
3954 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3959 if (optind >= argc) {
3960 rrd_set_error("missing filename");
3964 if (im->logarithmic == 1 && im->minval <= 0) {
3966 ("for a logarithmic yaxis you must specify a lower-limit > 0");
3970 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3971 /* error string is set in parsetime.c */
3975 if (start_tmp < 3600 * 24 * 365 * 10) {
3976 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3981 if (end_tmp < start_tmp) {
3982 rrd_set_error("start (%ld) should be less than end (%ld)",
3983 start_tmp, end_tmp);
3987 im->start = start_tmp;
3989 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3992 int rrd_graph_color(
3999 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4001 color = strstr(var, "#");
4002 if (color == NULL) {
4003 if (optional == 0) {
4004 rrd_set_error("Found no color in %s", err);
4013 rest = strstr(color, ":");
4021 sscanf(color, "#%6lx%n", &col, &n);
4022 col = (col << 8) + 0xff /* shift left by 8 */ ;
4024 rrd_set_error("Color problem in %s", err);
4027 sscanf(color, "#%8lx%n", &col, &n);
4031 rrd_set_error("Color problem in %s", err);
4033 if (rrd_test_error())
4048 while (*ptr != '\0')
4049 if (*ptr++ == '%') {
4051 /* line cannot end with percent char */
4055 /* '%s', '%S' and '%%' are allowed */
4056 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4059 /* %c is allowed (but use only with vdef!) */
4060 else if (*ptr == 'c') {
4065 /* or else '% 6.2lf' and such are allowed */
4067 /* optional padding character */
4068 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4071 /* This should take care of 'm.n' with all three optional */
4072 while (*ptr >= '0' && *ptr <= '9')
4076 while (*ptr >= '0' && *ptr <= '9')
4079 /* Either 'le', 'lf' or 'lg' must follow here */
4082 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4097 struct graph_desc_t *gdes;
4098 const char *const str;
4100 /* A VDEF currently is either "func" or "param,func"
4101 * so the parsing is rather simple. Change if needed.
4108 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4109 if (n == (int) strlen(str)) { /* matched */
4113 sscanf(str, "%29[A-Z]%n", func, &n);
4114 if (n == (int) strlen(str)) { /* matched */
4117 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4122 if (!strcmp("PERCENT", func))
4123 gdes->vf.op = VDEF_PERCENT;
4124 else if (!strcmp("MAXIMUM", func))
4125 gdes->vf.op = VDEF_MAXIMUM;
4126 else if (!strcmp("AVERAGE", func))
4127 gdes->vf.op = VDEF_AVERAGE;
4128 else if (!strcmp("MINIMUM", func))
4129 gdes->vf.op = VDEF_MINIMUM;
4130 else if (!strcmp("TOTAL", func))
4131 gdes->vf.op = VDEF_TOTAL;
4132 else if (!strcmp("FIRST", func))
4133 gdes->vf.op = VDEF_FIRST;
4134 else if (!strcmp("LAST", func))
4135 gdes->vf.op = VDEF_LAST;
4136 else if (!strcmp("LSLSLOPE", func))
4137 gdes->vf.op = VDEF_LSLSLOPE;
4138 else if (!strcmp("LSLINT", func))
4139 gdes->vf.op = VDEF_LSLINT;
4140 else if (!strcmp("LSLCORREL", func))
4141 gdes->vf.op = VDEF_LSLCORREL;
4143 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4148 switch (gdes->vf.op) {
4150 if (isnan(param)) { /* no parameter given */
4151 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4155 if (param >= 0.0 && param <= 100.0) {
4156 gdes->vf.param = param;
4157 gdes->vf.val = DNAN; /* undefined */
4158 gdes->vf.when = 0; /* undefined */
4160 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4173 case VDEF_LSLCORREL:
4175 gdes->vf.param = DNAN;
4176 gdes->vf.val = DNAN;
4179 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4195 graph_desc_t *src, *dst;
4199 dst = &im->gdes[gdi];
4200 src = &im->gdes[dst->vidx];
4201 data = src->data + src->ds;
4202 steps = (src->end - src->start) / src->step;
4205 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4209 switch (dst->vf.op) {
4215 if ((array = malloc(steps * sizeof(double))) == NULL) {
4216 rrd_set_error("malloc VDEV_PERCENT");
4219 for (step = 0; step < steps; step++) {
4220 array[step] = data[step * src->ds_cnt];
4222 qsort(array, step, sizeof(double), vdef_percent_compar);
4224 field = (steps - 1) * dst->vf.param / 100;
4225 dst->vf.val = array[field];
4226 dst->vf.when = 0; /* no time component */
4229 for (step = 0; step < steps; step++)
4230 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4231 step == field ? '*' : ' ');
4237 while (step != steps && isnan(data[step * src->ds_cnt]))
4239 if (step == steps) {
4243 dst->vf.val = data[step * src->ds_cnt];
4244 dst->vf.when = src->start + (step + 1) * src->step;
4246 while (step != steps) {
4247 if (finite(data[step * src->ds_cnt])) {
4248 if (data[step * src->ds_cnt] > dst->vf.val) {
4249 dst->vf.val = data[step * src->ds_cnt];
4250 dst->vf.when = src->start + (step + 1) * src->step;
4261 for (step = 0; step < steps; step++) {
4262 if (finite(data[step * src->ds_cnt])) {
4263 sum += data[step * src->ds_cnt];
4268 if (dst->vf.op == VDEF_TOTAL) {
4269 dst->vf.val = sum * src->step;
4270 dst->vf.when = 0; /* no time component */
4272 dst->vf.val = sum / cnt;
4273 dst->vf.when = 0; /* no time component */
4283 while (step != steps && isnan(data[step * src->ds_cnt]))
4285 if (step == steps) {
4289 dst->vf.val = data[step * src->ds_cnt];
4290 dst->vf.when = src->start + (step + 1) * src->step;
4292 while (step != steps) {
4293 if (finite(data[step * src->ds_cnt])) {
4294 if (data[step * src->ds_cnt] < dst->vf.val) {
4295 dst->vf.val = data[step * src->ds_cnt];
4296 dst->vf.when = src->start + (step + 1) * src->step;
4303 /* The time value returned here is one step before the
4304 * actual time value. This is the start of the first
4308 while (step != steps && isnan(data[step * src->ds_cnt]))
4310 if (step == steps) { /* all entries were NaN */
4314 dst->vf.val = data[step * src->ds_cnt];
4315 dst->vf.when = src->start + step * src->step;
4319 /* The time value returned here is the
4320 * actual time value. This is the end of the last
4324 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4326 if (step < 0) { /* all entries were NaN */
4330 dst->vf.val = data[step * src->ds_cnt];
4331 dst->vf.when = src->start + (step + 1) * src->step;
4336 case VDEF_LSLCORREL:{
4337 /* Bestfit line by linear least squares method */
4340 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4348 for (step = 0; step < steps; step++) {
4349 if (finite(data[step * src->ds_cnt])) {
4352 SUMxx += step * step;
4353 SUMxy += step * data[step * src->ds_cnt];
4354 SUMy += data[step * src->ds_cnt];
4355 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4359 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4360 y_intercept = (SUMy - slope * SUMx) / cnt;
4363 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4364 (SUMx * SUMx) / cnt) * (SUMyy -
4370 if (dst->vf.op == VDEF_LSLSLOPE) {
4371 dst->vf.val = slope;
4373 } else if (dst->vf.op == VDEF_LSLINT) {
4374 dst->vf.val = y_intercept;
4376 } else if (dst->vf.op == VDEF_LSLCORREL) {
4377 dst->vf.val = correl;
4391 /* NaN < -INF < finite_values < INF */
4392 int vdef_percent_compar(
4397 /* Equality is not returned; this doesn't hurt except
4398 * (maybe) for a little performance.
4401 /* First catch NaN values. They are smallest */
4402 if (isnan(*(double *) a))
4404 if (isnan(*(double *) b))
4407 /* NaN doesn't reach this part so INF and -INF are extremes.
4408 * The sign from isinf() is compatible with the sign we return
4410 if (isinf(*(double *) a))
4411 return isinf(*(double *) a);
4412 if (isinf(*(double *) b))
4413 return isinf(*(double *) b);
4415 /* If we reach this, both values must be finite */
4416 if (*(double *) a < *(double *) b)