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);
1266 tm.tm_sec -= tm.tm_sec % basestep;
1270 tm.tm_min -= tm.tm_min % basestep;
1275 tm.tm_hour -= tm.tm_hour % basestep;
1278 /* we do NOT look at the basestep for this ... */
1284 /* we do NOT look at the basestep for this ... */
1288 tm.tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1289 if (tm.tm_wday == 0)
1290 tm.tm_mday -= 7; /* we want the *previous* monday */
1297 tm.tm_mon -= tm.tm_mon % basestep;
1306 tm.tm_year -= (tm.tm_year + 1900) % basestep;
1312 /* identify the point where the next gridline, label ... gets placed */
1313 time_t find_next_time(
1314 time_t current, /* what is the initial time */
1315 enum tmt_en baseint, /* what is the basic interval */
1316 long basestep /* how many if these do we jump a time */
1322 localtime_r(¤t, &tm);
1326 tm.tm_sec += basestep;
1329 tm.tm_min += basestep;
1332 tm.tm_hour += basestep;
1335 tm.tm_mday += basestep;
1338 tm.tm_mday += 7 * basestep;
1341 tm.tm_mon += basestep;
1344 tm.tm_year += basestep;
1346 madetime = mktime(&tm);
1347 } while (madetime == -1); /* this is necessary to skip impssible times
1348 like the daylight saving time skips */
1354 /* calculate values required for PRINT and GPRINT functions */
1360 long i, ii, validsteps;
1363 int graphelement = 0;
1366 double magfact = -1;
1371 /* wow initializing tmvdef is quite a task :-) */
1372 time_t now = time(NULL);
1374 localtime_r(&now, &tmvdef);
1377 for (i = 0; i < im->gdes_c; i++) {
1378 vidx = im->gdes[i].vidx;
1379 switch (im->gdes[i].gf) {
1383 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1384 rrd_set_error("realloc prdata");
1388 /* PRINT and GPRINT can now print VDEF generated values.
1389 * There's no need to do any calculations on them as these
1390 * calculations were already made.
1392 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1393 printval = im->gdes[vidx].vf.val;
1394 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1395 } else { /* need to calculate max,min,avg etcetera */
1396 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1397 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1400 for (ii = im->gdes[vidx].ds;
1401 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1402 if (!finite(im->gdes[vidx].data[ii]))
1404 if (isnan(printval)) {
1405 printval = im->gdes[vidx].data[ii];
1410 switch (im->gdes[i].cf) {
1413 case CF_DEVSEASONAL:
1417 printval += im->gdes[vidx].data[ii];
1420 printval = min(printval, im->gdes[vidx].data[ii]);
1424 printval = max(printval, im->gdes[vidx].data[ii]);
1427 printval = im->gdes[vidx].data[ii];
1430 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1431 if (validsteps > 1) {
1432 printval = (printval / validsteps);
1435 } /* prepare printval */
1437 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1438 /* Magfact is set to -1 upon entry to print_calc. If it
1439 * is still less than 0, then we need to run auto_scale.
1440 * Otherwise, put the value into the correct units. If
1441 * the value is 0, then do not set the symbol or magnification
1442 * so next the calculation will be performed again. */
1443 if (magfact < 0.0) {
1444 auto_scale(im, &printval, &si_symb, &magfact);
1445 if (printval == 0.0)
1448 printval /= magfact;
1450 *(++percent_s) = 's';
1451 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1452 auto_scale(im, &printval, &si_symb, &magfact);
1455 if (im->gdes[i].gf == GF_PRINT) {
1456 (*prdata)[prlines - 2] =
1457 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1458 (*prdata)[prlines - 1] = NULL;
1459 if (im->gdes[i].strftm) {
1460 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1461 im->gdes[i].format, &tmvdef);
1463 if (bad_format(im->gdes[i].format)) {
1464 rrd_set_error("bad format for PRINT in '%s'",
1465 im->gdes[i].format);
1468 #ifdef HAVE_SNPRINTF
1469 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1470 im->gdes[i].format, printval, si_symb);
1472 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1479 if (im->gdes[i].strftm) {
1480 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1481 im->gdes[i].format, &tmvdef);
1483 if (bad_format(im->gdes[i].format)) {
1484 rrd_set_error("bad format for GPRINT in '%s'",
1485 im->gdes[i].format);
1488 #ifdef HAVE_SNPRINTF
1489 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1490 im->gdes[i].format, printval, si_symb);
1492 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1505 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1506 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1511 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1512 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1520 #ifdef WITH_PIECHART
1528 ("STACK should already be turned into LINE or AREA here");
1533 return graphelement;
1537 /* place legends with color spots */
1542 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1543 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1544 int fill = 0, fill_last;
1546 int leg_x = border, leg_y = im->yimg;
1547 int leg_y_prev = im->yimg;
1550 int i, ii, mark = 0;
1551 char prt_fctn; /*special printfunctions */
1554 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1555 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1556 rrd_set_error("malloc for legspace");
1560 for (i = 0; i < im->gdes_c; i++) {
1563 /* hid legends for rules which are not displayed */
1565 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1566 if (im->gdes[i].gf == GF_HRULE &&
1567 (im->gdes[i].yrule < im->minval
1568 || im->gdes[i].yrule > im->maxval))
1569 im->gdes[i].legend[0] = '\0';
1571 if (im->gdes[i].gf == GF_VRULE &&
1572 (im->gdes[i].xrule < im->start
1573 || im->gdes[i].xrule > im->end))
1574 im->gdes[i].legend[0] = '\0';
1577 leg_cc = strlen(im->gdes[i].legend);
1579 /* is there a controle code ant the end of the legend string ? */
1580 /* and it is not a tab \\t */
1581 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1582 && im->gdes[i].legend[leg_cc - 1] != 't') {
1583 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1585 im->gdes[i].legend[leg_cc] = '\0';
1589 /* only valid control codes */
1590 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1595 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1597 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1598 im->gdes[i].legend, prt_fctn);
1603 /* remove exess space */
1604 if (prt_fctn == 'n') {
1608 while (prt_fctn == 'g' &&
1609 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1611 im->gdes[i].legend[leg_cc] = '\0';
1614 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1617 /* no interleg space if string ends in \g */
1618 fill += legspace[i];
1620 fill += gfx_get_text_width(im->canvas, fill + border,
1621 im->text_prop[TEXT_PROP_LEGEND].
1623 im->text_prop[TEXT_PROP_LEGEND].
1625 im->gdes[i].legend, 0);
1630 /* who said there was a special tag ... ? */
1631 if (prt_fctn == 'g') {
1634 if (prt_fctn == '\0') {
1635 if (i == im->gdes_c - 1)
1638 /* is it time to place the legends ? */
1639 if (fill > im->ximg - 2 * border) {
1654 if (prt_fctn != '\0') {
1656 if (leg_c >= 2 && prt_fctn == 'j') {
1657 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1661 if (prt_fctn == 'c')
1662 leg_x = (im->ximg - fill) / 2.0;
1663 if (prt_fctn == 'r')
1664 leg_x = im->ximg - fill - border;
1666 for (ii = mark; ii <= i; ii++) {
1667 if (im->gdes[ii].legend[0] == '\0')
1668 continue; /* skip empty legends */
1669 im->gdes[ii].leg_x = leg_x;
1670 im->gdes[ii].leg_y = leg_y;
1672 gfx_get_text_width(im->canvas, leg_x,
1673 im->text_prop[TEXT_PROP_LEGEND].
1675 im->text_prop[TEXT_PROP_LEGEND].
1677 im->gdes[ii].legend, 0)
1682 /* only add y space if there was text on the line */
1683 if (leg_x > border || prt_fctn == 's')
1684 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1685 if (prt_fctn == 's')
1686 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1692 im->yimg = leg_y_prev;
1693 /* if we did place some legends we have to add vertical space */
1694 if (leg_y != im->yimg) {
1695 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1702 /* create a grid on the graph. it determines what to do
1703 from the values of xsize, start and end */
1705 /* the xaxis labels are determined from the number of seconds per pixel
1706 in the requested graph */
1710 int calc_horizontal_grid(
1717 int decimals, fractionals;
1719 im->ygrid_scale.labfact = 2;
1720 range = im->maxval - im->minval;
1721 scaledrange = range / im->magfact;
1723 /* does the scale of this graph make it impossible to put lines
1724 on it? If so, give up. */
1725 if (isnan(scaledrange)) {
1729 /* find grid spaceing */
1731 if (isnan(im->ygridstep)) {
1732 if (im->extra_flags & ALTYGRID) {
1733 /* find the value with max number of digits. Get number of digits */
1736 (max(fabs(im->maxval), fabs(im->minval)) *
1737 im->viewfactor / im->magfact));
1738 if (decimals <= 0) /* everything is small. make place for zero */
1741 im->ygrid_scale.gridstep =
1743 floor(log10(range * im->viewfactor / im->magfact))) /
1744 im->viewfactor * im->magfact;
1746 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1747 im->ygrid_scale.gridstep = 0.1;
1748 /* should have at least 5 lines but no more then 15 */
1749 if (range / im->ygrid_scale.gridstep < 5)
1750 im->ygrid_scale.gridstep /= 10;
1751 if (range / im->ygrid_scale.gridstep > 15)
1752 im->ygrid_scale.gridstep *= 10;
1753 if (range / im->ygrid_scale.gridstep > 5) {
1754 im->ygrid_scale.labfact = 1;
1755 if (range / im->ygrid_scale.gridstep > 8)
1756 im->ygrid_scale.labfact = 2;
1758 im->ygrid_scale.gridstep /= 5;
1759 im->ygrid_scale.labfact = 5;
1763 (im->ygrid_scale.gridstep *
1764 (double) im->ygrid_scale.labfact * im->viewfactor /
1766 if (fractionals < 0) { /* small amplitude. */
1767 int len = decimals - fractionals + 1;
1769 if (im->unitslength < len + 2)
1770 im->unitslength = len + 2;
1771 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1772 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1774 int len = decimals + 1;
1776 if (im->unitslength < len + 2)
1777 im->unitslength = len + 2;
1778 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1779 (im->symbol != ' ' ? " %c" : ""));
1782 for (i = 0; ylab[i].grid > 0; i++) {
1783 pixel = im->ysize / (scaledrange / ylab[i].grid);
1789 for (i = 0; i < 4; i++) {
1790 if (pixel * ylab[gridind].lfac[i] >=
1791 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1792 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1797 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1800 im->ygrid_scale.gridstep = im->ygridstep;
1801 im->ygrid_scale.labfact = im->ylabfact;
1806 int draw_horizontal_grid(
1811 char graph_label[100];
1813 double X0 = im->xorigin;
1814 double X1 = im->xorigin + im->xsize;
1816 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1817 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1821 im->ygrid_scale.gridstep / (double) im->magfact *
1822 (double) im->viewfactor;
1823 MaxY = scaledstep * (double) egrid;
1824 for (i = sgrid; i <= egrid; i++) {
1825 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1826 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1828 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1829 && floor(Y0 + 0.5) <= im->yorigin) {
1830 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1831 with the chosen settings. Add a label if required by settings, or if
1832 there is only one label so far and the next grid line is out of bounds. */
1833 if (i % im->ygrid_scale.labfact == 0
1835 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1836 if (im->symbol == ' ') {
1837 if (im->extra_flags & ALTYGRID) {
1838 sprintf(graph_label, im->ygrid_scale.labfmt,
1839 scaledstep * (double) i);
1842 sprintf(graph_label, "%4.1f",
1843 scaledstep * (double) i);
1845 sprintf(graph_label, "%4.0f",
1846 scaledstep * (double) i);
1850 char sisym = (i == 0 ? ' ' : im->symbol);
1852 if (im->extra_flags & ALTYGRID) {
1853 sprintf(graph_label, im->ygrid_scale.labfmt,
1854 scaledstep * (double) i, sisym);
1857 sprintf(graph_label, "%4.1f %c",
1858 scaledstep * (double) i, sisym);
1860 sprintf(graph_label, "%4.0f %c",
1861 scaledstep * (double) i, sisym);
1867 gfx_new_text(im->canvas,
1868 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1869 im->graph_col[GRC_FONT],
1870 im->text_prop[TEXT_PROP_AXIS].font,
1871 im->text_prop[TEXT_PROP_AXIS].size,
1872 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1874 gfx_new_dashed_line(im->canvas,
1877 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1878 im->grid_dash_on, im->grid_dash_off);
1880 } else if (!(im->extra_flags & NOMINOR)) {
1881 gfx_new_dashed_line(im->canvas,
1884 GRIDWIDTH, im->graph_col[GRC_GRID],
1885 im->grid_dash_on, im->grid_dash_off);
1893 /* this is frexp for base 10 */
1904 iexp = floor(log(fabs(x)) / log(10));
1905 mnt = x / pow(10.0, iexp);
1908 mnt = x / pow(10.0, iexp);
1914 static int AlmostEqual2sComplement(
1920 int aInt = *(int *) &A;
1921 int bInt = *(int *) &B;
1924 /* Make sure maxUlps is non-negative and small enough that the
1925 default NAN won't compare as equal to anything. */
1927 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1929 /* Make aInt lexicographically ordered as a twos-complement int */
1932 aInt = 0x80000000l - aInt;
1934 /* Make bInt lexicographically ordered as a twos-complement int */
1937 bInt = 0x80000000l - bInt;
1939 intDiff = abs(aInt - bInt);
1941 if (intDiff <= maxUlps)
1947 /* logaritmic horizontal grid */
1948 int horizontal_log_grid(
1951 double yloglab[][10] = {
1952 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1953 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1954 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1955 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1956 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1957 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
1960 int i, j, val_exp, min_exp;
1961 double nex; /* number of decades in data */
1962 double logscale; /* scale in logarithmic space */
1963 int exfrac = 1; /* decade spacing */
1964 int mid = -1; /* row in yloglab for major grid */
1965 double mspac; /* smallest major grid spacing (pixels) */
1966 int flab; /* first value in yloglab to use */
1967 double value, tmp, pre_value;
1969 char graph_label[100];
1971 nex = log10(im->maxval / im->minval);
1972 logscale = im->ysize / nex;
1974 /* major spacing for data with high dynamic range */
1975 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1982 /* major spacing for less dynamic data */
1984 /* search best row in yloglab */
1986 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
1987 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1988 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
1989 && yloglab[mid][0] > 0);
1993 /* find first value in yloglab */
1995 yloglab[mid][flab] < 10
1996 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1997 if (yloglab[mid][flab] == 10.0) {
2002 if (val_exp % exfrac)
2003 val_exp += abs(-val_exp % exfrac);
2006 X1 = im->xorigin + im->xsize;
2012 value = yloglab[mid][flab] * pow(10.0, val_exp);
2013 if (AlmostEqual2sComplement(value, pre_value, 4))
2014 break; /* it seems we are not converging */
2018 Y0 = ytr(im, value);
2019 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2022 /* major grid line */
2023 gfx_new_dashed_line(im->canvas,
2026 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2027 im->grid_dash_on, im->grid_dash_off);
2030 if (im->extra_flags & FORCE_UNITS_SI) {
2035 scale = floor(val_exp / 3.0);
2037 pvalue = pow(10.0, val_exp % 3);
2039 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2040 pvalue *= yloglab[mid][flab];
2042 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2043 ((scale + si_symbcenter) >= 0))
2044 symbol = si_symbol[scale + si_symbcenter];
2048 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2050 sprintf(graph_label, "%3.0e", value);
2051 gfx_new_text(im->canvas,
2052 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2053 im->graph_col[GRC_FONT],
2054 im->text_prop[TEXT_PROP_AXIS].font,
2055 im->text_prop[TEXT_PROP_AXIS].size,
2056 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
2060 if (mid < 4 && exfrac == 1) {
2061 /* find first and last minor line behind current major line
2062 * i is the first line and j tha last */
2064 min_exp = val_exp - 1;
2065 for (i = 1; yloglab[mid][i] < 10.0; i++);
2066 i = yloglab[mid][i - 1] + 1;
2070 i = yloglab[mid][flab - 1] + 1;
2071 j = yloglab[mid][flab];
2074 /* draw minor lines below current major line */
2075 for (; i < j; i++) {
2077 value = i * pow(10.0, min_exp);
2078 if (value < im->minval)
2081 Y0 = ytr(im, value);
2082 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2086 gfx_new_dashed_line(im->canvas,
2089 GRIDWIDTH, im->graph_col[GRC_GRID],
2090 im->grid_dash_on, im->grid_dash_off);
2092 } else if (exfrac > 1) {
2093 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2094 value = pow(10.0, i);
2095 if (value < im->minval)
2098 Y0 = ytr(im, value);
2099 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2103 gfx_new_dashed_line(im->canvas,
2106 GRIDWIDTH, im->graph_col[GRC_GRID],
2107 im->grid_dash_on, im->grid_dash_off);
2112 if (yloglab[mid][++flab] == 10.0) {
2118 /* draw minor lines after highest major line */
2119 if (mid < 4 && exfrac == 1) {
2120 /* find first and last minor line below current major line
2121 * i is the first line and j tha last */
2123 min_exp = val_exp - 1;
2124 for (i = 1; yloglab[mid][i] < 10.0; i++);
2125 i = yloglab[mid][i - 1] + 1;
2129 i = yloglab[mid][flab - 1] + 1;
2130 j = yloglab[mid][flab];
2133 /* draw minor lines below current major line */
2134 for (; i < j; i++) {
2136 value = i * pow(10.0, min_exp);
2137 if (value < im->minval)
2140 Y0 = ytr(im, value);
2141 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2145 gfx_new_dashed_line(im->canvas,
2148 GRIDWIDTH, im->graph_col[GRC_GRID],
2149 im->grid_dash_on, im->grid_dash_off);
2152 /* fancy minor gridlines */
2153 else if (exfrac > 1) {
2154 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2155 value = pow(10.0, i);
2156 if (value < im->minval)
2159 Y0 = ytr(im, value);
2160 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2164 gfx_new_dashed_line(im->canvas,
2167 GRIDWIDTH, im->graph_col[GRC_GRID],
2168 im->grid_dash_on, im->grid_dash_off);
2179 int xlab_sel; /* which sort of label and grid ? */
2180 time_t ti, tilab, timajor;
2182 char graph_label[100];
2183 double X0, Y0, Y1; /* points for filled graph and more */
2186 /* the type of time grid is determined by finding
2187 the number of seconds per pixel in the graph */
2190 if (im->xlab_user.minsec == -1) {
2191 factor = (im->end - im->start) / im->xsize;
2193 while (xlab[xlab_sel + 1].minsec != -1
2194 && xlab[xlab_sel + 1].minsec <= factor) {
2196 } /* pick the last one */
2197 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2198 && xlab[xlab_sel].length > (im->end - im->start)) {
2200 } /* go back to the smallest size */
2201 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2202 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2203 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2204 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2205 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2206 im->xlab_user.labst = xlab[xlab_sel].labst;
2207 im->xlab_user.precis = xlab[xlab_sel].precis;
2208 im->xlab_user.stst = xlab[xlab_sel].stst;
2211 /* y coords are the same for every line ... */
2213 Y1 = im->yorigin - im->ysize;
2216 /* paint the minor grid */
2217 if (!(im->extra_flags & NOMINOR)) {
2218 for (ti = find_first_time(im->start,
2219 im->xlab_user.gridtm,
2220 im->xlab_user.gridst),
2221 timajor = find_first_time(im->start,
2222 im->xlab_user.mgridtm,
2223 im->xlab_user.mgridst);
2226 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2228 /* are we inside the graph ? */
2229 if (ti < im->start || ti > im->end)
2231 while (timajor < ti) {
2232 timajor = find_next_time(timajor,
2233 im->xlab_user.mgridtm,
2234 im->xlab_user.mgridst);
2237 continue; /* skip as falls on major grid line */
2239 gfx_new_dashed_line(im->canvas, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2240 im->graph_col[GRC_GRID],
2241 im->grid_dash_on, im->grid_dash_off);
2246 /* paint the major grid */
2247 for (ti = find_first_time(im->start,
2248 im->xlab_user.mgridtm,
2249 im->xlab_user.mgridst);
2251 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2253 /* are we inside the graph ? */
2254 if (ti < im->start || ti > im->end)
2257 gfx_new_dashed_line(im->canvas, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2258 im->graph_col[GRC_MGRID],
2259 im->grid_dash_on, im->grid_dash_off);
2262 /* paint the labels below the graph */
2263 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2264 im->xlab_user.labtm,
2265 im->xlab_user.labst);
2266 ti <= im->end - im->xlab_user.precis / 2;
2267 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2269 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2270 /* are we inside the graph ? */
2271 if (tilab < im->start || tilab > im->end)
2275 localtime_r(&tilab, &tm);
2276 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2278 # error "your libc has no strftime I guess we'll abort the exercise here."
2280 gfx_new_text(im->canvas,
2282 Y0 + im->text_prop[TEXT_PROP_AXIS].size * 1.4 + 5,
2283 im->graph_col[GRC_FONT],
2284 im->text_prop[TEXT_PROP_AXIS].font,
2285 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2286 GFX_H_CENTER, GFX_V_BOTTOM, graph_label);
2296 /* draw x and y axis */
2297 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2298 im->xorigin+im->xsize,im->yorigin-im->ysize,
2299 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2301 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2302 im->xorigin+im->xsize,im->yorigin-im->ysize,
2303 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2305 gfx_new_line(im->canvas, im->xorigin - 4, im->yorigin,
2306 im->xorigin + im->xsize + 4, im->yorigin,
2307 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2309 gfx_new_line(im->canvas, im->xorigin, im->yorigin + 4,
2310 im->xorigin, im->yorigin - im->ysize - 4,
2311 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2314 /* arrow for X and Y axis direction */
2315 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 */
2316 im->graph_col[GRC_ARROW]);
2318 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 */
2319 im->graph_col[GRC_ARROW]);
2328 double X0, Y0; /* points for filled graph and more */
2331 /* draw 3d border */
2332 node = gfx_new_area(im->canvas, 0, im->yimg,
2333 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2334 gfx_add_point(node, im->ximg - 2, 2);
2335 gfx_add_point(node, im->ximg, 0);
2336 gfx_add_point(node, 0, 0);
2337 /* gfx_add_point( node , 0,im->yimg ); */
2339 node = gfx_new_area(im->canvas, 2, im->yimg - 2,
2340 im->ximg - 2, im->yimg - 2,
2341 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2342 gfx_add_point(node, im->ximg, 0);
2343 gfx_add_point(node, im->ximg, im->yimg);
2344 gfx_add_point(node, 0, im->yimg);
2345 /* gfx_add_point( node , 0,im->yimg ); */
2348 if (im->draw_x_grid == 1)
2351 if (im->draw_y_grid == 1) {
2352 if (im->logarithmic) {
2353 res = horizontal_log_grid(im);
2355 res = draw_horizontal_grid(im);
2358 /* dont draw horizontal grid if there is no min and max val */
2360 char *nodata = "No Data found";
2362 gfx_new_text(im->canvas, im->ximg / 2,
2363 (2 * im->yorigin - im->ysize) / 2,
2364 im->graph_col[GRC_FONT],
2365 im->text_prop[TEXT_PROP_AXIS].font,
2366 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2367 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2371 /* yaxis unit description */
2372 gfx_new_text(im->canvas,
2373 10, (im->yorigin - im->ysize / 2),
2374 im->graph_col[GRC_FONT],
2375 im->text_prop[TEXT_PROP_UNIT].font,
2376 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2377 RRDGRAPH_YLEGEND_ANGLE,
2378 GFX_H_LEFT, GFX_V_CENTER, im->ylegend);
2381 gfx_new_text(im->canvas,
2382 im->ximg / 2, im->text_prop[TEXT_PROP_TITLE].size * 1.3 + 4,
2383 im->graph_col[GRC_FONT],
2384 im->text_prop[TEXT_PROP_TITLE].font,
2385 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2386 GFX_H_CENTER, GFX_V_CENTER, im->title);
2387 /* rrdtool 'logo' */
2388 gfx_new_text(im->canvas,
2390 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2391 im->text_prop[TEXT_PROP_AXIS].font,
2392 5.5, im->tabwidth, 270,
2393 GFX_H_RIGHT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2395 /* graph watermark */
2396 if (im->watermark[0] != '\0') {
2397 gfx_new_text(im->canvas,
2398 im->ximg / 2, im->yimg - 6,
2399 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2400 im->text_prop[TEXT_PROP_AXIS].font,
2401 5.5, im->tabwidth, 0,
2402 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2406 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2407 for (i = 0; i < im->gdes_c; i++) {
2408 if (im->gdes[i].legend[0] == '\0')
2411 /* im->gdes[i].leg_y is the bottom of the legend */
2412 X0 = im->gdes[i].leg_x;
2413 Y0 = im->gdes[i].leg_y;
2414 gfx_new_text(im->canvas, X0, Y0,
2415 im->graph_col[GRC_FONT],
2416 im->text_prop[TEXT_PROP_LEGEND].font,
2417 im->text_prop[TEXT_PROP_LEGEND].size,
2418 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2419 im->gdes[i].legend);
2420 /* The legend for GRAPH items starts with "M " to have
2421 enough space for the box */
2422 if (im->gdes[i].gf != GF_PRINT &&
2423 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2426 boxH = gfx_get_text_width(im->canvas, 0,
2427 im->text_prop[TEXT_PROP_LEGEND].
2429 im->text_prop[TEXT_PROP_LEGEND].
2430 size, im->tabwidth, "o", 0) * 1.2;
2433 /* make sure transparent colors show up the same way as in the graph */
2434 node = gfx_new_area(im->canvas,
2437 X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2438 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2440 node = gfx_new_area(im->canvas,
2442 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2443 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2444 node = gfx_new_line(im->canvas,
2446 X0, Y0, 1.0, im->graph_col[GRC_FRAME]);
2447 gfx_add_point(node, X0 + boxH, Y0);
2448 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2449 gfx_close_path(node);
2456 /*****************************************************
2457 * lazy check make sure we rely need to create this graph
2458 *****************************************************/
2465 struct stat imgstat;
2468 return 0; /* no lazy option */
2469 if (stat(im->graphfile, &imgstat) != 0)
2470 return 0; /* can't stat */
2471 /* one pixel in the existing graph is more then what we would
2473 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2475 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2476 return 0; /* the file does not exist */
2477 switch (im->canvas->imgformat) {
2479 size = PngSize(fd, &(im->ximg), &(im->yimg));
2488 #ifdef WITH_PIECHART
2500 double step = M_PI / 50; /* Number of iterations for the circle;
2501 ** 10 is definitely too low, more than
2502 ** 50 seems to be overkill
2505 /* Strange but true: we have to work clockwise or else
2506 ** anti aliasing nor transparency don't work.
2508 ** This test is here to make sure we do it right, also
2509 ** this makes the for...next loop more easy to implement.
2510 ** The return will occur if the user enters a negative number
2511 ** (which shouldn't be done according to the specs) or if the
2512 ** programmers do something wrong (which, as we all know, never
2513 ** happens anyway :)
2515 if (endangle < startangle)
2518 /* Hidden feature: Radius decreases each full circle */
2520 while (angle >= 2 * M_PI) {
2525 node = gfx_new_area(im->canvas,
2526 PieCenterX + sin(startangle) * Radius,
2527 PieCenterY - cos(startangle) * Radius,
2530 PieCenterX + sin(endangle) * Radius,
2531 PieCenterY - cos(endangle) * Radius, color);
2532 for (angle = endangle; angle - startangle >= step; angle -= step) {
2534 PieCenterX + sin(angle) * Radius,
2535 PieCenterY - cos(angle) * Radius);
2541 int graph_size_location(
2544 #ifdef WITH_PIECHART
2550 /* The actual size of the image to draw is determined from
2551 ** several sources. The size given on the command line is
2552 ** the graph area but we need more as we have to draw labels
2553 ** and other things outside the graph area
2556 /* +-+-------------------------------------------+
2557 ** |l|.................title.....................|
2558 ** |e+--+-------------------------------+--------+
2561 ** |l| l| main graph area | chart |
2564 ** |r+--+-------------------------------+--------+
2565 ** |e| | x-axis labels | |
2566 ** |v+--+-------------------------------+--------+
2567 ** | |..............legends......................|
2568 ** +-+-------------------------------------------+
2570 ** +---------------------------------------------+
2572 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2573 #ifdef WITH_PIECHART
2578 Xlegend = 0, Ylegend = 0,
2580 Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2582 if (im->extra_flags & ONLY_GRAPH) {
2584 im->ximg = im->xsize;
2585 im->yimg = im->ysize;
2586 im->yorigin = im->ysize;
2591 if (im->ylegend[0] != '\0') {
2592 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2596 if (im->title[0] != '\0') {
2597 /* The title is placed "inbetween" two text lines so it
2598 ** automatically has some vertical spacing. The horizontal
2599 ** spacing is added here, on each side.
2601 /* don't care for the with of the title
2602 Xtitle = gfx_get_text_width(im->canvas, 0,
2603 im->text_prop[TEXT_PROP_TITLE].font,
2604 im->text_prop[TEXT_PROP_TITLE].size,
2606 im->title, 0) + 2*Xspacing; */
2607 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2613 if (im->draw_x_grid) {
2614 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2616 if (im->draw_y_grid || im->forceleftspace) {
2617 Xylabel = gfx_get_text_width(im->canvas, 0,
2618 im->text_prop[TEXT_PROP_AXIS].font,
2619 im->text_prop[TEXT_PROP_AXIS].size,
2621 "0", 0) * im->unitslength;
2624 #ifdef WITH_PIECHART
2626 im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2632 /* Now calculate the total size. Insert some spacing where
2633 desired. im->xorigin and im->yorigin need to correspond
2634 with the lower left corner of the main graph area or, if
2635 this one is not set, the imaginary box surrounding the
2638 /* The legend width cannot yet be determined, as a result we
2639 ** have problems adjusting the image to it. For now, we just
2640 ** forget about it at all; the legend will have to fit in the
2641 ** size already allocated.
2643 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2645 #ifdef WITH_PIECHART
2650 im->ximg += Xspacing;
2651 #ifdef WITH_PIECHART
2653 im->ximg += Xspacing;
2656 im->xorigin = Xspacing + Xylabel;
2658 /* the length of the title should not influence with width of the graph
2659 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2661 if (Xvertical) { /* unit description */
2662 im->ximg += Xvertical;
2663 im->xorigin += Xvertical;
2667 /* The vertical size is interesting... we need to compare
2668 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2669 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2670 ** in order to start even thinking about Ylegend or Ywatermark.
2672 ** Do it in three portions: First calculate the inner part,
2673 ** then do the legend, then adjust the total height of the img,
2674 ** adding space for a watermark if one exists;
2677 /* reserve space for main and/or pie */
2679 im->yimg = Ymain + Yxlabel;
2681 #ifdef WITH_PIECHART
2682 if (im->yimg < Ypie)
2686 im->yorigin = im->yimg - Yxlabel;
2688 /* reserve space for the title *or* some padding above the graph */
2691 im->yorigin += Ytitle;
2693 im->yimg += 1.5 * Yspacing;
2694 im->yorigin += 1.5 * Yspacing;
2696 /* reserve space for padding below the graph */
2697 im->yimg += Yspacing;
2699 /* Determine where to place the legends onto the image.
2700 ** Adjust im->yimg to match the space requirements.
2702 if (leg_place(im) == -1)
2705 if (im->watermark[0] != '\0') {
2706 im->yimg += Ywatermark;
2709 if (Xlegend > im->ximg) {
2711 /* reposition Pie */
2715 #ifdef WITH_PIECHART
2716 /* The pie is placed in the upper right hand corner,
2717 ** just below the title (if any) and with sufficient
2721 im->pie_x = im->ximg - Xspacing - Xpie / 2;
2722 im->pie_y = im->yorigin - Ymain + Ypie / 2;
2724 im->pie_x = im->ximg / 2;
2725 im->pie_y = im->yorigin - Ypie / 2;
2733 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2734 /* yes we are loosing precision by doing tos with floats instead of doubles
2735 but it seems more stable this way. */
2738 /* draw that picture thing ... */
2744 int lazy = lazy_check(im);
2746 #ifdef WITH_PIECHART
2748 double PieStart = 0.0;
2753 double areazero = 0.0;
2754 graph_desc_t *lastgdes = NULL;
2756 /* if we are lazy and there is nothing to PRINT ... quit now */
2757 if (lazy && im->prt_c == 0)
2760 /* pull the data from the rrd files ... */
2762 if (data_fetch(im) == -1)
2765 /* evaluate VDEF and CDEF operations ... */
2766 if (data_calc(im) == -1)
2769 #ifdef WITH_PIECHART
2770 /* check if we need to draw a piechart */
2771 for (i = 0; i < im->gdes_c; i++) {
2772 if (im->gdes[i].gf == GF_PART) {
2779 /* calculate and PRINT and GPRINT definitions. We have to do it at
2780 * this point because it will affect the length of the legends
2781 * if there are no graph elements we stop here ...
2782 * if we are lazy, try to quit ...
2784 i = print_calc(im, calcpr);
2788 #ifdef WITH_PIECHART
2794 #ifdef WITH_PIECHART
2795 /* If there's only the pie chart to draw, signal this */
2800 /* get actual drawing data and find min and max values */
2801 if (data_proc(im) == -1)
2804 if (!im->logarithmic) {
2807 /* identify si magnitude Kilo, Mega Giga ? */
2808 if (!im->rigid && !im->logarithmic)
2809 expand_range(im); /* make sure the upper and lower limit are
2812 if (!calc_horizontal_grid(im))
2819 /**************************************************************
2820 *** Calculating sizes and locations became a bit confusing ***
2821 *** so I moved this into a separate function. ***
2822 **************************************************************/
2823 if (graph_size_location(im, i
2824 #ifdef WITH_PIECHART
2830 /* the actual graph is created by going through the individual
2831 graph elements and then drawing them */
2833 node = gfx_new_area(im->canvas,
2836 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2838 gfx_add_point(node, im->ximg, 0);
2840 #ifdef WITH_PIECHART
2841 if (piechart != 2) {
2843 node = gfx_new_area(im->canvas,
2844 im->xorigin, im->yorigin,
2845 im->xorigin + im->xsize, im->yorigin,
2846 im->xorigin + im->xsize, im->yorigin - im->ysize,
2847 im->graph_col[GRC_CANVAS]);
2849 gfx_add_point(node, im->xorigin, im->yorigin - im->ysize);
2851 if (im->minval > 0.0)
2852 areazero = im->minval;
2853 if (im->maxval < 0.0)
2854 areazero = im->maxval;
2855 #ifdef WITH_PIECHART
2859 #ifdef WITH_PIECHART
2861 pie_part(im, im->graph_col[GRC_CANVAS], im->pie_x, im->pie_y,
2862 im->piesize * 0.5, 0, 2 * M_PI);
2866 for (i = 0; i < im->gdes_c; i++) {
2867 switch (im->gdes[i].gf) {
2880 for (ii = 0; ii < im->xsize; ii++) {
2881 if (!isnan(im->gdes[i].p_data[ii]) &&
2882 im->gdes[i].p_data[ii] != 0.0) {
2883 if (im->gdes[i].yrule > 0) {
2884 gfx_new_line(im->canvas,
2885 im->xorigin + ii, im->yorigin,
2888 im->gdes[i].yrule * im->ysize, 1.0,
2890 } else if (im->gdes[i].yrule < 0) {
2891 gfx_new_line(im->canvas,
2893 im->yorigin - im->ysize,
2896 im->gdes[i].yrule) *
2897 im->ysize, 1.0, im->gdes[i].col);
2905 /* fix data points at oo and -oo */
2906 for (ii = 0; ii < im->xsize; ii++) {
2907 if (isinf(im->gdes[i].p_data[ii])) {
2908 if (im->gdes[i].p_data[ii] > 0) {
2909 im->gdes[i].p_data[ii] = im->maxval;
2911 im->gdes[i].p_data[ii] = im->minval;
2917 /* *******************************************************
2922 -------|--t-1--t--------------------------------
2924 if we know the value at time t was a then
2925 we draw a square from t-1 to t with the value a.
2927 ********************************************************* */
2928 if (im->gdes[i].col != 0x0) {
2929 /* GF_LINE and friend */
2930 if (im->gdes[i].gf == GF_LINE) {
2931 double last_y = 0.0;
2934 for (ii = 1; ii < im->xsize; ii++) {
2935 if (isnan(im->gdes[i].p_data[ii])
2936 || (im->slopemode == 1
2937 && isnan(im->gdes[i].p_data[ii - 1]))) {
2942 last_y = ytr(im, im->gdes[i].p_data[ii]);
2943 if (im->slopemode == 0) {
2944 node = gfx_new_line(im->canvas,
2945 ii - 1 + im->xorigin,
2946 last_y, ii + im->xorigin,
2948 im->gdes[i].linewidth,
2951 node = gfx_new_line(im->canvas,
2952 ii - 1 + im->xorigin,
2956 ii + im->xorigin, last_y,
2957 im->gdes[i].linewidth,
2961 double new_y = ytr(im, im->gdes[i].p_data[ii]);
2963 if (im->slopemode == 0
2964 && !AlmostEqual2sComplement(new_y, last_y,
2966 gfx_add_point(node, ii - 1 + im->xorigin,
2970 gfx_add_point(node, ii + im->xorigin, new_y);
2976 double *foreY = malloc(sizeof(double) * im->xsize * 2);
2977 double *foreX = malloc(sizeof(double) * im->xsize * 2);
2978 double *backY = malloc(sizeof(double) * im->xsize * 2);
2979 double *backX = malloc(sizeof(double) * im->xsize * 2);
2982 for (ii = 0; ii <= im->xsize; ii++) {
2985 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
2990 && AlmostEqual2sComplement(foreY[lastI],
2992 && AlmostEqual2sComplement(foreY[lastI],
2997 node = gfx_new_area(im->canvas,
3000 foreX[cntI], foreY[cntI],
3002 while (cntI < idxI) {
3007 AlmostEqual2sComplement(foreY[lastI],
3010 AlmostEqual2sComplement(foreY[lastI],
3015 gfx_add_point(node, foreX[cntI], foreY[cntI]);
3017 gfx_add_point(node, backX[idxI], backY[idxI]);
3023 AlmostEqual2sComplement(backY[lastI],
3026 AlmostEqual2sComplement(backY[lastI],
3031 gfx_add_point(node, backX[idxI], backY[idxI]);
3040 if (ii == im->xsize)
3043 /* keep things simple for now, just draw these bars
3044 do not try to build a big and complex area */
3047 if (im->slopemode == 0 && ii == 0) {
3050 if (isnan(im->gdes[i].p_data[ii])) {
3054 ytop = ytr(im, im->gdes[i].p_data[ii]);
3055 if (lastgdes && im->gdes[i].stack) {
3056 ybase = ytr(im, lastgdes->p_data[ii]);
3058 ybase = ytr(im, areazero);
3060 if (ybase == ytop) {
3064 /* every area has to be wound clock-wise,
3065 so we have to make sur base remains base */
3067 double extra = ytop;
3072 if (im->slopemode == 0) {
3073 backY[++idxI] = ybase - 0.2;
3074 backX[idxI] = ii + im->xorigin - 1;
3075 foreY[idxI] = ytop + 0.2;
3076 foreX[idxI] = ii + im->xorigin - 1;
3078 backY[++idxI] = ybase - 0.2;
3079 backX[idxI] = ii + im->xorigin;
3080 foreY[idxI] = ytop + 0.2;
3081 foreX[idxI] = ii + im->xorigin;
3083 /* close up any remaining area */
3088 } /* else GF_LINE */
3090 /* if color != 0x0 */
3091 /* make sure we do not run into trouble when stacking on NaN */
3092 for (ii = 0; ii < im->xsize; ii++) {
3093 if (isnan(im->gdes[i].p_data[ii])) {
3094 if (lastgdes && (im->gdes[i].stack)) {
3095 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3097 im->gdes[i].p_data[ii] = areazero;
3101 lastgdes = &(im->gdes[i]);
3103 #ifdef WITH_PIECHART
3105 if (isnan(im->gdes[i].yrule)) /* fetch variable */
3106 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
3108 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
3109 pie_part(im, im->gdes[i].col,
3110 im->pie_x, im->pie_y, im->piesize * 0.4,
3111 M_PI * 2.0 * PieStart / 100.0,
3112 M_PI * 2.0 * (PieStart + im->gdes[i].yrule) / 100.0);
3113 PieStart += im->gdes[i].yrule;
3119 ("STACK should already be turned into LINE or AREA here");
3125 #ifdef WITH_PIECHART
3126 if (piechart == 2) {
3127 im->draw_x_grid = 0;
3128 im->draw_y_grid = 0;
3133 /* grid_paint also does the text */
3134 if (!(im->extra_flags & ONLY_GRAPH))
3138 if (!(im->extra_flags & ONLY_GRAPH))
3141 /* the RULES are the last thing to paint ... */
3142 for (i = 0; i < im->gdes_c; i++) {
3144 switch (im->gdes[i].gf) {
3146 if (im->gdes[i].yrule >= im->minval
3147 && im->gdes[i].yrule <= im->maxval)
3148 gfx_new_line(im->canvas,
3149 im->xorigin, ytr(im, im->gdes[i].yrule),
3150 im->xorigin + im->xsize, ytr(im,
3152 1.0, im->gdes[i].col);
3155 if (im->gdes[i].xrule >= im->start
3156 && im->gdes[i].xrule <= im->end)
3157 gfx_new_line(im->canvas,
3158 xtr(im, im->gdes[i].xrule), im->yorigin,
3159 xtr(im, im->gdes[i].xrule),
3160 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3168 if (strcmp(im->graphfile, "-") == 0) {
3169 fo = im->graphhandle ? im->graphhandle : stdout;
3170 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3171 /* Change translation mode for stdout to BINARY */
3172 _setmode(_fileno(fo), O_BINARY);
3175 if ((fo = fopen(im->graphfile, "wb")) == NULL) {
3176 rrd_set_error("Opening '%s' for write: %s", im->graphfile,
3177 rrd_strerror(errno));
3181 gfx_render(im->canvas, im->ximg, im->yimg, 0x00000000, fo);
3182 if (strcmp(im->graphfile, "-") != 0)
3188 /*****************************************************
3190 *****************************************************/
3197 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3198 * sizeof(graph_desc_t))) ==
3200 rrd_set_error("realloc graph_descs");
3205 im->gdes[im->gdes_c - 1].step = im->step;
3206 im->gdes[im->gdes_c - 1].step_orig = im->step;
3207 im->gdes[im->gdes_c - 1].stack = 0;
3208 im->gdes[im->gdes_c - 1].linewidth = 0;
3209 im->gdes[im->gdes_c - 1].debug = 0;
3210 im->gdes[im->gdes_c - 1].start = im->start;
3211 im->gdes[im->gdes_c - 1].start_orig = im->start;
3212 im->gdes[im->gdes_c - 1].end = im->end;
3213 im->gdes[im->gdes_c - 1].end_orig = im->end;
3214 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3215 im->gdes[im->gdes_c - 1].data = NULL;
3216 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3217 im->gdes[im->gdes_c - 1].data_first = 0;
3218 im->gdes[im->gdes_c - 1].p_data = NULL;
3219 im->gdes[im->gdes_c - 1].rpnp = NULL;
3220 im->gdes[im->gdes_c - 1].shift = 0;
3221 im->gdes[im->gdes_c - 1].col = 0x0;
3222 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3223 im->gdes[im->gdes_c - 1].format[0] = '\0';
3224 im->gdes[im->gdes_c - 1].strftm = 0;
3225 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3226 im->gdes[im->gdes_c - 1].ds = -1;
3227 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3228 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3229 im->gdes[im->gdes_c - 1].p_data = NULL;
3230 im->gdes[im->gdes_c - 1].yrule = DNAN;
3231 im->gdes[im->gdes_c - 1].xrule = 0;
3235 /* copies input untill the first unescaped colon is found
3236 or until input ends. backslashes have to be escaped as well */
3238 const char *const input,
3244 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3245 if (input[inp] == '\\' &&
3246 input[inp + 1] != '\0' &&
3247 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3248 output[outp++] = input[++inp];
3250 output[outp++] = input[inp];
3253 output[outp] = '\0';
3257 /* Some surgery done on this function, it became ridiculously big.
3259 ** - initializing now in rrd_graph_init()
3260 ** - options parsing now in rrd_graph_options()
3261 ** - script parsing now in rrd_graph_script()
3275 rrd_graph_init(&im);
3276 im.graphhandle = stream;
3278 rrd_graph_options(argc, argv, &im);
3279 if (rrd_test_error()) {
3284 if (strlen(argv[optind]) >= MAXPATH) {
3285 rrd_set_error("filename (including path) too long");
3289 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3290 im.graphfile[MAXPATH - 1] = '\0';
3292 rrd_graph_script(argc, argv, &im, 1);
3293 if (rrd_test_error()) {
3298 /* Everything is now read and the actual work can start */
3301 if (graph_paint(&im, prdata) == -1) {
3306 /* The image is generated and needs to be output.
3307 ** Also, if needed, print a line with information about the image.
3318 /* maybe prdata is not allocated yet ... lets do it now */
3319 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3320 rrd_set_error("malloc imginfo");
3325 malloc((strlen(im.imginfo) + 200 +
3326 strlen(im.graphfile)) * sizeof(char)))
3328 rrd_set_error("malloc imginfo");
3331 filename = im.graphfile + strlen(im.graphfile);
3332 while (filename > im.graphfile) {
3333 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3338 sprintf((*prdata)[0], im.imginfo, filename,
3339 (long) (im.canvas->zoom * im.ximg),
3340 (long) (im.canvas->zoom * im.yimg));
3346 void rrd_graph_init(
3354 #ifdef HAVE_SETLOCALE
3355 setlocale(LC_TIME, "");
3356 #ifdef HAVE_MBSTOWCS
3357 setlocale(LC_CTYPE, "");
3363 im->xlab_user.minsec = -1;
3369 im->ylegend[0] = '\0';
3370 im->title[0] = '\0';
3371 im->watermark[0] = '\0';
3374 im->unitsexponent = 9999;
3375 im->unitslength = 6;
3376 im->forceleftspace = 0;
3378 im->viewfactor = 1.0;
3379 im->extra_flags = 0;
3385 im->logarithmic = 0;
3386 im->ygridstep = DNAN;
3387 im->draw_x_grid = 1;
3388 im->draw_y_grid = 1;
3393 im->canvas = gfx_new_canvas();
3394 im->grid_dash_on = 1;
3395 im->grid_dash_off = 1;
3396 im->tabwidth = 40.0;
3398 for (i = 0; i < DIM(graph_col); i++)
3399 im->graph_col[i] = graph_col[i];
3401 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3404 char rrd_win_default_font[1000];
3406 windir = getenv("windir");
3407 /* %windir% is something like D:\windows or C:\winnt */
3408 if (windir != NULL) {
3409 strncpy(rrd_win_default_font, windir, 500);
3410 rrd_win_default_font[500] = '\0';
3411 strcat(rrd_win_default_font, "\\fonts\\");
3412 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3413 for (i = 0; i < DIM(text_prop); i++) {
3414 strncpy(text_prop[i].font, rrd_win_default_font,
3415 sizeof(text_prop[i].font) - 1);
3416 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3424 deffont = getenv("RRD_DEFAULT_FONT");
3425 if (deffont != NULL) {
3426 for (i = 0; i < DIM(text_prop); i++) {
3427 strncpy(text_prop[i].font, deffont,
3428 sizeof(text_prop[i].font) - 1);
3429 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3433 for (i = 0; i < DIM(text_prop); i++) {
3434 im->text_prop[i].size = text_prop[i].size;
3435 strcpy(im->text_prop[i].font, text_prop[i].font);
3439 void rrd_graph_options(
3445 char *parsetime_error = NULL;
3446 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3447 time_t start_tmp = 0, end_tmp = 0;
3449 struct rrd_time_value start_tv, end_tv;
3453 opterr = 0; /* initialize getopt */
3455 parsetime("end-24h", &start_tv);
3456 parsetime("now", &end_tv);
3458 /* defines for long options without a short equivalent. should be bytes,
3459 and may not collide with (the ASCII value of) short options */
3460 #define LONGOPT_UNITS_SI 255
3463 static struct option long_options[] = {
3464 {"start", required_argument, 0, 's'},
3465 {"end", required_argument, 0, 'e'},
3466 {"x-grid", required_argument, 0, 'x'},
3467 {"y-grid", required_argument, 0, 'y'},
3468 {"vertical-label", required_argument, 0, 'v'},
3469 {"width", required_argument, 0, 'w'},
3470 {"height", required_argument, 0, 'h'},
3471 {"interlaced", no_argument, 0, 'i'},
3472 {"upper-limit", required_argument, 0, 'u'},
3473 {"lower-limit", required_argument, 0, 'l'},
3474 {"rigid", no_argument, 0, 'r'},
3475 {"base", required_argument, 0, 'b'},
3476 {"logarithmic", no_argument, 0, 'o'},
3477 {"color", required_argument, 0, 'c'},
3478 {"font", required_argument, 0, 'n'},
3479 {"title", required_argument, 0, 't'},
3480 {"imginfo", required_argument, 0, 'f'},
3481 {"imgformat", required_argument, 0, 'a'},
3482 {"lazy", no_argument, 0, 'z'},
3483 {"zoom", required_argument, 0, 'm'},
3484 {"no-legend", no_argument, 0, 'g'},
3485 {"force-rules-legend", no_argument, 0, 'F'},
3486 {"only-graph", no_argument, 0, 'j'},
3487 {"alt-y-grid", no_argument, 0, 'Y'},
3488 {"no-minor", no_argument, 0, 'I'},
3489 {"slope-mode", no_argument, 0, 'E'},
3490 {"alt-autoscale", no_argument, 0, 'A'},
3491 {"alt-autoscale-min", no_argument, 0, 'J'},
3492 {"alt-autoscale-max", no_argument, 0, 'M'},
3493 {"no-gridfit", no_argument, 0, 'N'},
3494 {"units-exponent", required_argument, 0, 'X'},
3495 {"units-length", required_argument, 0, 'L'},
3496 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3497 {"step", required_argument, 0, 'S'},
3498 {"tabwidth", required_argument, 0, 'T'},
3499 {"font-render-mode", required_argument, 0, 'R'},
3500 {"font-smoothing-threshold", required_argument, 0, 'B'},
3501 {"watermark", required_argument, 0, 'W'},
3502 {"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 */
3505 int option_index = 0;
3507 int col_start, col_end;
3509 opt = getopt_long(argc, argv,
3510 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3511 long_options, &option_index);
3518 im->extra_flags |= NOMINOR;
3521 im->extra_flags |= ALTYGRID;
3524 im->extra_flags |= ALTAUTOSCALE;
3527 im->extra_flags |= ALTAUTOSCALE_MIN;
3530 im->extra_flags |= ALTAUTOSCALE_MAX;
3533 im->extra_flags |= ONLY_GRAPH;
3536 im->extra_flags |= NOLEGEND;
3539 im->extra_flags |= FORCE_RULES_LEGEND;
3541 case LONGOPT_UNITS_SI:
3542 if (im->extra_flags & FORCE_UNITS) {
3543 rrd_set_error("--units can only be used once!");
3546 if (strcmp(optarg, "si") == 0)
3547 im->extra_flags |= FORCE_UNITS_SI;
3549 rrd_set_error("invalid argument for --units: %s", optarg);
3554 im->unitsexponent = atoi(optarg);
3557 im->unitslength = atoi(optarg);
3558 im->forceleftspace = 1;
3561 im->tabwidth = atof(optarg);
3564 im->step = atoi(optarg);
3570 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3571 rrd_set_error("start time: %s", parsetime_error);
3576 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3577 rrd_set_error("end time: %s", parsetime_error);
3582 if (strcmp(optarg, "none") == 0) {
3583 im->draw_x_grid = 0;
3588 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3590 &im->xlab_user.gridst,
3592 &im->xlab_user.mgridst,
3594 &im->xlab_user.labst,
3595 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3596 strncpy(im->xlab_form, optarg + stroff,
3597 sizeof(im->xlab_form) - 1);
3598 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3599 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3600 rrd_set_error("unknown keyword %s", scan_gtm);
3602 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3604 rrd_set_error("unknown keyword %s", scan_mtm);
3606 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3608 rrd_set_error("unknown keyword %s", scan_ltm);
3611 im->xlab_user.minsec = 1;
3612 im->xlab_user.stst = im->xlab_form;
3614 rrd_set_error("invalid x-grid format");
3620 if (strcmp(optarg, "none") == 0) {
3621 im->draw_y_grid = 0;
3625 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3626 if (im->ygridstep <= 0) {
3627 rrd_set_error("grid step must be > 0");
3629 } else if (im->ylabfact < 1) {
3630 rrd_set_error("label factor must be > 0");
3634 rrd_set_error("invalid y-grid format");
3639 strncpy(im->ylegend, optarg, 150);
3640 im->ylegend[150] = '\0';
3643 im->maxval = atof(optarg);
3646 im->minval = atof(optarg);
3649 im->base = atol(optarg);
3650 if (im->base != 1024 && im->base != 1000) {
3652 ("the only sensible value for base apart from 1000 is 1024");
3657 long_tmp = atol(optarg);
3658 if (long_tmp < 10) {
3659 rrd_set_error("width below 10 pixels");
3662 im->xsize = long_tmp;
3665 long_tmp = atol(optarg);
3666 if (long_tmp < 10) {
3667 rrd_set_error("height below 10 pixels");
3670 im->ysize = long_tmp;
3673 im->canvas->interlaced = 1;
3679 im->imginfo = optarg;
3682 if ((int) (im->canvas->imgformat = if_conv(optarg)) == -1) {
3683 rrd_set_error("unsupported graphics format '%s'", optarg);
3695 im->logarithmic = 1;
3699 "%10[A-Z]#%n%8lx%n",
3700 col_nam, &col_start, &color, &col_end) == 2) {
3702 int col_len = col_end - col_start;
3706 color = (((color & 0xF00) * 0x110000) |
3707 ((color & 0x0F0) * 0x011000) |
3708 ((color & 0x00F) * 0x001100) | 0x000000FF);
3711 color = (((color & 0xF000) * 0x11000) |
3712 ((color & 0x0F00) * 0x01100) |
3713 ((color & 0x00F0) * 0x00110) |
3714 ((color & 0x000F) * 0x00011)
3718 color = (color << 8) + 0xff /* shift left by 8 */ ;
3723 rrd_set_error("the color format is #RRGGBB[AA]");
3726 if ((ci = grc_conv(col_nam)) != -1) {
3727 im->graph_col[ci] = color;
3729 rrd_set_error("invalid color name '%s'", col_nam);
3733 rrd_set_error("invalid color def format");
3740 char font[1024] = "";
3742 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3743 int sindex, propidx;
3745 if ((sindex = text_prop_conv(prop)) != -1) {
3746 for (propidx = sindex; propidx < TEXT_PROP_LAST;
3749 im->text_prop[propidx].size = size;
3751 if (strlen(font) > 0) {
3752 strcpy(im->text_prop[propidx].font, font);
3754 if (propidx == sindex && sindex != 0)
3758 rrd_set_error("invalid fonttag '%s'", prop);
3762 rrd_set_error("invalid text property format");
3768 im->canvas->zoom = atof(optarg);
3769 if (im->canvas->zoom <= 0.0) {
3770 rrd_set_error("zoom factor must be > 0");
3775 strncpy(im->title, optarg, 150);
3776 im->title[150] = '\0';
3780 if (strcmp(optarg, "normal") == 0)
3781 im->canvas->aa_type = AA_NORMAL;
3782 else if (strcmp(optarg, "light") == 0)
3783 im->canvas->aa_type = AA_LIGHT;
3784 else if (strcmp(optarg, "mono") == 0)
3785 im->canvas->aa_type = AA_NONE;
3787 rrd_set_error("unknown font-render-mode '%s'", optarg);
3793 im->canvas->font_aa_threshold = atof(optarg);
3797 strncpy(im->watermark, optarg, 100);
3798 im->watermark[99] = '\0';
3803 rrd_set_error("unknown option '%c'", optopt);
3805 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3810 if (optind >= argc) {
3811 rrd_set_error("missing filename");
3815 if (im->logarithmic == 1 && im->minval <= 0) {
3817 ("for a logarithmic yaxis you must specify a lower-limit > 0");
3821 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3822 /* error string is set in parsetime.c */
3826 if (start_tmp < 3600 * 24 * 365 * 10) {
3827 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3832 if (end_tmp < start_tmp) {
3833 rrd_set_error("start (%ld) should be less than end (%ld)",
3834 start_tmp, end_tmp);
3838 im->start = start_tmp;
3840 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3843 int rrd_graph_color(
3850 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
3852 color = strstr(var, "#");
3853 if (color == NULL) {
3854 if (optional == 0) {
3855 rrd_set_error("Found no color in %s", err);
3864 rest = strstr(color, ":");
3872 sscanf(color, "#%6lx%n", &col, &n);
3873 col = (col << 8) + 0xff /* shift left by 8 */ ;
3875 rrd_set_error("Color problem in %s", err);
3878 sscanf(color, "#%8lx%n", &col, &n);
3882 rrd_set_error("Color problem in %s", err);
3884 if (rrd_test_error())
3899 while (*ptr != '\0')
3900 if (*ptr++ == '%') {
3902 /* line cannot end with percent char */
3906 /* '%s', '%S' and '%%' are allowed */
3907 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
3910 /* %c is allowed (but use only with vdef!) */
3911 else if (*ptr == 'c') {
3916 /* or else '% 6.2lf' and such are allowed */
3918 /* optional padding character */
3919 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
3922 /* This should take care of 'm.n' with all three optional */
3923 while (*ptr >= '0' && *ptr <= '9')
3927 while (*ptr >= '0' && *ptr <= '9')
3930 /* Either 'le', 'lf' or 'lg' must follow here */
3933 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
3948 struct graph_desc_t *gdes;
3949 const char *const str;
3951 /* A VDEF currently is either "func" or "param,func"
3952 * so the parsing is rather simple. Change if needed.
3959 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
3960 if (n == (int) strlen(str)) { /* matched */
3964 sscanf(str, "%29[A-Z]%n", func, &n);
3965 if (n == (int) strlen(str)) { /* matched */
3968 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
3973 if (!strcmp("PERCENT", func))
3974 gdes->vf.op = VDEF_PERCENT;
3975 else if (!strcmp("MAXIMUM", func))
3976 gdes->vf.op = VDEF_MAXIMUM;
3977 else if (!strcmp("AVERAGE", func))
3978 gdes->vf.op = VDEF_AVERAGE;
3979 else if (!strcmp("MINIMUM", func))
3980 gdes->vf.op = VDEF_MINIMUM;
3981 else if (!strcmp("TOTAL", func))
3982 gdes->vf.op = VDEF_TOTAL;
3983 else if (!strcmp("FIRST", func))
3984 gdes->vf.op = VDEF_FIRST;
3985 else if (!strcmp("LAST", func))
3986 gdes->vf.op = VDEF_LAST;
3987 else if (!strcmp("LSLSLOPE", func))
3988 gdes->vf.op = VDEF_LSLSLOPE;
3989 else if (!strcmp("LSLINT", func))
3990 gdes->vf.op = VDEF_LSLINT;
3991 else if (!strcmp("LSLCORREL", func))
3992 gdes->vf.op = VDEF_LSLCORREL;
3994 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
3999 switch (gdes->vf.op) {
4001 if (isnan(param)) { /* no parameter given */
4002 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4006 if (param >= 0.0 && param <= 100.0) {
4007 gdes->vf.param = param;
4008 gdes->vf.val = DNAN; /* undefined */
4009 gdes->vf.when = 0; /* undefined */
4011 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4024 case VDEF_LSLCORREL:
4026 gdes->vf.param = DNAN;
4027 gdes->vf.val = DNAN;
4030 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4046 graph_desc_t *src, *dst;
4050 dst = &im->gdes[gdi];
4051 src = &im->gdes[dst->vidx];
4052 data = src->data + src->ds;
4053 steps = (src->end - src->start) / src->step;
4056 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4060 switch (dst->vf.op) {
4066 if ((array = malloc(steps * sizeof(double))) == NULL) {
4067 rrd_set_error("malloc VDEV_PERCENT");
4070 for (step = 0; step < steps; step++) {
4071 array[step] = data[step * src->ds_cnt];
4073 qsort(array, step, sizeof(double), vdef_percent_compar);
4075 field = (steps - 1) * dst->vf.param / 100;
4076 dst->vf.val = array[field];
4077 dst->vf.when = 0; /* no time component */
4080 for (step = 0; step < steps; step++)
4081 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4082 step == field ? '*' : ' ');
4088 while (step != steps && isnan(data[step * src->ds_cnt]))
4090 if (step == steps) {
4094 dst->vf.val = data[step * src->ds_cnt];
4095 dst->vf.when = src->start + (step + 1) * src->step;
4097 while (step != steps) {
4098 if (finite(data[step * src->ds_cnt])) {
4099 if (data[step * src->ds_cnt] > dst->vf.val) {
4100 dst->vf.val = data[step * src->ds_cnt];
4101 dst->vf.when = src->start + (step + 1) * src->step;
4112 for (step = 0; step < steps; step++) {
4113 if (finite(data[step * src->ds_cnt])) {
4114 sum += data[step * src->ds_cnt];
4119 if (dst->vf.op == VDEF_TOTAL) {
4120 dst->vf.val = sum * src->step;
4121 dst->vf.when = 0; /* no time component */
4123 dst->vf.val = sum / cnt;
4124 dst->vf.when = 0; /* no time component */
4134 while (step != steps && isnan(data[step * src->ds_cnt]))
4136 if (step == steps) {
4140 dst->vf.val = data[step * src->ds_cnt];
4141 dst->vf.when = src->start + (step + 1) * src->step;
4143 while (step != steps) {
4144 if (finite(data[step * src->ds_cnt])) {
4145 if (data[step * src->ds_cnt] < dst->vf.val) {
4146 dst->vf.val = data[step * src->ds_cnt];
4147 dst->vf.when = src->start + (step + 1) * src->step;
4154 /* The time value returned here is one step before the
4155 * actual time value. This is the start of the first
4159 while (step != steps && isnan(data[step * src->ds_cnt]))
4161 if (step == steps) { /* all entries were NaN */
4165 dst->vf.val = data[step * src->ds_cnt];
4166 dst->vf.when = src->start + step * src->step;
4170 /* The time value returned here is the
4171 * actual time value. This is the end of the last
4175 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4177 if (step < 0) { /* all entries were NaN */
4181 dst->vf.val = data[step * src->ds_cnt];
4182 dst->vf.when = src->start + (step + 1) * src->step;
4187 case VDEF_LSLCORREL:{
4188 /* Bestfit line by linear least squares method */
4191 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4199 for (step = 0; step < steps; step++) {
4200 if (finite(data[step * src->ds_cnt])) {
4203 SUMxx += step * step;
4204 SUMxy += step * data[step * src->ds_cnt];
4205 SUMy += data[step * src->ds_cnt];
4206 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4210 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4211 y_intercept = (SUMy - slope * SUMx) / cnt;
4214 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4215 (SUMx * SUMx) / cnt) * (SUMyy -
4221 if (dst->vf.op == VDEF_LSLSLOPE) {
4222 dst->vf.val = slope;
4224 } else if (dst->vf.op == VDEF_LSLINT) {
4225 dst->vf.val = y_intercept;
4227 } else if (dst->vf.op == VDEF_LSLCORREL) {
4228 dst->vf.val = correl;
4242 /* NaN < -INF < finite_values < INF */
4243 int vdef_percent_compar(
4248 /* Equality is not returned; this doesn't hurt except
4249 * (maybe) for a little performance.
4252 /* First catch NaN values. They are smallest */
4253 if (isnan(*(double *) a))
4255 if (isnan(*(double *) b))
4258 /* NaN doesn't reach this part so INF and -INF are extremes.
4259 * The sign from isinf() is compatible with the sign we return
4261 if (isinf(*(double *) a))
4262 return isinf(*(double *) a);
4263 if (isinf(*(double *) b))
4264 return isinf(*(double *) b);
4266 /* If we reach this, both values must be finite */
4267 if (*(double *) a < *(double *) b)