1 /****************************************************************************
2 * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
20 #include "plbasename.h"
23 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
32 #include "rrd_graph.h"
33 #include "rrd_client.h"
35 /* some constant definitions */
39 #ifndef RRD_DEFAULT_FONT
40 /* there is special code later to pick Cour.ttf when running on windows */
41 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
44 text_prop_t text_prop[] = {
45 {8.0, RRD_DEFAULT_FONT,NULL}
47 {9.0, RRD_DEFAULT_FONT,NULL}
49 {7.0, RRD_DEFAULT_FONT,NULL}
51 {8.0, RRD_DEFAULT_FONT,NULL}
53 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
55 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
59 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
61 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
63 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
65 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
67 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
69 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
71 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
73 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
75 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
77 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
78 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
80 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
82 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
84 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
86 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
88 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
91 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
94 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
97 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
99 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
100 365 * 24 * 3600, "%y"}
102 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
105 /* sensible y label intervals ...*/
129 {20.0, {1, 5, 10, 20}
135 {100.0, {1, 2, 5, 10}
138 {200.0, {1, 5, 10, 20}
141 {500.0, {1, 2, 4, 10}
149 gfx_color_t graph_col[] = /* default colors */
151 {1.00, 1.00, 1.00, 1.00}, /* canvas */
152 {0.95, 0.95, 0.95, 1.00}, /* background */
153 {0.81, 0.81, 0.81, 1.00}, /* shade A */
154 {0.62, 0.62, 0.62, 1.00}, /* shade B */
155 {0.56, 0.56, 0.56, 0.75}, /* grid */
156 {0.87, 0.31, 0.31, 0.60}, /* major grid */
157 {0.00, 0.00, 0.00, 1.00}, /* font */
158 {0.50, 0.12, 0.12, 1.00}, /* arrow */
159 {0.12, 0.12, 0.12, 1.00}, /* axis */
160 {0.00, 0.00, 0.00, 1.00} /* frame */
167 # define DPRINT(x) (void)(printf x, printf("\n"))
173 /* initialize with xtr(im,0); */
181 pixie = (double) im->xsize / (double) (im->end - im->start);
184 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
187 /* translate data values into y coordinates */
196 if (!im->logarithmic)
197 pixie = (double) im->ysize / (im->maxval - im->minval);
200 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
202 } else if (!im->logarithmic) {
203 yval = im->yorigin - pixie * (value - im->minval);
205 if (value < im->minval) {
208 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
216 /* conversion function for symbolic entry names */
219 #define conv_if(VV,VVV) \
220 if (strcmp(#VV, string) == 0) return VVV ;
226 conv_if(PRINT, GF_PRINT);
227 conv_if(GPRINT, GF_GPRINT);
228 conv_if(COMMENT, GF_COMMENT);
229 conv_if(HRULE, GF_HRULE);
230 conv_if(VRULE, GF_VRULE);
231 conv_if(LINE, GF_LINE);
232 conv_if(AREA, GF_AREA);
233 conv_if(STACK, GF_STACK);
234 conv_if(TICK, GF_TICK);
235 conv_if(TEXTALIGN, GF_TEXTALIGN);
236 conv_if(DEF, GF_DEF);
237 conv_if(CDEF, GF_CDEF);
238 conv_if(VDEF, GF_VDEF);
239 conv_if(XPORT, GF_XPORT);
240 conv_if(SHIFT, GF_SHIFT);
242 return (enum gf_en)(-1);
245 enum gfx_if_en if_conv(
249 conv_if(PNG, IF_PNG);
250 conv_if(SVG, IF_SVG);
251 conv_if(EPS, IF_EPS);
252 conv_if(PDF, IF_PDF);
254 return (enum gfx_if_en)(-1);
257 enum tmt_en tmt_conv(
261 conv_if(SECOND, TMT_SECOND);
262 conv_if(MINUTE, TMT_MINUTE);
263 conv_if(HOUR, TMT_HOUR);
264 conv_if(DAY, TMT_DAY);
265 conv_if(WEEK, TMT_WEEK);
266 conv_if(MONTH, TMT_MONTH);
267 conv_if(YEAR, TMT_YEAR);
268 return (enum tmt_en)(-1);
271 enum grc_en grc_conv(
275 conv_if(BACK, GRC_BACK);
276 conv_if(CANVAS, GRC_CANVAS);
277 conv_if(SHADEA, GRC_SHADEA);
278 conv_if(SHADEB, GRC_SHADEB);
279 conv_if(GRID, GRC_GRID);
280 conv_if(MGRID, GRC_MGRID);
281 conv_if(FONT, GRC_FONT);
282 conv_if(ARROW, GRC_ARROW);
283 conv_if(AXIS, GRC_AXIS);
284 conv_if(FRAME, GRC_FRAME);
286 return (enum grc_en)(-1);
289 enum text_prop_en text_prop_conv(
293 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
294 conv_if(TITLE, TEXT_PROP_TITLE);
295 conv_if(AXIS, TEXT_PROP_AXIS);
296 conv_if(UNIT, TEXT_PROP_UNIT);
297 conv_if(LEGEND, TEXT_PROP_LEGEND);
298 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
299 return (enum text_prop_en)(-1);
309 cairo_status_t status = (cairo_status_t) 0;
314 if (im->daemon_addr != NULL)
315 free(im->daemon_addr);
317 for (i = 0; i < (unsigned) im->gdes_c; i++) {
318 if (im->gdes[i].data_first) {
319 /* careful here, because a single pointer can occur several times */
320 free(im->gdes[i].data);
321 if (im->gdes[i].ds_namv) {
322 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
323 free(im->gdes[i].ds_namv[ii]);
324 free(im->gdes[i].ds_namv);
327 /* free allocated memory used for dashed lines */
328 if (im->gdes[i].p_dashes != NULL)
329 free(im->gdes[i].p_dashes);
331 free(im->gdes[i].p_data);
332 free(im->gdes[i].rpnp);
335 if (im->font_options)
336 cairo_font_options_destroy(im->font_options);
339 status = cairo_status(im->cr);
340 cairo_destroy(im->cr);
342 if (im->rendered_image) {
343 free(im->rendered_image);
347 g_object_unref (im->layout);
351 cairo_surface_destroy(im->surface);
354 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
355 cairo_status_to_string(status));
360 /* find SI magnitude symbol for the given number*/
362 image_desc_t *im, /* image description */
368 char *symbol[] = { "a", /* 10e-18 Atto */
369 "f", /* 10e-15 Femto */
370 "p", /* 10e-12 Pico */
371 "n", /* 10e-9 Nano */
372 "u", /* 10e-6 Micro */
373 "m", /* 10e-3 Milli */
378 "T", /* 10e12 Tera */
379 "P", /* 10e15 Peta */
386 if (*value == 0.0 || isnan(*value)) {
390 sindex = floor(log(fabs(*value)) / log((double) im->base));
391 *magfact = pow((double) im->base, (double) sindex);
392 (*value) /= (*magfact);
394 if (sindex <= symbcenter && sindex >= -symbcenter) {
395 (*symb_ptr) = symbol[sindex + symbcenter];
402 static char si_symbol[] = {
403 'a', /* 10e-18 Atto */
404 'f', /* 10e-15 Femto */
405 'p', /* 10e-12 Pico */
406 'n', /* 10e-9 Nano */
407 'u', /* 10e-6 Micro */
408 'm', /* 10e-3 Milli */
413 'T', /* 10e12 Tera */
414 'P', /* 10e15 Peta */
417 static const int si_symbcenter = 6;
419 /* find SI magnitude symbol for the numbers on the y-axis*/
421 image_desc_t *im /* image description */
425 double digits, viewdigits = 0;
428 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
429 log((double) im->base));
431 if (im->unitsexponent != 9999) {
432 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
433 viewdigits = floor((double)(im->unitsexponent / 3));
438 im->magfact = pow((double) im->base, digits);
441 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
444 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
446 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
447 ((viewdigits + si_symbcenter) >= 0))
448 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
453 /* move min and max values around to become sensible */
458 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
459 600.0, 500.0, 400.0, 300.0, 250.0,
460 200.0, 125.0, 100.0, 90.0, 80.0,
461 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
462 25.0, 20.0, 10.0, 9.0, 8.0,
463 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
464 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
465 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
468 double scaled_min, scaled_max;
475 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
476 im->minval, im->maxval, im->magfact);
479 if (isnan(im->ygridstep)) {
480 if (im->extra_flags & ALTAUTOSCALE) {
481 /* measure the amplitude of the function. Make sure that
482 graph boundaries are slightly higher then max/min vals
483 so we can see amplitude on the graph */
486 delt = im->maxval - im->minval;
488 fact = 2.0 * pow(10.0,
490 (max(fabs(im->minval), fabs(im->maxval)) /
493 adj = (fact - delt) * 0.55;
496 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
497 im->minval, im->maxval, delt, fact, adj);
502 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
503 /* measure the amplitude of the function. Make sure that
504 graph boundaries are slightly lower than min vals
505 so we can see amplitude on the graph */
506 adj = (im->maxval - im->minval) * 0.1;
508 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
509 /* measure the amplitude of the function. Make sure that
510 graph boundaries are slightly higher than max vals
511 so we can see amplitude on the graph */
512 adj = (im->maxval - im->minval) * 0.1;
515 scaled_min = im->minval / im->magfact;
516 scaled_max = im->maxval / im->magfact;
518 for (i = 1; sensiblevalues[i] > 0; i++) {
519 if (sensiblevalues[i - 1] >= scaled_min &&
520 sensiblevalues[i] <= scaled_min)
521 im->minval = sensiblevalues[i] * (im->magfact);
523 if (-sensiblevalues[i - 1] <= scaled_min &&
524 -sensiblevalues[i] >= scaled_min)
525 im->minval = -sensiblevalues[i - 1] * (im->magfact);
527 if (sensiblevalues[i - 1] >= scaled_max &&
528 sensiblevalues[i] <= scaled_max)
529 im->maxval = sensiblevalues[i - 1] * (im->magfact);
531 if (-sensiblevalues[i - 1] <= scaled_max &&
532 -sensiblevalues[i] >= scaled_max)
533 im->maxval = -sensiblevalues[i] * (im->magfact);
537 /* adjust min and max to the grid definition if there is one */
538 im->minval = (double) im->ylabfact * im->ygridstep *
539 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
540 im->maxval = (double) im->ylabfact * im->ygridstep *
541 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
545 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
546 im->minval, im->maxval, im->magfact);
554 if (isnan(im->minval) || isnan(im->maxval))
557 if (im->logarithmic) {
558 double ya, yb, ypix, ypixfrac;
559 double log10_range = log10(im->maxval) - log10(im->minval);
561 ya = pow((double) 10, floor(log10(im->minval)));
562 while (ya < im->minval)
565 return; /* don't have y=10^x gridline */
567 if (yb <= im->maxval) {
568 /* we have at least 2 y=10^x gridlines.
569 Make sure distance between them in pixels
570 are an integer by expanding im->maxval */
571 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
572 double factor = y_pixel_delta / floor(y_pixel_delta);
573 double new_log10_range = factor * log10_range;
574 double new_ymax_log10 = log10(im->minval) + new_log10_range;
576 im->maxval = pow(10, new_ymax_log10);
577 ytr(im, DNAN); /* reset precalc */
578 log10_range = log10(im->maxval) - log10(im->minval);
580 /* make sure first y=10^x gridline is located on
581 integer pixel position by moving scale slightly
582 downwards (sub-pixel movement) */
583 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
584 ypixfrac = ypix - floor(ypix);
585 if (ypixfrac > 0 && ypixfrac < 1) {
586 double yfrac = ypixfrac / im->ysize;
588 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
589 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
590 ytr(im, DNAN); /* reset precalc */
593 /* Make sure we have an integer pixel distance between
594 each minor gridline */
595 double ypos1 = ytr(im, im->minval);
596 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
597 double y_pixel_delta = ypos1 - ypos2;
598 double factor = y_pixel_delta / floor(y_pixel_delta);
599 double new_range = factor * (im->maxval - im->minval);
600 double gridstep = im->ygrid_scale.gridstep;
601 double minor_y, minor_y_px, minor_y_px_frac;
603 if (im->maxval > 0.0)
604 im->maxval = im->minval + new_range;
606 im->minval = im->maxval - new_range;
607 ytr(im, DNAN); /* reset precalc */
608 /* make sure first minor gridline is on integer pixel y coord */
609 minor_y = gridstep * floor(im->minval / gridstep);
610 while (minor_y < im->minval)
612 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
613 minor_y_px_frac = minor_y_px - floor(minor_y_px);
614 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
615 double yfrac = minor_y_px_frac / im->ysize;
616 double range = im->maxval - im->minval;
618 im->minval = im->minval - yfrac * range;
619 im->maxval = im->maxval - yfrac * range;
620 ytr(im, DNAN); /* reset precalc */
622 calc_horizontal_grid(im); /* recalc with changed im->maxval */
626 /* reduce data reimplementation by Alex */
629 enum cf_en cf, /* which consolidation function ? */
630 unsigned long cur_step, /* step the data currently is in */
631 time_t *start, /* start, end and step as requested ... */
632 time_t *end, /* ... by the application will be ... */
633 unsigned long *step, /* ... adjusted to represent reality */
634 unsigned long *ds_cnt, /* number of data sources in file */
636 { /* two dimensional array containing the data */
637 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
638 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
640 rrd_value_t *srcptr, *dstptr;
642 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
645 row_cnt = ((*end) - (*start)) / cur_step;
651 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
652 row_cnt, reduce_factor, *start, *end, cur_step);
653 for (col = 0; col < row_cnt; col++) {
654 printf("time %10lu: ", *start + (col + 1) * cur_step);
655 for (i = 0; i < *ds_cnt; i++)
656 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
661 /* We have to combine [reduce_factor] rows of the source
662 ** into one row for the destination. Doing this we also
663 ** need to take care to combine the correct rows. First
664 ** alter the start and end time so that they are multiples
665 ** of the new step time. We cannot reduce the amount of
666 ** time so we have to move the end towards the future and
667 ** the start towards the past.
669 end_offset = (*end) % (*step);
670 start_offset = (*start) % (*step);
672 /* If there is a start offset (which cannot be more than
673 ** one destination row), skip the appropriate number of
674 ** source rows and one destination row. The appropriate
675 ** number is what we do know (start_offset/cur_step) of
676 ** the new interval (*step/cur_step aka reduce_factor).
679 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
680 printf("row_cnt before: %lu\n", row_cnt);
683 (*start) = (*start) - start_offset;
684 skiprows = reduce_factor - start_offset / cur_step;
685 srcptr += skiprows * *ds_cnt;
686 for (col = 0; col < (*ds_cnt); col++)
691 printf("row_cnt between: %lu\n", row_cnt);
694 /* At the end we have some rows that are not going to be
695 ** used, the amount is end_offset/cur_step
698 (*end) = (*end) - end_offset + (*step);
699 skiprows = end_offset / cur_step;
703 printf("row_cnt after: %lu\n", row_cnt);
706 /* Sanity check: row_cnt should be multiple of reduce_factor */
707 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
709 if (row_cnt % reduce_factor) {
710 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
711 row_cnt, reduce_factor);
712 printf("BUG in reduce_data()\n");
716 /* Now combine reduce_factor intervals at a time
717 ** into one interval for the destination.
720 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
721 for (col = 0; col < (*ds_cnt); col++) {
722 rrd_value_t newval = DNAN;
723 unsigned long validval = 0;
725 for (i = 0; i < reduce_factor; i++) {
726 if (isnan(srcptr[i * (*ds_cnt) + col])) {
731 newval = srcptr[i * (*ds_cnt) + col];
740 newval += srcptr[i * (*ds_cnt) + col];
743 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
746 /* an interval contains a failure if any subintervals contained a failure */
748 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
751 newval = srcptr[i * (*ds_cnt) + col];
777 srcptr += (*ds_cnt) * reduce_factor;
778 row_cnt -= reduce_factor;
780 /* If we had to alter the endtime, we didn't have enough
781 ** source rows to fill the last row. Fill it with NaN.
784 for (col = 0; col < (*ds_cnt); col++)
787 row_cnt = ((*end) - (*start)) / *step;
789 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
790 row_cnt, *start, *end, *step);
791 for (col = 0; col < row_cnt; col++) {
792 printf("time %10lu: ", *start + (col + 1) * (*step));
793 for (i = 0; i < *ds_cnt; i++)
794 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
801 /* get the data required for the graphs from the
810 /* pull the data from the rrd files ... */
811 for (i = 0; i < (int) im->gdes_c; i++) {
812 /* only GF_DEF elements fetch data */
813 if (im->gdes[i].gf != GF_DEF)
817 /* do we have it already ? */
818 for (ii = 0; ii < i; ii++) {
819 if (im->gdes[ii].gf != GF_DEF)
821 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
822 && (im->gdes[i].cf == im->gdes[ii].cf)
823 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
824 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
825 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
826 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
827 /* OK, the data is already there.
828 ** Just copy the header portion
830 im->gdes[i].start = im->gdes[ii].start;
831 im->gdes[i].end = im->gdes[ii].end;
832 im->gdes[i].step = im->gdes[ii].step;
833 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
834 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
835 im->gdes[i].data = im->gdes[ii].data;
836 im->gdes[i].data_first = 0;
843 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
846 * - a connection to the daemon has been established
847 * - this is the first occurrence of that RRD file
849 if (rrdc_is_connected(im->daemon_addr))
854 for (ii = 0; ii < i; ii++)
856 if (strcmp (im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
865 status = rrdc_flush (im->gdes[i].rrd);
868 rrd_set_error ("rrdc_flush (%s) failed with status %i.",
869 im->gdes[i].rrd, status);
873 } /* if (rrdc_is_connected()) */
875 if ((rrd_fetch_fn(im->gdes[i].rrd,
881 &im->gdes[i].ds_namv,
882 &im->gdes[i].data)) == -1) {
885 im->gdes[i].data_first = 1;
887 if (ft_step < im->gdes[i].step) {
888 reduce_data(im->gdes[i].cf_reduce,
893 &im->gdes[i].ds_cnt, &im->gdes[i].data);
895 im->gdes[i].step = ft_step;
899 /* lets see if the required data source is really there */
900 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
901 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
905 if (im->gdes[i].ds == -1) {
906 rrd_set_error("No DS called '%s' in '%s'",
907 im->gdes[i].ds_nam, im->gdes[i].rrd);
915 /* evaluate the expressions in the CDEF functions */
917 /*************************************************************
919 *************************************************************/
921 long find_var_wrapper(
925 return find_var((image_desc_t *) arg1, key);
928 /* find gdes containing var*/
935 for (ii = 0; ii < im->gdes_c - 1; ii++) {
936 if ((im->gdes[ii].gf == GF_DEF
937 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
938 && (strcmp(im->gdes[ii].vname, key) == 0)) {
945 /* find the greatest common divisor for all the numbers
946 in the 0 terminated num array */
953 for (i = 0; num[i + 1] != 0; i++) {
955 rest = num[i] % num[i + 1];
961 /* return i==0?num[i]:num[i-1]; */
965 /* run the rpn calculator on all the VDEF and CDEF arguments */
972 long *steparray, rpi;
977 rpnstack_init(&rpnstack);
979 for (gdi = 0; gdi < im->gdes_c; gdi++) {
980 /* Look for GF_VDEF and GF_CDEF in the same loop,
981 * so CDEFs can use VDEFs and vice versa
983 switch (im->gdes[gdi].gf) {
987 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
989 /* remove current shift */
990 vdp->start -= vdp->shift;
991 vdp->end -= vdp->shift;
994 if (im->gdes[gdi].shidx >= 0)
995 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
998 vdp->shift = im->gdes[gdi].shval;
1000 /* normalize shift to multiple of consolidated step */
1001 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1004 vdp->start += vdp->shift;
1005 vdp->end += vdp->shift;
1009 /* A VDEF has no DS. This also signals other parts
1010 * of rrdtool that this is a VDEF value, not a CDEF.
1012 im->gdes[gdi].ds_cnt = 0;
1013 if (vdef_calc(im, gdi)) {
1014 rrd_set_error("Error processing VDEF '%s'",
1015 im->gdes[gdi].vname);
1016 rpnstack_free(&rpnstack);
1021 im->gdes[gdi].ds_cnt = 1;
1022 im->gdes[gdi].ds = 0;
1023 im->gdes[gdi].data_first = 1;
1024 im->gdes[gdi].start = 0;
1025 im->gdes[gdi].end = 0;
1030 /* Find the variables in the expression.
1031 * - VDEF variables are substituted by their values
1032 * and the opcode is changed into OP_NUMBER.
1033 * - CDEF variables are analized for their step size,
1034 * the lowest common denominator of all the step
1035 * sizes of the data sources involved is calculated
1036 * and the resulting number is the step size for the
1037 * resulting data source.
1039 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1040 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1041 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1042 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1044 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1047 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1048 im->gdes[gdi].vname, im->gdes[ptr].vname);
1049 printf("DEBUG: value from vdef is %f\n",
1050 im->gdes[ptr].vf.val);
1052 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1053 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1054 } else { /* normal variables and PREF(variables) */
1056 /* add one entry to the array that keeps track of the step sizes of the
1057 * data sources going into the CDEF. */
1059 (long*)rrd_realloc(steparray,
1061 1) * sizeof(*steparray))) == NULL) {
1062 rrd_set_error("realloc steparray");
1063 rpnstack_free(&rpnstack);
1067 steparray[stepcnt - 1] = im->gdes[ptr].step;
1069 /* adjust start and end of cdef (gdi) so
1070 * that it runs from the latest start point
1071 * to the earliest endpoint of any of the
1072 * rras involved (ptr)
1075 if (im->gdes[gdi].start < im->gdes[ptr].start)
1076 im->gdes[gdi].start = im->gdes[ptr].start;
1078 if (im->gdes[gdi].end == 0 ||
1079 im->gdes[gdi].end > im->gdes[ptr].end)
1080 im->gdes[gdi].end = im->gdes[ptr].end;
1082 /* store pointer to the first element of
1083 * the rra providing data for variable,
1084 * further save step size and data source
1087 im->gdes[gdi].rpnp[rpi].data =
1088 im->gdes[ptr].data + im->gdes[ptr].ds;
1089 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1090 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1092 /* backoff the *.data ptr; this is done so
1093 * rpncalc() function doesn't have to treat
1094 * the first case differently
1096 } /* if ds_cnt != 0 */
1097 } /* if OP_VARIABLE */
1098 } /* loop through all rpi */
1100 /* move the data pointers to the correct period */
1101 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1102 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1103 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1104 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1106 im->gdes[gdi].start - im->gdes[ptr].start;
1109 im->gdes[gdi].rpnp[rpi].data +=
1110 (diff / im->gdes[ptr].step) *
1111 im->gdes[ptr].ds_cnt;
1115 if (steparray == NULL) {
1116 rrd_set_error("rpn expressions without DEF"
1117 " or CDEF variables are not supported");
1118 rpnstack_free(&rpnstack);
1121 steparray[stepcnt] = 0;
1122 /* Now find the resulting step. All steps in all
1123 * used RRAs have to be visited
1125 im->gdes[gdi].step = lcd(steparray);
1127 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1128 im->gdes[gdi].start)
1129 / im->gdes[gdi].step)
1130 * sizeof(double))) == NULL) {
1131 rrd_set_error("malloc im->gdes[gdi].data");
1132 rpnstack_free(&rpnstack);
1136 /* Step through the new cdef results array and
1137 * calculate the values
1139 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1140 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1141 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1143 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1144 * in this case we are advancing by timesteps;
1145 * we use the fact that time_t is a synonym for long
1147 if (rpn_calc(rpnp, &rpnstack, (long) now,
1148 im->gdes[gdi].data, ++dataidx) == -1) {
1149 /* rpn_calc sets the error string */
1150 rpnstack_free(&rpnstack);
1153 } /* enumerate over time steps within a CDEF */
1158 } /* enumerate over CDEFs */
1159 rpnstack_free(&rpnstack);
1163 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1164 /* yes we are loosing precision by doing tos with floats instead of doubles
1165 but it seems more stable this way. */
1167 static int AlmostEqual2sComplement(
1173 int aInt = *(int *) &A;
1174 int bInt = *(int *) &B;
1177 /* Make sure maxUlps is non-negative and small enough that the
1178 default NAN won't compare as equal to anything. */
1180 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1182 /* Make aInt lexicographically ordered as a twos-complement int */
1185 aInt = 0x80000000l - aInt;
1187 /* Make bInt lexicographically ordered as a twos-complement int */
1190 bInt = 0x80000000l - bInt;
1192 intDiff = abs(aInt - bInt);
1194 if (intDiff <= maxUlps)
1200 /* massage data so, that we get one value for each x coordinate in the graph */
1205 double pixstep = (double) (im->end - im->start)
1206 / (double) im->xsize; /* how much time
1207 passes in one pixel */
1209 double minval = DNAN, maxval = DNAN;
1211 unsigned long gr_time;
1213 /* memory for the processed data */
1214 for (i = 0; i < im->gdes_c; i++) {
1215 if ((im->gdes[i].gf == GF_LINE) ||
1216 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1217 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1218 * sizeof(rrd_value_t))) == NULL) {
1219 rrd_set_error("malloc data_proc");
1225 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1228 gr_time = im->start + pixstep * i; /* time of the current step */
1231 for (ii = 0; ii < im->gdes_c; ii++) {
1234 switch (im->gdes[ii].gf) {
1238 if (!im->gdes[ii].stack)
1240 value = im->gdes[ii].yrule;
1241 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1242 /* The time of the data doesn't necessarily match
1243 ** the time of the graph. Beware.
1245 vidx = im->gdes[ii].vidx;
1246 if (im->gdes[vidx].gf == GF_VDEF) {
1247 value = im->gdes[vidx].vf.val;
1249 if (((long int) gr_time >=
1250 (long int) im->gdes[vidx].start)
1251 && ((long int) gr_time <=
1252 (long int) im->gdes[vidx].end)) {
1253 value = im->gdes[vidx].data[(unsigned long)
1259 im->gdes[vidx].step)
1260 * im->gdes[vidx].ds_cnt +
1267 if (!isnan(value)) {
1269 im->gdes[ii].p_data[i] = paintval;
1270 /* GF_TICK: the data values are not
1271 ** relevant for min and max
1273 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1274 if ((isnan(minval) || paintval < minval) &&
1275 !(im->logarithmic && paintval <= 0.0))
1277 if (isnan(maxval) || paintval > maxval)
1281 im->gdes[ii].p_data[i] = DNAN;
1286 ("STACK should already be turned into LINE or AREA here");
1295 /* if min or max have not been asigned a value this is because
1296 there was no data in the graph ... this is not good ...
1297 lets set these to dummy values then ... */
1299 if (im->logarithmic) {
1300 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1301 minval = 0.0; /* catching this right away below */
1304 /* in logarithm mode, where minval is smaller or equal
1305 to 0 make the beast just way smaller than maxval */
1307 minval = maxval / 10e8;
1310 if (isnan(minval) || isnan(maxval)) {
1316 /* adjust min and max values given by the user */
1317 /* for logscale we add something on top */
1318 if (isnan(im->minval)
1319 || ((!im->rigid) && im->minval > minval)
1321 if (im->logarithmic)
1322 im->minval = minval / 2.0;
1324 im->minval = minval;
1326 if (isnan(im->maxval)
1327 || (!im->rigid && im->maxval < maxval)
1329 if (im->logarithmic)
1330 im->maxval = maxval * 2.0;
1332 im->maxval = maxval;
1335 /* make sure min is smaller than max */
1336 if (im->minval > im->maxval) {
1338 im->minval = 0.99 * im->maxval;
1340 im->minval = 1.01 * im->maxval;
1343 /* make sure min and max are not equal */
1344 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1350 /* make sure min and max are not both zero */
1351 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1360 /* identify the point where the first gridline, label ... gets placed */
1362 time_t find_first_time(
1363 time_t start, /* what is the initial time */
1364 enum tmt_en baseint, /* what is the basic interval */
1365 long basestep /* how many if these do we jump a time */
1370 localtime_r(&start, &tm);
1374 tm. tm_sec -= tm.tm_sec % basestep;
1379 tm. tm_min -= tm.tm_min % basestep;
1385 tm. tm_hour -= tm.tm_hour % basestep;
1389 /* we do NOT look at the basestep for this ... */
1396 /* we do NOT look at the basestep for this ... */
1400 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1402 if (tm.tm_wday == 0)
1403 tm. tm_mday -= 7; /* we want the *previous* monday */
1411 tm. tm_mon -= tm.tm_mon % basestep;
1422 tm.tm_year + 1900) %basestep;
1428 /* identify the point where the next gridline, label ... gets placed */
1429 time_t find_next_time(
1430 time_t current, /* what is the initial time */
1431 enum tmt_en baseint, /* what is the basic interval */
1432 long basestep /* how many if these do we jump a time */
1438 localtime_r(¤t, &tm);
1443 tm. tm_sec += basestep;
1447 tm. tm_min += basestep;
1451 tm. tm_hour += basestep;
1455 tm. tm_mday += basestep;
1459 tm. tm_mday += 7 * basestep;
1463 tm. tm_mon += basestep;
1467 tm. tm_year += basestep;
1469 madetime = mktime(&tm);
1470 } while (madetime == -1); /* this is necessary to skip impssible times
1471 like the daylight saving time skips */
1477 /* calculate values required for PRINT and GPRINT functions */
1482 long i, ii, validsteps;
1485 int graphelement = 0;
1488 double magfact = -1;
1493 /* wow initializing tmvdef is quite a task :-) */
1494 time_t now = time(NULL);
1496 localtime_r(&now, &tmvdef);
1497 for (i = 0; i < im->gdes_c; i++) {
1498 vidx = im->gdes[i].vidx;
1499 switch (im->gdes[i].gf) {
1502 /* PRINT and GPRINT can now print VDEF generated values.
1503 * There's no need to do any calculations on them as these
1504 * calculations were already made.
1506 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1507 printval = im->gdes[vidx].vf.val;
1508 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1509 } else { /* need to calculate max,min,avg etcetera */
1510 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1511 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1514 for (ii = im->gdes[vidx].ds;
1515 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1516 if (!finite(im->gdes[vidx].data[ii]))
1518 if (isnan(printval)) {
1519 printval = im->gdes[vidx].data[ii];
1524 switch (im->gdes[i].cf) {
1528 case CF_DEVSEASONAL:
1532 printval += im->gdes[vidx].data[ii];
1535 printval = min(printval, im->gdes[vidx].data[ii]);
1539 printval = max(printval, im->gdes[vidx].data[ii]);
1542 printval = im->gdes[vidx].data[ii];
1545 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1546 if (validsteps > 1) {
1547 printval = (printval / validsteps);
1550 } /* prepare printval */
1552 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1553 /* Magfact is set to -1 upon entry to print_calc. If it
1554 * is still less than 0, then we need to run auto_scale.
1555 * Otherwise, put the value into the correct units. If
1556 * the value is 0, then do not set the symbol or magnification
1557 * so next the calculation will be performed again. */
1558 if (magfact < 0.0) {
1559 auto_scale(im, &printval, &si_symb, &magfact);
1560 if (printval == 0.0)
1563 printval /= magfact;
1565 *(++percent_s) = 's';
1566 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1567 auto_scale(im, &printval, &si_symb, &magfact);
1570 if (im->gdes[i].gf == GF_PRINT) {
1571 rrd_infoval_t prline;
1573 if (im->gdes[i].strftm) {
1574 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1575 strftime(prline.u_str,
1576 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1577 } else if (bad_format(im->gdes[i].format)) {
1579 ("bad format for PRINT in '%s'", im->gdes[i].format);
1583 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1587 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1592 if (im->gdes[i].strftm) {
1593 strftime(im->gdes[i].legend,
1594 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1596 if (bad_format(im->gdes[i].format)) {
1598 ("bad format for GPRINT in '%s'",
1599 im->gdes[i].format);
1602 #ifdef HAVE_SNPRINTF
1603 snprintf(im->gdes[i].legend,
1605 im->gdes[i].format, printval, si_symb);
1607 sprintf(im->gdes[i].legend,
1608 im->gdes[i].format, printval, si_symb);
1620 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1621 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1626 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1627 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1636 #ifdef WITH_PIECHART
1644 ("STACK should already be turned into LINE or AREA here");
1649 return graphelement;
1654 /* place legends with color spots */
1660 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1661 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1662 int fill = 0, fill_last;
1663 double legendwidth; // = im->ximg - 2 * border;
1665 double leg_x = border;
1666 int leg_y = 0; //im->yimg;
1667 int leg_y_prev = 0; // im->yimg;
1670 int i, ii, mark = 0;
1671 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1674 char saved_legend[FMT_LEG_LEN + 5];
1680 legendwidth = im->legendwidth - 2 * border;
1684 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1685 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1686 rrd_set_error("malloc for legspace");
1690 for (i = 0; i < im->gdes_c; i++) {
1691 char prt_fctn; /*special printfunctions */
1693 strcpy(saved_legend, im->gdes[i].legend);
1697 /* hide legends for rules which are not displayed */
1698 if (im->gdes[i].gf == GF_TEXTALIGN) {
1699 default_txtalign = im->gdes[i].txtalign;
1702 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1703 if (im->gdes[i].gf == GF_HRULE
1704 && (im->gdes[i].yrule <
1705 im->minval || im->gdes[i].yrule > im->maxval))
1706 im->gdes[i].legend[0] = '\0';
1707 if (im->gdes[i].gf == GF_VRULE
1708 && (im->gdes[i].xrule <
1709 im->start || im->gdes[i].xrule > im->end))
1710 im->gdes[i].legend[0] = '\0';
1713 /* turn \\t into tab */
1714 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1715 memmove(tab, tab + 1, strlen(tab));
1719 leg_cc = strlen(im->gdes[i].legend);
1720 /* is there a controle code at the end of the legend string ? */
1721 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1722 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1724 im->gdes[i].legend[leg_cc] = '\0';
1728 /* only valid control codes */
1729 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1733 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1736 ("Unknown control code at the end of '%s\\%c'",
1737 im->gdes[i].legend, prt_fctn);
1741 if (prt_fctn == 'n') {
1745 /* remove exess space from the end of the legend for \g */
1746 while (prt_fctn == 'g' &&
1747 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1749 im->gdes[i].legend[leg_cc] = '\0';
1754 /* no interleg space if string ends in \g */
1755 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1757 fill += legspace[i];
1760 gfx_get_text_width(im,
1766 im->tabwidth, im->gdes[i].legend);
1771 /* who said there was a special tag ... ? */
1772 if (prt_fctn == 'g') {
1776 if (prt_fctn == '\0') {
1777 if(calc_width && (fill > legendwidth)){
1780 if (i == im->gdes_c - 1 || fill > legendwidth) {
1781 /* just one legend item is left right or center */
1782 switch (default_txtalign) {
1797 /* is it time to place the legends ? */
1798 if (fill > legendwidth) {
1806 if (leg_c == 1 && prt_fctn == 'j') {
1811 if (prt_fctn != '\0') {
1813 if (leg_c >= 2 && prt_fctn == 'j') {
1814 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1818 if (prt_fctn == 'c')
1819 leg_x = (double)(legendwidth - fill) / 2.0;
1820 if (prt_fctn == 'r')
1821 leg_x = legendwidth - fill - border;
1822 for (ii = mark; ii <= i; ii++) {
1823 if (im->gdes[ii].legend[0] == '\0')
1824 continue; /* skip empty legends */
1825 im->gdes[ii].leg_x = leg_x;
1826 im->gdes[ii].leg_y = leg_y + border;
1828 (double)gfx_get_text_width(im, leg_x,
1833 im->tabwidth, im->gdes[ii].legend)
1834 +(double)legspace[ii]
1838 if (leg_x > border || prt_fctn == 's')
1839 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1840 if (prt_fctn == 's')
1841 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1843 if(calc_width && (fill > legendwidth)){
1852 strcpy(im->gdes[i].legend, saved_legend);
1857 im->legendwidth = legendwidth + 2 * border;
1860 im->legendheight = leg_y + border * 0.6;
1867 /* create a grid on the graph. it determines what to do
1868 from the values of xsize, start and end */
1870 /* the xaxis labels are determined from the number of seconds per pixel
1871 in the requested graph */
1873 int calc_horizontal_grid(
1881 int decimals, fractionals;
1883 im->ygrid_scale.labfact = 2;
1884 range = im->maxval - im->minval;
1885 scaledrange = range / im->magfact;
1886 /* does the scale of this graph make it impossible to put lines
1887 on it? If so, give up. */
1888 if (isnan(scaledrange)) {
1892 /* find grid spaceing */
1894 if (isnan(im->ygridstep)) {
1895 if (im->extra_flags & ALTYGRID) {
1896 /* find the value with max number of digits. Get number of digits */
1899 (max(fabs(im->maxval), fabs(im->minval)) *
1900 im->viewfactor / im->magfact));
1901 if (decimals <= 0) /* everything is small. make place for zero */
1903 im->ygrid_scale.gridstep =
1905 floor(log10(range * im->viewfactor / im->magfact))) /
1906 im->viewfactor * im->magfact;
1907 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1908 im->ygrid_scale.gridstep = 0.1;
1909 /* should have at least 5 lines but no more then 15 */
1910 if (range / im->ygrid_scale.gridstep < 5
1911 && im->ygrid_scale.gridstep >= 30)
1912 im->ygrid_scale.gridstep /= 10;
1913 if (range / im->ygrid_scale.gridstep > 15)
1914 im->ygrid_scale.gridstep *= 10;
1915 if (range / im->ygrid_scale.gridstep > 5) {
1916 im->ygrid_scale.labfact = 1;
1917 if (range / im->ygrid_scale.gridstep > 8
1918 || im->ygrid_scale.gridstep <
1919 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1920 im->ygrid_scale.labfact = 2;
1922 im->ygrid_scale.gridstep /= 5;
1923 im->ygrid_scale.labfact = 5;
1927 (im->ygrid_scale.gridstep *
1928 (double) im->ygrid_scale.labfact * im->viewfactor /
1930 if (fractionals < 0) { /* small amplitude. */
1931 int len = decimals - fractionals + 1;
1933 if (im->unitslength < len + 2)
1934 im->unitslength = len + 2;
1935 sprintf(im->ygrid_scale.labfmt,
1937 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1939 int len = decimals + 1;
1941 if (im->unitslength < len + 2)
1942 im->unitslength = len + 2;
1943 sprintf(im->ygrid_scale.labfmt,
1944 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1946 } else { /* classic rrd grid */
1947 for (i = 0; ylab[i].grid > 0; i++) {
1948 pixel = im->ysize / (scaledrange / ylab[i].grid);
1954 for (i = 0; i < 4; i++) {
1955 if (pixel * ylab[gridind].lfac[i] >=
1956 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1957 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1962 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1965 im->ygrid_scale.gridstep = im->ygridstep;
1966 im->ygrid_scale.labfact = im->ylabfact;
1971 int draw_horizontal_grid(
1977 char graph_label[100];
1979 double X0 = im->xorigin;
1980 double X1 = im->xorigin + im->xsize;
1981 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1982 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1984 double second_axis_magfact = 0;
1985 char *second_axis_symb = "";
1988 im->ygrid_scale.gridstep /
1989 (double) im->magfact * (double) im->viewfactor;
1990 MaxY = scaledstep * (double) egrid;
1991 for (i = sgrid; i <= egrid; i++) {
1993 im->ygrid_scale.gridstep * i);
1995 im->ygrid_scale.gridstep * (i + 1));
1997 if (floor(Y0 + 0.5) >=
1998 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1999 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2000 with the chosen settings. Add a label if required by settings, or if
2001 there is only one label so far and the next grid line is out of bounds. */
2002 if (i % im->ygrid_scale.labfact == 0
2004 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2005 if (im->symbol == ' ') {
2006 if (im->extra_flags & ALTYGRID) {
2007 sprintf(graph_label,
2008 im->ygrid_scale.labfmt,
2009 scaledstep * (double) i);
2012 sprintf(graph_label, "%4.1f",
2013 scaledstep * (double) i);
2015 sprintf(graph_label, "%4.0f",
2016 scaledstep * (double) i);
2020 char sisym = (i == 0 ? ' ' : im->symbol);
2022 if (im->extra_flags & ALTYGRID) {
2023 sprintf(graph_label,
2024 im->ygrid_scale.labfmt,
2025 scaledstep * (double) i, sisym);
2028 sprintf(graph_label, "%4.1f %c",
2029 scaledstep * (double) i, sisym);
2031 sprintf(graph_label, "%4.0f %c",
2032 scaledstep * (double) i, sisym);
2037 if (im->second_axis_scale != 0){
2038 char graph_label_right[100];
2039 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2040 if (im->second_axis_format[0] == '\0'){
2041 if (!second_axis_magfact){
2042 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2043 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2045 sval /= second_axis_magfact;
2048 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2050 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2054 sprintf(graph_label_right,im->second_axis_format,sval);
2058 im->graph_col[GRC_FONT],
2059 im->text_prop[TEXT_PROP_AXIS].font_desc,
2060 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2061 graph_label_right );
2067 text_prop[TEXT_PROP_AXIS].
2069 im->graph_col[GRC_FONT],
2071 text_prop[TEXT_PROP_AXIS].
2074 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2075 gfx_line(im, X0 - 2, Y0, X0, Y0,
2076 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2077 gfx_line(im, X1, Y0, X1 + 2, Y0,
2078 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2079 gfx_dashed_line(im, X0 - 2, Y0,
2085 im->grid_dash_on, im->grid_dash_off);
2086 } else if (!(im->extra_flags & NOMINOR)) {
2089 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2090 gfx_line(im, X1, Y0, X1 + 2, Y0,
2091 GRIDWIDTH, im->graph_col[GRC_GRID]);
2092 gfx_dashed_line(im, X0 - 1, Y0,
2096 graph_col[GRC_GRID],
2097 im->grid_dash_on, im->grid_dash_off);
2104 /* this is frexp for base 10 */
2115 iexp = floor(log((double)fabs(x)) / log((double)10));
2116 mnt = x / pow(10.0, iexp);
2119 mnt = x / pow(10.0, iexp);
2126 /* logaritmic horizontal grid */
2127 int horizontal_log_grid(
2131 double yloglab[][10] = {
2133 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2135 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2137 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2154 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2156 int i, j, val_exp, min_exp;
2157 double nex; /* number of decades in data */
2158 double logscale; /* scale in logarithmic space */
2159 int exfrac = 1; /* decade spacing */
2160 int mid = -1; /* row in yloglab for major grid */
2161 double mspac; /* smallest major grid spacing (pixels) */
2162 int flab; /* first value in yloglab to use */
2163 double value, tmp, pre_value;
2165 char graph_label[100];
2167 nex = log10(im->maxval / im->minval);
2168 logscale = im->ysize / nex;
2169 /* major spacing for data with high dynamic range */
2170 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2177 /* major spacing for less dynamic data */
2179 /* search best row in yloglab */
2181 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2182 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2185 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2188 /* find first value in yloglab */
2190 yloglab[mid][flab] < 10
2191 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2192 if (yloglab[mid][flab] == 10.0) {
2197 if (val_exp % exfrac)
2198 val_exp += abs(-val_exp % exfrac);
2200 X1 = im->xorigin + im->xsize;
2205 value = yloglab[mid][flab] * pow(10.0, val_exp);
2206 if (AlmostEqual2sComplement(value, pre_value, 4))
2207 break; /* it seems we are not converging */
2209 Y0 = ytr(im, value);
2210 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2212 /* major grid line */
2214 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2215 gfx_line(im, X1, Y0, X1 + 2, Y0,
2216 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2217 gfx_dashed_line(im, X0 - 2, Y0,
2222 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2224 if (im->extra_flags & FORCE_UNITS_SI) {
2229 scale = floor(val_exp / 3.0);
2231 pvalue = pow(10.0, val_exp % 3);
2233 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2234 pvalue *= yloglab[mid][flab];
2235 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2236 && ((scale + si_symbcenter) >= 0))
2237 symbol = si_symbol[scale + si_symbcenter];
2240 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2242 sprintf(graph_label, "%3.0e", value);
2244 if (im->second_axis_scale != 0){
2245 char graph_label_right[100];
2246 double sval = value*im->second_axis_scale+im->second_axis_shift;
2247 if (im->second_axis_format[0] == '\0'){
2248 if (im->extra_flags & FORCE_UNITS_SI) {
2251 auto_scale(im,&sval,&symb,&mfac);
2252 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2255 sprintf(graph_label_right,"%3.0e", sval);
2259 sprintf(graph_label_right,im->second_axis_format,sval);
2264 im->graph_col[GRC_FONT],
2265 im->text_prop[TEXT_PROP_AXIS].font_desc,
2266 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2267 graph_label_right );
2273 text_prop[TEXT_PROP_AXIS].
2275 im->graph_col[GRC_FONT],
2277 text_prop[TEXT_PROP_AXIS].
2280 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2282 if (mid < 4 && exfrac == 1) {
2283 /* find first and last minor line behind current major line
2284 * i is the first line and j tha last */
2286 min_exp = val_exp - 1;
2287 for (i = 1; yloglab[mid][i] < 10.0; i++);
2288 i = yloglab[mid][i - 1] + 1;
2292 i = yloglab[mid][flab - 1] + 1;
2293 j = yloglab[mid][flab];
2296 /* draw minor lines below current major line */
2297 for (; i < j; i++) {
2299 value = i * pow(10.0, min_exp);
2300 if (value < im->minval)
2302 Y0 = ytr(im, value);
2303 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2308 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2309 gfx_line(im, X1, Y0, X1 + 2, Y0,
2310 GRIDWIDTH, im->graph_col[GRC_GRID]);
2311 gfx_dashed_line(im, X0 - 1, Y0,
2315 graph_col[GRC_GRID],
2316 im->grid_dash_on, im->grid_dash_off);
2318 } else if (exfrac > 1) {
2319 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2320 value = pow(10.0, i);
2321 if (value < im->minval)
2323 Y0 = ytr(im, value);
2324 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2329 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2330 gfx_line(im, X1, Y0, X1 + 2, Y0,
2331 GRIDWIDTH, im->graph_col[GRC_GRID]);
2332 gfx_dashed_line(im, X0 - 1, Y0,
2336 graph_col[GRC_GRID],
2337 im->grid_dash_on, im->grid_dash_off);
2342 if (yloglab[mid][++flab] == 10.0) {
2348 /* draw minor lines after highest major line */
2349 if (mid < 4 && exfrac == 1) {
2350 /* find first and last minor line below current major line
2351 * i is the first line and j tha last */
2353 min_exp = val_exp - 1;
2354 for (i = 1; yloglab[mid][i] < 10.0; i++);
2355 i = yloglab[mid][i - 1] + 1;
2359 i = yloglab[mid][flab - 1] + 1;
2360 j = yloglab[mid][flab];
2363 /* draw minor lines below current major line */
2364 for (; i < j; i++) {
2366 value = i * pow(10.0, min_exp);
2367 if (value < im->minval)
2369 Y0 = ytr(im, value);
2370 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2374 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2375 gfx_line(im, X1, Y0, X1 + 2, Y0,
2376 GRIDWIDTH, im->graph_col[GRC_GRID]);
2377 gfx_dashed_line(im, X0 - 1, Y0,
2381 graph_col[GRC_GRID],
2382 im->grid_dash_on, im->grid_dash_off);
2385 /* fancy minor gridlines */
2386 else if (exfrac > 1) {
2387 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2388 value = pow(10.0, i);
2389 if (value < im->minval)
2391 Y0 = ytr(im, value);
2392 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2396 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2397 gfx_line(im, X1, Y0, X1 + 2, Y0,
2398 GRIDWIDTH, im->graph_col[GRC_GRID]);
2399 gfx_dashed_line(im, X0 - 1, Y0,
2403 graph_col[GRC_GRID],
2404 im->grid_dash_on, im->grid_dash_off);
2415 int xlab_sel; /* which sort of label and grid ? */
2416 time_t ti, tilab, timajor;
2418 char graph_label[100];
2419 double X0, Y0, Y1; /* points for filled graph and more */
2422 /* the type of time grid is determined by finding
2423 the number of seconds per pixel in the graph */
2424 if (im->xlab_user.minsec == -1) {
2425 factor = (im->end - im->start) / im->xsize;
2427 while (xlab[xlab_sel + 1].minsec !=
2428 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2430 } /* pick the last one */
2431 while (xlab[xlab_sel - 1].minsec ==
2432 xlab[xlab_sel].minsec
2433 && xlab[xlab_sel].length > (im->end - im->start)) {
2435 } /* go back to the smallest size */
2436 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2437 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2438 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2439 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2440 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2441 im->xlab_user.labst = xlab[xlab_sel].labst;
2442 im->xlab_user.precis = xlab[xlab_sel].precis;
2443 im->xlab_user.stst = xlab[xlab_sel].stst;
2446 /* y coords are the same for every line ... */
2448 Y1 = im->yorigin - im->ysize;
2449 /* paint the minor grid */
2450 if (!(im->extra_flags & NOMINOR)) {
2451 for (ti = find_first_time(im->start,
2459 find_first_time(im->start,
2466 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2468 /* are we inside the graph ? */
2469 if (ti < im->start || ti > im->end)
2471 while (timajor < ti) {
2472 timajor = find_next_time(timajor,
2475 mgridtm, im->xlab_user.mgridst);
2478 continue; /* skip as falls on major grid line */
2480 gfx_line(im, X0, Y1 - 2, X0, Y1,
2481 GRIDWIDTH, im->graph_col[GRC_GRID]);
2482 gfx_line(im, X0, Y0, X0, Y0 + 2,
2483 GRIDWIDTH, im->graph_col[GRC_GRID]);
2484 gfx_dashed_line(im, X0, Y0 + 1, X0,
2487 graph_col[GRC_GRID],
2488 im->grid_dash_on, im->grid_dash_off);
2492 /* paint the major grid */
2493 for (ti = find_first_time(im->start,
2501 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2503 /* are we inside the graph ? */
2504 if (ti < im->start || ti > im->end)
2507 gfx_line(im, X0, Y1 - 2, X0, Y1,
2508 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2509 gfx_line(im, X0, Y0, X0, Y0 + 3,
2510 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2511 gfx_dashed_line(im, X0, Y0 + 3, X0,
2515 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2517 /* paint the labels below the graph */
2519 find_first_time(im->start -
2528 im->xlab_user.precis / 2;
2529 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2531 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2532 /* are we inside the graph ? */
2533 if (tilab < im->start || tilab > im->end)
2536 localtime_r(&tilab, &tm);
2537 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2539 # error "your libc has no strftime I guess we'll abort the exercise here."
2544 im->graph_col[GRC_FONT],
2546 text_prop[TEXT_PROP_AXIS].
2549 GFX_H_CENTER, GFX_V_TOP, graph_label);
2558 /* draw x and y axis */
2559 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2560 im->xorigin+im->xsize,im->yorigin-im->ysize,
2561 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2563 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2564 im->xorigin+im->xsize,im->yorigin-im->ysize,
2565 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2567 gfx_line(im, im->xorigin - 4,
2569 im->xorigin + im->xsize +
2570 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2571 gfx_line(im, im->xorigin,
2574 im->yorigin - im->ysize -
2575 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2576 /* arrow for X and Y axis direction */
2577 gfx_new_area(im, im->xorigin + im->xsize + 2, im->yorigin - 3, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin, /* horyzontal */
2578 im->graph_col[GRC_ARROW]);
2580 gfx_new_area(im, im->xorigin - 3, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin, im->yorigin - im->ysize - 7, /* vertical */
2581 im->graph_col[GRC_ARROW]);
2583 if (im->second_axis_scale != 0){
2584 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2585 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2586 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2588 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2589 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2590 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2591 im->graph_col[GRC_ARROW]);
2602 double X0, Y0; /* points for filled graph and more */
2603 struct gfx_color_t water_color;
2605 if (im->draw_3d_border > 0) {
2606 /* draw 3d border */
2607 i = im->draw_3d_border;
2608 gfx_new_area(im, 0, im->yimg,
2609 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2610 gfx_add_point(im, im->ximg - i, i);
2611 gfx_add_point(im, im->ximg, 0);
2612 gfx_add_point(im, 0, 0);
2614 gfx_new_area(im, i, im->yimg - i,
2616 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2617 gfx_add_point(im, im->ximg, 0);
2618 gfx_add_point(im, im->ximg, im->yimg);
2619 gfx_add_point(im, 0, im->yimg);
2622 if (im->draw_x_grid == 1)
2624 if (im->draw_y_grid == 1) {
2625 if (im->logarithmic) {
2626 res = horizontal_log_grid(im);
2628 res = draw_horizontal_grid(im);
2631 /* dont draw horizontal grid if there is no min and max val */
2633 char *nodata = "No Data found";
2635 gfx_text(im, im->ximg / 2,
2638 im->graph_col[GRC_FONT],
2640 text_prop[TEXT_PROP_AXIS].
2643 GFX_H_CENTER, GFX_V_CENTER, nodata);
2647 /* yaxis unit description */
2648 if (im->ylegend[0] != '\0'){
2650 im->xOriginLegendY+10,
2652 im->graph_col[GRC_FONT],
2654 text_prop[TEXT_PROP_UNIT].
2657 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2660 if (im->second_axis_legend[0] != '\0'){
2662 im->xOriginLegendY2+10,
2663 im->yOriginLegendY2,
2664 im->graph_col[GRC_FONT],
2665 im->text_prop[TEXT_PROP_UNIT].font_desc,
2667 RRDGRAPH_YLEGEND_ANGLE,
2668 GFX_H_CENTER, GFX_V_CENTER,
2669 im->second_axis_legend);
2674 im->xOriginTitle, im->yOriginTitle+6,
2675 im->graph_col[GRC_FONT],
2677 text_prop[TEXT_PROP_TITLE].
2679 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2680 /* rrdtool 'logo' */
2681 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2682 water_color = im->graph_col[GRC_FONT];
2683 water_color.alpha = 0.3;
2684 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2685 gfx_text(im, xpos, 5,
2688 text_prop[TEXT_PROP_WATERMARK].
2689 font_desc, im->tabwidth,
2690 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2692 /* graph watermark */
2693 if (im->watermark[0] != '\0') {
2694 water_color = im->graph_col[GRC_FONT];
2695 water_color.alpha = 0.3;
2697 im->ximg / 2, im->yimg - 6,
2700 text_prop[TEXT_PROP_WATERMARK].
2701 font_desc, im->tabwidth, 0,
2702 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2706 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2707 for (i = 0; i < im->gdes_c; i++) {
2708 if (im->gdes[i].legend[0] == '\0')
2710 /* im->gdes[i].leg_y is the bottom of the legend */
2711 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2712 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2713 gfx_text(im, X0, Y0,
2714 im->graph_col[GRC_FONT],
2717 [TEXT_PROP_LEGEND].font_desc,
2719 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2720 /* The legend for GRAPH items starts with "M " to have
2721 enough space for the box */
2722 if (im->gdes[i].gf != GF_PRINT &&
2723 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2727 boxH = gfx_get_text_width(im, 0,
2732 im->tabwidth, "o") * 1.2;
2734 /* shift the box up a bit */
2736 if (im->gdes[i].gf == GF_HRULE) { /* [-] */
2738 cairo_new_path(im->cr);
2739 cairo_set_line_width(im->cr, 1.0);
2742 X0 + boxH, Y0 - boxV / 2,
2743 1.0, im->gdes[i].col);
2745 } else if (im->gdes[i].gf == GF_VRULE) { /* [|] */
2747 cairo_new_path(im->cr);
2748 cairo_set_line_width(im->cr, 1.0);
2751 X0 + boxH / 2, Y0 - boxV,
2752 1.0, im->gdes[i].col);
2754 } else if (im->gdes[i].gf == GF_LINE) { /* [/] */
2756 cairo_new_path(im->cr);
2757 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2760 X0 + boxH, Y0 - boxV,
2761 im->gdes[i].linewidth, im->gdes[i].col);
2764 /* make sure transparent colors show up the same way as in the graph */
2767 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2768 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2770 gfx_new_area(im, X0, Y0 - boxV, X0,
2771 Y0, X0 + boxH, Y0, im->gdes[i].col);
2772 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2775 cairo_new_path(im->cr);
2776 cairo_set_line_width(im->cr, 1.0);
2779 gfx_line_fit(im, &X0, &Y0);
2780 gfx_line_fit(im, &X1, &Y1);
2781 cairo_move_to(im->cr, X0, Y0);
2782 cairo_line_to(im->cr, X1, Y0);
2783 cairo_line_to(im->cr, X1, Y1);
2784 cairo_line_to(im->cr, X0, Y1);
2785 cairo_close_path(im->cr);
2786 cairo_set_source_rgba(im->cr,
2787 im->graph_col[GRC_FRAME].red,
2788 im->graph_col[GRC_FRAME].green,
2789 im->graph_col[GRC_FRAME].blue,
2790 im->graph_col[GRC_FRAME].alpha);
2792 if (im->gdes[i].dash) {
2793 /* make box borders in legend dashed if the graph is dashed */
2797 cairo_set_dash(im->cr, dashes, 1, 0.0);
2799 cairo_stroke(im->cr);
2800 cairo_restore(im->cr);
2807 /*****************************************************
2808 * lazy check make sure we rely need to create this graph
2809 *****************************************************/
2816 struct stat imgstat;
2819 return 0; /* no lazy option */
2820 if (strlen(im->graphfile) == 0)
2821 return 0; /* inmemory option */
2822 if (stat(im->graphfile, &imgstat) != 0)
2823 return 0; /* can't stat */
2824 /* one pixel in the existing graph is more then what we would
2826 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2828 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2829 return 0; /* the file does not exist */
2830 switch (im->imgformat) {
2832 size = PngSize(fd, &(im->ximg), &(im->yimg));
2842 int graph_size_location(
2847 /* The actual size of the image to draw is determined from
2848 ** several sources. The size given on the command line is
2849 ** the graph area but we need more as we have to draw labels
2850 ** and other things outside the graph area. If the option
2851 ** --full-size-mode is selected the size defines the total
2852 ** image size and the size available for the graph is
2856 /** +---+-----------------------------------+
2857 ** | y |...............graph title.........|
2858 ** | +---+-------------------------------+
2862 ** | s | x | main graph area |
2867 ** | l | b +-------------------------------+
2868 ** | e | l | x axis labels |
2869 ** +---+---+-------------------------------+
2870 ** |....................legends............|
2871 ** +---------------------------------------+
2873 ** +---------------------------------------+
2876 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2877 0, Xylabel = 0, Xmain = 0, Ymain =
2878 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2880 // no legends and no the shall be plotted it's easy
2881 if (im->extra_flags & ONLY_GRAPH) {
2883 im->ximg = im->xsize;
2884 im->yimg = im->ysize;
2885 im->yorigin = im->ysize;
2890 if(im->watermark[0] != '\0') {
2891 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2894 // calculate the width of the left vertical legend
2895 if (im->ylegend[0] != '\0') {
2896 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2899 // calculate the width of the right vertical legend
2900 if (im->second_axis_legend[0] != '\0') {
2901 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2904 Xvertical2 = Xspacing;
2907 if (im->title[0] != '\0') {
2908 /* The title is placed "inbetween" two text lines so it
2909 ** automatically has some vertical spacing. The horizontal
2910 ** spacing is added here, on each side.
2912 /* if necessary, reduce the font size of the title until it fits the image width */
2913 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2916 // we have no title; get a little clearing from the top
2917 Ytitle = 1.5 * Yspacing;
2921 if (im->draw_x_grid) {
2922 // calculate the height of the horizontal labelling
2923 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2925 if (im->draw_y_grid || im->forceleftspace) {
2926 // calculate the width of the vertical labelling
2928 gfx_get_text_width(im, 0,
2929 im->text_prop[TEXT_PROP_AXIS].font_desc,
2930 im->tabwidth, "0") * im->unitslength;
2934 // add some space to the labelling
2935 Xylabel += Xspacing;
2937 /* If the legend is printed besides the graph the width has to be
2938 ** calculated first. Placing the legend north or south of the
2939 ** graph requires the width calculation first, so the legend is
2940 ** skipped for the moment.
2942 im->legendheight = 0;
2943 im->legendwidth = 0;
2944 if (!(im->extra_flags & NOLEGEND)) {
2945 if(im->legendposition == WEST || im->legendposition == EAST){
2946 if (leg_place(im, 1) == -1){
2952 if (im->extra_flags & FULL_SIZE_MODE) {
2954 /* The actual size of the image to draw has been determined by the user.
2955 ** The graph area is the space remaining after accounting for the legend,
2956 ** the watermark, the axis labels, and the title.
2958 im->ximg = im->xsize;
2959 im->yimg = im->ysize;
2963 /* Now calculate the total size. Insert some spacing where
2964 desired. im->xorigin and im->yorigin need to correspond
2965 with the lower left corner of the main graph area or, if
2966 this one is not set, the imaginary box surrounding the
2968 /* Initial size calculation for the main graph area */
2970 Xmain -= Xylabel;// + Xspacing;
2971 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2972 Xmain -= im->legendwidth;// + Xspacing;
2974 if (im->second_axis_scale != 0){
2977 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2981 Xmain -= Xvertical + Xvertical2;
2983 /* limit the remaining space to 0 */
2989 /* Putting the legend north or south, the height can now be calculated */
2990 if (!(im->extra_flags & NOLEGEND)) {
2991 if(im->legendposition == NORTH || im->legendposition == SOUTH){
2992 im->legendwidth = im->ximg;
2993 if (leg_place(im, 0) == -1){
2999 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3000 Ymain -= Yxlabel + im->legendheight;
3006 /* reserve space for the title *or* some padding above the graph */
3009 /* reserve space for padding below the graph */
3010 if (im->extra_flags & NOLEGEND) {
3014 if (im->watermark[0] != '\0') {
3015 Ymain -= Ywatermark;
3017 /* limit the remaining height to 0 */
3022 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3024 /* The actual size of the image to draw is determined from
3025 ** several sources. The size given on the command line is
3026 ** the graph area but we need more as we have to draw labels
3027 ** and other things outside the graph area.
3031 Xmain = im->xsize; // + Xspacing;
3035 im->ximg = Xmain + Xylabel;
3036 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3037 im->ximg += Xspacing;
3040 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3041 im->ximg += im->legendwidth;// + Xspacing;
3043 if (im->second_axis_scale != 0){
3044 im->ximg += Xylabel;
3047 im->ximg += Xvertical + Xvertical2;
3049 if (!(im->extra_flags & NOLEGEND)) {
3050 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3051 im->legendwidth = im->ximg;
3052 if (leg_place(im, 0) == -1){
3058 im->yimg = Ymain + Yxlabel;
3059 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3060 im->yimg += im->legendheight;
3063 /* reserve space for the title *or* some padding above the graph */
3067 im->yimg += 1.5 * Yspacing;
3069 /* reserve space for padding below the graph */
3070 if (im->extra_flags & NOLEGEND) {
3071 im->yimg += Yspacing;
3074 if (im->watermark[0] != '\0') {
3075 im->yimg += Ywatermark;
3080 /* In case of putting the legend in west or east position the first
3081 ** legend calculation might lead to wrong positions if some items
3082 ** are not aligned on the left hand side (e.g. centered) as the
3083 ** legendwidth wight have been increased after the item was placed.
3084 ** In this case the positions have to be recalculated.
3086 if (!(im->extra_flags & NOLEGEND)) {
3087 if(im->legendposition == WEST || im->legendposition == EAST){
3088 if (leg_place(im, 0) == -1){
3094 /* After calculating all dimensions
3095 ** it is now possible to calculate
3098 switch(im->legendposition){
3100 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3101 im->yOriginTitle = 0;
3103 im->xOriginLegend = 0;
3104 im->yOriginLegend = Ytitle;
3106 im->xOriginLegendY = 0;
3107 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3109 im->xorigin = Xvertical + Xylabel;
3110 im->yorigin = Ytitle + im->legendheight + Ymain;
3112 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3113 if (im->second_axis_scale != 0){
3114 im->xOriginLegendY2 += Xylabel;
3116 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3121 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3122 im->yOriginTitle = 0;
3124 im->xOriginLegend = 0;
3125 im->yOriginLegend = Ytitle;
3127 im->xOriginLegendY = im->legendwidth;
3128 im->yOriginLegendY = Ytitle + (Ymain / 2);
3130 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3131 im->yorigin = Ytitle + Ymain;
3133 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3134 if (im->second_axis_scale != 0){
3135 im->xOriginLegendY2 += Xylabel;
3137 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3142 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3143 im->yOriginTitle = 0;
3145 im->xOriginLegend = 0;
3146 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3148 im->xOriginLegendY = 0;
3149 im->yOriginLegendY = Ytitle + (Ymain / 2);
3151 im->xorigin = Xvertical + Xylabel;
3152 im->yorigin = Ytitle + Ymain;
3154 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3155 if (im->second_axis_scale != 0){
3156 im->xOriginLegendY2 += Xylabel;
3158 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3163 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3164 im->yOriginTitle = 0;
3166 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3167 if (im->second_axis_scale != 0){
3168 im->xOriginLegend += Xylabel;
3170 im->yOriginLegend = Ytitle;
3172 im->xOriginLegendY = 0;
3173 im->yOriginLegendY = Ytitle + (Ymain / 2);
3175 im->xorigin = Xvertical + Xylabel;
3176 im->yorigin = Ytitle + Ymain;
3178 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3179 if (im->second_axis_scale != 0){
3180 im->xOriginLegendY2 += Xylabel;
3182 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3184 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3185 im->xOriginTitle += Xspacing;
3186 im->xOriginLegend += Xspacing;
3187 im->xOriginLegendY += Xspacing;
3188 im->xorigin += Xspacing;
3189 im->xOriginLegendY2 += Xspacing;
3199 static cairo_status_t cairo_output(
3203 unsigned int length)
3205 image_desc_t *im = (image_desc_t*)closure;
3207 im->rendered_image =
3208 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3209 if (im->rendered_image == NULL)
3210 return CAIRO_STATUS_WRITE_ERROR;
3211 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3212 im->rendered_image_size += length;
3213 return CAIRO_STATUS_SUCCESS;
3216 /* draw that picture thing ... */
3221 int lazy = lazy_check(im);
3222 double areazero = 0.0;
3223 graph_desc_t *lastgdes = NULL;
3226 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3228 /* pull the data from the rrd files ... */
3229 if (data_fetch(im) == -1)
3231 /* evaluate VDEF and CDEF operations ... */
3232 if (data_calc(im) == -1)
3234 /* calculate and PRINT and GPRINT definitions. We have to do it at
3235 * this point because it will affect the length of the legends
3236 * if there are no graph elements (i==0) we stop here ...
3237 * if we are lazy, try to quit ...
3243 /* if we want and can be lazy ... quit now */
3247 /**************************************************************
3248 *** Calculating sizes and locations became a bit confusing ***
3249 *** so I moved this into a separate function. ***
3250 **************************************************************/
3251 if (graph_size_location(im, i) == -1)
3254 info.u_cnt = im->xorigin;
3255 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3256 info.u_cnt = im->yorigin - im->ysize;
3257 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3258 info.u_cnt = im->xsize;
3259 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3260 info.u_cnt = im->ysize;
3261 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3262 info.u_cnt = im->ximg;
3263 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3264 info.u_cnt = im->yimg;
3265 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3266 info.u_cnt = im->start;
3267 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3268 info.u_cnt = im->end;
3269 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3271 /* if we want and can be lazy ... quit now */
3275 /* get actual drawing data and find min and max values */
3276 if (data_proc(im) == -1)
3278 if (!im->logarithmic) {
3282 /* identify si magnitude Kilo, Mega Giga ? */
3283 if (!im->rigid && !im->logarithmic)
3284 expand_range(im); /* make sure the upper and lower limit are
3287 info.u_val = im->minval;
3288 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3289 info.u_val = im->maxval;
3290 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3293 if (!calc_horizontal_grid(im))
3298 apply_gridfit(im); */
3299 /* the actual graph is created by going through the individual
3300 graph elements and then drawing them */
3301 cairo_surface_destroy(im->surface);
3302 switch (im->imgformat) {
3305 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3306 im->ximg * im->zoom,
3307 im->yimg * im->zoom);
3311 im->surface = strlen(im->graphfile)
3312 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3313 im->yimg * im->zoom)
3314 : cairo_pdf_surface_create_for_stream
3315 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3319 im->surface = strlen(im->graphfile)
3321 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3322 im->yimg * im->zoom)
3323 : cairo_ps_surface_create_for_stream
3324 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3328 im->surface = strlen(im->graphfile)
3330 cairo_svg_surface_create(im->
3332 im->ximg * im->zoom, im->yimg * im->zoom)
3333 : cairo_svg_surface_create_for_stream
3334 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3335 cairo_svg_surface_restrict_to_version
3336 (im->surface, CAIRO_SVG_VERSION_1_1);
3339 cairo_destroy(im->cr);
3340 im->cr = cairo_create(im->surface);
3341 cairo_set_antialias(im->cr, im->graph_antialias);
3342 cairo_scale(im->cr, im->zoom, im->zoom);
3343 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3344 gfx_new_area(im, 0, 0, 0, im->yimg,
3345 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3346 gfx_add_point(im, im->ximg, 0);
3348 gfx_new_area(im, im->xorigin,
3351 im->xsize, im->yorigin,
3354 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3355 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3357 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3358 im->xsize, im->ysize + 2.0);
3360 if (im->minval > 0.0)
3361 areazero = im->minval;
3362 if (im->maxval < 0.0)
3363 areazero = im->maxval;
3364 for (i = 0; i < im->gdes_c; i++) {
3365 switch (im->gdes[i].gf) {
3379 for (ii = 0; ii < im->xsize; ii++) {
3380 if (!isnan(im->gdes[i].p_data[ii])
3381 && im->gdes[i].p_data[ii] != 0.0) {
3382 if (im->gdes[i].yrule > 0) {
3389 im->ysize, 1.0, im->gdes[i].col);
3390 } else if (im->gdes[i].yrule < 0) {
3393 im->yorigin - im->ysize - 1.0,
3395 im->yorigin - im->ysize -
3398 im->ysize, 1.0, im->gdes[i].col);
3405 /* fix data points at oo and -oo */
3406 for (ii = 0; ii < im->xsize; ii++) {
3407 if (isinf(im->gdes[i].p_data[ii])) {
3408 if (im->gdes[i].p_data[ii] > 0) {
3409 im->gdes[i].p_data[ii] = im->maxval;
3411 im->gdes[i].p_data[ii] = im->minval;
3417 /* *******************************************************
3422 -------|--t-1--t--------------------------------
3424 if we know the value at time t was a then
3425 we draw a square from t-1 to t with the value a.
3427 ********************************************************* */
3428 if (im->gdes[i].col.alpha != 0.0) {
3429 /* GF_LINE and friend */
3430 if (im->gdes[i].gf == GF_LINE) {
3431 double last_y = 0.0;
3435 cairo_new_path(im->cr);
3436 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3437 if (im->gdes[i].dash) {
3438 cairo_set_dash(im->cr,
3439 im->gdes[i].p_dashes,
3440 im->gdes[i].ndash, im->gdes[i].offset);
3443 for (ii = 1; ii < im->xsize; ii++) {
3444 if (isnan(im->gdes[i].p_data[ii])
3445 || (im->slopemode == 1
3446 && isnan(im->gdes[i].p_data[ii - 1]))) {
3451 last_y = ytr(im, im->gdes[i].p_data[ii]);
3452 if (im->slopemode == 0) {
3453 double x = ii - 1 + im->xorigin;
3456 gfx_line_fit(im, &x, &y);
3457 cairo_move_to(im->cr, x, y);
3458 x = ii + im->xorigin;
3460 gfx_line_fit(im, &x, &y);
3461 cairo_line_to(im->cr, x, y);
3463 double x = ii - 1 + im->xorigin;
3465 ytr(im, im->gdes[i].p_data[ii - 1]);
3466 gfx_line_fit(im, &x, &y);
3467 cairo_move_to(im->cr, x, y);
3468 x = ii + im->xorigin;
3470 gfx_line_fit(im, &x, &y);
3471 cairo_line_to(im->cr, x, y);
3475 double x1 = ii + im->xorigin;
3476 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3478 if (im->slopemode == 0
3479 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3480 double x = ii - 1 + im->xorigin;
3483 gfx_line_fit(im, &x, &y);
3484 cairo_line_to(im->cr, x, y);
3487 gfx_line_fit(im, &x1, &y1);
3488 cairo_line_to(im->cr, x1, y1);
3491 cairo_set_source_rgba(im->cr,
3497 col.blue, im->gdes[i].col.alpha);
3498 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3499 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3500 cairo_stroke(im->cr);
3501 cairo_restore(im->cr);
3505 (double *) malloc(sizeof(double) * im->xsize * 2);
3507 (double *) malloc(sizeof(double) * im->xsize * 2);
3509 (double *) malloc(sizeof(double) * im->xsize * 2);
3511 (double *) malloc(sizeof(double) * im->xsize * 2);
3514 for (ii = 0; ii <= im->xsize; ii++) {
3517 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3523 AlmostEqual2sComplement(foreY
3527 AlmostEqual2sComplement(foreY
3537 foreY[cntI], im->gdes[i].col);
3538 while (cntI < idxI) {
3543 AlmostEqual2sComplement(foreY
3547 AlmostEqual2sComplement(foreY
3554 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3556 gfx_add_point(im, backX[idxI], backY[idxI]);
3562 AlmostEqual2sComplement(backY
3566 AlmostEqual2sComplement(backY
3573 gfx_add_point(im, backX[idxI], backY[idxI]);
3583 if (ii == im->xsize)
3585 if (im->slopemode == 0 && ii == 0) {
3588 if (isnan(im->gdes[i].p_data[ii])) {
3592 ytop = ytr(im, im->gdes[i].p_data[ii]);
3593 if (lastgdes && im->gdes[i].stack) {
3594 ybase = ytr(im, lastgdes->p_data[ii]);
3596 ybase = ytr(im, areazero);
3598 if (ybase == ytop) {
3604 double extra = ytop;
3609 if (im->slopemode == 0) {
3610 backY[++idxI] = ybase - 0.2;
3611 backX[idxI] = ii + im->xorigin - 1;
3612 foreY[idxI] = ytop + 0.2;
3613 foreX[idxI] = ii + im->xorigin - 1;
3615 backY[++idxI] = ybase - 0.2;
3616 backX[idxI] = ii + im->xorigin;
3617 foreY[idxI] = ytop + 0.2;
3618 foreX[idxI] = ii + im->xorigin;
3620 /* close up any remaining area */
3625 } /* else GF_LINE */
3627 /* if color != 0x0 */
3628 /* make sure we do not run into trouble when stacking on NaN */
3629 for (ii = 0; ii < im->xsize; ii++) {
3630 if (isnan(im->gdes[i].p_data[ii])) {
3631 if (lastgdes && (im->gdes[i].stack)) {
3632 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3634 im->gdes[i].p_data[ii] = areazero;
3638 lastgdes = &(im->gdes[i]);
3642 ("STACK should already be turned into LINE or AREA here");
3647 cairo_reset_clip(im->cr);
3649 /* grid_paint also does the text */
3650 if (!(im->extra_flags & ONLY_GRAPH))
3652 if (!(im->extra_flags & ONLY_GRAPH))
3654 /* the RULES are the last thing to paint ... */
3655 for (i = 0; i < im->gdes_c; i++) {
3657 switch (im->gdes[i].gf) {
3659 if (im->gdes[i].yrule >= im->minval
3660 && im->gdes[i].yrule <= im->maxval) {
3662 if (im->gdes[i].dash) {
3663 cairo_set_dash(im->cr,
3664 im->gdes[i].p_dashes,
3665 im->gdes[i].ndash, im->gdes[i].offset);
3667 gfx_line(im, im->xorigin,
3668 ytr(im, im->gdes[i].yrule),
3669 im->xorigin + im->xsize,
3670 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3671 cairo_stroke(im->cr);
3672 cairo_restore(im->cr);
3676 if (im->gdes[i].xrule >= im->start
3677 && im->gdes[i].xrule <= im->end) {
3679 if (im->gdes[i].dash) {
3680 cairo_set_dash(im->cr,
3681 im->gdes[i].p_dashes,
3682 im->gdes[i].ndash, im->gdes[i].offset);
3685 xtr(im, im->gdes[i].xrule),
3686 im->yorigin, xtr(im,
3690 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3691 cairo_stroke(im->cr);
3692 cairo_restore(im->cr);
3701 switch (im->imgformat) {
3704 cairo_status_t status;
3706 status = strlen(im->graphfile) ?
3707 cairo_surface_write_to_png(im->surface, im->graphfile)
3708 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3711 if (status != CAIRO_STATUS_SUCCESS) {
3712 rrd_set_error("Could not save png to '%s'", im->graphfile);
3718 if (strlen(im->graphfile)) {
3719 cairo_show_page(im->cr);
3721 cairo_surface_finish(im->surface);
3730 /*****************************************************
3732 *****************************************************/
3739 if ((im->gdes = (graph_desc_t *)
3740 rrd_realloc(im->gdes, (im->gdes_c)
3741 * sizeof(graph_desc_t))) == NULL) {
3742 rrd_set_error("realloc graph_descs");
3747 im->gdes[im->gdes_c - 1].step = im->step;
3748 im->gdes[im->gdes_c - 1].step_orig = im->step;
3749 im->gdes[im->gdes_c - 1].stack = 0;
3750 im->gdes[im->gdes_c - 1].linewidth = 0;
3751 im->gdes[im->gdes_c - 1].debug = 0;
3752 im->gdes[im->gdes_c - 1].start = im->start;
3753 im->gdes[im->gdes_c - 1].start_orig = im->start;
3754 im->gdes[im->gdes_c - 1].end = im->end;
3755 im->gdes[im->gdes_c - 1].end_orig = im->end;
3756 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3757 im->gdes[im->gdes_c - 1].data = NULL;
3758 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3759 im->gdes[im->gdes_c - 1].data_first = 0;
3760 im->gdes[im->gdes_c - 1].p_data = NULL;
3761 im->gdes[im->gdes_c - 1].rpnp = NULL;
3762 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3763 im->gdes[im->gdes_c - 1].shift = 0.0;
3764 im->gdes[im->gdes_c - 1].dash = 0;
3765 im->gdes[im->gdes_c - 1].ndash = 0;
3766 im->gdes[im->gdes_c - 1].offset = 0;
3767 im->gdes[im->gdes_c - 1].col.red = 0.0;
3768 im->gdes[im->gdes_c - 1].col.green = 0.0;
3769 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3770 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3771 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3772 im->gdes[im->gdes_c - 1].format[0] = '\0';
3773 im->gdes[im->gdes_c - 1].strftm = 0;
3774 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3775 im->gdes[im->gdes_c - 1].ds = -1;
3776 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3777 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3778 im->gdes[im->gdes_c - 1].yrule = DNAN;
3779 im->gdes[im->gdes_c - 1].xrule = 0;
3783 /* copies input untill the first unescaped colon is found
3784 or until input ends. backslashes have to be escaped as well */
3786 const char *const input,
3792 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3793 if (input[inp] == '\\'
3794 && input[inp + 1] != '\0'
3795 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3796 output[outp++] = input[++inp];
3798 output[outp++] = input[inp];
3801 output[outp] = '\0';
3805 /* Now just a wrapper around rrd_graph_v */
3817 rrd_info_t *grinfo = NULL;
3820 grinfo = rrd_graph_v(argc, argv);
3826 if (strcmp(walker->key, "image_info") == 0) {
3829 (char**)rrd_realloc((*prdata),
3830 (prlines + 1) * sizeof(char *))) == NULL) {
3831 rrd_set_error("realloc prdata");
3834 /* imginfo goes to position 0 in the prdata array */
3835 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3836 + 2) * sizeof(char));
3837 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3838 (*prdata)[prlines] = NULL;
3840 /* skip anything else */
3841 walker = walker->next;
3849 if (strcmp(walker->key, "image_width") == 0) {
3850 *xsize = walker->value.u_cnt;
3851 } else if (strcmp(walker->key, "image_height") == 0) {
3852 *ysize = walker->value.u_cnt;
3853 } else if (strcmp(walker->key, "value_min") == 0) {
3854 *ymin = walker->value.u_val;
3855 } else if (strcmp(walker->key, "value_max") == 0) {
3856 *ymax = walker->value.u_val;
3857 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3860 (char**)rrd_realloc((*prdata),
3861 (prlines + 1) * sizeof(char *))) == NULL) {
3862 rrd_set_error("realloc prdata");
3865 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3866 + 2) * sizeof(char));
3867 (*prdata)[prlines] = NULL;
3868 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3869 } else if (strcmp(walker->key, "image") == 0) {
3870 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3871 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3872 rrd_set_error("writing image");
3876 /* skip anything else */
3877 walker = walker->next;
3879 rrd_info_free(grinfo);
3884 /* Some surgery done on this function, it became ridiculously big.
3886 ** - initializing now in rrd_graph_init()
3887 ** - options parsing now in rrd_graph_options()
3888 ** - script parsing now in rrd_graph_script()
3890 rrd_info_t *rrd_graph_v(
3897 rrd_graph_init(&im);
3898 /* a dummy surface so that we can measure text sizes for placements */
3899 old_locale = setlocale(LC_NUMERIC, "C");
3900 rrd_graph_options(argc, argv, &im);
3901 if (rrd_test_error()) {
3902 rrd_info_free(im.grinfo);
3907 if (optind >= argc) {
3908 rrd_info_free(im.grinfo);
3910 rrd_set_error("missing filename");
3914 if (strlen(argv[optind]) >= MAXPATH) {
3915 rrd_set_error("filename (including path) too long");
3916 rrd_info_free(im.grinfo);
3921 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3922 im.graphfile[MAXPATH - 1] = '\0';
3924 if (strcmp(im.graphfile, "-") == 0) {
3925 im.graphfile[0] = '\0';
3928 rrd_graph_script(argc, argv, &im, 1);
3929 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3931 if (rrd_test_error()) {
3932 rrd_info_free(im.grinfo);
3937 /* Everything is now read and the actual work can start */
3939 if (graph_paint(&im) == -1) {
3940 rrd_info_free(im.grinfo);
3946 /* The image is generated and needs to be output.
3947 ** Also, if needed, print a line with information about the image.
3955 path = strdup(im.graphfile);
3956 filename = basename(path);
3958 sprintf_alloc(im.imginfo,
3961 im.ximg), (long) (im.zoom * im.yimg));
3962 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3966 if (im.rendered_image) {
3969 img.u_blo.size = im.rendered_image_size;
3970 img.u_blo.ptr = im.rendered_image;
3971 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3980 image_desc_t *im,int prop,char *font, double size ){
3982 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3983 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3984 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3987 im->text_prop[prop].size = size;
3989 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3990 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3994 void rrd_graph_init(
3999 char *deffont = getenv("RRD_DEFAULT_FONT");
4000 static PangoFontMap *fontmap = NULL;
4001 PangoContext *context;
4008 im->daemon_addr = NULL;
4009 im->draw_x_grid = 1;
4010 im->draw_y_grid = 1;
4011 im->draw_3d_border = 2;
4012 im->extra_flags = 0;
4013 im->font_options = cairo_font_options_create();
4014 im->forceleftspace = 0;
4017 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4018 im->grid_dash_off = 1;
4019 im->grid_dash_on = 1;
4021 im->grinfo = (rrd_info_t *) NULL;
4022 im->grinfo_current = (rrd_info_t *) NULL;
4023 im->imgformat = IF_PNG;
4026 im->legenddirection = TOP_DOWN;
4027 im->legendheight = 0;
4028 im->legendposition = SOUTH;
4029 im->legendwidth = 0;
4030 im->logarithmic = 0;
4036 im->rendered_image_size = 0;
4037 im->rendered_image = NULL;
4041 im->tabwidth = 40.0;
4042 im->title[0] = '\0';
4043 im->unitsexponent = 9999;
4044 im->unitslength = 6;
4045 im->viewfactor = 1.0;
4046 im->watermark[0] = '\0';
4047 im->with_markup = 0;
4049 im->xlab_user.minsec = -1;
4051 im->xOriginLegend = 0;
4052 im->xOriginLegendY = 0;
4053 im->xOriginLegendY2 = 0;
4054 im->xOriginTitle = 0;
4056 im->ygridstep = DNAN;
4058 im->ylegend[0] = '\0';
4059 im->second_axis_scale = 0; /* 0 disables it */
4060 im->second_axis_shift = 0; /* no shift by default */
4061 im->second_axis_legend[0] = '\0';
4062 im->second_axis_format[0] = '\0';
4064 im->yOriginLegend = 0;
4065 im->yOriginLegendY = 0;
4066 im->yOriginLegendY2 = 0;
4067 im->yOriginTitle = 0;
4071 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4072 im->cr = cairo_create(im->surface);
4074 for (i = 0; i < DIM(text_prop); i++) {
4075 im->text_prop[i].size = -1;
4076 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4079 if (fontmap == NULL){
4080 fontmap = pango_cairo_font_map_get_default();
4083 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4085 pango_cairo_context_set_resolution(context, 100);
4087 pango_cairo_update_context(im->cr,context);
4089 im->layout = pango_layout_new(context);
4091 // im->layout = pango_cairo_create_layout(im->cr);
4094 cairo_font_options_set_hint_style
4095 (im->font_options, CAIRO_HINT_STYLE_FULL);
4096 cairo_font_options_set_hint_metrics
4097 (im->font_options, CAIRO_HINT_METRICS_ON);
4098 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4102 for (i = 0; i < DIM(graph_col); i++)
4103 im->graph_col[i] = graph_col[i];
4109 void rrd_graph_options(
4116 char *parsetime_error = NULL;
4117 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4118 time_t start_tmp = 0, end_tmp = 0;
4120 rrd_time_value_t start_tv, end_tv;
4121 long unsigned int color;
4123 /* defines for long options without a short equivalent. should be bytes,
4124 and may not collide with (the ASCII value of) short options */
4125 #define LONGOPT_UNITS_SI 255
4128 struct option long_options[] = {
4129 { "alt-autoscale", no_argument, 0, 'A'},
4130 { "imgformat", required_argument, 0, 'a'},
4131 { "font-smoothing-threshold", required_argument, 0, 'B'},
4132 { "base", required_argument, 0, 'b'},
4133 { "color", required_argument, 0, 'c'},
4134 { "full-size-mode", no_argument, 0, 'D'},
4135 { "daemon", required_argument, 0, 'd'},
4136 { "slope-mode", no_argument, 0, 'E'},
4137 { "end", required_argument, 0, 'e'},
4138 { "force-rules-legend", no_argument, 0, 'F'},
4139 { "imginfo", required_argument, 0, 'f'},
4140 { "graph-render-mode", required_argument, 0, 'G'},
4141 { "no-legend", no_argument, 0, 'g'},
4142 { "height", required_argument, 0, 'h'},
4143 { "no-minor", no_argument, 0, 'I'},
4144 { "interlaced", no_argument, 0, 'i'},
4145 { "alt-autoscale-min", no_argument, 0, 'J'},
4146 { "only-graph", no_argument, 0, 'j'},
4147 { "units-length", required_argument, 0, 'L'},
4148 { "lower-limit", required_argument, 0, 'l'},
4149 { "alt-autoscale-max", no_argument, 0, 'M'},
4150 { "zoom", required_argument, 0, 'm'},
4151 { "no-gridfit", no_argument, 0, 'N'},
4152 { "font", required_argument, 0, 'n'},
4153 { "logarithmic", no_argument, 0, 'o'},
4154 { "pango-markup", no_argument, 0, 'P'},
4155 { "font-render-mode", required_argument, 0, 'R'},
4156 { "rigid", no_argument, 0, 'r'},
4157 { "step", required_argument, 0, 'S'},
4158 { "start", required_argument, 0, 's'},
4159 { "tabwidth", required_argument, 0, 'T'},
4160 { "title", required_argument, 0, 't'},
4161 { "upper-limit", required_argument, 0, 'u'},
4162 { "vertical-label", required_argument, 0, 'v'},
4163 { "watermark", required_argument, 0, 'W'},
4164 { "width", required_argument, 0, 'w'},
4165 { "units-exponent", required_argument, 0, 'X'},
4166 { "x-grid", required_argument, 0, 'x'},
4167 { "alt-y-grid", no_argument, 0, 'Y'},
4168 { "y-grid", required_argument, 0, 'y'},
4169 { "lazy", no_argument, 0, 'z'},
4170 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4171 { "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 */
4172 { "disable-rrdtool-tag",no_argument, 0, 1001},
4173 { "right-axis", required_argument, 0, 1002},
4174 { "right-axis-label", required_argument, 0, 1003},
4175 { "right-axis-format", required_argument, 0, 1004},
4176 { "legend-position", required_argument, 0, 1005},
4177 { "legend-direction", required_argument, 0, 1006},
4178 { "border", required_argument, 0, 1007},
4179 { "grid-dash", required_argument, 0, 1008},
4185 opterr = 0; /* initialize getopt */
4186 rrd_parsetime("end-24h", &start_tv);
4187 rrd_parsetime("now", &end_tv);
4189 int option_index = 0;
4191 int col_start, col_end;
4193 opt = getopt_long(argc, argv,
4194 "Aa:B:b:c:Dd:Ee:Ff:G:gh:IiJjL:l:Nn:Bb:oPR:rS:s:T:t:u:v:W:w:X:x:Yy:z",
4195 long_options, &option_index);
4200 im->extra_flags |= NOMINOR;
4203 im->extra_flags |= ALTYGRID;
4206 im->extra_flags |= ALTAUTOSCALE;
4209 im->extra_flags |= ALTAUTOSCALE_MIN;
4212 im->extra_flags |= ALTAUTOSCALE_MAX;
4215 im->extra_flags |= ONLY_GRAPH;
4218 im->extra_flags |= NOLEGEND;
4221 if (strcmp(optarg, "north") == 0) {
4222 im->legendposition = NORTH;
4223 } else if (strcmp(optarg, "west") == 0) {
4224 im->legendposition = WEST;
4225 } else if (strcmp(optarg, "south") == 0) {
4226 im->legendposition = SOUTH;
4227 } else if (strcmp(optarg, "east") == 0) {
4228 im->legendposition = EAST;
4230 rrd_set_error("unknown legend-position '%s'", optarg);
4235 if (strcmp(optarg, "topdown") == 0) {
4236 im->legenddirection = TOP_DOWN;
4237 } else if (strcmp(optarg, "bottomup") == 0) {
4238 im->legenddirection = BOTTOM_UP;
4240 rrd_set_error("unknown legend-position '%s'", optarg);
4245 im->extra_flags |= FORCE_RULES_LEGEND;
4248 im->extra_flags |= NO_RRDTOOL_TAG;
4250 case LONGOPT_UNITS_SI:
4251 if (im->extra_flags & FORCE_UNITS) {
4252 rrd_set_error("--units can only be used once!");
4255 if (strcmp(optarg, "si") == 0)
4256 im->extra_flags |= FORCE_UNITS_SI;
4258 rrd_set_error("invalid argument for --units: %s", optarg);
4263 im->unitsexponent = atoi(optarg);
4266 im->unitslength = atoi(optarg);
4267 im->forceleftspace = 1;
4270 im->tabwidth = atof(optarg);
4273 im->step = atoi(optarg);
4279 im->with_markup = 1;
4282 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4283 rrd_set_error("start time: %s", parsetime_error);
4288 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4289 rrd_set_error("end time: %s", parsetime_error);
4294 if (strcmp(optarg, "none") == 0) {
4295 im->draw_x_grid = 0;
4299 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4301 &im->xlab_user.gridst,
4303 &im->xlab_user.mgridst,
4305 &im->xlab_user.labst,
4306 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4307 strncpy(im->xlab_form, optarg + stroff,
4308 sizeof(im->xlab_form) - 1);
4309 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4311 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4312 rrd_set_error("unknown keyword %s", scan_gtm);
4315 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4317 rrd_set_error("unknown keyword %s", scan_mtm);
4320 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4321 rrd_set_error("unknown keyword %s", scan_ltm);
4324 im->xlab_user.minsec = 1;
4325 im->xlab_user.stst = im->xlab_form;
4327 rrd_set_error("invalid x-grid format");
4333 if (strcmp(optarg, "none") == 0) {
4334 im->draw_y_grid = 0;
4337 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4338 if (im->ygridstep <= 0) {
4339 rrd_set_error("grid step must be > 0");
4341 } else if (im->ylabfact < 1) {
4342 rrd_set_error("label factor must be > 0");
4346 rrd_set_error("invalid y-grid format");
4351 im->draw_3d_border = atoi(optarg);
4353 case 1008: /* grid-dash */
4357 &im->grid_dash_off) != 2) {
4358 rrd_set_error("expected grid-dash format float:float");
4362 case 1002: /* right y axis */
4366 &im->second_axis_scale,
4367 &im->second_axis_shift) == 2) {
4368 if(im->second_axis_scale==0){
4369 rrd_set_error("the second_axis_scale must not be 0");
4373 rrd_set_error("invalid right-axis format expected scale:shift");
4378 strncpy(im->second_axis_legend,optarg,150);
4379 im->second_axis_legend[150]='\0';
4382 if (bad_format(optarg)){
4383 rrd_set_error("use either %le or %lf formats");
4386 strncpy(im->second_axis_format,optarg,150);
4387 im->second_axis_format[150]='\0';
4390 strncpy(im->ylegend, optarg, 150);
4391 im->ylegend[150] = '\0';
4394 im->maxval = atof(optarg);
4397 im->minval = atof(optarg);
4400 im->base = atol(optarg);
4401 if (im->base != 1024 && im->base != 1000) {
4403 ("the only sensible value for base apart from 1000 is 1024");
4408 long_tmp = atol(optarg);
4409 if (long_tmp < 10) {
4410 rrd_set_error("width below 10 pixels");
4413 im->xsize = long_tmp;
4416 long_tmp = atol(optarg);
4417 if (long_tmp < 10) {
4418 rrd_set_error("height below 10 pixels");
4421 im->ysize = long_tmp;
4424 im->extra_flags |= FULL_SIZE_MODE;
4427 /* interlaced png not supported at the moment */
4433 im->imginfo = optarg;
4437 (im->imgformat = if_conv(optarg)) == -1) {
4438 rrd_set_error("unsupported graphics format '%s'", optarg);
4449 im->logarithmic = 1;
4453 "%10[A-Z]#%n%8lx%n",
4454 col_nam, &col_start, &color, &col_end) == 2) {
4456 int col_len = col_end - col_start;
4461 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4469 (((color & 0xF000) *
4470 0x11000) | ((color & 0x0F00) *
4471 0x01100) | ((color &
4474 ((color & 0x000F) * 0x00011)
4478 color = (color << 8) + 0xff /* shift left by 8 */ ;
4483 rrd_set_error("the color format is #RRGGBB[AA]");
4486 if ((ci = grc_conv(col_nam)) != -1) {
4487 im->graph_col[ci] = gfx_hex_to_col(color);
4489 rrd_set_error("invalid color name '%s'", col_nam);
4493 rrd_set_error("invalid color def format");
4502 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4503 int sindex, propidx;
4505 if ((sindex = text_prop_conv(prop)) != -1) {
4506 for (propidx = sindex;
4507 propidx < TEXT_PROP_LAST; propidx++) {
4509 rrd_set_font_desc(im,propidx,NULL,size);
4511 if ((int) strlen(optarg) > end+2) {
4512 if (optarg[end] == ':') {
4513 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4516 ("expected : after font size in '%s'",
4521 /* only run the for loop for DEFAULT (0) for
4522 all others, we break here. woodo programming */
4523 if (propidx == sindex && sindex != 0)
4527 rrd_set_error("invalid fonttag '%s'", prop);
4531 rrd_set_error("invalid text property format");
4537 im->zoom = atof(optarg);
4538 if (im->zoom <= 0.0) {
4539 rrd_set_error("zoom factor must be > 0");
4544 strncpy(im->title, optarg, 150);
4545 im->title[150] = '\0';
4548 if (strcmp(optarg, "normal") == 0) {
4549 cairo_font_options_set_antialias
4550 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4551 cairo_font_options_set_hint_style
4552 (im->font_options, CAIRO_HINT_STYLE_FULL);
4553 } else if (strcmp(optarg, "light") == 0) {
4554 cairo_font_options_set_antialias
4555 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4556 cairo_font_options_set_hint_style
4557 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4558 } else if (strcmp(optarg, "mono") == 0) {
4559 cairo_font_options_set_antialias
4560 (im->font_options, CAIRO_ANTIALIAS_NONE);
4561 cairo_font_options_set_hint_style
4562 (im->font_options, CAIRO_HINT_STYLE_FULL);
4564 rrd_set_error("unknown font-render-mode '%s'", optarg);
4569 if (strcmp(optarg, "normal") == 0)
4570 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4571 else if (strcmp(optarg, "mono") == 0)
4572 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4574 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4579 /* not supported curently */
4582 strncpy(im->watermark, optarg, 100);
4583 im->watermark[99] = '\0';
4587 if (im->daemon_addr != NULL)
4589 rrd_set_error ("You cannot specify --daemon "
4594 im->daemon_addr = strdup(optarg);
4595 if (im->daemon_addr == NULL)
4597 rrd_set_error("strdup failed");
4605 rrd_set_error("unknown option '%c'", optopt);
4607 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4612 { /* try to connect to rrdcached */
4613 int status = rrdc_connect(im->daemon_addr);
4614 if (status != 0) return;
4617 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4618 pango_layout_context_changed(im->layout);
4622 if (im->logarithmic && im->minval <= 0) {
4624 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4628 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4629 /* error string is set in rrd_parsetime.c */
4633 if (start_tmp < 3600 * 24 * 365 * 10) {
4635 ("the first entry to fetch should be after 1980 (%ld)",
4640 if (end_tmp < start_tmp) {
4642 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4646 im->start = start_tmp;
4648 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4651 int rrd_graph_color(
4659 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4661 color = strstr(var, "#");
4662 if (color == NULL) {
4663 if (optional == 0) {
4664 rrd_set_error("Found no color in %s", err);
4671 long unsigned int col;
4673 rest = strstr(color, ":");
4680 sscanf(color, "#%6lx%n", &col, &n);
4681 col = (col << 8) + 0xff /* shift left by 8 */ ;
4683 rrd_set_error("Color problem in %s", err);
4686 sscanf(color, "#%8lx%n", &col, &n);
4690 rrd_set_error("Color problem in %s", err);
4692 if (rrd_test_error())
4694 gdp->col = gfx_hex_to_col(col);
4707 while (*ptr != '\0')
4708 if (*ptr++ == '%') {
4710 /* line cannot end with percent char */
4713 /* '%s', '%S' and '%%' are allowed */
4714 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4716 /* %c is allowed (but use only with vdef!) */
4717 else if (*ptr == 'c') {
4722 /* or else '% 6.2lf' and such are allowed */
4724 /* optional padding character */
4725 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4727 /* This should take care of 'm.n' with all three optional */
4728 while (*ptr >= '0' && *ptr <= '9')
4732 while (*ptr >= '0' && *ptr <= '9')
4734 /* Either 'le', 'lf' or 'lg' must follow here */
4737 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4752 const char *const str)
4754 /* A VDEF currently is either "func" or "param,func"
4755 * so the parsing is rather simple. Change if needed.
4762 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4763 if (n == (int) strlen(str)) { /* matched */
4767 sscanf(str, "%29[A-Z]%n", func, &n);
4768 if (n == (int) strlen(str)) { /* matched */
4772 ("Unknown function string '%s' in VDEF '%s'",
4777 if (!strcmp("PERCENT", func))
4778 gdes->vf.op = VDEF_PERCENT;
4779 else if (!strcmp("PERCENTNAN", func))
4780 gdes->vf.op = VDEF_PERCENTNAN;
4781 else if (!strcmp("MAXIMUM", func))
4782 gdes->vf.op = VDEF_MAXIMUM;
4783 else if (!strcmp("AVERAGE", func))
4784 gdes->vf.op = VDEF_AVERAGE;
4785 else if (!strcmp("STDEV", func))
4786 gdes->vf.op = VDEF_STDEV;
4787 else if (!strcmp("MINIMUM", func))
4788 gdes->vf.op = VDEF_MINIMUM;
4789 else if (!strcmp("TOTAL", func))
4790 gdes->vf.op = VDEF_TOTAL;
4791 else if (!strcmp("FIRST", func))
4792 gdes->vf.op = VDEF_FIRST;
4793 else if (!strcmp("LAST", func))
4794 gdes->vf.op = VDEF_LAST;
4795 else if (!strcmp("LSLSLOPE", func))
4796 gdes->vf.op = VDEF_LSLSLOPE;
4797 else if (!strcmp("LSLINT", func))
4798 gdes->vf.op = VDEF_LSLINT;
4799 else if (!strcmp("LSLCORREL", func))
4800 gdes->vf.op = VDEF_LSLCORREL;
4803 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4806 switch (gdes->vf.op) {
4808 case VDEF_PERCENTNAN:
4809 if (isnan(param)) { /* no parameter given */
4811 ("Function '%s' needs parameter in VDEF '%s'\n",
4815 if (param >= 0.0 && param <= 100.0) {
4816 gdes->vf.param = param;
4817 gdes->vf.val = DNAN; /* undefined */
4818 gdes->vf.when = 0; /* undefined */
4821 ("Parameter '%f' out of range in VDEF '%s'\n",
4822 param, gdes->vname);
4835 case VDEF_LSLCORREL:
4837 gdes->vf.param = DNAN;
4838 gdes->vf.val = DNAN;
4842 ("Function '%s' needs no parameter in VDEF '%s'\n",
4856 graph_desc_t *src, *dst;
4860 dst = &im->gdes[gdi];
4861 src = &im->gdes[dst->vidx];
4862 data = src->data + src->ds;
4864 steps = (src->end - src->start) / src->step;
4867 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4868 src->start, src->end, steps);
4870 switch (dst->vf.op) {
4874 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4875 rrd_set_error("malloc VDEV_PERCENT");
4878 for (step = 0; step < steps; step++) {
4879 array[step] = data[step * src->ds_cnt];
4881 qsort(array, step, sizeof(double), vdef_percent_compar);
4882 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4883 dst->vf.val = array[field];
4884 dst->vf.when = 0; /* no time component */
4887 for (step = 0; step < steps; step++)
4888 printf("DEBUG: %3li:%10.2f %c\n",
4889 step, array[step], step == field ? '*' : ' ');
4893 case VDEF_PERCENTNAN:{
4896 /* count number of "valid" values */
4898 for (step = 0; step < steps; step++) {
4899 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4901 /* and allocate it */
4902 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4903 rrd_set_error("malloc VDEV_PERCENT");
4906 /* and fill it in */
4908 for (step = 0; step < steps; step++) {
4909 if (!isnan(data[step * src->ds_cnt])) {
4910 array[field] = data[step * src->ds_cnt];
4914 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4915 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4916 dst->vf.val = array[field];
4917 dst->vf.when = 0; /* no time component */
4923 while (step != steps && isnan(data[step * src->ds_cnt]))
4925 if (step == steps) {
4929 dst->vf.val = data[step * src->ds_cnt];
4930 dst->vf.when = src->start + (step + 1) * src->step;
4932 while (step != steps) {
4933 if (finite(data[step * src->ds_cnt])) {
4934 if (data[step * src->ds_cnt] > dst->vf.val) {
4935 dst->vf.val = data[step * src->ds_cnt];
4936 dst->vf.when = src->start + (step + 1) * src->step;
4947 double average = 0.0;
4949 for (step = 0; step < steps; step++) {
4950 if (finite(data[step * src->ds_cnt])) {
4951 sum += data[step * src->ds_cnt];
4956 if (dst->vf.op == VDEF_TOTAL) {
4957 dst->vf.val = sum * src->step;
4958 dst->vf.when = 0; /* no time component */
4959 } else if (dst->vf.op == VDEF_AVERAGE) {
4960 dst->vf.val = sum / cnt;
4961 dst->vf.when = 0; /* no time component */
4963 average = sum / cnt;
4965 for (step = 0; step < steps; step++) {
4966 if (finite(data[step * src->ds_cnt])) {
4967 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4970 dst->vf.val = pow(sum / cnt, 0.5);
4971 dst->vf.when = 0; /* no time component */
4981 while (step != steps && isnan(data[step * src->ds_cnt]))
4983 if (step == steps) {
4987 dst->vf.val = data[step * src->ds_cnt];
4988 dst->vf.when = src->start + (step + 1) * src->step;
4990 while (step != steps) {
4991 if (finite(data[step * src->ds_cnt])) {
4992 if (data[step * src->ds_cnt] < dst->vf.val) {
4993 dst->vf.val = data[step * src->ds_cnt];
4994 dst->vf.when = src->start + (step + 1) * src->step;
5001 /* The time value returned here is one step before the
5002 * actual time value. This is the start of the first
5006 while (step != steps && isnan(data[step * src->ds_cnt]))
5008 if (step == steps) { /* all entries were NaN */
5012 dst->vf.val = data[step * src->ds_cnt];
5013 dst->vf.when = src->start + step * src->step;
5017 /* The time value returned here is the
5018 * actual time value. This is the end of the last
5022 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5024 if (step < 0) { /* all entries were NaN */
5028 dst->vf.val = data[step * src->ds_cnt];
5029 dst->vf.when = src->start + (step + 1) * src->step;
5034 case VDEF_LSLCORREL:{
5035 /* Bestfit line by linear least squares method */
5038 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5045 for (step = 0; step < steps; step++) {
5046 if (finite(data[step * src->ds_cnt])) {
5049 SUMxx += step * step;
5050 SUMxy += step * data[step * src->ds_cnt];
5051 SUMy += data[step * src->ds_cnt];
5052 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5056 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5057 y_intercept = (SUMy - slope * SUMx) / cnt;
5060 (SUMx * SUMy) / cnt) /
5062 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5064 if (dst->vf.op == VDEF_LSLSLOPE) {
5065 dst->vf.val = slope;
5067 } else if (dst->vf.op == VDEF_LSLINT) {
5068 dst->vf.val = y_intercept;
5070 } else if (dst->vf.op == VDEF_LSLCORREL) {
5071 dst->vf.val = correl;
5084 /* NaN < -INF < finite_values < INF */
5085 int vdef_percent_compar(
5091 /* Equality is not returned; this doesn't hurt except
5092 * (maybe) for a little performance.
5095 /* First catch NaN values. They are smallest */
5096 if (isnan(*(double *) a))
5098 if (isnan(*(double *) b))
5100 /* NaN doesn't reach this part so INF and -INF are extremes.
5101 * The sign from isinf() is compatible with the sign we return
5103 if (isinf(*(double *) a))
5104 return isinf(*(double *) a);
5105 if (isinf(*(double *) b))
5106 return isinf(*(double *) b);
5107 /* If we reach this, both values must be finite */
5108 if (*(double *) a < *(double *) b)
5117 rrd_info_type_t type,
5118 rrd_infoval_t value)
5120 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5121 if (im->grinfo == NULL) {
5122 im->grinfo = im->grinfo_current;