1 /****************************************************************************
2 * RRDtool 1.4.2 Copyright by Tobi Oetiker, 1997-2009
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 */
1734 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1737 ("Unknown control code at the end of '%s\\%c'",
1738 im->gdes[i].legend, prt_fctn);
1742 if (prt_fctn == 'n') {
1746 /* remove exess space from the end of the legend for \g */
1747 while (prt_fctn == 'g' &&
1748 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1750 im->gdes[i].legend[leg_cc] = '\0';
1755 /* no interleg space if string ends in \g */
1756 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1758 fill += legspace[i];
1761 gfx_get_text_width(im,
1767 im->tabwidth, im->gdes[i].legend);
1772 /* who said there was a special tag ... ? */
1773 if (prt_fctn == 'g') {
1777 if (prt_fctn == '\0') {
1778 if(calc_width && (fill > legendwidth)){
1781 if (i == im->gdes_c - 1 || fill > legendwidth) {
1782 /* just one legend item is left right or center */
1783 switch (default_txtalign) {
1798 /* is it time to place the legends ? */
1799 if (fill > legendwidth) {
1807 if (leg_c == 1 && prt_fctn == 'j') {
1812 if (prt_fctn != '\0') {
1814 if (leg_c >= 2 && prt_fctn == 'j') {
1815 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1819 if (prt_fctn == 'c')
1820 leg_x = (double)(legendwidth - fill) / 2.0;
1821 if (prt_fctn == 'r')
1822 leg_x = legendwidth - fill + border;
1823 for (ii = mark; ii <= i; ii++) {
1824 if (im->gdes[ii].legend[0] == '\0')
1825 continue; /* skip empty legends */
1826 im->gdes[ii].leg_x = leg_x;
1827 im->gdes[ii].leg_y = leg_y + border;
1829 (double)gfx_get_text_width(im, leg_x,
1834 im->tabwidth, im->gdes[ii].legend)
1835 +(double)legspace[ii]
1839 if (leg_x > border || prt_fctn == 's')
1840 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1841 if (prt_fctn == 's')
1842 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1843 if (prt_fctn == 'u')
1844 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1846 if(calc_width && (fill > legendwidth)){
1855 strcpy(im->gdes[i].legend, saved_legend);
1860 im->legendwidth = legendwidth + 2 * border;
1863 im->legendheight = leg_y + border * 0.6;
1870 /* create a grid on the graph. it determines what to do
1871 from the values of xsize, start and end */
1873 /* the xaxis labels are determined from the number of seconds per pixel
1874 in the requested graph */
1876 int calc_horizontal_grid(
1884 int decimals, fractionals;
1886 im->ygrid_scale.labfact = 2;
1887 range = im->maxval - im->minval;
1888 scaledrange = range / im->magfact;
1889 /* does the scale of this graph make it impossible to put lines
1890 on it? If so, give up. */
1891 if (isnan(scaledrange)) {
1895 /* find grid spaceing */
1897 if (isnan(im->ygridstep)) {
1898 if (im->extra_flags & ALTYGRID) {
1899 /* find the value with max number of digits. Get number of digits */
1902 (max(fabs(im->maxval), fabs(im->minval)) *
1903 im->viewfactor / im->magfact));
1904 if (decimals <= 0) /* everything is small. make place for zero */
1906 im->ygrid_scale.gridstep =
1908 floor(log10(range * im->viewfactor / im->magfact))) /
1909 im->viewfactor * im->magfact;
1910 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1911 im->ygrid_scale.gridstep = 0.1;
1912 /* should have at least 5 lines but no more then 15 */
1913 if (range / im->ygrid_scale.gridstep < 5
1914 && im->ygrid_scale.gridstep >= 30)
1915 im->ygrid_scale.gridstep /= 10;
1916 if (range / im->ygrid_scale.gridstep > 15)
1917 im->ygrid_scale.gridstep *= 10;
1918 if (range / im->ygrid_scale.gridstep > 5) {
1919 im->ygrid_scale.labfact = 1;
1920 if (range / im->ygrid_scale.gridstep > 8
1921 || im->ygrid_scale.gridstep <
1922 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1923 im->ygrid_scale.labfact = 2;
1925 im->ygrid_scale.gridstep /= 5;
1926 im->ygrid_scale.labfact = 5;
1930 (im->ygrid_scale.gridstep *
1931 (double) im->ygrid_scale.labfact * im->viewfactor /
1933 if (fractionals < 0) { /* small amplitude. */
1934 int len = decimals - fractionals + 1;
1936 if (im->unitslength < len + 2)
1937 im->unitslength = len + 2;
1938 sprintf(im->ygrid_scale.labfmt,
1940 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1942 int len = decimals + 1;
1944 if (im->unitslength < len + 2)
1945 im->unitslength = len + 2;
1946 sprintf(im->ygrid_scale.labfmt,
1947 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1949 } else { /* classic rrd grid */
1950 for (i = 0; ylab[i].grid > 0; i++) {
1951 pixel = im->ysize / (scaledrange / ylab[i].grid);
1957 for (i = 0; i < 4; i++) {
1958 if (pixel * ylab[gridind].lfac[i] >=
1959 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1960 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1965 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1968 im->ygrid_scale.gridstep = im->ygridstep;
1969 im->ygrid_scale.labfact = im->ylabfact;
1974 int draw_horizontal_grid(
1980 char graph_label[100];
1982 double X0 = im->xorigin;
1983 double X1 = im->xorigin + im->xsize;
1984 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1985 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1987 double second_axis_magfact = 0;
1988 char *second_axis_symb = "";
1991 im->ygrid_scale.gridstep /
1992 (double) im->magfact * (double) im->viewfactor;
1993 MaxY = scaledstep * (double) egrid;
1994 for (i = sgrid; i <= egrid; i++) {
1996 im->ygrid_scale.gridstep * i);
1998 im->ygrid_scale.gridstep * (i + 1));
2000 if (floor(Y0 + 0.5) >=
2001 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2002 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2003 with the chosen settings. Add a label if required by settings, or if
2004 there is only one label so far and the next grid line is out of bounds. */
2005 if (i % im->ygrid_scale.labfact == 0
2007 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2008 if (im->symbol == ' ') {
2009 if (im->extra_flags & ALTYGRID) {
2010 sprintf(graph_label,
2011 im->ygrid_scale.labfmt,
2012 scaledstep * (double) i);
2015 sprintf(graph_label, "%4.1f",
2016 scaledstep * (double) i);
2018 sprintf(graph_label, "%4.0f",
2019 scaledstep * (double) i);
2023 char sisym = (i == 0 ? ' ' : im->symbol);
2025 if (im->extra_flags & ALTYGRID) {
2026 sprintf(graph_label,
2027 im->ygrid_scale.labfmt,
2028 scaledstep * (double) i, sisym);
2031 sprintf(graph_label, "%4.1f %c",
2032 scaledstep * (double) i, sisym);
2034 sprintf(graph_label, "%4.0f %c",
2035 scaledstep * (double) i, sisym);
2040 if (im->second_axis_scale != 0){
2041 char graph_label_right[100];
2042 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2043 if (im->second_axis_format[0] == '\0'){
2044 if (!second_axis_magfact){
2045 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2046 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2048 sval /= second_axis_magfact;
2051 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2053 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2057 sprintf(graph_label_right,im->second_axis_format,sval);
2061 im->graph_col[GRC_FONT],
2062 im->text_prop[TEXT_PROP_AXIS].font_desc,
2063 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2064 graph_label_right );
2070 text_prop[TEXT_PROP_AXIS].
2072 im->graph_col[GRC_FONT],
2074 text_prop[TEXT_PROP_AXIS].
2077 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2078 gfx_line(im, X0 - 2, Y0, X0, Y0,
2079 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2080 gfx_line(im, X1, Y0, X1 + 2, Y0,
2081 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2082 gfx_dashed_line(im, X0 - 2, Y0,
2088 im->grid_dash_on, im->grid_dash_off);
2089 } else if (!(im->extra_flags & NOMINOR)) {
2092 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2093 gfx_line(im, X1, Y0, X1 + 2, Y0,
2094 GRIDWIDTH, im->graph_col[GRC_GRID]);
2095 gfx_dashed_line(im, X0 - 1, Y0,
2099 graph_col[GRC_GRID],
2100 im->grid_dash_on, im->grid_dash_off);
2107 /* this is frexp for base 10 */
2118 iexp = floor(log((double)fabs(x)) / log((double)10));
2119 mnt = x / pow(10.0, iexp);
2122 mnt = x / pow(10.0, iexp);
2129 /* logaritmic horizontal grid */
2130 int horizontal_log_grid(
2134 double yloglab[][10] = {
2136 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2138 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2140 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2157 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2159 int i, j, val_exp, min_exp;
2160 double nex; /* number of decades in data */
2161 double logscale; /* scale in logarithmic space */
2162 int exfrac = 1; /* decade spacing */
2163 int mid = -1; /* row in yloglab for major grid */
2164 double mspac; /* smallest major grid spacing (pixels) */
2165 int flab; /* first value in yloglab to use */
2166 double value, tmp, pre_value;
2168 char graph_label[100];
2170 nex = log10(im->maxval / im->minval);
2171 logscale = im->ysize / nex;
2172 /* major spacing for data with high dynamic range */
2173 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2180 /* major spacing for less dynamic data */
2182 /* search best row in yloglab */
2184 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2185 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2188 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2191 /* find first value in yloglab */
2193 yloglab[mid][flab] < 10
2194 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2195 if (yloglab[mid][flab] == 10.0) {
2200 if (val_exp % exfrac)
2201 val_exp += abs(-val_exp % exfrac);
2203 X1 = im->xorigin + im->xsize;
2208 value = yloglab[mid][flab] * pow(10.0, val_exp);
2209 if (AlmostEqual2sComplement(value, pre_value, 4))
2210 break; /* it seems we are not converging */
2212 Y0 = ytr(im, value);
2213 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2215 /* major grid line */
2217 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2218 gfx_line(im, X1, Y0, X1 + 2, Y0,
2219 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2220 gfx_dashed_line(im, X0 - 2, Y0,
2225 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2227 if (im->extra_flags & FORCE_UNITS_SI) {
2232 scale = floor(val_exp / 3.0);
2234 pvalue = pow(10.0, val_exp % 3);
2236 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2237 pvalue *= yloglab[mid][flab];
2238 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2239 && ((scale + si_symbcenter) >= 0))
2240 symbol = si_symbol[scale + si_symbcenter];
2243 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2245 sprintf(graph_label, "%3.0e", value);
2247 if (im->second_axis_scale != 0){
2248 char graph_label_right[100];
2249 double sval = value*im->second_axis_scale+im->second_axis_shift;
2250 if (im->second_axis_format[0] == '\0'){
2251 if (im->extra_flags & FORCE_UNITS_SI) {
2254 auto_scale(im,&sval,&symb,&mfac);
2255 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2258 sprintf(graph_label_right,"%3.0e", sval);
2262 sprintf(graph_label_right,im->second_axis_format,sval);
2267 im->graph_col[GRC_FONT],
2268 im->text_prop[TEXT_PROP_AXIS].font_desc,
2269 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2270 graph_label_right );
2276 text_prop[TEXT_PROP_AXIS].
2278 im->graph_col[GRC_FONT],
2280 text_prop[TEXT_PROP_AXIS].
2283 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2285 if (mid < 4 && exfrac == 1) {
2286 /* find first and last minor line behind current major line
2287 * i is the first line and j tha last */
2289 min_exp = val_exp - 1;
2290 for (i = 1; yloglab[mid][i] < 10.0; i++);
2291 i = yloglab[mid][i - 1] + 1;
2295 i = yloglab[mid][flab - 1] + 1;
2296 j = yloglab[mid][flab];
2299 /* draw minor lines below current major line */
2300 for (; i < j; i++) {
2302 value = i * pow(10.0, min_exp);
2303 if (value < im->minval)
2305 Y0 = ytr(im, value);
2306 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2311 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2312 gfx_line(im, X1, Y0, X1 + 2, Y0,
2313 GRIDWIDTH, im->graph_col[GRC_GRID]);
2314 gfx_dashed_line(im, X0 - 1, Y0,
2318 graph_col[GRC_GRID],
2319 im->grid_dash_on, im->grid_dash_off);
2321 } else if (exfrac > 1) {
2322 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2323 value = pow(10.0, i);
2324 if (value < im->minval)
2326 Y0 = ytr(im, value);
2327 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2332 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2333 gfx_line(im, X1, Y0, X1 + 2, Y0,
2334 GRIDWIDTH, im->graph_col[GRC_GRID]);
2335 gfx_dashed_line(im, X0 - 1, Y0,
2339 graph_col[GRC_GRID],
2340 im->grid_dash_on, im->grid_dash_off);
2345 if (yloglab[mid][++flab] == 10.0) {
2351 /* draw minor lines after highest major line */
2352 if (mid < 4 && exfrac == 1) {
2353 /* find first and last minor line below current major line
2354 * i is the first line and j tha last */
2356 min_exp = val_exp - 1;
2357 for (i = 1; yloglab[mid][i] < 10.0; i++);
2358 i = yloglab[mid][i - 1] + 1;
2362 i = yloglab[mid][flab - 1] + 1;
2363 j = yloglab[mid][flab];
2366 /* draw minor lines below current major line */
2367 for (; i < j; i++) {
2369 value = i * pow(10.0, min_exp);
2370 if (value < im->minval)
2372 Y0 = ytr(im, value);
2373 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2377 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2378 gfx_line(im, X1, Y0, X1 + 2, Y0,
2379 GRIDWIDTH, im->graph_col[GRC_GRID]);
2380 gfx_dashed_line(im, X0 - 1, Y0,
2384 graph_col[GRC_GRID],
2385 im->grid_dash_on, im->grid_dash_off);
2388 /* fancy minor gridlines */
2389 else if (exfrac > 1) {
2390 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2391 value = pow(10.0, i);
2392 if (value < im->minval)
2394 Y0 = ytr(im, value);
2395 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2399 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2400 gfx_line(im, X1, Y0, X1 + 2, Y0,
2401 GRIDWIDTH, im->graph_col[GRC_GRID]);
2402 gfx_dashed_line(im, X0 - 1, Y0,
2406 graph_col[GRC_GRID],
2407 im->grid_dash_on, im->grid_dash_off);
2418 int xlab_sel; /* which sort of label and grid ? */
2419 time_t ti, tilab, timajor;
2421 char graph_label[100];
2422 double X0, Y0, Y1; /* points for filled graph and more */
2425 /* the type of time grid is determined by finding
2426 the number of seconds per pixel in the graph */
2427 if (im->xlab_user.minsec == -1) {
2428 factor = (im->end - im->start) / im->xsize;
2430 while (xlab[xlab_sel + 1].minsec !=
2431 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2433 } /* pick the last one */
2434 while (xlab[xlab_sel - 1].minsec ==
2435 xlab[xlab_sel].minsec
2436 && xlab[xlab_sel].length > (im->end - im->start)) {
2438 } /* go back to the smallest size */
2439 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2440 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2441 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2442 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2443 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2444 im->xlab_user.labst = xlab[xlab_sel].labst;
2445 im->xlab_user.precis = xlab[xlab_sel].precis;
2446 im->xlab_user.stst = xlab[xlab_sel].stst;
2449 /* y coords are the same for every line ... */
2451 Y1 = im->yorigin - im->ysize;
2452 /* paint the minor grid */
2453 if (!(im->extra_flags & NOMINOR)) {
2454 for (ti = find_first_time(im->start,
2462 find_first_time(im->start,
2469 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2471 /* are we inside the graph ? */
2472 if (ti < im->start || ti > im->end)
2474 while (timajor < ti) {
2475 timajor = find_next_time(timajor,
2478 mgridtm, im->xlab_user.mgridst);
2481 continue; /* skip as falls on major grid line */
2483 gfx_line(im, X0, Y1 - 2, X0, Y1,
2484 GRIDWIDTH, im->graph_col[GRC_GRID]);
2485 gfx_line(im, X0, Y0, X0, Y0 + 2,
2486 GRIDWIDTH, im->graph_col[GRC_GRID]);
2487 gfx_dashed_line(im, X0, Y0 + 1, X0,
2490 graph_col[GRC_GRID],
2491 im->grid_dash_on, im->grid_dash_off);
2495 /* paint the major grid */
2496 for (ti = find_first_time(im->start,
2504 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2506 /* are we inside the graph ? */
2507 if (ti < im->start || ti > im->end)
2510 gfx_line(im, X0, Y1 - 2, X0, Y1,
2511 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2512 gfx_line(im, X0, Y0, X0, Y0 + 3,
2513 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2514 gfx_dashed_line(im, X0, Y0 + 3, X0,
2518 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2520 /* paint the labels below the graph */
2522 find_first_time(im->start -
2531 im->xlab_user.precis / 2;
2532 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2534 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2535 /* are we inside the graph ? */
2536 if (tilab < im->start || tilab > im->end)
2539 localtime_r(&tilab, &tm);
2540 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2542 # error "your libc has no strftime I guess we'll abort the exercise here."
2547 im->graph_col[GRC_FONT],
2549 text_prop[TEXT_PROP_AXIS].
2552 GFX_H_CENTER, GFX_V_TOP, graph_label);
2561 /* draw x and y axis */
2562 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2563 im->xorigin+im->xsize,im->yorigin-im->ysize,
2564 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2566 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2567 im->xorigin+im->xsize,im->yorigin-im->ysize,
2568 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2570 gfx_line(im, im->xorigin - 4,
2572 im->xorigin + im->xsize +
2573 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2574 gfx_line(im, im->xorigin,
2577 im->yorigin - im->ysize -
2578 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2579 /* arrow for X and Y axis direction */
2580 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 */
2581 im->graph_col[GRC_ARROW]);
2583 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 */
2584 im->graph_col[GRC_ARROW]);
2586 if (im->second_axis_scale != 0){
2587 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2588 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2589 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2591 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2592 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2593 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2594 im->graph_col[GRC_ARROW]);
2605 double X0, Y0; /* points for filled graph and more */
2606 struct gfx_color_t water_color;
2608 if (im->draw_3d_border > 0) {
2609 /* draw 3d border */
2610 i = im->draw_3d_border;
2611 gfx_new_area(im, 0, im->yimg,
2612 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2613 gfx_add_point(im, im->ximg - i, i);
2614 gfx_add_point(im, im->ximg, 0);
2615 gfx_add_point(im, 0, 0);
2617 gfx_new_area(im, i, im->yimg - i,
2619 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2620 gfx_add_point(im, im->ximg, 0);
2621 gfx_add_point(im, im->ximg, im->yimg);
2622 gfx_add_point(im, 0, im->yimg);
2625 if (im->draw_x_grid == 1)
2627 if (im->draw_y_grid == 1) {
2628 if (im->logarithmic) {
2629 res = horizontal_log_grid(im);
2631 res = draw_horizontal_grid(im);
2634 /* dont draw horizontal grid if there is no min and max val */
2636 char *nodata = "No Data found";
2638 gfx_text(im, im->ximg / 2,
2641 im->graph_col[GRC_FONT],
2643 text_prop[TEXT_PROP_AXIS].
2646 GFX_H_CENTER, GFX_V_CENTER, nodata);
2650 /* yaxis unit description */
2651 if (im->ylegend[0] != '\0'){
2653 im->xOriginLegendY+10,
2655 im->graph_col[GRC_FONT],
2657 text_prop[TEXT_PROP_UNIT].
2660 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2663 if (im->second_axis_legend[0] != '\0'){
2665 im->xOriginLegendY2+10,
2666 im->yOriginLegendY2,
2667 im->graph_col[GRC_FONT],
2668 im->text_prop[TEXT_PROP_UNIT].font_desc,
2670 RRDGRAPH_YLEGEND_ANGLE,
2671 GFX_H_CENTER, GFX_V_CENTER,
2672 im->second_axis_legend);
2677 im->xOriginTitle, im->yOriginTitle+6,
2678 im->graph_col[GRC_FONT],
2680 text_prop[TEXT_PROP_TITLE].
2682 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2683 /* rrdtool 'logo' */
2684 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2685 water_color = im->graph_col[GRC_FONT];
2686 water_color.alpha = 0.3;
2687 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2688 gfx_text(im, xpos, 5,
2691 text_prop[TEXT_PROP_WATERMARK].
2692 font_desc, im->tabwidth,
2693 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2695 /* graph watermark */
2696 if (im->watermark[0] != '\0') {
2697 water_color = im->graph_col[GRC_FONT];
2698 water_color.alpha = 0.3;
2700 im->ximg / 2, im->yimg - 6,
2703 text_prop[TEXT_PROP_WATERMARK].
2704 font_desc, im->tabwidth, 0,
2705 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2709 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2710 for (i = 0; i < im->gdes_c; i++) {
2711 if (im->gdes[i].legend[0] == '\0')
2713 /* im->gdes[i].leg_y is the bottom of the legend */
2714 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2715 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2716 gfx_text(im, X0, Y0,
2717 im->graph_col[GRC_FONT],
2720 [TEXT_PROP_LEGEND].font_desc,
2722 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2723 /* The legend for GRAPH items starts with "M " to have
2724 enough space for the box */
2725 if (im->gdes[i].gf != GF_PRINT &&
2726 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2730 boxH = gfx_get_text_width(im, 0,
2735 im->tabwidth, "o") * 1.2;
2737 /* shift the box up a bit */
2740 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2742 cairo_new_path(im->cr);
2743 cairo_set_line_width(im->cr, 1.0);
2746 X0 + boxH, Y0 - boxV / 2,
2747 1.0, im->gdes[i].col);
2749 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2751 cairo_new_path(im->cr);
2752 cairo_set_line_width(im->cr, 1.0);
2755 X0 + boxH / 2, Y0 - boxV,
2756 1.0, im->gdes[i].col);
2758 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2760 cairo_new_path(im->cr);
2761 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2764 X0 + boxH, Y0 - boxV,
2765 im->gdes[i].linewidth, im->gdes[i].col);
2768 /* make sure transparent colors show up the same way as in the graph */
2771 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2772 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2774 gfx_new_area(im, X0, Y0 - boxV, X0,
2775 Y0, X0 + boxH, Y0, im->gdes[i].col);
2776 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2779 cairo_new_path(im->cr);
2780 cairo_set_line_width(im->cr, 1.0);
2783 gfx_line_fit(im, &X0, &Y0);
2784 gfx_line_fit(im, &X1, &Y1);
2785 cairo_move_to(im->cr, X0, Y0);
2786 cairo_line_to(im->cr, X1, Y0);
2787 cairo_line_to(im->cr, X1, Y1);
2788 cairo_line_to(im->cr, X0, Y1);
2789 cairo_close_path(im->cr);
2790 cairo_set_source_rgba(im->cr,
2791 im->graph_col[GRC_FRAME].red,
2792 im->graph_col[GRC_FRAME].green,
2793 im->graph_col[GRC_FRAME].blue,
2794 im->graph_col[GRC_FRAME].alpha);
2796 if (im->gdes[i].dash) {
2797 /* make box borders in legend dashed if the graph is dashed */
2801 cairo_set_dash(im->cr, dashes, 1, 0.0);
2803 cairo_stroke(im->cr);
2804 cairo_restore(im->cr);
2811 /*****************************************************
2812 * lazy check make sure we rely need to create this graph
2813 *****************************************************/
2820 struct stat imgstat;
2823 return 0; /* no lazy option */
2824 if (strlen(im->graphfile) == 0)
2825 return 0; /* inmemory option */
2826 if (stat(im->graphfile, &imgstat) != 0)
2827 return 0; /* can't stat */
2828 /* one pixel in the existing graph is more then what we would
2830 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2832 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2833 return 0; /* the file does not exist */
2834 switch (im->imgformat) {
2836 size = PngSize(fd, &(im->ximg), &(im->yimg));
2846 int graph_size_location(
2851 /* The actual size of the image to draw is determined from
2852 ** several sources. The size given on the command line is
2853 ** the graph area but we need more as we have to draw labels
2854 ** and other things outside the graph area. If the option
2855 ** --full-size-mode is selected the size defines the total
2856 ** image size and the size available for the graph is
2860 /** +---+-----------------------------------+
2861 ** | y |...............graph title.........|
2862 ** | +---+-------------------------------+
2866 ** | s | x | main graph area |
2871 ** | l | b +-------------------------------+
2872 ** | e | l | x axis labels |
2873 ** +---+---+-------------------------------+
2874 ** |....................legends............|
2875 ** +---------------------------------------+
2877 ** +---------------------------------------+
2880 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2881 0, Xylabel = 0, Xmain = 0, Ymain =
2882 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2884 // no legends and no the shall be plotted it's easy
2885 if (im->extra_flags & ONLY_GRAPH) {
2887 im->ximg = im->xsize;
2888 im->yimg = im->ysize;
2889 im->yorigin = im->ysize;
2894 if(im->watermark[0] != '\0') {
2895 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2898 // calculate the width of the left vertical legend
2899 if (im->ylegend[0] != '\0') {
2900 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2903 // calculate the width of the right vertical legend
2904 if (im->second_axis_legend[0] != '\0') {
2905 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2908 Xvertical2 = Xspacing;
2911 if (im->title[0] != '\0') {
2912 /* The title is placed "inbetween" two text lines so it
2913 ** automatically has some vertical spacing. The horizontal
2914 ** spacing is added here, on each side.
2916 /* if necessary, reduce the font size of the title until it fits the image width */
2917 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2920 // we have no title; get a little clearing from the top
2921 Ytitle = 1.5 * Yspacing;
2925 if (im->draw_x_grid) {
2926 // calculate the height of the horizontal labelling
2927 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2929 if (im->draw_y_grid || im->forceleftspace) {
2930 // calculate the width of the vertical labelling
2932 gfx_get_text_width(im, 0,
2933 im->text_prop[TEXT_PROP_AXIS].font_desc,
2934 im->tabwidth, "0") * im->unitslength;
2938 // add some space to the labelling
2939 Xylabel += Xspacing;
2941 /* If the legend is printed besides the graph the width has to be
2942 ** calculated first. Placing the legend north or south of the
2943 ** graph requires the width calculation first, so the legend is
2944 ** skipped for the moment.
2946 im->legendheight = 0;
2947 im->legendwidth = 0;
2948 if (!(im->extra_flags & NOLEGEND)) {
2949 if(im->legendposition == WEST || im->legendposition == EAST){
2950 if (leg_place(im, 1) == -1){
2956 if (im->extra_flags & FULL_SIZE_MODE) {
2958 /* The actual size of the image to draw has been determined by the user.
2959 ** The graph area is the space remaining after accounting for the legend,
2960 ** the watermark, the axis labels, and the title.
2962 im->ximg = im->xsize;
2963 im->yimg = im->ysize;
2967 /* Now calculate the total size. Insert some spacing where
2968 desired. im->xorigin and im->yorigin need to correspond
2969 with the lower left corner of the main graph area or, if
2970 this one is not set, the imaginary box surrounding the
2972 /* Initial size calculation for the main graph area */
2974 Xmain -= Xylabel;// + Xspacing;
2975 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2976 Xmain -= im->legendwidth;// + Xspacing;
2978 if (im->second_axis_scale != 0){
2981 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2985 Xmain -= Xvertical + Xvertical2;
2987 /* limit the remaining space to 0 */
2993 /* Putting the legend north or south, the height can now be calculated */
2994 if (!(im->extra_flags & NOLEGEND)) {
2995 if(im->legendposition == NORTH || im->legendposition == SOUTH){
2996 im->legendwidth = im->ximg;
2997 if (leg_place(im, 0) == -1){
3003 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3004 Ymain -= Yxlabel + im->legendheight;
3010 /* reserve space for the title *or* some padding above the graph */
3013 /* reserve space for padding below the graph */
3014 if (im->extra_flags & NOLEGEND) {
3018 if (im->watermark[0] != '\0') {
3019 Ymain -= Ywatermark;
3021 /* limit the remaining height to 0 */
3026 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3028 /* The actual size of the image to draw is determined from
3029 ** several sources. The size given on the command line is
3030 ** the graph area but we need more as we have to draw labels
3031 ** and other things outside the graph area.
3035 Xmain = im->xsize; // + Xspacing;
3039 im->ximg = Xmain + Xylabel;
3040 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3041 im->ximg += Xspacing;
3044 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3045 im->ximg += im->legendwidth;// + Xspacing;
3047 if (im->second_axis_scale != 0){
3048 im->ximg += Xylabel;
3051 im->ximg += Xvertical + Xvertical2;
3053 if (!(im->extra_flags & NOLEGEND)) {
3054 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3055 im->legendwidth = im->ximg;
3056 if (leg_place(im, 0) == -1){
3062 im->yimg = Ymain + Yxlabel;
3063 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3064 im->yimg += im->legendheight;
3067 /* reserve space for the title *or* some padding above the graph */
3071 im->yimg += 1.5 * Yspacing;
3073 /* reserve space for padding below the graph */
3074 if (im->extra_flags & NOLEGEND) {
3075 im->yimg += Yspacing;
3078 if (im->watermark[0] != '\0') {
3079 im->yimg += Ywatermark;
3084 /* In case of putting the legend in west or east position the first
3085 ** legend calculation might lead to wrong positions if some items
3086 ** are not aligned on the left hand side (e.g. centered) as the
3087 ** legendwidth wight have been increased after the item was placed.
3088 ** In this case the positions have to be recalculated.
3090 if (!(im->extra_flags & NOLEGEND)) {
3091 if(im->legendposition == WEST || im->legendposition == EAST){
3092 if (leg_place(im, 0) == -1){
3098 /* After calculating all dimensions
3099 ** it is now possible to calculate
3102 switch(im->legendposition){
3104 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3105 im->yOriginTitle = 0;
3107 im->xOriginLegend = 0;
3108 im->yOriginLegend = Ytitle;
3110 im->xOriginLegendY = 0;
3111 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3113 im->xorigin = Xvertical + Xylabel;
3114 im->yorigin = Ytitle + im->legendheight + Ymain;
3116 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3117 if (im->second_axis_scale != 0){
3118 im->xOriginLegendY2 += Xylabel;
3120 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3125 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3126 im->yOriginTitle = 0;
3128 im->xOriginLegend = 0;
3129 im->yOriginLegend = Ytitle;
3131 im->xOriginLegendY = im->legendwidth;
3132 im->yOriginLegendY = Ytitle + (Ymain / 2);
3134 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3135 im->yorigin = Ytitle + Ymain;
3137 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3138 if (im->second_axis_scale != 0){
3139 im->xOriginLegendY2 += Xylabel;
3141 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3146 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3147 im->yOriginTitle = 0;
3149 im->xOriginLegend = 0;
3150 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3152 im->xOriginLegendY = 0;
3153 im->yOriginLegendY = Ytitle + (Ymain / 2);
3155 im->xorigin = Xvertical + Xylabel;
3156 im->yorigin = Ytitle + Ymain;
3158 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3159 if (im->second_axis_scale != 0){
3160 im->xOriginLegendY2 += Xylabel;
3162 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3167 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3168 im->yOriginTitle = 0;
3170 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3171 if (im->second_axis_scale != 0){
3172 im->xOriginLegend += Xylabel;
3174 im->yOriginLegend = Ytitle;
3176 im->xOriginLegendY = 0;
3177 im->yOriginLegendY = Ytitle + (Ymain / 2);
3179 im->xorigin = Xvertical + Xylabel;
3180 im->yorigin = Ytitle + Ymain;
3182 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3183 if (im->second_axis_scale != 0){
3184 im->xOriginLegendY2 += Xylabel;
3186 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3188 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3189 im->xOriginTitle += Xspacing;
3190 im->xOriginLegend += Xspacing;
3191 im->xOriginLegendY += Xspacing;
3192 im->xorigin += Xspacing;
3193 im->xOriginLegendY2 += Xspacing;
3203 static cairo_status_t cairo_output(
3207 unsigned int length)
3209 image_desc_t *im = (image_desc_t*)closure;
3211 im->rendered_image =
3212 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3213 if (im->rendered_image == NULL)
3214 return CAIRO_STATUS_WRITE_ERROR;
3215 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3216 im->rendered_image_size += length;
3217 return CAIRO_STATUS_SUCCESS;
3220 /* draw that picture thing ... */
3225 int lazy = lazy_check(im);
3226 double areazero = 0.0;
3227 graph_desc_t *lastgdes = NULL;
3230 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3232 /* pull the data from the rrd files ... */
3233 if (data_fetch(im) == -1)
3235 /* evaluate VDEF and CDEF operations ... */
3236 if (data_calc(im) == -1)
3238 /* calculate and PRINT and GPRINT definitions. We have to do it at
3239 * this point because it will affect the length of the legends
3240 * if there are no graph elements (i==0) we stop here ...
3241 * if we are lazy, try to quit ...
3247 /* if we want and can be lazy ... quit now */
3251 /**************************************************************
3252 *** Calculating sizes and locations became a bit confusing ***
3253 *** so I moved this into a separate function. ***
3254 **************************************************************/
3255 if (graph_size_location(im, i) == -1)
3258 info.u_cnt = im->xorigin;
3259 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3260 info.u_cnt = im->yorigin - im->ysize;
3261 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3262 info.u_cnt = im->xsize;
3263 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3264 info.u_cnt = im->ysize;
3265 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3266 info.u_cnt = im->ximg;
3267 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3268 info.u_cnt = im->yimg;
3269 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3270 info.u_cnt = im->start;
3271 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3272 info.u_cnt = im->end;
3273 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3275 /* if we want and can be lazy ... quit now */
3279 /* get actual drawing data and find min and max values */
3280 if (data_proc(im) == -1)
3282 if (!im->logarithmic) {
3286 /* identify si magnitude Kilo, Mega Giga ? */
3287 if (!im->rigid && !im->logarithmic)
3288 expand_range(im); /* make sure the upper and lower limit are
3291 info.u_val = im->minval;
3292 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3293 info.u_val = im->maxval;
3294 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3297 if (!calc_horizontal_grid(im))
3302 apply_gridfit(im); */
3303 /* the actual graph is created by going through the individual
3304 graph elements and then drawing them */
3305 cairo_surface_destroy(im->surface);
3306 switch (im->imgformat) {
3309 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3310 im->ximg * im->zoom,
3311 im->yimg * im->zoom);
3315 im->surface = strlen(im->graphfile)
3316 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3317 im->yimg * im->zoom)
3318 : cairo_pdf_surface_create_for_stream
3319 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3323 im->surface = strlen(im->graphfile)
3325 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3326 im->yimg * im->zoom)
3327 : cairo_ps_surface_create_for_stream
3328 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3332 im->surface = strlen(im->graphfile)
3334 cairo_svg_surface_create(im->
3336 im->ximg * im->zoom, im->yimg * im->zoom)
3337 : cairo_svg_surface_create_for_stream
3338 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3339 cairo_svg_surface_restrict_to_version
3340 (im->surface, CAIRO_SVG_VERSION_1_1);
3343 cairo_destroy(im->cr);
3344 im->cr = cairo_create(im->surface);
3345 cairo_set_antialias(im->cr, im->graph_antialias);
3346 cairo_scale(im->cr, im->zoom, im->zoom);
3347 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3348 gfx_new_area(im, 0, 0, 0, im->yimg,
3349 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3350 gfx_add_point(im, im->ximg, 0);
3352 gfx_new_area(im, im->xorigin,
3355 im->xsize, im->yorigin,
3358 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3359 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3361 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3362 im->xsize, im->ysize + 2.0);
3364 if (im->minval > 0.0)
3365 areazero = im->minval;
3366 if (im->maxval < 0.0)
3367 areazero = im->maxval;
3368 for (i = 0; i < im->gdes_c; i++) {
3369 switch (im->gdes[i].gf) {
3383 for (ii = 0; ii < im->xsize; ii++) {
3384 if (!isnan(im->gdes[i].p_data[ii])
3385 && im->gdes[i].p_data[ii] != 0.0) {
3386 if (im->gdes[i].yrule > 0) {
3393 im->ysize, 1.0, im->gdes[i].col);
3394 } else if (im->gdes[i].yrule < 0) {
3397 im->yorigin - im->ysize - 1.0,
3399 im->yorigin - im->ysize -
3402 im->ysize, 1.0, im->gdes[i].col);
3409 /* fix data points at oo and -oo */
3410 for (ii = 0; ii < im->xsize; ii++) {
3411 if (isinf(im->gdes[i].p_data[ii])) {
3412 if (im->gdes[i].p_data[ii] > 0) {
3413 im->gdes[i].p_data[ii] = im->maxval;
3415 im->gdes[i].p_data[ii] = im->minval;
3421 /* *******************************************************
3426 -------|--t-1--t--------------------------------
3428 if we know the value at time t was a then
3429 we draw a square from t-1 to t with the value a.
3431 ********************************************************* */
3432 if (im->gdes[i].col.alpha != 0.0) {
3433 /* GF_LINE and friend */
3434 if (im->gdes[i].gf == GF_LINE) {
3435 double last_y = 0.0;
3439 cairo_new_path(im->cr);
3440 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3441 if (im->gdes[i].dash) {
3442 cairo_set_dash(im->cr,
3443 im->gdes[i].p_dashes,
3444 im->gdes[i].ndash, im->gdes[i].offset);
3447 for (ii = 1; ii < im->xsize; ii++) {
3448 if (isnan(im->gdes[i].p_data[ii])
3449 || (im->slopemode == 1
3450 && isnan(im->gdes[i].p_data[ii - 1]))) {
3455 last_y = ytr(im, im->gdes[i].p_data[ii]);
3456 if (im->slopemode == 0) {
3457 double x = ii - 1 + im->xorigin;
3460 gfx_line_fit(im, &x, &y);
3461 cairo_move_to(im->cr, x, y);
3462 x = ii + im->xorigin;
3464 gfx_line_fit(im, &x, &y);
3465 cairo_line_to(im->cr, x, y);
3467 double x = ii - 1 + im->xorigin;
3469 ytr(im, im->gdes[i].p_data[ii - 1]);
3470 gfx_line_fit(im, &x, &y);
3471 cairo_move_to(im->cr, x, y);
3472 x = ii + im->xorigin;
3474 gfx_line_fit(im, &x, &y);
3475 cairo_line_to(im->cr, x, y);
3479 double x1 = ii + im->xorigin;
3480 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3482 if (im->slopemode == 0
3483 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3484 double x = ii - 1 + im->xorigin;
3487 gfx_line_fit(im, &x, &y);
3488 cairo_line_to(im->cr, x, y);
3491 gfx_line_fit(im, &x1, &y1);
3492 cairo_line_to(im->cr, x1, y1);
3495 cairo_set_source_rgba(im->cr,
3501 col.blue, im->gdes[i].col.alpha);
3502 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3503 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3504 cairo_stroke(im->cr);
3505 cairo_restore(im->cr);
3509 (double *) malloc(sizeof(double) * im->xsize * 2);
3511 (double *) malloc(sizeof(double) * im->xsize * 2);
3513 (double *) malloc(sizeof(double) * im->xsize * 2);
3515 (double *) malloc(sizeof(double) * im->xsize * 2);
3518 for (ii = 0; ii <= im->xsize; ii++) {
3521 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3527 AlmostEqual2sComplement(foreY
3531 AlmostEqual2sComplement(foreY
3541 foreY[cntI], im->gdes[i].col);
3542 while (cntI < idxI) {
3547 AlmostEqual2sComplement(foreY
3551 AlmostEqual2sComplement(foreY
3558 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3560 gfx_add_point(im, backX[idxI], backY[idxI]);
3566 AlmostEqual2sComplement(backY
3570 AlmostEqual2sComplement(backY
3577 gfx_add_point(im, backX[idxI], backY[idxI]);
3587 if (ii == im->xsize)
3589 if (im->slopemode == 0 && ii == 0) {
3592 if (isnan(im->gdes[i].p_data[ii])) {
3596 ytop = ytr(im, im->gdes[i].p_data[ii]);
3597 if (lastgdes && im->gdes[i].stack) {
3598 ybase = ytr(im, lastgdes->p_data[ii]);
3600 ybase = ytr(im, areazero);
3602 if (ybase == ytop) {
3608 double extra = ytop;
3613 if (im->slopemode == 0) {
3614 backY[++idxI] = ybase - 0.2;
3615 backX[idxI] = ii + im->xorigin - 1;
3616 foreY[idxI] = ytop + 0.2;
3617 foreX[idxI] = ii + im->xorigin - 1;
3619 backY[++idxI] = ybase - 0.2;
3620 backX[idxI] = ii + im->xorigin;
3621 foreY[idxI] = ytop + 0.2;
3622 foreX[idxI] = ii + im->xorigin;
3624 /* close up any remaining area */
3629 } /* else GF_LINE */
3631 /* if color != 0x0 */
3632 /* make sure we do not run into trouble when stacking on NaN */
3633 for (ii = 0; ii < im->xsize; ii++) {
3634 if (isnan(im->gdes[i].p_data[ii])) {
3635 if (lastgdes && (im->gdes[i].stack)) {
3636 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3638 im->gdes[i].p_data[ii] = areazero;
3642 lastgdes = &(im->gdes[i]);
3646 ("STACK should already be turned into LINE or AREA here");
3651 cairo_reset_clip(im->cr);
3653 /* grid_paint also does the text */
3654 if (!(im->extra_flags & ONLY_GRAPH))
3656 if (!(im->extra_flags & ONLY_GRAPH))
3658 /* the RULES are the last thing to paint ... */
3659 for (i = 0; i < im->gdes_c; i++) {
3661 switch (im->gdes[i].gf) {
3663 if (im->gdes[i].yrule >= im->minval
3664 && im->gdes[i].yrule <= im->maxval) {
3666 if (im->gdes[i].dash) {
3667 cairo_set_dash(im->cr,
3668 im->gdes[i].p_dashes,
3669 im->gdes[i].ndash, im->gdes[i].offset);
3671 gfx_line(im, im->xorigin,
3672 ytr(im, im->gdes[i].yrule),
3673 im->xorigin + im->xsize,
3674 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3675 cairo_stroke(im->cr);
3676 cairo_restore(im->cr);
3680 if (im->gdes[i].xrule >= im->start
3681 && im->gdes[i].xrule <= im->end) {
3683 if (im->gdes[i].dash) {
3684 cairo_set_dash(im->cr,
3685 im->gdes[i].p_dashes,
3686 im->gdes[i].ndash, im->gdes[i].offset);
3689 xtr(im, im->gdes[i].xrule),
3690 im->yorigin, xtr(im,
3694 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3695 cairo_stroke(im->cr);
3696 cairo_restore(im->cr);
3705 switch (im->imgformat) {
3708 cairo_status_t status;
3710 status = strlen(im->graphfile) ?
3711 cairo_surface_write_to_png(im->surface, im->graphfile)
3712 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3715 if (status != CAIRO_STATUS_SUCCESS) {
3716 rrd_set_error("Could not save png to '%s'", im->graphfile);
3722 if (strlen(im->graphfile)) {
3723 cairo_show_page(im->cr);
3725 cairo_surface_finish(im->surface);
3734 /*****************************************************
3736 *****************************************************/
3743 if ((im->gdes = (graph_desc_t *)
3744 rrd_realloc(im->gdes, (im->gdes_c)
3745 * sizeof(graph_desc_t))) == NULL) {
3746 rrd_set_error("realloc graph_descs");
3751 im->gdes[im->gdes_c - 1].step = im->step;
3752 im->gdes[im->gdes_c - 1].step_orig = im->step;
3753 im->gdes[im->gdes_c - 1].stack = 0;
3754 im->gdes[im->gdes_c - 1].linewidth = 0;
3755 im->gdes[im->gdes_c - 1].debug = 0;
3756 im->gdes[im->gdes_c - 1].start = im->start;
3757 im->gdes[im->gdes_c - 1].start_orig = im->start;
3758 im->gdes[im->gdes_c - 1].end = im->end;
3759 im->gdes[im->gdes_c - 1].end_orig = im->end;
3760 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3761 im->gdes[im->gdes_c - 1].data = NULL;
3762 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3763 im->gdes[im->gdes_c - 1].data_first = 0;
3764 im->gdes[im->gdes_c - 1].p_data = NULL;
3765 im->gdes[im->gdes_c - 1].rpnp = NULL;
3766 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3767 im->gdes[im->gdes_c - 1].shift = 0.0;
3768 im->gdes[im->gdes_c - 1].dash = 0;
3769 im->gdes[im->gdes_c - 1].ndash = 0;
3770 im->gdes[im->gdes_c - 1].offset = 0;
3771 im->gdes[im->gdes_c - 1].col.red = 0.0;
3772 im->gdes[im->gdes_c - 1].col.green = 0.0;
3773 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3774 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3775 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3776 im->gdes[im->gdes_c - 1].format[0] = '\0';
3777 im->gdes[im->gdes_c - 1].strftm = 0;
3778 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3779 im->gdes[im->gdes_c - 1].ds = -1;
3780 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3781 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3782 im->gdes[im->gdes_c - 1].yrule = DNAN;
3783 im->gdes[im->gdes_c - 1].xrule = 0;
3787 /* copies input untill the first unescaped colon is found
3788 or until input ends. backslashes have to be escaped as well */
3790 const char *const input,
3796 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3797 if (input[inp] == '\\'
3798 && input[inp + 1] != '\0'
3799 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3800 output[outp++] = input[++inp];
3802 output[outp++] = input[inp];
3805 output[outp] = '\0';
3809 /* Now just a wrapper around rrd_graph_v */
3821 rrd_info_t *grinfo = NULL;
3824 grinfo = rrd_graph_v(argc, argv);
3830 if (strcmp(walker->key, "image_info") == 0) {
3833 (char**)rrd_realloc((*prdata),
3834 (prlines + 1) * sizeof(char *))) == NULL) {
3835 rrd_set_error("realloc prdata");
3838 /* imginfo goes to position 0 in the prdata array */
3839 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3840 + 2) * sizeof(char));
3841 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3842 (*prdata)[prlines] = NULL;
3844 /* skip anything else */
3845 walker = walker->next;
3853 if (strcmp(walker->key, "image_width") == 0) {
3854 *xsize = walker->value.u_cnt;
3855 } else if (strcmp(walker->key, "image_height") == 0) {
3856 *ysize = walker->value.u_cnt;
3857 } else if (strcmp(walker->key, "value_min") == 0) {
3858 *ymin = walker->value.u_val;
3859 } else if (strcmp(walker->key, "value_max") == 0) {
3860 *ymax = walker->value.u_val;
3861 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3864 (char**)rrd_realloc((*prdata),
3865 (prlines + 1) * sizeof(char *))) == NULL) {
3866 rrd_set_error("realloc prdata");
3869 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3870 + 2) * sizeof(char));
3871 (*prdata)[prlines] = NULL;
3872 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3873 } else if (strcmp(walker->key, "image") == 0) {
3874 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3875 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3876 rrd_set_error("writing image");
3880 /* skip anything else */
3881 walker = walker->next;
3883 rrd_info_free(grinfo);
3888 /* Some surgery done on this function, it became ridiculously big.
3890 ** - initializing now in rrd_graph_init()
3891 ** - options parsing now in rrd_graph_options()
3892 ** - script parsing now in rrd_graph_script()
3894 rrd_info_t *rrd_graph_v(
3901 rrd_graph_init(&im);
3902 /* a dummy surface so that we can measure text sizes for placements */
3903 old_locale = setlocale(LC_NUMERIC, "C");
3904 rrd_graph_options(argc, argv, &im);
3905 if (rrd_test_error()) {
3906 rrd_info_free(im.grinfo);
3911 if (optind >= argc) {
3912 rrd_info_free(im.grinfo);
3914 rrd_set_error("missing filename");
3918 if (strlen(argv[optind]) >= MAXPATH) {
3919 rrd_set_error("filename (including path) too long");
3920 rrd_info_free(im.grinfo);
3925 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3926 im.graphfile[MAXPATH - 1] = '\0';
3928 if (strcmp(im.graphfile, "-") == 0) {
3929 im.graphfile[0] = '\0';
3932 rrd_graph_script(argc, argv, &im, 1);
3933 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3935 if (rrd_test_error()) {
3936 rrd_info_free(im.grinfo);
3941 /* Everything is now read and the actual work can start */
3943 if (graph_paint(&im) == -1) {
3944 rrd_info_free(im.grinfo);
3950 /* The image is generated and needs to be output.
3951 ** Also, if needed, print a line with information about the image.
3959 path = strdup(im.graphfile);
3960 filename = basename(path);
3962 sprintf_alloc(im.imginfo,
3965 im.ximg), (long) (im.zoom * im.yimg));
3966 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3970 if (im.rendered_image) {
3973 img.u_blo.size = im.rendered_image_size;
3974 img.u_blo.ptr = im.rendered_image;
3975 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3984 image_desc_t *im,int prop,char *font, double size ){
3986 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3987 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3988 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3991 im->text_prop[prop].size = size;
3993 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3994 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3998 void rrd_graph_init(
4003 char *deffont = getenv("RRD_DEFAULT_FONT");
4004 static PangoFontMap *fontmap = NULL;
4005 PangoContext *context;
4012 im->daemon_addr = NULL;
4013 im->draw_x_grid = 1;
4014 im->draw_y_grid = 1;
4015 im->draw_3d_border = 2;
4016 im->dynamic_labels = 0;
4017 im->extra_flags = 0;
4018 im->font_options = cairo_font_options_create();
4019 im->forceleftspace = 0;
4022 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4023 im->grid_dash_off = 1;
4024 im->grid_dash_on = 1;
4026 im->grinfo = (rrd_info_t *) NULL;
4027 im->grinfo_current = (rrd_info_t *) NULL;
4028 im->imgformat = IF_PNG;
4031 im->legenddirection = TOP_DOWN;
4032 im->legendheight = 0;
4033 im->legendposition = SOUTH;
4034 im->legendwidth = 0;
4035 im->logarithmic = 0;
4041 im->rendered_image_size = 0;
4042 im->rendered_image = NULL;
4046 im->tabwidth = 40.0;
4047 im->title[0] = '\0';
4048 im->unitsexponent = 9999;
4049 im->unitslength = 6;
4050 im->viewfactor = 1.0;
4051 im->watermark[0] = '\0';
4052 im->with_markup = 0;
4054 im->xlab_user.minsec = -1;
4056 im->xOriginLegend = 0;
4057 im->xOriginLegendY = 0;
4058 im->xOriginLegendY2 = 0;
4059 im->xOriginTitle = 0;
4061 im->ygridstep = DNAN;
4063 im->ylegend[0] = '\0';
4064 im->second_axis_scale = 0; /* 0 disables it */
4065 im->second_axis_shift = 0; /* no shift by default */
4066 im->second_axis_legend[0] = '\0';
4067 im->second_axis_format[0] = '\0';
4069 im->yOriginLegend = 0;
4070 im->yOriginLegendY = 0;
4071 im->yOriginLegendY2 = 0;
4072 im->yOriginTitle = 0;
4076 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4077 im->cr = cairo_create(im->surface);
4079 for (i = 0; i < DIM(text_prop); i++) {
4080 im->text_prop[i].size = -1;
4081 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4084 if (fontmap == NULL){
4085 fontmap = pango_cairo_font_map_get_default();
4088 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4090 pango_cairo_context_set_resolution(context, 100);
4092 pango_cairo_update_context(im->cr,context);
4094 im->layout = pango_layout_new(context);
4096 // im->layout = pango_cairo_create_layout(im->cr);
4099 cairo_font_options_set_hint_style
4100 (im->font_options, CAIRO_HINT_STYLE_FULL);
4101 cairo_font_options_set_hint_metrics
4102 (im->font_options, CAIRO_HINT_METRICS_ON);
4103 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4107 for (i = 0; i < DIM(graph_col); i++)
4108 im->graph_col[i] = graph_col[i];
4114 void rrd_graph_options(
4121 char *parsetime_error = NULL;
4122 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4123 time_t start_tmp = 0, end_tmp = 0;
4125 rrd_time_value_t start_tv, end_tv;
4126 long unsigned int color;
4128 /* defines for long options without a short equivalent. should be bytes,
4129 and may not collide with (the ASCII value of) short options */
4130 #define LONGOPT_UNITS_SI 255
4133 struct option long_options[] = {
4134 { "alt-autoscale", no_argument, 0, 'A'},
4135 { "imgformat", required_argument, 0, 'a'},
4136 { "font-smoothing-threshold", required_argument, 0, 'B'},
4137 { "base", required_argument, 0, 'b'},
4138 { "color", required_argument, 0, 'c'},
4139 { "full-size-mode", no_argument, 0, 'D'},
4140 { "daemon", required_argument, 0, 'd'},
4141 { "slope-mode", no_argument, 0, 'E'},
4142 { "end", required_argument, 0, 'e'},
4143 { "force-rules-legend", no_argument, 0, 'F'},
4144 { "imginfo", required_argument, 0, 'f'},
4145 { "graph-render-mode", required_argument, 0, 'G'},
4146 { "no-legend", no_argument, 0, 'g'},
4147 { "height", required_argument, 0, 'h'},
4148 { "no-minor", no_argument, 0, 'I'},
4149 { "interlaced", no_argument, 0, 'i'},
4150 { "alt-autoscale-min", no_argument, 0, 'J'},
4151 { "only-graph", no_argument, 0, 'j'},
4152 { "units-length", required_argument, 0, 'L'},
4153 { "lower-limit", required_argument, 0, 'l'},
4154 { "alt-autoscale-max", no_argument, 0, 'M'},
4155 { "zoom", required_argument, 0, 'm'},
4156 { "no-gridfit", no_argument, 0, 'N'},
4157 { "font", required_argument, 0, 'n'},
4158 { "logarithmic", no_argument, 0, 'o'},
4159 { "pango-markup", no_argument, 0, 'P'},
4160 { "font-render-mode", required_argument, 0, 'R'},
4161 { "rigid", no_argument, 0, 'r'},
4162 { "step", required_argument, 0, 'S'},
4163 { "start", required_argument, 0, 's'},
4164 { "tabwidth", required_argument, 0, 'T'},
4165 { "title", required_argument, 0, 't'},
4166 { "upper-limit", required_argument, 0, 'u'},
4167 { "vertical-label", required_argument, 0, 'v'},
4168 { "watermark", required_argument, 0, 'W'},
4169 { "width", required_argument, 0, 'w'},
4170 { "units-exponent", required_argument, 0, 'X'},
4171 { "x-grid", required_argument, 0, 'x'},
4172 { "alt-y-grid", no_argument, 0, 'Y'},
4173 { "y-grid", required_argument, 0, 'y'},
4174 { "lazy", no_argument, 0, 'z'},
4175 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4176 { "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 */
4177 { "disable-rrdtool-tag",no_argument, 0, 1001},
4178 { "right-axis", required_argument, 0, 1002},
4179 { "right-axis-label", required_argument, 0, 1003},
4180 { "right-axis-format", required_argument, 0, 1004},
4181 { "legend-position", required_argument, 0, 1005},
4182 { "legend-direction", required_argument, 0, 1006},
4183 { "border", required_argument, 0, 1007},
4184 { "grid-dash", required_argument, 0, 1008},
4185 { "dynamic-labels", no_argument, 0, 1009},
4191 opterr = 0; /* initialize getopt */
4192 rrd_parsetime("end-24h", &start_tv);
4193 rrd_parsetime("now", &end_tv);
4195 int option_index = 0;
4197 int col_start, col_end;
4199 opt = getopt_long(argc, argv,
4200 "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",
4201 long_options, &option_index);
4206 im->extra_flags |= NOMINOR;
4209 im->extra_flags |= ALTYGRID;
4212 im->extra_flags |= ALTAUTOSCALE;
4215 im->extra_flags |= ALTAUTOSCALE_MIN;
4218 im->extra_flags |= ALTAUTOSCALE_MAX;
4221 im->extra_flags |= ONLY_GRAPH;
4224 im->extra_flags |= NOLEGEND;
4227 if (strcmp(optarg, "north") == 0) {
4228 im->legendposition = NORTH;
4229 } else if (strcmp(optarg, "west") == 0) {
4230 im->legendposition = WEST;
4231 } else if (strcmp(optarg, "south") == 0) {
4232 im->legendposition = SOUTH;
4233 } else if (strcmp(optarg, "east") == 0) {
4234 im->legendposition = EAST;
4236 rrd_set_error("unknown legend-position '%s'", optarg);
4241 if (strcmp(optarg, "topdown") == 0) {
4242 im->legenddirection = TOP_DOWN;
4243 } else if (strcmp(optarg, "bottomup") == 0) {
4244 im->legenddirection = BOTTOM_UP;
4246 rrd_set_error("unknown legend-position '%s'", optarg);
4251 im->extra_flags |= FORCE_RULES_LEGEND;
4254 im->extra_flags |= NO_RRDTOOL_TAG;
4256 case LONGOPT_UNITS_SI:
4257 if (im->extra_flags & FORCE_UNITS) {
4258 rrd_set_error("--units can only be used once!");
4261 if (strcmp(optarg, "si") == 0)
4262 im->extra_flags |= FORCE_UNITS_SI;
4264 rrd_set_error("invalid argument for --units: %s", optarg);
4269 im->unitsexponent = atoi(optarg);
4272 im->unitslength = atoi(optarg);
4273 im->forceleftspace = 1;
4276 im->tabwidth = atof(optarg);
4279 im->step = atoi(optarg);
4285 im->with_markup = 1;
4288 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4289 rrd_set_error("start time: %s", parsetime_error);
4294 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4295 rrd_set_error("end time: %s", parsetime_error);
4300 if (strcmp(optarg, "none") == 0) {
4301 im->draw_x_grid = 0;
4305 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4307 &im->xlab_user.gridst,
4309 &im->xlab_user.mgridst,
4311 &im->xlab_user.labst,
4312 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4313 strncpy(im->xlab_form, optarg + stroff,
4314 sizeof(im->xlab_form) - 1);
4315 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4317 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4318 rrd_set_error("unknown keyword %s", scan_gtm);
4321 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4323 rrd_set_error("unknown keyword %s", scan_mtm);
4326 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4327 rrd_set_error("unknown keyword %s", scan_ltm);
4330 im->xlab_user.minsec = 1;
4331 im->xlab_user.stst = im->xlab_form;
4333 rrd_set_error("invalid x-grid format");
4339 if (strcmp(optarg, "none") == 0) {
4340 im->draw_y_grid = 0;
4343 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4344 if (im->ygridstep <= 0) {
4345 rrd_set_error("grid step must be > 0");
4347 } else if (im->ylabfact < 1) {
4348 rrd_set_error("label factor must be > 0");
4352 rrd_set_error("invalid y-grid format");
4357 im->draw_3d_border = atoi(optarg);
4359 case 1008: /* grid-dash */
4363 &im->grid_dash_off) != 2) {
4364 rrd_set_error("expected grid-dash format float:float");
4368 case 1009: /* enable dynamic labels */
4369 im->dynamic_labels = 1;
4371 case 1002: /* right y axis */
4375 &im->second_axis_scale,
4376 &im->second_axis_shift) == 2) {
4377 if(im->second_axis_scale==0){
4378 rrd_set_error("the second_axis_scale must not be 0");
4382 rrd_set_error("invalid right-axis format expected scale:shift");
4387 strncpy(im->second_axis_legend,optarg,150);
4388 im->second_axis_legend[150]='\0';
4391 if (bad_format(optarg)){
4392 rrd_set_error("use either %le or %lf formats");
4395 strncpy(im->second_axis_format,optarg,150);
4396 im->second_axis_format[150]='\0';
4399 strncpy(im->ylegend, optarg, 150);
4400 im->ylegend[150] = '\0';
4403 im->maxval = atof(optarg);
4406 im->minval = atof(optarg);
4409 im->base = atol(optarg);
4410 if (im->base != 1024 && im->base != 1000) {
4412 ("the only sensible value for base apart from 1000 is 1024");
4417 long_tmp = atol(optarg);
4418 if (long_tmp < 10) {
4419 rrd_set_error("width below 10 pixels");
4422 im->xsize = long_tmp;
4425 long_tmp = atol(optarg);
4426 if (long_tmp < 10) {
4427 rrd_set_error("height below 10 pixels");
4430 im->ysize = long_tmp;
4433 im->extra_flags |= FULL_SIZE_MODE;
4436 /* interlaced png not supported at the moment */
4442 im->imginfo = optarg;
4446 (im->imgformat = if_conv(optarg)) == -1) {
4447 rrd_set_error("unsupported graphics format '%s'", optarg);
4458 im->logarithmic = 1;
4462 "%10[A-Z]#%n%8lx%n",
4463 col_nam, &col_start, &color, &col_end) == 2) {
4465 int col_len = col_end - col_start;
4470 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4478 (((color & 0xF000) *
4479 0x11000) | ((color & 0x0F00) *
4480 0x01100) | ((color &
4483 ((color & 0x000F) * 0x00011)
4487 color = (color << 8) + 0xff /* shift left by 8 */ ;
4492 rrd_set_error("the color format is #RRGGBB[AA]");
4495 if ((ci = grc_conv(col_nam)) != -1) {
4496 im->graph_col[ci] = gfx_hex_to_col(color);
4498 rrd_set_error("invalid color name '%s'", col_nam);
4502 rrd_set_error("invalid color def format");
4511 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4512 int sindex, propidx;
4514 if ((sindex = text_prop_conv(prop)) != -1) {
4515 for (propidx = sindex;
4516 propidx < TEXT_PROP_LAST; propidx++) {
4518 rrd_set_font_desc(im,propidx,NULL,size);
4520 if ((int) strlen(optarg) > end+2) {
4521 if (optarg[end] == ':') {
4522 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4525 ("expected : after font size in '%s'",
4530 /* only run the for loop for DEFAULT (0) for
4531 all others, we break here. woodo programming */
4532 if (propidx == sindex && sindex != 0)
4536 rrd_set_error("invalid fonttag '%s'", prop);
4540 rrd_set_error("invalid text property format");
4546 im->zoom = atof(optarg);
4547 if (im->zoom <= 0.0) {
4548 rrd_set_error("zoom factor must be > 0");
4553 strncpy(im->title, optarg, 150);
4554 im->title[150] = '\0';
4557 if (strcmp(optarg, "normal") == 0) {
4558 cairo_font_options_set_antialias
4559 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4560 cairo_font_options_set_hint_style
4561 (im->font_options, CAIRO_HINT_STYLE_FULL);
4562 } else if (strcmp(optarg, "light") == 0) {
4563 cairo_font_options_set_antialias
4564 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4565 cairo_font_options_set_hint_style
4566 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4567 } else if (strcmp(optarg, "mono") == 0) {
4568 cairo_font_options_set_antialias
4569 (im->font_options, CAIRO_ANTIALIAS_NONE);
4570 cairo_font_options_set_hint_style
4571 (im->font_options, CAIRO_HINT_STYLE_FULL);
4573 rrd_set_error("unknown font-render-mode '%s'", optarg);
4578 if (strcmp(optarg, "normal") == 0)
4579 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4580 else if (strcmp(optarg, "mono") == 0)
4581 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4583 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4588 /* not supported curently */
4591 strncpy(im->watermark, optarg, 100);
4592 im->watermark[99] = '\0';
4596 if (im->daemon_addr != NULL)
4598 rrd_set_error ("You cannot specify --daemon "
4603 im->daemon_addr = strdup(optarg);
4604 if (im->daemon_addr == NULL)
4606 rrd_set_error("strdup failed");
4614 rrd_set_error("unknown option '%c'", optopt);
4616 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4621 { /* try to connect to rrdcached */
4622 int status = rrdc_connect(im->daemon_addr);
4623 if (status != 0) return;
4626 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4627 pango_layout_context_changed(im->layout);
4631 if (im->logarithmic && im->minval <= 0) {
4633 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4637 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4638 /* error string is set in rrd_parsetime.c */
4642 if (start_tmp < 3600 * 24 * 365 * 10) {
4644 ("the first entry to fetch should be after 1980 (%ld)",
4649 if (end_tmp < start_tmp) {
4651 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4655 im->start = start_tmp;
4657 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4660 int rrd_graph_color(
4668 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4670 color = strstr(var, "#");
4671 if (color == NULL) {
4672 if (optional == 0) {
4673 rrd_set_error("Found no color in %s", err);
4680 long unsigned int col;
4682 rest = strstr(color, ":");
4689 sscanf(color, "#%6lx%n", &col, &n);
4690 col = (col << 8) + 0xff /* shift left by 8 */ ;
4692 rrd_set_error("Color problem in %s", err);
4695 sscanf(color, "#%8lx%n", &col, &n);
4699 rrd_set_error("Color problem in %s", err);
4701 if (rrd_test_error())
4703 gdp->col = gfx_hex_to_col(col);
4716 while (*ptr != '\0')
4717 if (*ptr++ == '%') {
4719 /* line cannot end with percent char */
4722 /* '%s', '%S' and '%%' are allowed */
4723 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4725 /* %c is allowed (but use only with vdef!) */
4726 else if (*ptr == 'c') {
4731 /* or else '% 6.2lf' and such are allowed */
4733 /* optional padding character */
4734 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4736 /* This should take care of 'm.n' with all three optional */
4737 while (*ptr >= '0' && *ptr <= '9')
4741 while (*ptr >= '0' && *ptr <= '9')
4743 /* Either 'le', 'lf' or 'lg' must follow here */
4746 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4761 const char *const str)
4763 /* A VDEF currently is either "func" or "param,func"
4764 * so the parsing is rather simple. Change if needed.
4771 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4772 if (n == (int) strlen(str)) { /* matched */
4776 sscanf(str, "%29[A-Z]%n", func, &n);
4777 if (n == (int) strlen(str)) { /* matched */
4781 ("Unknown function string '%s' in VDEF '%s'",
4786 if (!strcmp("PERCENT", func))
4787 gdes->vf.op = VDEF_PERCENT;
4788 else if (!strcmp("PERCENTNAN", func))
4789 gdes->vf.op = VDEF_PERCENTNAN;
4790 else if (!strcmp("MAXIMUM", func))
4791 gdes->vf.op = VDEF_MAXIMUM;
4792 else if (!strcmp("AVERAGE", func))
4793 gdes->vf.op = VDEF_AVERAGE;
4794 else if (!strcmp("STDEV", func))
4795 gdes->vf.op = VDEF_STDEV;
4796 else if (!strcmp("MINIMUM", func))
4797 gdes->vf.op = VDEF_MINIMUM;
4798 else if (!strcmp("TOTAL", func))
4799 gdes->vf.op = VDEF_TOTAL;
4800 else if (!strcmp("FIRST", func))
4801 gdes->vf.op = VDEF_FIRST;
4802 else if (!strcmp("LAST", func))
4803 gdes->vf.op = VDEF_LAST;
4804 else if (!strcmp("LSLSLOPE", func))
4805 gdes->vf.op = VDEF_LSLSLOPE;
4806 else if (!strcmp("LSLINT", func))
4807 gdes->vf.op = VDEF_LSLINT;
4808 else if (!strcmp("LSLCORREL", func))
4809 gdes->vf.op = VDEF_LSLCORREL;
4812 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4815 switch (gdes->vf.op) {
4817 case VDEF_PERCENTNAN:
4818 if (isnan(param)) { /* no parameter given */
4820 ("Function '%s' needs parameter in VDEF '%s'\n",
4824 if (param >= 0.0 && param <= 100.0) {
4825 gdes->vf.param = param;
4826 gdes->vf.val = DNAN; /* undefined */
4827 gdes->vf.when = 0; /* undefined */
4830 ("Parameter '%f' out of range in VDEF '%s'\n",
4831 param, gdes->vname);
4844 case VDEF_LSLCORREL:
4846 gdes->vf.param = DNAN;
4847 gdes->vf.val = DNAN;
4851 ("Function '%s' needs no parameter in VDEF '%s'\n",
4865 graph_desc_t *src, *dst;
4869 dst = &im->gdes[gdi];
4870 src = &im->gdes[dst->vidx];
4871 data = src->data + src->ds;
4873 steps = (src->end - src->start) / src->step;
4876 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4877 src->start, src->end, steps);
4879 switch (dst->vf.op) {
4883 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4884 rrd_set_error("malloc VDEV_PERCENT");
4887 for (step = 0; step < steps; step++) {
4888 array[step] = data[step * src->ds_cnt];
4890 qsort(array, step, sizeof(double), vdef_percent_compar);
4891 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4892 dst->vf.val = array[field];
4893 dst->vf.when = 0; /* no time component */
4896 for (step = 0; step < steps; step++)
4897 printf("DEBUG: %3li:%10.2f %c\n",
4898 step, array[step], step == field ? '*' : ' ');
4902 case VDEF_PERCENTNAN:{
4905 /* count number of "valid" values */
4907 for (step = 0; step < steps; step++) {
4908 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4910 /* and allocate it */
4911 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4912 rrd_set_error("malloc VDEV_PERCENT");
4915 /* and fill it in */
4917 for (step = 0; step < steps; step++) {
4918 if (!isnan(data[step * src->ds_cnt])) {
4919 array[field] = data[step * src->ds_cnt];
4923 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4924 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4925 dst->vf.val = array[field];
4926 dst->vf.when = 0; /* no time component */
4932 while (step != steps && isnan(data[step * src->ds_cnt]))
4934 if (step == steps) {
4938 dst->vf.val = data[step * src->ds_cnt];
4939 dst->vf.when = src->start + (step + 1) * src->step;
4941 while (step != steps) {
4942 if (finite(data[step * src->ds_cnt])) {
4943 if (data[step * src->ds_cnt] > dst->vf.val) {
4944 dst->vf.val = data[step * src->ds_cnt];
4945 dst->vf.when = src->start + (step + 1) * src->step;
4956 double average = 0.0;
4958 for (step = 0; step < steps; step++) {
4959 if (finite(data[step * src->ds_cnt])) {
4960 sum += data[step * src->ds_cnt];
4965 if (dst->vf.op == VDEF_TOTAL) {
4966 dst->vf.val = sum * src->step;
4967 dst->vf.when = 0; /* no time component */
4968 } else if (dst->vf.op == VDEF_AVERAGE) {
4969 dst->vf.val = sum / cnt;
4970 dst->vf.when = 0; /* no time component */
4972 average = sum / cnt;
4974 for (step = 0; step < steps; step++) {
4975 if (finite(data[step * src->ds_cnt])) {
4976 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4979 dst->vf.val = pow(sum / cnt, 0.5);
4980 dst->vf.when = 0; /* no time component */
4990 while (step != steps && isnan(data[step * src->ds_cnt]))
4992 if (step == steps) {
4996 dst->vf.val = data[step * src->ds_cnt];
4997 dst->vf.when = src->start + (step + 1) * src->step;
4999 while (step != steps) {
5000 if (finite(data[step * src->ds_cnt])) {
5001 if (data[step * src->ds_cnt] < dst->vf.val) {
5002 dst->vf.val = data[step * src->ds_cnt];
5003 dst->vf.when = src->start + (step + 1) * src->step;
5010 /* The time value returned here is one step before the
5011 * actual time value. This is the start of the first
5015 while (step != steps && isnan(data[step * src->ds_cnt]))
5017 if (step == steps) { /* all entries were NaN */
5021 dst->vf.val = data[step * src->ds_cnt];
5022 dst->vf.when = src->start + step * src->step;
5026 /* The time value returned here is the
5027 * actual time value. This is the end of the last
5031 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5033 if (step < 0) { /* all entries were NaN */
5037 dst->vf.val = data[step * src->ds_cnt];
5038 dst->vf.when = src->start + (step + 1) * src->step;
5043 case VDEF_LSLCORREL:{
5044 /* Bestfit line by linear least squares method */
5047 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5054 for (step = 0; step < steps; step++) {
5055 if (finite(data[step * src->ds_cnt])) {
5058 SUMxx += step * step;
5059 SUMxy += step * data[step * src->ds_cnt];
5060 SUMy += data[step * src->ds_cnt];
5061 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5065 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5066 y_intercept = (SUMy - slope * SUMx) / cnt;
5069 (SUMx * SUMy) / cnt) /
5071 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5073 if (dst->vf.op == VDEF_LSLSLOPE) {
5074 dst->vf.val = slope;
5076 } else if (dst->vf.op == VDEF_LSLINT) {
5077 dst->vf.val = y_intercept;
5079 } else if (dst->vf.op == VDEF_LSLCORREL) {
5080 dst->vf.val = correl;
5093 /* NaN < -INF < finite_values < INF */
5094 int vdef_percent_compar(
5100 /* Equality is not returned; this doesn't hurt except
5101 * (maybe) for a little performance.
5104 /* First catch NaN values. They are smallest */
5105 if (isnan(*(double *) a))
5107 if (isnan(*(double *) b))
5109 /* NaN doesn't reach this part so INF and -INF are extremes.
5110 * The sign from isinf() is compatible with the sign we return
5112 if (isinf(*(double *) a))
5113 return isinf(*(double *) a);
5114 if (isinf(*(double *) b))
5115 return isinf(*(double *) b);
5116 /* If we reach this, both values must be finite */
5117 if (*(double *) a < *(double *) b)
5126 rrd_info_type_t type,
5127 rrd_infoval_t value)
5129 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5130 if (im->grinfo == NULL) {
5131 im->grinfo = im->grinfo_current;