1 /****************************************************************************
2 * RRDtool 1.4.3 Copyright by Tobi Oetiker, 1997-2010
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 #ifdef HAVE_LANGINFO_H
36 #include "rrd_graph.h"
37 #include "rrd_client.h"
39 /* some constant definitions */
43 #ifndef RRD_DEFAULT_FONT
44 /* there is special code later to pick Cour.ttf when running on windows */
45 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
48 text_prop_t text_prop[] = {
49 {8.0, RRD_DEFAULT_FONT,NULL}
51 {9.0, RRD_DEFAULT_FONT,NULL}
53 {7.0, RRD_DEFAULT_FONT,NULL}
55 {8.0, RRD_DEFAULT_FONT,NULL}
57 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
59 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
63 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
65 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
67 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
69 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
71 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
73 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
75 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
77 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
79 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
81 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
82 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
84 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
86 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
88 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
90 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
92 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
95 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
98 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
101 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
103 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
104 365 * 24 * 3600, "%y"}
106 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
109 /* sensible y label intervals ...*/
133 {20.0, {1, 5, 10, 20}
139 {100.0, {1, 2, 5, 10}
142 {200.0, {1, 5, 10, 20}
145 {500.0, {1, 2, 4, 10}
153 gfx_color_t graph_col[] = /* default colors */
155 {1.00, 1.00, 1.00, 1.00}, /* canvas */
156 {0.95, 0.95, 0.95, 1.00}, /* background */
157 {0.81, 0.81, 0.81, 1.00}, /* shade A */
158 {0.62, 0.62, 0.62, 1.00}, /* shade B */
159 {0.56, 0.56, 0.56, 0.75}, /* grid */
160 {0.87, 0.31, 0.31, 0.60}, /* major grid */
161 {0.00, 0.00, 0.00, 1.00}, /* font */
162 {0.50, 0.12, 0.12, 1.00}, /* arrow */
163 {0.12, 0.12, 0.12, 1.00}, /* axis */
164 {0.00, 0.00, 0.00, 1.00} /* frame */
171 # define DPRINT(x) (void)(printf x, printf("\n"))
177 /* initialize with xtr(im,0); */
185 pixie = (double) im->xsize / (double) (im->end - im->start);
188 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
191 /* translate data values into y coordinates */
200 if (!im->logarithmic)
201 pixie = (double) im->ysize / (im->maxval - im->minval);
204 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
206 } else if (!im->logarithmic) {
207 yval = im->yorigin - pixie * (value - im->minval);
209 if (value < im->minval) {
212 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
220 /* conversion function for symbolic entry names */
223 #define conv_if(VV,VVV) \
224 if (strcmp(#VV, string) == 0) return VVV ;
230 conv_if(PRINT, GF_PRINT);
231 conv_if(GPRINT, GF_GPRINT);
232 conv_if(COMMENT, GF_COMMENT);
233 conv_if(HRULE, GF_HRULE);
234 conv_if(VRULE, GF_VRULE);
235 conv_if(LINE, GF_LINE);
236 conv_if(AREA, GF_AREA);
237 conv_if(GRAD, GF_GRAD);
238 conv_if(STACK, GF_STACK);
239 conv_if(TICK, GF_TICK);
240 conv_if(TEXTALIGN, GF_TEXTALIGN);
241 conv_if(DEF, GF_DEF);
242 conv_if(CDEF, GF_CDEF);
243 conv_if(VDEF, GF_VDEF);
244 conv_if(XPORT, GF_XPORT);
245 conv_if(SHIFT, GF_SHIFT);
247 return (enum gf_en)(-1);
250 enum gfx_if_en if_conv(
254 conv_if(PNG, IF_PNG);
255 conv_if(SVG, IF_SVG);
256 conv_if(EPS, IF_EPS);
257 conv_if(PDF, IF_PDF);
259 return (enum gfx_if_en)(-1);
262 enum tmt_en tmt_conv(
266 conv_if(SECOND, TMT_SECOND);
267 conv_if(MINUTE, TMT_MINUTE);
268 conv_if(HOUR, TMT_HOUR);
269 conv_if(DAY, TMT_DAY);
270 conv_if(WEEK, TMT_WEEK);
271 conv_if(MONTH, TMT_MONTH);
272 conv_if(YEAR, TMT_YEAR);
273 return (enum tmt_en)(-1);
276 enum grc_en grc_conv(
280 conv_if(BACK, GRC_BACK);
281 conv_if(CANVAS, GRC_CANVAS);
282 conv_if(SHADEA, GRC_SHADEA);
283 conv_if(SHADEB, GRC_SHADEB);
284 conv_if(GRID, GRC_GRID);
285 conv_if(MGRID, GRC_MGRID);
286 conv_if(FONT, GRC_FONT);
287 conv_if(ARROW, GRC_ARROW);
288 conv_if(AXIS, GRC_AXIS);
289 conv_if(FRAME, GRC_FRAME);
291 return (enum grc_en)(-1);
294 enum text_prop_en text_prop_conv(
298 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
299 conv_if(TITLE, TEXT_PROP_TITLE);
300 conv_if(AXIS, TEXT_PROP_AXIS);
301 conv_if(UNIT, TEXT_PROP_UNIT);
302 conv_if(LEGEND, TEXT_PROP_LEGEND);
303 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
304 return (enum text_prop_en)(-1);
314 cairo_status_t status = (cairo_status_t) 0;
319 if (im->daemon_addr != NULL)
320 free(im->daemon_addr);
322 for (i = 0; i < (unsigned) im->gdes_c; i++) {
323 if (im->gdes[i].data_first) {
324 /* careful here, because a single pointer can occur several times */
325 free(im->gdes[i].data);
326 if (im->gdes[i].ds_namv) {
327 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
328 free(im->gdes[i].ds_namv[ii]);
329 free(im->gdes[i].ds_namv);
332 /* free allocated memory used for dashed lines */
333 if (im->gdes[i].p_dashes != NULL)
334 free(im->gdes[i].p_dashes);
336 free(im->gdes[i].p_data);
337 free(im->gdes[i].rpnp);
341 for (i = 0; i < DIM(text_prop);i++){
342 pango_font_description_free(im->text_prop[i].font_desc);
343 im->text_prop[i].font_desc = NULL;
346 if (im->font_options)
347 cairo_font_options_destroy(im->font_options);
350 status = cairo_status(im->cr);
351 cairo_destroy(im->cr);
355 if (im->rendered_image) {
356 free(im->rendered_image);
360 g_object_unref (im->layout);
364 cairo_surface_destroy(im->surface);
367 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
368 cairo_status_to_string(status));
373 /* find SI magnitude symbol for the given number*/
375 image_desc_t *im, /* image description */
381 char *symbol[] = { "a", /* 10e-18 Atto */
382 "f", /* 10e-15 Femto */
383 "p", /* 10e-12 Pico */
384 "n", /* 10e-9 Nano */
385 "u", /* 10e-6 Micro */
386 "m", /* 10e-3 Milli */
391 "T", /* 10e12 Tera */
392 "P", /* 10e15 Peta */
399 if (*value == 0.0 || isnan(*value)) {
403 sindex = floor(log(fabs(*value)) / log((double) im->base));
404 *magfact = pow((double) im->base, (double) sindex);
405 (*value) /= (*magfact);
407 if (sindex <= symbcenter && sindex >= -symbcenter) {
408 (*symb_ptr) = symbol[sindex + symbcenter];
415 static char si_symbol[] = {
416 'a', /* 10e-18 Atto */
417 'f', /* 10e-15 Femto */
418 'p', /* 10e-12 Pico */
419 'n', /* 10e-9 Nano */
420 'u', /* 10e-6 Micro */
421 'm', /* 10e-3 Milli */
426 'T', /* 10e12 Tera */
427 'P', /* 10e15 Peta */
430 static const int si_symbcenter = 6;
432 /* find SI magnitude symbol for the numbers on the y-axis*/
434 image_desc_t *im /* image description */
438 double digits, viewdigits = 0;
441 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
442 log((double) im->base));
444 if (im->unitsexponent != 9999) {
445 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
446 viewdigits = floor((double)(im->unitsexponent / 3));
451 im->magfact = pow((double) im->base, digits);
454 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
457 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
459 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
460 ((viewdigits + si_symbcenter) >= 0))
461 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
466 /* move min and max values around to become sensible */
471 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
472 600.0, 500.0, 400.0, 300.0, 250.0,
473 200.0, 125.0, 100.0, 90.0, 80.0,
474 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
475 25.0, 20.0, 10.0, 9.0, 8.0,
476 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
477 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
478 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
481 double scaled_min, scaled_max;
488 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
489 im->minval, im->maxval, im->magfact);
492 if (isnan(im->ygridstep)) {
493 if (im->extra_flags & ALTAUTOSCALE) {
494 /* measure the amplitude of the function. Make sure that
495 graph boundaries are slightly higher then max/min vals
496 so we can see amplitude on the graph */
499 delt = im->maxval - im->minval;
501 fact = 2.0 * pow(10.0,
503 (max(fabs(im->minval), fabs(im->maxval)) /
506 adj = (fact - delt) * 0.55;
509 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
510 im->minval, im->maxval, delt, fact, adj);
515 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
516 /* measure the amplitude of the function. Make sure that
517 graph boundaries are slightly lower than min vals
518 so we can see amplitude on the graph */
519 adj = (im->maxval - im->minval) * 0.1;
521 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
522 /* measure the amplitude of the function. Make sure that
523 graph boundaries are slightly higher than max vals
524 so we can see amplitude on the graph */
525 adj = (im->maxval - im->minval) * 0.1;
528 scaled_min = im->minval / im->magfact;
529 scaled_max = im->maxval / im->magfact;
531 for (i = 1; sensiblevalues[i] > 0; i++) {
532 if (sensiblevalues[i - 1] >= scaled_min &&
533 sensiblevalues[i] <= scaled_min)
534 im->minval = sensiblevalues[i] * (im->magfact);
536 if (-sensiblevalues[i - 1] <= scaled_min &&
537 -sensiblevalues[i] >= scaled_min)
538 im->minval = -sensiblevalues[i - 1] * (im->magfact);
540 if (sensiblevalues[i - 1] >= scaled_max &&
541 sensiblevalues[i] <= scaled_max)
542 im->maxval = sensiblevalues[i - 1] * (im->magfact);
544 if (-sensiblevalues[i - 1] <= scaled_max &&
545 -sensiblevalues[i] >= scaled_max)
546 im->maxval = -sensiblevalues[i] * (im->magfact);
550 /* adjust min and max to the grid definition if there is one */
551 im->minval = (double) im->ylabfact * im->ygridstep *
552 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
553 im->maxval = (double) im->ylabfact * im->ygridstep *
554 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
558 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
559 im->minval, im->maxval, im->magfact);
567 if (isnan(im->minval) || isnan(im->maxval))
570 if (im->logarithmic) {
571 double ya, yb, ypix, ypixfrac;
572 double log10_range = log10(im->maxval) - log10(im->minval);
574 ya = pow((double) 10, floor(log10(im->minval)));
575 while (ya < im->minval)
578 return; /* don't have y=10^x gridline */
580 if (yb <= im->maxval) {
581 /* we have at least 2 y=10^x gridlines.
582 Make sure distance between them in pixels
583 are an integer by expanding im->maxval */
584 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
585 double factor = y_pixel_delta / floor(y_pixel_delta);
586 double new_log10_range = factor * log10_range;
587 double new_ymax_log10 = log10(im->minval) + new_log10_range;
589 im->maxval = pow(10, new_ymax_log10);
590 ytr(im, DNAN); /* reset precalc */
591 log10_range = log10(im->maxval) - log10(im->minval);
593 /* make sure first y=10^x gridline is located on
594 integer pixel position by moving scale slightly
595 downwards (sub-pixel movement) */
596 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
597 ypixfrac = ypix - floor(ypix);
598 if (ypixfrac > 0 && ypixfrac < 1) {
599 double yfrac = ypixfrac / im->ysize;
601 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
602 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
603 ytr(im, DNAN); /* reset precalc */
606 /* Make sure we have an integer pixel distance between
607 each minor gridline */
608 double ypos1 = ytr(im, im->minval);
609 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
610 double y_pixel_delta = ypos1 - ypos2;
611 double factor = y_pixel_delta / floor(y_pixel_delta);
612 double new_range = factor * (im->maxval - im->minval);
613 double gridstep = im->ygrid_scale.gridstep;
614 double minor_y, minor_y_px, minor_y_px_frac;
616 if (im->maxval > 0.0)
617 im->maxval = im->minval + new_range;
619 im->minval = im->maxval - new_range;
620 ytr(im, DNAN); /* reset precalc */
621 /* make sure first minor gridline is on integer pixel y coord */
622 minor_y = gridstep * floor(im->minval / gridstep);
623 while (minor_y < im->minval)
625 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
626 minor_y_px_frac = minor_y_px - floor(minor_y_px);
627 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
628 double yfrac = minor_y_px_frac / im->ysize;
629 double range = im->maxval - im->minval;
631 im->minval = im->minval - yfrac * range;
632 im->maxval = im->maxval - yfrac * range;
633 ytr(im, DNAN); /* reset precalc */
635 calc_horizontal_grid(im); /* recalc with changed im->maxval */
639 /* reduce data reimplementation by Alex */
642 enum cf_en cf, /* which consolidation function ? */
643 unsigned long cur_step, /* step the data currently is in */
644 time_t *start, /* start, end and step as requested ... */
645 time_t *end, /* ... by the application will be ... */
646 unsigned long *step, /* ... adjusted to represent reality */
647 unsigned long *ds_cnt, /* number of data sources in file */
649 { /* two dimensional array containing the data */
650 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
651 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
653 rrd_value_t *srcptr, *dstptr;
655 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
658 row_cnt = ((*end) - (*start)) / cur_step;
664 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
665 row_cnt, reduce_factor, *start, *end, cur_step);
666 for (col = 0; col < row_cnt; col++) {
667 printf("time %10lu: ", *start + (col + 1) * cur_step);
668 for (i = 0; i < *ds_cnt; i++)
669 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
674 /* We have to combine [reduce_factor] rows of the source
675 ** into one row for the destination. Doing this we also
676 ** need to take care to combine the correct rows. First
677 ** alter the start and end time so that they are multiples
678 ** of the new step time. We cannot reduce the amount of
679 ** time so we have to move the end towards the future and
680 ** the start towards the past.
682 end_offset = (*end) % (*step);
683 start_offset = (*start) % (*step);
685 /* If there is a start offset (which cannot be more than
686 ** one destination row), skip the appropriate number of
687 ** source rows and one destination row. The appropriate
688 ** number is what we do know (start_offset/cur_step) of
689 ** the new interval (*step/cur_step aka reduce_factor).
692 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
693 printf("row_cnt before: %lu\n", row_cnt);
696 (*start) = (*start) - start_offset;
697 skiprows = reduce_factor - start_offset / cur_step;
698 srcptr += skiprows * *ds_cnt;
699 for (col = 0; col < (*ds_cnt); col++)
704 printf("row_cnt between: %lu\n", row_cnt);
707 /* At the end we have some rows that are not going to be
708 ** used, the amount is end_offset/cur_step
711 (*end) = (*end) - end_offset + (*step);
712 skiprows = end_offset / cur_step;
716 printf("row_cnt after: %lu\n", row_cnt);
719 /* Sanity check: row_cnt should be multiple of reduce_factor */
720 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
722 if (row_cnt % reduce_factor) {
723 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
724 row_cnt, reduce_factor);
725 printf("BUG in reduce_data()\n");
729 /* Now combine reduce_factor intervals at a time
730 ** into one interval for the destination.
733 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
734 for (col = 0; col < (*ds_cnt); col++) {
735 rrd_value_t newval = DNAN;
736 unsigned long validval = 0;
738 for (i = 0; i < reduce_factor; i++) {
739 if (isnan(srcptr[i * (*ds_cnt) + col])) {
744 newval = srcptr[i * (*ds_cnt) + col];
753 newval += srcptr[i * (*ds_cnt) + col];
756 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
759 /* an interval contains a failure if any subintervals contained a failure */
761 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
764 newval = srcptr[i * (*ds_cnt) + col];
790 srcptr += (*ds_cnt) * reduce_factor;
791 row_cnt -= reduce_factor;
793 /* If we had to alter the endtime, we didn't have enough
794 ** source rows to fill the last row. Fill it with NaN.
797 for (col = 0; col < (*ds_cnt); col++)
800 row_cnt = ((*end) - (*start)) / *step;
802 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
803 row_cnt, *start, *end, *step);
804 for (col = 0; col < row_cnt; col++) {
805 printf("time %10lu: ", *start + (col + 1) * (*step));
806 for (i = 0; i < *ds_cnt; i++)
807 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
814 /* get the data required for the graphs from the
823 /* pull the data from the rrd files ... */
824 for (i = 0; i < (int) im->gdes_c; i++) {
825 /* only GF_DEF elements fetch data */
826 if (im->gdes[i].gf != GF_DEF)
830 /* do we have it already ? */
831 for (ii = 0; ii < i; ii++) {
832 if (im->gdes[ii].gf != GF_DEF)
834 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
835 && (im->gdes[i].cf == im->gdes[ii].cf)
836 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
837 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
838 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
839 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
840 /* OK, the data is already there.
841 ** Just copy the header portion
843 im->gdes[i].start = im->gdes[ii].start;
844 im->gdes[i].end = im->gdes[ii].end;
845 im->gdes[i].step = im->gdes[ii].step;
846 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
847 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
848 im->gdes[i].data = im->gdes[ii].data;
849 im->gdes[i].data_first = 0;
856 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
857 const char *rrd_daemon;
860 if (im->gdes[i].daemon[0] != 0)
861 rrd_daemon = im->gdes[i].daemon;
863 rrd_daemon = im->daemon_addr;
865 /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
866 * case. If "daemon" holds the same value as in the previous
867 * iteration, no actual new connection is established - the
868 * existing connection is re-used. */
869 rrdc_connect (rrd_daemon);
871 /* If connecting was successfull, use the daemon to query the data.
872 * If there is no connection, for example because no daemon address
873 * was specified, (try to) use the local file directly. */
874 if (rrdc_is_connected (rrd_daemon))
876 status = rrdc_fetch (im->gdes[i].rrd,
877 cf_to_string (im->gdes[i].cf),
882 &im->gdes[i].ds_namv,
889 if ((rrd_fetch_fn(im->gdes[i].rrd,
895 &im->gdes[i].ds_namv,
896 &im->gdes[i].data)) == -1) {
900 im->gdes[i].data_first = 1;
902 if (ft_step < im->gdes[i].step) {
903 reduce_data(im->gdes[i].cf_reduce,
908 &im->gdes[i].ds_cnt, &im->gdes[i].data);
910 im->gdes[i].step = ft_step;
914 /* lets see if the required data source is really there */
915 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
916 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
920 if (im->gdes[i].ds == -1) {
921 rrd_set_error("No DS called '%s' in '%s'",
922 im->gdes[i].ds_nam, im->gdes[i].rrd);
930 /* evaluate the expressions in the CDEF functions */
932 /*************************************************************
934 *************************************************************/
936 long find_var_wrapper(
940 return find_var((image_desc_t *) arg1, key);
943 /* find gdes containing var*/
950 for (ii = 0; ii < im->gdes_c - 1; ii++) {
951 if ((im->gdes[ii].gf == GF_DEF
952 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
953 && (strcmp(im->gdes[ii].vname, key) == 0)) {
960 /* find the greatest common divisor for all the numbers
961 in the 0 terminated num array */
968 for (i = 0; num[i + 1] != 0; i++) {
970 rest = num[i] % num[i + 1];
976 /* return i==0?num[i]:num[i-1]; */
980 /* run the rpn calculator on all the VDEF and CDEF arguments */
987 long *steparray, rpi;
992 rpnstack_init(&rpnstack);
994 for (gdi = 0; gdi < im->gdes_c; gdi++) {
995 /* Look for GF_VDEF and GF_CDEF in the same loop,
996 * so CDEFs can use VDEFs and vice versa
998 switch (im->gdes[gdi].gf) {
1002 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
1004 /* remove current shift */
1005 vdp->start -= vdp->shift;
1006 vdp->end -= vdp->shift;
1009 if (im->gdes[gdi].shidx >= 0)
1010 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1013 vdp->shift = im->gdes[gdi].shval;
1015 /* normalize shift to multiple of consolidated step */
1016 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1019 vdp->start += vdp->shift;
1020 vdp->end += vdp->shift;
1024 /* A VDEF has no DS. This also signals other parts
1025 * of rrdtool that this is a VDEF value, not a CDEF.
1027 im->gdes[gdi].ds_cnt = 0;
1028 if (vdef_calc(im, gdi)) {
1029 rrd_set_error("Error processing VDEF '%s'",
1030 im->gdes[gdi].vname);
1031 rpnstack_free(&rpnstack);
1036 im->gdes[gdi].ds_cnt = 1;
1037 im->gdes[gdi].ds = 0;
1038 im->gdes[gdi].data_first = 1;
1039 im->gdes[gdi].start = 0;
1040 im->gdes[gdi].end = 0;
1045 /* Find the variables in the expression.
1046 * - VDEF variables are substituted by their values
1047 * and the opcode is changed into OP_NUMBER.
1048 * - CDEF variables are analized for their step size,
1049 * the lowest common denominator of all the step
1050 * sizes of the data sources involved is calculated
1051 * and the resulting number is the step size for the
1052 * resulting data source.
1054 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1055 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1056 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1057 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1059 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1062 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1063 im->gdes[gdi].vname, im->gdes[ptr].vname);
1064 printf("DEBUG: value from vdef is %f\n",
1065 im->gdes[ptr].vf.val);
1067 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1068 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1069 } else { /* normal variables and PREF(variables) */
1071 /* add one entry to the array that keeps track of the step sizes of the
1072 * data sources going into the CDEF. */
1074 (long*)rrd_realloc(steparray,
1076 1) * sizeof(*steparray))) == NULL) {
1077 rrd_set_error("realloc steparray");
1078 rpnstack_free(&rpnstack);
1082 steparray[stepcnt - 1] = im->gdes[ptr].step;
1084 /* adjust start and end of cdef (gdi) so
1085 * that it runs from the latest start point
1086 * to the earliest endpoint of any of the
1087 * rras involved (ptr)
1090 if (im->gdes[gdi].start < im->gdes[ptr].start)
1091 im->gdes[gdi].start = im->gdes[ptr].start;
1093 if (im->gdes[gdi].end == 0 ||
1094 im->gdes[gdi].end > im->gdes[ptr].end)
1095 im->gdes[gdi].end = im->gdes[ptr].end;
1097 /* store pointer to the first element of
1098 * the rra providing data for variable,
1099 * further save step size and data source
1102 im->gdes[gdi].rpnp[rpi].data =
1103 im->gdes[ptr].data + im->gdes[ptr].ds;
1104 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1105 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1107 /* backoff the *.data ptr; this is done so
1108 * rpncalc() function doesn't have to treat
1109 * the first case differently
1111 } /* if ds_cnt != 0 */
1112 } /* if OP_VARIABLE */
1113 } /* loop through all rpi */
1115 /* move the data pointers to the correct period */
1116 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1117 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1118 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1119 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1121 im->gdes[gdi].start - im->gdes[ptr].start;
1124 im->gdes[gdi].rpnp[rpi].data +=
1125 (diff / im->gdes[ptr].step) *
1126 im->gdes[ptr].ds_cnt;
1130 if (steparray == NULL) {
1131 rrd_set_error("rpn expressions without DEF"
1132 " or CDEF variables are not supported");
1133 rpnstack_free(&rpnstack);
1136 steparray[stepcnt] = 0;
1137 /* Now find the resulting step. All steps in all
1138 * used RRAs have to be visited
1140 im->gdes[gdi].step = lcd(steparray);
1142 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1143 im->gdes[gdi].start)
1144 / im->gdes[gdi].step)
1145 * sizeof(double))) == NULL) {
1146 rrd_set_error("malloc im->gdes[gdi].data");
1147 rpnstack_free(&rpnstack);
1151 /* Step through the new cdef results array and
1152 * calculate the values
1154 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1155 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1156 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1158 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1159 * in this case we are advancing by timesteps;
1160 * we use the fact that time_t is a synonym for long
1162 if (rpn_calc(rpnp, &rpnstack, (long) now,
1163 im->gdes[gdi].data, ++dataidx) == -1) {
1164 /* rpn_calc sets the error string */
1165 rpnstack_free(&rpnstack);
1168 } /* enumerate over time steps within a CDEF */
1173 } /* enumerate over CDEFs */
1174 rpnstack_free(&rpnstack);
1178 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1179 /* yes we are loosing precision by doing tos with floats instead of doubles
1180 but it seems more stable this way. */
1182 static int AlmostEqual2sComplement(
1188 int aInt = *(int *) &A;
1189 int bInt = *(int *) &B;
1192 /* Make sure maxUlps is non-negative and small enough that the
1193 default NAN won't compare as equal to anything. */
1195 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1197 /* Make aInt lexicographically ordered as a twos-complement int */
1200 aInt = 0x80000000l - aInt;
1202 /* Make bInt lexicographically ordered as a twos-complement int */
1205 bInt = 0x80000000l - bInt;
1207 intDiff = abs(aInt - bInt);
1209 if (intDiff <= maxUlps)
1215 /* massage data so, that we get one value for each x coordinate in the graph */
1220 double pixstep = (double) (im->end - im->start)
1221 / (double) im->xsize; /* how much time
1222 passes in one pixel */
1224 double minval = DNAN, maxval = DNAN;
1226 unsigned long gr_time;
1228 /* memory for the processed data */
1229 for (i = 0; i < im->gdes_c; i++) {
1230 if ((im->gdes[i].gf == GF_LINE)
1231 || (im->gdes[i].gf == GF_AREA)
1232 || (im->gdes[i].gf == GF_TICK)
1233 || (im->gdes[i].gf == GF_GRAD)
1235 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1236 * sizeof(rrd_value_t))) == NULL) {
1237 rrd_set_error("malloc data_proc");
1243 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1246 gr_time = im->start + pixstep * i; /* time of the current step */
1249 for (ii = 0; ii < im->gdes_c; ii++) {
1252 switch (im->gdes[ii].gf) {
1257 if (!im->gdes[ii].stack)
1259 value = im->gdes[ii].yrule;
1260 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1261 /* The time of the data doesn't necessarily match
1262 ** the time of the graph. Beware.
1264 vidx = im->gdes[ii].vidx;
1265 if (im->gdes[vidx].gf == GF_VDEF) {
1266 value = im->gdes[vidx].vf.val;
1268 if (((long int) gr_time >=
1269 (long int) im->gdes[vidx].start)
1270 && ((long int) gr_time <
1271 (long int) im->gdes[vidx].end)) {
1272 value = im->gdes[vidx].data[(unsigned long)
1278 im->gdes[vidx].step)
1279 * im->gdes[vidx].ds_cnt +
1286 if (!isnan(value)) {
1288 im->gdes[ii].p_data[i] = paintval;
1289 /* GF_TICK: the data values are not
1290 ** relevant for min and max
1292 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1293 if ((isnan(minval) || paintval < minval) &&
1294 !(im->logarithmic && paintval <= 0.0))
1296 if (isnan(maxval) || paintval > maxval)
1300 im->gdes[ii].p_data[i] = DNAN;
1305 ("STACK should already be turned into LINE or AREA here");
1314 /* if min or max have not been asigned a value this is because
1315 there was no data in the graph ... this is not good ...
1316 lets set these to dummy values then ... */
1318 if (im->logarithmic) {
1319 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1320 minval = 0.0; /* catching this right away below */
1323 /* in logarithm mode, where minval is smaller or equal
1324 to 0 make the beast just way smaller than maxval */
1326 minval = maxval / 10e8;
1329 if (isnan(minval) || isnan(maxval)) {
1335 /* adjust min and max values given by the user */
1336 /* for logscale we add something on top */
1337 if (isnan(im->minval)
1338 || ((!im->rigid) && im->minval > minval)
1340 if (im->logarithmic)
1341 im->minval = minval / 2.0;
1343 im->minval = minval;
1345 if (isnan(im->maxval)
1346 || (!im->rigid && im->maxval < maxval)
1348 if (im->logarithmic)
1349 im->maxval = maxval * 2.0;
1351 im->maxval = maxval;
1354 /* make sure min is smaller than max */
1355 if (im->minval > im->maxval) {
1357 im->minval = 0.99 * im->maxval;
1359 im->minval = 1.01 * im->maxval;
1362 /* make sure min and max are not equal */
1363 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1369 /* make sure min and max are not both zero */
1370 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1377 static int find_first_weekday(void){
1378 static int first_weekday = -1;
1379 if (first_weekday == -1){
1380 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1381 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1382 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1383 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1384 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1385 else first_weekday = 1; /* we go for a monday default */
1390 return first_weekday;
1393 /* identify the point where the first gridline, label ... gets placed */
1395 time_t find_first_time(
1396 time_t start, /* what is the initial time */
1397 enum tmt_en baseint, /* what is the basic interval */
1398 long basestep /* how many if these do we jump a time */
1403 localtime_r(&start, &tm);
1407 tm. tm_sec -= tm.tm_sec % basestep;
1412 tm. tm_min -= tm.tm_min % basestep;
1418 tm. tm_hour -= tm.tm_hour % basestep;
1422 /* we do NOT look at the basestep for this ... */
1429 /* we do NOT look at the basestep for this ... */
1433 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1435 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1436 tm. tm_mday -= 7; /* we want the *previous* week */
1444 tm. tm_mon -= tm.tm_mon % basestep;
1455 tm.tm_year + 1900) %basestep;
1461 /* identify the point where the next gridline, label ... gets placed */
1462 time_t find_next_time(
1463 time_t current, /* what is the initial time */
1464 enum tmt_en baseint, /* what is the basic interval */
1465 long basestep /* how many if these do we jump a time */
1471 localtime_r(¤t, &tm);
1476 tm. tm_sec += basestep;
1480 tm. tm_min += basestep;
1484 tm. tm_hour += basestep;
1488 tm. tm_mday += basestep;
1492 tm. tm_mday += 7 * basestep;
1496 tm. tm_mon += basestep;
1500 tm. tm_year += basestep;
1502 madetime = mktime(&tm);
1503 } while (madetime == -1); /* this is necessary to skip impssible times
1504 like the daylight saving time skips */
1510 /* calculate values required for PRINT and GPRINT functions */
1515 long i, ii, validsteps;
1518 int graphelement = 0;
1521 double magfact = -1;
1526 /* wow initializing tmvdef is quite a task :-) */
1527 time_t now = time(NULL);
1529 localtime_r(&now, &tmvdef);
1530 for (i = 0; i < im->gdes_c; i++) {
1531 vidx = im->gdes[i].vidx;
1532 switch (im->gdes[i].gf) {
1535 /* PRINT and GPRINT can now print VDEF generated values.
1536 * There's no need to do any calculations on them as these
1537 * calculations were already made.
1539 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1540 printval = im->gdes[vidx].vf.val;
1541 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1542 } else { /* need to calculate max,min,avg etcetera */
1543 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1544 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1547 for (ii = im->gdes[vidx].ds;
1548 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1549 if (!finite(im->gdes[vidx].data[ii]))
1551 if (isnan(printval)) {
1552 printval = im->gdes[vidx].data[ii];
1557 switch (im->gdes[i].cf) {
1561 case CF_DEVSEASONAL:
1565 printval += im->gdes[vidx].data[ii];
1568 printval = min(printval, im->gdes[vidx].data[ii]);
1572 printval = max(printval, im->gdes[vidx].data[ii]);
1575 printval = im->gdes[vidx].data[ii];
1578 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1579 if (validsteps > 1) {
1580 printval = (printval / validsteps);
1583 } /* prepare printval */
1585 if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1586 /* Magfact is set to -1 upon entry to print_calc. If it
1587 * is still less than 0, then we need to run auto_scale.
1588 * Otherwise, put the value into the correct units. If
1589 * the value is 0, then do not set the symbol or magnification
1590 * so next the calculation will be performed again. */
1591 if (magfact < 0.0) {
1592 auto_scale(im, &printval, &si_symb, &magfact);
1593 if (printval == 0.0)
1596 printval /= magfact;
1598 *(++percent_s) = 's';
1599 } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1600 auto_scale(im, &printval, &si_symb, &magfact);
1603 if (im->gdes[i].gf == GF_PRINT) {
1604 rrd_infoval_t prline;
1606 if (im->gdes[i].strftm) {
1607 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1608 if (im->gdes[vidx].vf.never == 1) {
1609 time_clean(prline.u_str, im->gdes[i].format);
1611 strftime(prline.u_str,
1612 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1614 } else if (bad_format(im->gdes[i].format)) {
1616 ("bad format for PRINT in '%s'", im->gdes[i].format);
1620 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1624 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1629 if (im->gdes[i].strftm) {
1630 if (im->gdes[vidx].vf.never == 1) {
1631 time_clean(im->gdes[i].legend, im->gdes[i].format);
1633 strftime(im->gdes[i].legend,
1634 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1637 if (bad_format(im->gdes[i].format)) {
1639 ("bad format for GPRINT in '%s'",
1640 im->gdes[i].format);
1643 #ifdef HAVE_SNPRINTF
1644 snprintf(im->gdes[i].legend,
1646 im->gdes[i].format, printval, si_symb);
1648 sprintf(im->gdes[i].legend,
1649 im->gdes[i].format, printval, si_symb);
1662 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1663 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1668 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1669 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1678 #ifdef WITH_PIECHART
1686 ("STACK should already be turned into LINE or AREA here");
1691 return graphelement;
1696 /* place legends with color spots */
1702 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1703 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1704 int fill = 0, fill_last;
1705 double legendwidth; // = im->ximg - 2 * border;
1707 double leg_x = border;
1708 int leg_y = 0; //im->yimg;
1709 int leg_y_prev = 0; // im->yimg;
1712 int i, ii, mark = 0;
1713 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1716 char saved_legend[FMT_LEG_LEN + 5];
1722 legendwidth = im->legendwidth - 2 * border;
1726 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1727 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1728 rrd_set_error("malloc for legspace");
1732 for (i = 0; i < im->gdes_c; i++) {
1733 char prt_fctn; /*special printfunctions */
1735 strcpy(saved_legend, im->gdes[i].legend);
1739 /* hide legends for rules which are not displayed */
1740 if (im->gdes[i].gf == GF_TEXTALIGN) {
1741 default_txtalign = im->gdes[i].txtalign;
1744 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1745 if (im->gdes[i].gf == GF_HRULE
1746 && (im->gdes[i].yrule <
1747 im->minval || im->gdes[i].yrule > im->maxval))
1748 im->gdes[i].legend[0] = '\0';
1749 if (im->gdes[i].gf == GF_VRULE
1750 && (im->gdes[i].xrule <
1751 im->start || im->gdes[i].xrule > im->end))
1752 im->gdes[i].legend[0] = '\0';
1755 /* turn \\t into tab */
1756 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1757 memmove(tab, tab + 1, strlen(tab));
1761 leg_cc = strlen(im->gdes[i].legend);
1762 /* is there a controle code at the end of the legend string ? */
1763 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1764 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1766 im->gdes[i].legend[leg_cc] = '\0';
1770 /* only valid control codes */
1771 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1776 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1779 ("Unknown control code at the end of '%s\\%c'",
1780 im->gdes[i].legend, prt_fctn);
1784 if (prt_fctn == 'n') {
1788 /* remove exess space from the end of the legend for \g */
1789 while (prt_fctn == 'g' &&
1790 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1792 im->gdes[i].legend[leg_cc] = '\0';
1797 /* no interleg space if string ends in \g */
1798 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1800 fill += legspace[i];
1803 gfx_get_text_width(im,
1809 im->tabwidth, im->gdes[i].legend);
1814 /* who said there was a special tag ... ? */
1815 if (prt_fctn == 'g') {
1819 if (prt_fctn == '\0') {
1820 if(calc_width && (fill > legendwidth)){
1823 if (i == im->gdes_c - 1 || fill > legendwidth) {
1824 /* just one legend item is left right or center */
1825 switch (default_txtalign) {
1840 /* is it time to place the legends ? */
1841 if (fill > legendwidth) {
1849 if (leg_c == 1 && prt_fctn == 'j') {
1854 if (prt_fctn != '\0') {
1856 if (leg_c >= 2 && prt_fctn == 'j') {
1857 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1861 if (prt_fctn == 'c')
1862 leg_x = (double)(legendwidth - fill) / 2.0;
1863 if (prt_fctn == 'r')
1864 leg_x = legendwidth - fill + border;
1865 for (ii = mark; ii <= i; ii++) {
1866 if (im->gdes[ii].legend[0] == '\0')
1867 continue; /* skip empty legends */
1868 im->gdes[ii].leg_x = leg_x;
1869 im->gdes[ii].leg_y = leg_y + border;
1871 (double)gfx_get_text_width(im, leg_x,
1876 im->tabwidth, im->gdes[ii].legend)
1877 +(double)legspace[ii]
1881 if (leg_x > border || prt_fctn == 's')
1882 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1883 if (prt_fctn == 's')
1884 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1885 if (prt_fctn == 'u')
1886 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1888 if(calc_width && (fill > legendwidth)){
1897 strcpy(im->gdes[i].legend, saved_legend);
1902 im->legendwidth = legendwidth + 2 * border;
1905 im->legendheight = leg_y + border * 0.6;
1912 /* create a grid on the graph. it determines what to do
1913 from the values of xsize, start and end */
1915 /* the xaxis labels are determined from the number of seconds per pixel
1916 in the requested graph */
1918 int calc_horizontal_grid(
1926 int decimals, fractionals;
1928 im->ygrid_scale.labfact = 2;
1929 range = im->maxval - im->minval;
1930 scaledrange = range / im->magfact;
1931 /* does the scale of this graph make it impossible to put lines
1932 on it? If so, give up. */
1933 if (isnan(scaledrange)) {
1937 /* find grid spaceing */
1939 if (isnan(im->ygridstep)) {
1940 if (im->extra_flags & ALTYGRID) {
1941 /* find the value with max number of digits. Get number of digits */
1944 (max(fabs(im->maxval), fabs(im->minval)) *
1945 im->viewfactor / im->magfact));
1946 if (decimals <= 0) /* everything is small. make place for zero */
1948 im->ygrid_scale.gridstep =
1950 floor(log10(range * im->viewfactor / im->magfact))) /
1951 im->viewfactor * im->magfact;
1952 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1953 im->ygrid_scale.gridstep = 0.1;
1954 /* should have at least 5 lines but no more then 15 */
1955 if (range / im->ygrid_scale.gridstep < 5
1956 && im->ygrid_scale.gridstep >= 30)
1957 im->ygrid_scale.gridstep /= 10;
1958 if (range / im->ygrid_scale.gridstep > 15)
1959 im->ygrid_scale.gridstep *= 10;
1960 if (range / im->ygrid_scale.gridstep > 5) {
1961 im->ygrid_scale.labfact = 1;
1962 if (range / im->ygrid_scale.gridstep > 8
1963 || im->ygrid_scale.gridstep <
1964 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1965 im->ygrid_scale.labfact = 2;
1967 im->ygrid_scale.gridstep /= 5;
1968 im->ygrid_scale.labfact = 5;
1972 (im->ygrid_scale.gridstep *
1973 (double) im->ygrid_scale.labfact * im->viewfactor /
1975 if (fractionals < 0) { /* small amplitude. */
1976 int len = decimals - fractionals + 1;
1978 if (im->unitslength < len + 2)
1979 im->unitslength = len + 2;
1980 sprintf(im->ygrid_scale.labfmt,
1982 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1984 int len = decimals + 1;
1986 if (im->unitslength < len + 2)
1987 im->unitslength = len + 2;
1988 sprintf(im->ygrid_scale.labfmt,
1989 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1991 } else { /* classic rrd grid */
1992 for (i = 0; ylab[i].grid > 0; i++) {
1993 pixel = im->ysize / (scaledrange / ylab[i].grid);
1999 for (i = 0; i < 4; i++) {
2000 if (pixel * ylab[gridind].lfac[i] >=
2001 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2002 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2007 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2010 im->ygrid_scale.gridstep = im->ygridstep;
2011 im->ygrid_scale.labfact = im->ylabfact;
2016 int draw_horizontal_grid(
2022 char graph_label[100];
2024 double X0 = im->xorigin;
2025 double X1 = im->xorigin + im->xsize;
2026 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2027 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2029 double second_axis_magfact = 0;
2030 char *second_axis_symb = "";
2033 im->ygrid_scale.gridstep /
2034 (double) im->magfact * (double) im->viewfactor;
2035 MaxY = scaledstep * (double) egrid;
2036 for (i = sgrid; i <= egrid; i++) {
2038 im->ygrid_scale.gridstep * i);
2040 im->ygrid_scale.gridstep * (i + 1));
2042 if (floor(Y0 + 0.5) >=
2043 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2044 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2045 with the chosen settings. Add a label if required by settings, or if
2046 there is only one label so far and the next grid line is out of bounds. */
2047 if (i % im->ygrid_scale.labfact == 0
2049 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2050 if (im->symbol == ' ') {
2051 if (im->extra_flags & ALTYGRID) {
2052 sprintf(graph_label,
2053 im->ygrid_scale.labfmt,
2054 scaledstep * (double) i);
2057 sprintf(graph_label, "%4.1f",
2058 scaledstep * (double) i);
2060 sprintf(graph_label, "%4.0f",
2061 scaledstep * (double) i);
2065 char sisym = (i == 0 ? ' ' : im->symbol);
2067 if (im->extra_flags & ALTYGRID) {
2068 sprintf(graph_label,
2069 im->ygrid_scale.labfmt,
2070 scaledstep * (double) i, sisym);
2073 sprintf(graph_label, "%4.1f %c",
2074 scaledstep * (double) i, sisym);
2076 sprintf(graph_label, "%4.0f %c",
2077 scaledstep * (double) i, sisym);
2082 if (im->second_axis_scale != 0){
2083 char graph_label_right[100];
2084 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2085 if (im->second_axis_format[0] == '\0'){
2086 if (!second_axis_magfact){
2087 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2088 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2090 sval /= second_axis_magfact;
2093 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2095 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2099 sprintf(graph_label_right,im->second_axis_format,sval);
2103 im->graph_col[GRC_FONT],
2104 im->text_prop[TEXT_PROP_AXIS].font_desc,
2105 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2106 graph_label_right );
2112 text_prop[TEXT_PROP_AXIS].
2114 im->graph_col[GRC_FONT],
2116 text_prop[TEXT_PROP_AXIS].
2119 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2120 gfx_line(im, X0 - 2, Y0, X0, Y0,
2121 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2122 gfx_line(im, X1, Y0, X1 + 2, Y0,
2123 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2124 gfx_dashed_line(im, X0 - 2, Y0,
2130 im->grid_dash_on, im->grid_dash_off);
2131 } else if (!(im->extra_flags & NOMINOR)) {
2134 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2135 gfx_line(im, X1, Y0, X1 + 2, Y0,
2136 GRIDWIDTH, im->graph_col[GRC_GRID]);
2137 gfx_dashed_line(im, X0 - 1, Y0,
2141 graph_col[GRC_GRID],
2142 im->grid_dash_on, im->grid_dash_off);
2149 /* this is frexp for base 10 */
2160 iexp = floor(log((double)fabs(x)) / log((double)10));
2161 mnt = x / pow(10.0, iexp);
2164 mnt = x / pow(10.0, iexp);
2171 /* logaritmic horizontal grid */
2172 int horizontal_log_grid(
2176 double yloglab[][10] = {
2178 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2180 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2182 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2199 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2201 int i, j, val_exp, min_exp;
2202 double nex; /* number of decades in data */
2203 double logscale; /* scale in logarithmic space */
2204 int exfrac = 1; /* decade spacing */
2205 int mid = -1; /* row in yloglab for major grid */
2206 double mspac; /* smallest major grid spacing (pixels) */
2207 int flab; /* first value in yloglab to use */
2208 double value, tmp, pre_value;
2210 char graph_label[100];
2212 nex = log10(im->maxval / im->minval);
2213 logscale = im->ysize / nex;
2214 /* major spacing for data with high dynamic range */
2215 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2222 /* major spacing for less dynamic data */
2224 /* search best row in yloglab */
2226 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2227 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2230 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2233 /* find first value in yloglab */
2235 yloglab[mid][flab] < 10
2236 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2237 if (yloglab[mid][flab] == 10.0) {
2242 if (val_exp % exfrac)
2243 val_exp += abs(-val_exp % exfrac);
2245 X1 = im->xorigin + im->xsize;
2250 value = yloglab[mid][flab] * pow(10.0, val_exp);
2251 if (AlmostEqual2sComplement(value, pre_value, 4))
2252 break; /* it seems we are not converging */
2254 Y0 = ytr(im, value);
2255 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2257 /* major grid line */
2259 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2260 gfx_line(im, X1, Y0, X1 + 2, Y0,
2261 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2262 gfx_dashed_line(im, X0 - 2, Y0,
2267 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2269 if (im->extra_flags & FORCE_UNITS_SI) {
2274 scale = floor(val_exp / 3.0);
2276 pvalue = pow(10.0, val_exp % 3);
2278 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2279 pvalue *= yloglab[mid][flab];
2280 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2281 && ((scale + si_symbcenter) >= 0))
2282 symbol = si_symbol[scale + si_symbcenter];
2285 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2287 sprintf(graph_label, "%3.0e", value);
2289 if (im->second_axis_scale != 0){
2290 char graph_label_right[100];
2291 double sval = value*im->second_axis_scale+im->second_axis_shift;
2292 if (im->second_axis_format[0] == '\0'){
2293 if (im->extra_flags & FORCE_UNITS_SI) {
2296 auto_scale(im,&sval,&symb,&mfac);
2297 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2300 sprintf(graph_label_right,"%3.0e", sval);
2304 sprintf(graph_label_right,im->second_axis_format,sval,"");
2309 im->graph_col[GRC_FONT],
2310 im->text_prop[TEXT_PROP_AXIS].font_desc,
2311 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2312 graph_label_right );
2318 text_prop[TEXT_PROP_AXIS].
2320 im->graph_col[GRC_FONT],
2322 text_prop[TEXT_PROP_AXIS].
2325 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2327 if (mid < 4 && exfrac == 1) {
2328 /* find first and last minor line behind current major line
2329 * i is the first line and j tha last */
2331 min_exp = val_exp - 1;
2332 for (i = 1; yloglab[mid][i] < 10.0; i++);
2333 i = yloglab[mid][i - 1] + 1;
2337 i = yloglab[mid][flab - 1] + 1;
2338 j = yloglab[mid][flab];
2341 /* draw minor lines below current major line */
2342 for (; i < j; i++) {
2344 value = i * pow(10.0, min_exp);
2345 if (value < im->minval)
2347 Y0 = ytr(im, value);
2348 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2353 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2354 gfx_line(im, X1, Y0, X1 + 2, Y0,
2355 GRIDWIDTH, im->graph_col[GRC_GRID]);
2356 gfx_dashed_line(im, X0 - 1, Y0,
2360 graph_col[GRC_GRID],
2361 im->grid_dash_on, im->grid_dash_off);
2363 } else if (exfrac > 1) {
2364 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2365 value = pow(10.0, i);
2366 if (value < im->minval)
2368 Y0 = ytr(im, value);
2369 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2374 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2375 gfx_line(im, X1, Y0, X1 + 2, Y0,
2376 GRIDWIDTH, im->graph_col[GRC_GRID]);
2377 gfx_dashed_line(im, X0 - 1, Y0,
2381 graph_col[GRC_GRID],
2382 im->grid_dash_on, im->grid_dash_off);
2387 if (yloglab[mid][++flab] == 10.0) {
2393 /* draw minor lines after highest major line */
2394 if (mid < 4 && exfrac == 1) {
2395 /* find first and last minor line below current major line
2396 * i is the first line and j tha last */
2398 min_exp = val_exp - 1;
2399 for (i = 1; yloglab[mid][i] < 10.0; i++);
2400 i = yloglab[mid][i - 1] + 1;
2404 i = yloglab[mid][flab - 1] + 1;
2405 j = yloglab[mid][flab];
2408 /* draw minor lines below current major line */
2409 for (; i < j; i++) {
2411 value = i * pow(10.0, min_exp);
2412 if (value < im->minval)
2414 Y0 = ytr(im, value);
2415 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2419 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2420 gfx_line(im, X1, Y0, X1 + 2, Y0,
2421 GRIDWIDTH, im->graph_col[GRC_GRID]);
2422 gfx_dashed_line(im, X0 - 1, Y0,
2426 graph_col[GRC_GRID],
2427 im->grid_dash_on, im->grid_dash_off);
2430 /* fancy minor gridlines */
2431 else if (exfrac > 1) {
2432 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2433 value = pow(10.0, i);
2434 if (value < im->minval)
2436 Y0 = ytr(im, value);
2437 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2441 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2442 gfx_line(im, X1, Y0, X1 + 2, Y0,
2443 GRIDWIDTH, im->graph_col[GRC_GRID]);
2444 gfx_dashed_line(im, X0 - 1, Y0,
2448 graph_col[GRC_GRID],
2449 im->grid_dash_on, im->grid_dash_off);
2460 int xlab_sel; /* which sort of label and grid ? */
2461 time_t ti, tilab, timajor;
2463 char graph_label[100];
2464 double X0, Y0, Y1; /* points for filled graph and more */
2467 /* the type of time grid is determined by finding
2468 the number of seconds per pixel in the graph */
2469 if (im->xlab_user.minsec == -1) {
2470 factor = (im->end - im->start) / im->xsize;
2472 while (xlab[xlab_sel + 1].minsec !=
2473 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2475 } /* pick the last one */
2476 while (xlab[xlab_sel - 1].minsec ==
2477 xlab[xlab_sel].minsec
2478 && xlab[xlab_sel].length > (im->end - im->start)) {
2480 } /* go back to the smallest size */
2481 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2482 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2483 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2484 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2485 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2486 im->xlab_user.labst = xlab[xlab_sel].labst;
2487 im->xlab_user.precis = xlab[xlab_sel].precis;
2488 im->xlab_user.stst = xlab[xlab_sel].stst;
2491 /* y coords are the same for every line ... */
2493 Y1 = im->yorigin - im->ysize;
2494 /* paint the minor grid */
2495 if (!(im->extra_flags & NOMINOR)) {
2496 for (ti = find_first_time(im->start,
2504 find_first_time(im->start,
2511 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2513 /* are we inside the graph ? */
2514 if (ti < im->start || ti > im->end)
2516 while (timajor < ti) {
2517 timajor = find_next_time(timajor,
2520 mgridtm, im->xlab_user.mgridst);
2523 continue; /* skip as falls on major grid line */
2525 gfx_line(im, X0, Y1 - 2, X0, Y1,
2526 GRIDWIDTH, im->graph_col[GRC_GRID]);
2527 gfx_line(im, X0, Y0, X0, Y0 + 2,
2528 GRIDWIDTH, im->graph_col[GRC_GRID]);
2529 gfx_dashed_line(im, X0, Y0 + 1, X0,
2532 graph_col[GRC_GRID],
2533 im->grid_dash_on, im->grid_dash_off);
2537 /* paint the major grid */
2538 for (ti = find_first_time(im->start,
2546 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2548 /* are we inside the graph ? */
2549 if (ti < im->start || ti > im->end)
2552 gfx_line(im, X0, Y1 - 2, X0, Y1,
2553 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2554 gfx_line(im, X0, Y0, X0, Y0 + 3,
2555 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2556 gfx_dashed_line(im, X0, Y0 + 3, X0,
2560 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2562 /* paint the labels below the graph */
2564 find_first_time(im->start -
2573 im->xlab_user.precis / 2;
2574 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2576 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2577 /* are we inside the graph ? */
2578 if (tilab < im->start || tilab > im->end)
2581 localtime_r(&tilab, &tm);
2582 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2584 # error "your libc has no strftime I guess we'll abort the exercise here."
2589 im->graph_col[GRC_FONT],
2591 text_prop[TEXT_PROP_AXIS].
2594 GFX_H_CENTER, GFX_V_TOP, graph_label);
2603 /* draw x and y axis */
2604 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2605 im->xorigin+im->xsize,im->yorigin-im->ysize,
2606 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2608 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2609 im->xorigin+im->xsize,im->yorigin-im->ysize,
2610 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2612 gfx_line(im, im->xorigin - 4,
2614 im->xorigin + im->xsize +
2615 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2616 gfx_line(im, im->xorigin,
2619 im->yorigin - im->ysize -
2620 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2621 /* arrow for X and Y axis direction */
2622 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 */
2623 im->graph_col[GRC_ARROW]);
2625 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 */
2626 im->graph_col[GRC_ARROW]);
2628 if (im->second_axis_scale != 0){
2629 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2630 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2631 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2633 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2634 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2635 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2636 im->graph_col[GRC_ARROW]);
2647 double X0, Y0; /* points for filled graph and more */
2648 struct gfx_color_t water_color;
2650 if (im->draw_3d_border > 0) {
2651 /* draw 3d border */
2652 i = im->draw_3d_border;
2653 gfx_new_area(im, 0, im->yimg,
2654 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2655 gfx_add_point(im, im->ximg - i, i);
2656 gfx_add_point(im, im->ximg, 0);
2657 gfx_add_point(im, 0, 0);
2659 gfx_new_area(im, i, im->yimg - i,
2661 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2662 gfx_add_point(im, im->ximg, 0);
2663 gfx_add_point(im, im->ximg, im->yimg);
2664 gfx_add_point(im, 0, im->yimg);
2667 if (im->draw_x_grid == 1)
2669 if (im->draw_y_grid == 1) {
2670 if (im->logarithmic) {
2671 res = horizontal_log_grid(im);
2673 res = draw_horizontal_grid(im);
2676 /* dont draw horizontal grid if there is no min and max val */
2678 char *nodata = "No Data found";
2680 gfx_text(im, im->ximg / 2,
2683 im->graph_col[GRC_FONT],
2685 text_prop[TEXT_PROP_AXIS].
2688 GFX_H_CENTER, GFX_V_CENTER, nodata);
2692 /* yaxis unit description */
2693 if (im->ylegend[0] != '\0'){
2695 im->xOriginLegendY+10,
2697 im->graph_col[GRC_FONT],
2699 text_prop[TEXT_PROP_UNIT].
2702 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2705 if (im->second_axis_legend[0] != '\0'){
2707 im->xOriginLegendY2+10,
2708 im->yOriginLegendY2,
2709 im->graph_col[GRC_FONT],
2710 im->text_prop[TEXT_PROP_UNIT].font_desc,
2712 RRDGRAPH_YLEGEND_ANGLE,
2713 GFX_H_CENTER, GFX_V_CENTER,
2714 im->second_axis_legend);
2719 im->xOriginTitle, im->yOriginTitle+6,
2720 im->graph_col[GRC_FONT],
2722 text_prop[TEXT_PROP_TITLE].
2724 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2725 /* rrdtool 'logo' */
2726 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2727 water_color = im->graph_col[GRC_FONT];
2728 water_color.alpha = 0.3;
2729 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2730 gfx_text(im, xpos, 5,
2733 text_prop[TEXT_PROP_WATERMARK].
2734 font_desc, im->tabwidth,
2735 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2737 /* graph watermark */
2738 if (im->watermark[0] != '\0') {
2739 water_color = im->graph_col[GRC_FONT];
2740 water_color.alpha = 0.3;
2742 im->ximg / 2, im->yimg - 6,
2745 text_prop[TEXT_PROP_WATERMARK].
2746 font_desc, im->tabwidth, 0,
2747 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2751 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2752 for (i = 0; i < im->gdes_c; i++) {
2753 if (im->gdes[i].legend[0] == '\0')
2755 /* im->gdes[i].leg_y is the bottom of the legend */
2756 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2757 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2758 gfx_text(im, X0, Y0,
2759 im->graph_col[GRC_FONT],
2762 [TEXT_PROP_LEGEND].font_desc,
2764 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2765 /* The legend for GRAPH items starts with "M " to have
2766 enough space for the box */
2767 if (im->gdes[i].gf != GF_PRINT &&
2768 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2772 boxH = gfx_get_text_width(im, 0,
2777 im->tabwidth, "o") * 1.2;
2779 /* shift the box up a bit */
2782 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2784 cairo_new_path(im->cr);
2785 cairo_set_line_width(im->cr, 1.0);
2788 X0 + boxH, Y0 - boxV / 2,
2789 1.0, im->gdes[i].col);
2791 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2793 cairo_new_path(im->cr);
2794 cairo_set_line_width(im->cr, 1.0);
2797 X0 + boxH / 2, Y0 - boxV,
2798 1.0, im->gdes[i].col);
2800 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2802 cairo_new_path(im->cr);
2803 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2806 X0 + boxH, Y0 - boxV,
2807 im->gdes[i].linewidth, im->gdes[i].col);
2810 /* make sure transparent colors show up the same way as in the graph */
2813 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2814 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2816 gfx_new_area(im, X0, Y0 - boxV, X0,
2817 Y0, X0 + boxH, Y0, im->gdes[i].col);
2818 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2821 cairo_new_path(im->cr);
2822 cairo_set_line_width(im->cr, 1.0);
2825 gfx_line_fit(im, &X0, &Y0);
2826 gfx_line_fit(im, &X1, &Y1);
2827 cairo_move_to(im->cr, X0, Y0);
2828 cairo_line_to(im->cr, X1, Y0);
2829 cairo_line_to(im->cr, X1, Y1);
2830 cairo_line_to(im->cr, X0, Y1);
2831 cairo_close_path(im->cr);
2832 cairo_set_source_rgba(im->cr,
2833 im->graph_col[GRC_FRAME].red,
2834 im->graph_col[GRC_FRAME].green,
2835 im->graph_col[GRC_FRAME].blue,
2836 im->graph_col[GRC_FRAME].alpha);
2838 if (im->gdes[i].dash) {
2839 /* make box borders in legend dashed if the graph is dashed */
2843 cairo_set_dash(im->cr, dashes, 1, 0.0);
2845 cairo_stroke(im->cr);
2846 cairo_restore(im->cr);
2853 /*****************************************************
2854 * lazy check make sure we rely need to create this graph
2855 *****************************************************/
2862 struct stat imgstat;
2865 return 0; /* no lazy option */
2866 if (strlen(im->graphfile) == 0)
2867 return 0; /* inmemory option */
2868 if (stat(im->graphfile, &imgstat) != 0)
2869 return 0; /* can't stat */
2870 /* one pixel in the existing graph is more then what we would
2872 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2874 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2875 return 0; /* the file does not exist */
2876 switch (im->imgformat) {
2878 size = PngSize(fd, &(im->ximg), &(im->yimg));
2888 int graph_size_location(
2893 /* The actual size of the image to draw is determined from
2894 ** several sources. The size given on the command line is
2895 ** the graph area but we need more as we have to draw labels
2896 ** and other things outside the graph area. If the option
2897 ** --full-size-mode is selected the size defines the total
2898 ** image size and the size available for the graph is
2902 /** +---+-----------------------------------+
2903 ** | y |...............graph title.........|
2904 ** | +---+-------------------------------+
2908 ** | s | x | main graph area |
2913 ** | l | b +-------------------------------+
2914 ** | e | l | x axis labels |
2915 ** +---+---+-------------------------------+
2916 ** |....................legends............|
2917 ** +---------------------------------------+
2919 ** +---------------------------------------+
2922 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2923 0, Xylabel = 0, Xmain = 0, Ymain =
2924 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2926 // no legends and no the shall be plotted it's easy
2927 if (im->extra_flags & ONLY_GRAPH) {
2929 im->ximg = im->xsize;
2930 im->yimg = im->ysize;
2931 im->yorigin = im->ysize;
2936 if(im->watermark[0] != '\0') {
2937 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2940 // calculate the width of the left vertical legend
2941 if (im->ylegend[0] != '\0') {
2942 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2945 // calculate the width of the right vertical legend
2946 if (im->second_axis_legend[0] != '\0') {
2947 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2950 Xvertical2 = Xspacing;
2953 if (im->title[0] != '\0') {
2954 /* The title is placed "inbetween" two text lines so it
2955 ** automatically has some vertical spacing. The horizontal
2956 ** spacing is added here, on each side.
2958 /* if necessary, reduce the font size of the title until it fits the image width */
2959 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2962 // we have no title; get a little clearing from the top
2963 Ytitle = 1.5 * Yspacing;
2967 if (im->draw_x_grid) {
2968 // calculate the height of the horizontal labelling
2969 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2971 if (im->draw_y_grid || im->forceleftspace) {
2972 // calculate the width of the vertical labelling
2974 gfx_get_text_width(im, 0,
2975 im->text_prop[TEXT_PROP_AXIS].font_desc,
2976 im->tabwidth, "0") * im->unitslength;
2980 // add some space to the labelling
2981 Xylabel += Xspacing;
2983 /* If the legend is printed besides the graph the width has to be
2984 ** calculated first. Placing the legend north or south of the
2985 ** graph requires the width calculation first, so the legend is
2986 ** skipped for the moment.
2988 im->legendheight = 0;
2989 im->legendwidth = 0;
2990 if (!(im->extra_flags & NOLEGEND)) {
2991 if(im->legendposition == WEST || im->legendposition == EAST){
2992 if (leg_place(im, 1) == -1){
2998 if (im->extra_flags & FULL_SIZE_MODE) {
3000 /* The actual size of the image to draw has been determined by the user.
3001 ** The graph area is the space remaining after accounting for the legend,
3002 ** the watermark, the axis labels, and the title.
3004 im->ximg = im->xsize;
3005 im->yimg = im->ysize;
3009 /* Now calculate the total size. Insert some spacing where
3010 desired. im->xorigin and im->yorigin need to correspond
3011 with the lower left corner of the main graph area or, if
3012 this one is not set, the imaginary box surrounding the
3014 /* Initial size calculation for the main graph area */
3016 Xmain -= Xylabel;// + Xspacing;
3017 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3018 Xmain -= im->legendwidth;// + Xspacing;
3020 if (im->second_axis_scale != 0){
3023 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3027 Xmain -= Xvertical + Xvertical2;
3029 /* limit the remaining space to 0 */
3035 /* Putting the legend north or south, the height can now be calculated */
3036 if (!(im->extra_flags & NOLEGEND)) {
3037 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3038 im->legendwidth = im->ximg;
3039 if (leg_place(im, 0) == -1){
3045 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3046 Ymain -= Yxlabel + im->legendheight;
3052 /* reserve space for the title *or* some padding above the graph */
3055 /* reserve space for padding below the graph */
3056 if (im->extra_flags & NOLEGEND) {
3060 if (im->watermark[0] != '\0') {
3061 Ymain -= Ywatermark;
3063 /* limit the remaining height to 0 */
3068 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3070 /* The actual size of the image to draw is determined from
3071 ** several sources. The size given on the command line is
3072 ** the graph area but we need more as we have to draw labels
3073 ** and other things outside the graph area.
3077 Xmain = im->xsize; // + Xspacing;
3081 im->ximg = Xmain + Xylabel;
3082 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3083 im->ximg += Xspacing;
3086 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3087 im->ximg += im->legendwidth;// + Xspacing;
3089 if (im->second_axis_scale != 0){
3090 im->ximg += Xylabel;
3093 im->ximg += Xvertical + Xvertical2;
3095 if (!(im->extra_flags & NOLEGEND)) {
3096 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3097 im->legendwidth = im->ximg;
3098 if (leg_place(im, 0) == -1){
3104 im->yimg = Ymain + Yxlabel;
3105 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3106 im->yimg += im->legendheight;
3109 /* reserve space for the title *or* some padding above the graph */
3113 im->yimg += 1.5 * Yspacing;
3115 /* reserve space for padding below the graph */
3116 if (im->extra_flags & NOLEGEND) {
3117 im->yimg += Yspacing;
3120 if (im->watermark[0] != '\0') {
3121 im->yimg += Ywatermark;
3126 /* In case of putting the legend in west or east position the first
3127 ** legend calculation might lead to wrong positions if some items
3128 ** are not aligned on the left hand side (e.g. centered) as the
3129 ** legendwidth wight have been increased after the item was placed.
3130 ** In this case the positions have to be recalculated.
3132 if (!(im->extra_flags & NOLEGEND)) {
3133 if(im->legendposition == WEST || im->legendposition == EAST){
3134 if (leg_place(im, 0) == -1){
3140 /* After calculating all dimensions
3141 ** it is now possible to calculate
3144 switch(im->legendposition){
3146 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3147 im->yOriginTitle = 0;
3149 im->xOriginLegend = 0;
3150 im->yOriginLegend = Ytitle;
3152 im->xOriginLegendY = 0;
3153 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3155 im->xorigin = Xvertical + Xylabel;
3156 im->yorigin = Ytitle + im->legendheight + Ymain;
3158 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3159 if (im->second_axis_scale != 0){
3160 im->xOriginLegendY2 += Xylabel;
3162 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3167 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3168 im->yOriginTitle = 0;
3170 im->xOriginLegend = 0;
3171 im->yOriginLegend = Ytitle;
3173 im->xOriginLegendY = im->legendwidth;
3174 im->yOriginLegendY = Ytitle + (Ymain / 2);
3176 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3177 im->yorigin = Ytitle + Ymain;
3179 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3180 if (im->second_axis_scale != 0){
3181 im->xOriginLegendY2 += Xylabel;
3183 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3188 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3189 im->yOriginTitle = 0;
3191 im->xOriginLegend = 0;
3192 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3194 im->xOriginLegendY = 0;
3195 im->yOriginLegendY = Ytitle + (Ymain / 2);
3197 im->xorigin = Xvertical + Xylabel;
3198 im->yorigin = Ytitle + Ymain;
3200 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3201 if (im->second_axis_scale != 0){
3202 im->xOriginLegendY2 += Xylabel;
3204 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3209 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3210 im->yOriginTitle = 0;
3212 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3213 if (im->second_axis_scale != 0){
3214 im->xOriginLegend += Xylabel;
3216 im->yOriginLegend = Ytitle;
3218 im->xOriginLegendY = 0;
3219 im->yOriginLegendY = Ytitle + (Ymain / 2);
3221 im->xorigin = Xvertical + Xylabel;
3222 im->yorigin = Ytitle + Ymain;
3224 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3225 if (im->second_axis_scale != 0){
3226 im->xOriginLegendY2 += Xylabel;
3228 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3230 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3231 im->xOriginTitle += Xspacing;
3232 im->xOriginLegend += Xspacing;
3233 im->xOriginLegendY += Xspacing;
3234 im->xorigin += Xspacing;
3235 im->xOriginLegendY2 += Xspacing;
3245 static cairo_status_t cairo_output(
3249 unsigned int length)
3251 image_desc_t *im = (image_desc_t*)closure;
3253 im->rendered_image =
3254 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3255 if (im->rendered_image == NULL)
3256 return CAIRO_STATUS_WRITE_ERROR;
3257 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3258 im->rendered_image_size += length;
3259 return CAIRO_STATUS_SUCCESS;
3262 /* draw that picture thing ... */
3267 int lazy = lazy_check(im);
3268 double areazero = 0.0;
3269 graph_desc_t *lastgdes = NULL;
3272 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3274 /* pull the data from the rrd files ... */
3275 if (data_fetch(im) == -1)
3277 /* evaluate VDEF and CDEF operations ... */
3278 if (data_calc(im) == -1)
3280 /* calculate and PRINT and GPRINT definitions. We have to do it at
3281 * this point because it will affect the length of the legends
3282 * if there are no graph elements (i==0) we stop here ...
3283 * if we are lazy, try to quit ...
3289 /* if we want and can be lazy ... quit now */
3293 /**************************************************************
3294 *** Calculating sizes and locations became a bit confusing ***
3295 *** so I moved this into a separate function. ***
3296 **************************************************************/
3297 if (graph_size_location(im, i) == -1)
3300 info.u_cnt = im->xorigin;
3301 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3302 info.u_cnt = im->yorigin - im->ysize;
3303 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3304 info.u_cnt = im->xsize;
3305 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3306 info.u_cnt = im->ysize;
3307 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3308 info.u_cnt = im->ximg;
3309 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3310 info.u_cnt = im->yimg;
3311 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3312 info.u_cnt = im->start;
3313 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3314 info.u_cnt = im->end;
3315 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3317 /* if we want and can be lazy ... quit now */
3321 /* get actual drawing data and find min and max values */
3322 if (data_proc(im) == -1)
3324 if (!im->logarithmic) {
3328 /* identify si magnitude Kilo, Mega Giga ? */
3329 if (!im->rigid && !im->logarithmic)
3330 expand_range(im); /* make sure the upper and lower limit are
3333 info.u_val = im->minval;
3334 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3335 info.u_val = im->maxval;
3336 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3339 if (!calc_horizontal_grid(im))
3344 apply_gridfit(im); */
3345 /* the actual graph is created by going through the individual
3346 graph elements and then drawing them */
3347 cairo_surface_destroy(im->surface);
3348 switch (im->imgformat) {
3351 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3352 im->ximg * im->zoom,
3353 im->yimg * im->zoom);
3357 im->surface = strlen(im->graphfile)
3358 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3359 im->yimg * im->zoom)
3360 : cairo_pdf_surface_create_for_stream
3361 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3365 im->surface = strlen(im->graphfile)
3367 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3368 im->yimg * im->zoom)
3369 : cairo_ps_surface_create_for_stream
3370 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3374 im->surface = strlen(im->graphfile)
3376 cairo_svg_surface_create(im->
3378 im->ximg * im->zoom, im->yimg * im->zoom)
3379 : cairo_svg_surface_create_for_stream
3380 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3381 cairo_svg_surface_restrict_to_version
3382 (im->surface, CAIRO_SVG_VERSION_1_1);
3385 cairo_destroy(im->cr);
3386 im->cr = cairo_create(im->surface);
3387 cairo_set_antialias(im->cr, im->graph_antialias);
3388 cairo_scale(im->cr, im->zoom, im->zoom);
3389 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3390 gfx_new_area(im, 0, 0, 0, im->yimg,
3391 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3392 gfx_add_point(im, im->ximg, 0);
3394 gfx_new_area(im, im->xorigin,
3397 im->xsize, im->yorigin,
3400 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3401 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3403 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3404 im->xsize, im->ysize + 2.0);
3406 if (im->minval > 0.0)
3407 areazero = im->minval;
3408 if (im->maxval < 0.0)
3409 areazero = im->maxval;
3410 for (i = 0; i < im->gdes_c; i++) {
3411 switch (im->gdes[i].gf) {
3425 for (ii = 0; ii < im->xsize; ii++) {
3426 if (!isnan(im->gdes[i].p_data[ii])
3427 && im->gdes[i].p_data[ii] != 0.0) {
3428 if (im->gdes[i].yrule > 0) {
3435 im->ysize, 1.0, im->gdes[i].col);
3436 } else if (im->gdes[i].yrule < 0) {
3439 im->yorigin - im->ysize - 1.0,
3441 im->yorigin - im->ysize -
3444 im->ysize, 1.0, im->gdes[i].col);
3452 rrd_value_t diffval = im->maxval - im->minval;
3453 rrd_value_t maxlimit = im->maxval + 9 * diffval;
3454 rrd_value_t minlimit = im->minval - 9 * diffval;
3455 for (ii = 0; ii < im->xsize; ii++) {
3456 /* fix data points at oo and -oo */
3457 if (isinf(im->gdes[i].p_data[ii])) {
3458 if (im->gdes[i].p_data[ii] > 0) {
3459 im->gdes[i].p_data[ii] = im->maxval;
3461 im->gdes[i].p_data[ii] = im->minval;
3464 /* some versions of cairo go unstable when trying
3465 to draw way out of the canvas ... lets not even try */
3466 if (im->gdes[i].p_data[ii] > maxlimit) {
3467 im->gdes[i].p_data[ii] = maxlimit;
3469 if (im->gdes[i].p_data[ii] < minlimit) {
3470 im->gdes[i].p_data[ii] = minlimit;
3474 /* *******************************************************
3479 -------|--t-1--t--------------------------------
3481 if we know the value at time t was a then
3482 we draw a square from t-1 to t with the value a.
3484 ********************************************************* */
3485 if (im->gdes[i].col.alpha != 0.0) {
3486 /* GF_LINE and friend */
3487 if (im->gdes[i].gf == GF_LINE) {
3488 double last_y = 0.0;
3492 cairo_new_path(im->cr);
3493 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3494 if (im->gdes[i].dash) {
3495 cairo_set_dash(im->cr,
3496 im->gdes[i].p_dashes,
3497 im->gdes[i].ndash, im->gdes[i].offset);
3500 for (ii = 1; ii < im->xsize; ii++) {
3501 if (isnan(im->gdes[i].p_data[ii])
3502 || (im->slopemode == 1
3503 && isnan(im->gdes[i].p_data[ii - 1]))) {
3508 last_y = ytr(im, im->gdes[i].p_data[ii]);
3509 if (im->slopemode == 0) {
3510 double x = ii - 1 + im->xorigin;
3513 gfx_line_fit(im, &x, &y);
3514 cairo_move_to(im->cr, x, y);
3515 x = ii + im->xorigin;
3517 gfx_line_fit(im, &x, &y);
3518 cairo_line_to(im->cr, x, y);
3520 double x = ii - 1 + im->xorigin;
3522 ytr(im, im->gdes[i].p_data[ii - 1]);
3523 gfx_line_fit(im, &x, &y);
3524 cairo_move_to(im->cr, x, y);
3525 x = ii + im->xorigin;
3527 gfx_line_fit(im, &x, &y);
3528 cairo_line_to(im->cr, x, y);
3532 double x1 = ii + im->xorigin;
3533 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3535 if (im->slopemode == 0
3536 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3537 double x = ii - 1 + im->xorigin;
3540 gfx_line_fit(im, &x, &y);
3541 cairo_line_to(im->cr, x, y);
3544 gfx_line_fit(im, &x1, &y1);
3545 cairo_line_to(im->cr, x1, y1);
3548 cairo_set_source_rgba(im->cr,
3554 col.blue, im->gdes[i].col.alpha);
3555 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3556 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3557 cairo_stroke(im->cr);
3558 cairo_restore(im->cr);
3564 (double *) malloc(sizeof(double) * im->xsize * 2);
3566 (double *) malloc(sizeof(double) * im->xsize * 2);
3568 (double *) malloc(sizeof(double) * im->xsize * 2);
3570 (double *) malloc(sizeof(double) * im->xsize * 2);
3573 for (ii = 0; ii <= im->xsize; ii++) {
3576 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3582 AlmostEqual2sComplement(foreY
3586 AlmostEqual2sComplement(foreY
3592 if (im->gdes[i].gf != GF_GRAD) {
3597 foreY[cntI], im->gdes[i].col);
3599 lastx = foreX[cntI];
3600 lasty = foreY[cntI];
3602 while (cntI < idxI) {
3607 AlmostEqual2sComplement(foreY
3611 AlmostEqual2sComplement(foreY
3618 if (im->gdes[i].gf != GF_GRAD) {
3619 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3621 gfx_add_rect_fadey(im,
3623 foreX[cntI], foreY[cntI], lasty,
3626 im->gdes[i].gradheight
3628 lastx = foreX[cntI];
3629 lasty = foreY[cntI];
3632 if (im->gdes[i].gf != GF_GRAD) {
3633 gfx_add_point(im, backX[idxI], backY[idxI]);
3635 gfx_add_rect_fadey(im,
3637 backX[idxI], backY[idxI], lasty,
3640 im->gdes[i].gradheight);
3641 lastx = backX[idxI];
3642 lasty = backY[idxI];
3649 AlmostEqual2sComplement(backY
3653 AlmostEqual2sComplement(backY
3660 if (im->gdes[i].gf != GF_GRAD) {
3661 gfx_add_point(im, backX[idxI], backY[idxI]);
3663 gfx_add_rect_fadey(im,
3665 backX[idxI], backY[idxI], lasty,
3668 im->gdes[i].gradheight);
3669 lastx = backX[idxI];
3670 lasty = backY[idxI];
3675 if (im->gdes[i].gf != GF_GRAD)
3682 if (ii == im->xsize)
3684 if (im->slopemode == 0 && ii == 0) {
3687 if (isnan(im->gdes[i].p_data[ii])) {
3691 ytop = ytr(im, im->gdes[i].p_data[ii]);
3692 if (lastgdes && im->gdes[i].stack) {
3693 ybase = ytr(im, lastgdes->p_data[ii]);
3695 ybase = ytr(im, areazero);
3697 if (ybase == ytop) {
3703 double extra = ytop;
3708 if (im->slopemode == 0) {
3709 backY[++idxI] = ybase - 0.2;
3710 backX[idxI] = ii + im->xorigin - 1;
3711 foreY[idxI] = ytop + 0.2;
3712 foreX[idxI] = ii + im->xorigin - 1;
3714 backY[++idxI] = ybase - 0.2;
3715 backX[idxI] = ii + im->xorigin;
3716 foreY[idxI] = ytop + 0.2;
3717 foreX[idxI] = ii + im->xorigin;
3719 /* close up any remaining area */
3724 } /* else GF_LINE */
3726 /* if color != 0x0 */
3727 /* make sure we do not run into trouble when stacking on NaN */
3728 for (ii = 0; ii < im->xsize; ii++) {
3729 if (isnan(im->gdes[i].p_data[ii])) {
3730 if (lastgdes && (im->gdes[i].stack)) {
3731 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3733 im->gdes[i].p_data[ii] = areazero;
3737 lastgdes = &(im->gdes[i]);
3739 } /* GF_AREA, GF_LINE, GF_GRAD */
3742 ("STACK should already be turned into LINE or AREA here");
3747 cairo_reset_clip(im->cr);
3749 /* grid_paint also does the text */
3750 if (!(im->extra_flags & ONLY_GRAPH))
3752 if (!(im->extra_flags & ONLY_GRAPH))
3754 /* the RULES are the last thing to paint ... */
3755 for (i = 0; i < im->gdes_c; i++) {
3757 switch (im->gdes[i].gf) {
3759 if (im->gdes[i].yrule >= im->minval
3760 && im->gdes[i].yrule <= im->maxval) {
3762 if (im->gdes[i].dash) {
3763 cairo_set_dash(im->cr,
3764 im->gdes[i].p_dashes,
3765 im->gdes[i].ndash, im->gdes[i].offset);
3767 gfx_line(im, im->xorigin,
3768 ytr(im, im->gdes[i].yrule),
3769 im->xorigin + im->xsize,
3770 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3771 cairo_stroke(im->cr);
3772 cairo_restore(im->cr);
3776 if (im->gdes[i].xrule >= im->start
3777 && im->gdes[i].xrule <= im->end) {
3779 if (im->gdes[i].dash) {
3780 cairo_set_dash(im->cr,
3781 im->gdes[i].p_dashes,
3782 im->gdes[i].ndash, im->gdes[i].offset);
3785 xtr(im, im->gdes[i].xrule),
3786 im->yorigin, xtr(im,
3790 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3791 cairo_stroke(im->cr);
3792 cairo_restore(im->cr);
3801 switch (im->imgformat) {
3804 cairo_status_t status;
3806 status = strlen(im->graphfile) ?
3807 cairo_surface_write_to_png(im->surface, im->graphfile)
3808 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3811 if (status != CAIRO_STATUS_SUCCESS) {
3812 rrd_set_error("Could not save png to '%s'", im->graphfile);
3818 if (strlen(im->graphfile)) {
3819 cairo_show_page(im->cr);
3821 cairo_surface_finish(im->surface);
3830 /*****************************************************
3832 *****************************************************/
3839 if ((im->gdes = (graph_desc_t *)
3840 rrd_realloc(im->gdes, (im->gdes_c)
3841 * sizeof(graph_desc_t))) == NULL) {
3842 rrd_set_error("realloc graph_descs");
3847 im->gdes[im->gdes_c - 1].step = im->step;
3848 im->gdes[im->gdes_c - 1].step_orig = im->step;
3849 im->gdes[im->gdes_c - 1].stack = 0;
3850 im->gdes[im->gdes_c - 1].linewidth = 0;
3851 im->gdes[im->gdes_c - 1].debug = 0;
3852 im->gdes[im->gdes_c - 1].start = im->start;
3853 im->gdes[im->gdes_c - 1].start_orig = im->start;
3854 im->gdes[im->gdes_c - 1].end = im->end;
3855 im->gdes[im->gdes_c - 1].end_orig = im->end;
3856 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3857 im->gdes[im->gdes_c - 1].data = NULL;
3858 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3859 im->gdes[im->gdes_c - 1].data_first = 0;
3860 im->gdes[im->gdes_c - 1].p_data = NULL;
3861 im->gdes[im->gdes_c - 1].rpnp = NULL;
3862 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3863 im->gdes[im->gdes_c - 1].shift = 0.0;
3864 im->gdes[im->gdes_c - 1].dash = 0;
3865 im->gdes[im->gdes_c - 1].ndash = 0;
3866 im->gdes[im->gdes_c - 1].offset = 0;
3867 im->gdes[im->gdes_c - 1].col.red = 0.0;
3868 im->gdes[im->gdes_c - 1].col.green = 0.0;
3869 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3870 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3871 im->gdes[im->gdes_c - 1].col2.red = 0.0;
3872 im->gdes[im->gdes_c - 1].col2.green = 0.0;
3873 im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3874 im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3875 im->gdes[im->gdes_c - 1].gradheight = 50.0;
3876 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3877 im->gdes[im->gdes_c - 1].format[0] = '\0';
3878 im->gdes[im->gdes_c - 1].strftm = 0;
3879 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3880 im->gdes[im->gdes_c - 1].ds = -1;
3881 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3882 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3883 im->gdes[im->gdes_c - 1].yrule = DNAN;
3884 im->gdes[im->gdes_c - 1].xrule = 0;
3885 im->gdes[im->gdes_c - 1].daemon[0] = 0;
3889 /* copies input untill the first unescaped colon is found
3890 or until input ends. backslashes have to be escaped as well */
3892 const char *const input,
3898 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3899 if (input[inp] == '\\'
3900 && input[inp + 1] != '\0'
3901 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3902 output[outp++] = input[++inp];
3904 output[outp++] = input[inp];
3907 output[outp] = '\0';
3911 /* Now just a wrapper around rrd_graph_v */
3923 rrd_info_t *grinfo = NULL;
3926 grinfo = rrd_graph_v(argc, argv);
3932 if (strcmp(walker->key, "image_info") == 0) {
3935 (char**)rrd_realloc((*prdata),
3936 (prlines + 1) * sizeof(char *))) == NULL) {
3937 rrd_set_error("realloc prdata");
3940 /* imginfo goes to position 0 in the prdata array */
3941 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3942 + 2) * sizeof(char));
3943 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3944 (*prdata)[prlines] = NULL;
3946 /* skip anything else */
3947 walker = walker->next;
3955 if (strcmp(walker->key, "image_width") == 0) {
3956 *xsize = walker->value.u_cnt;
3957 } else if (strcmp(walker->key, "image_height") == 0) {
3958 *ysize = walker->value.u_cnt;
3959 } else if (strcmp(walker->key, "value_min") == 0) {
3960 *ymin = walker->value.u_val;
3961 } else if (strcmp(walker->key, "value_max") == 0) {
3962 *ymax = walker->value.u_val;
3963 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3966 (char**)rrd_realloc((*prdata),
3967 (prlines + 1) * sizeof(char *))) == NULL) {
3968 rrd_set_error("realloc prdata");
3971 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3972 + 2) * sizeof(char));
3973 (*prdata)[prlines] = NULL;
3974 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3975 } else if (strcmp(walker->key, "image") == 0) {
3976 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3977 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3978 rrd_set_error("writing image");
3982 /* skip anything else */
3983 walker = walker->next;
3985 rrd_info_free(grinfo);
3990 /* Some surgery done on this function, it became ridiculously big.
3992 ** - initializing now in rrd_graph_init()
3993 ** - options parsing now in rrd_graph_options()
3994 ** - script parsing now in rrd_graph_script()
3996 rrd_info_t *rrd_graph_v(
4003 rrd_graph_init(&im);
4004 /* a dummy surface so that we can measure text sizes for placements */
4005 old_locale = setlocale(LC_NUMERIC, NULL);
4006 setlocale(LC_NUMERIC, "C");
4007 rrd_graph_options(argc, argv, &im);
4008 if (rrd_test_error()) {
4009 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4010 rrd_info_free(im.grinfo);
4015 if (optind >= argc) {
4016 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4017 rrd_info_free(im.grinfo);
4019 rrd_set_error("missing filename");
4023 if (strlen(argv[optind]) >= MAXPATH) {
4024 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4025 rrd_set_error("filename (including path) too long");
4026 rrd_info_free(im.grinfo);
4031 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4032 im.graphfile[MAXPATH - 1] = '\0';
4034 if (strcmp(im.graphfile, "-") == 0) {
4035 im.graphfile[0] = '\0';
4038 rrd_graph_script(argc, argv, &im, 1);
4039 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4041 if (rrd_test_error()) {
4042 rrd_info_free(im.grinfo);
4047 /* Everything is now read and the actual work can start */
4049 if (graph_paint(&im) == -1) {
4050 rrd_info_free(im.grinfo);
4056 /* The image is generated and needs to be output.
4057 ** Also, if needed, print a line with information about the image.
4065 path = strdup(im.graphfile);
4066 filename = basename(path);
4068 sprintf_alloc(im.imginfo,
4071 im.ximg), (long) (im.zoom * im.yimg));
4072 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4076 if (im.rendered_image) {
4079 img.u_blo.size = im.rendered_image_size;
4080 img.u_blo.ptr = im.rendered_image;
4081 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4090 image_desc_t *im,int prop,char *font, double size ){
4092 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4093 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4094 /* if we already got one, drop it first */
4095 pango_font_description_free(im->text_prop[prop].font_desc);
4096 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4099 im->text_prop[prop].size = size;
4101 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4102 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4106 void rrd_graph_init(
4111 char *deffont = getenv("RRD_DEFAULT_FONT");
4112 static PangoFontMap *fontmap = NULL;
4113 PangoContext *context;
4120 im->daemon_addr = NULL;
4121 im->draw_x_grid = 1;
4122 im->draw_y_grid = 1;
4123 im->draw_3d_border = 2;
4124 im->dynamic_labels = 0;
4125 im->extra_flags = 0;
4126 im->font_options = cairo_font_options_create();
4127 im->forceleftspace = 0;
4130 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4131 im->grid_dash_off = 1;
4132 im->grid_dash_on = 1;
4134 im->grinfo = (rrd_info_t *) NULL;
4135 im->grinfo_current = (rrd_info_t *) NULL;
4136 im->imgformat = IF_PNG;
4139 im->legenddirection = TOP_DOWN;
4140 im->legendheight = 0;
4141 im->legendposition = SOUTH;
4142 im->legendwidth = 0;
4143 im->logarithmic = 0;
4149 im->rendered_image_size = 0;
4150 im->rendered_image = NULL;
4154 im->tabwidth = 40.0;
4155 im->title[0] = '\0';
4156 im->unitsexponent = 9999;
4157 im->unitslength = 6;
4158 im->viewfactor = 1.0;
4159 im->watermark[0] = '\0';
4160 im->with_markup = 0;
4162 im->xlab_user.minsec = -1;
4164 im->xOriginLegend = 0;
4165 im->xOriginLegendY = 0;
4166 im->xOriginLegendY2 = 0;
4167 im->xOriginTitle = 0;
4169 im->ygridstep = DNAN;
4171 im->ylegend[0] = '\0';
4172 im->second_axis_scale = 0; /* 0 disables it */
4173 im->second_axis_shift = 0; /* no shift by default */
4174 im->second_axis_legend[0] = '\0';
4175 im->second_axis_format[0] = '\0';
4177 im->yOriginLegend = 0;
4178 im->yOriginLegendY = 0;
4179 im->yOriginLegendY2 = 0;
4180 im->yOriginTitle = 0;
4184 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4185 im->cr = cairo_create(im->surface);
4187 for (i = 0; i < DIM(text_prop); i++) {
4188 im->text_prop[i].size = -1;
4189 im->text_prop[i].font_desc = NULL;
4190 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4193 if (fontmap == NULL){
4194 fontmap = pango_cairo_font_map_get_default();
4197 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4199 pango_cairo_context_set_resolution(context, 100);
4201 pango_cairo_update_context(im->cr,context);
4203 im->layout = pango_layout_new(context);
4204 g_object_unref (context);
4206 // im->layout = pango_cairo_create_layout(im->cr);
4209 cairo_font_options_set_hint_style
4210 (im->font_options, CAIRO_HINT_STYLE_FULL);
4211 cairo_font_options_set_hint_metrics
4212 (im->font_options, CAIRO_HINT_METRICS_ON);
4213 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4217 for (i = 0; i < DIM(graph_col); i++)
4218 im->graph_col[i] = graph_col[i];
4224 void rrd_graph_options(
4231 char *parsetime_error = NULL;
4232 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4233 time_t start_tmp = 0, end_tmp = 0;
4235 rrd_time_value_t start_tv, end_tv;
4236 long unsigned int color;
4238 /* defines for long options without a short equivalent. should be bytes,
4239 and may not collide with (the ASCII value of) short options */
4240 #define LONGOPT_UNITS_SI 255
4243 struct option long_options[] = {
4244 { "alt-autoscale", no_argument, 0, 'A'},
4245 { "imgformat", required_argument, 0, 'a'},
4246 { "font-smoothing-threshold", required_argument, 0, 'B'},
4247 { "base", required_argument, 0, 'b'},
4248 { "color", required_argument, 0, 'c'},
4249 { "full-size-mode", no_argument, 0, 'D'},
4250 { "daemon", required_argument, 0, 'd'},
4251 { "slope-mode", no_argument, 0, 'E'},
4252 { "end", required_argument, 0, 'e'},
4253 { "force-rules-legend", no_argument, 0, 'F'},
4254 { "imginfo", required_argument, 0, 'f'},
4255 { "graph-render-mode", required_argument, 0, 'G'},
4256 { "no-legend", no_argument, 0, 'g'},
4257 { "height", required_argument, 0, 'h'},
4258 { "no-minor", no_argument, 0, 'I'},
4259 { "interlaced", no_argument, 0, 'i'},
4260 { "alt-autoscale-min", no_argument, 0, 'J'},
4261 { "only-graph", no_argument, 0, 'j'},
4262 { "units-length", required_argument, 0, 'L'},
4263 { "lower-limit", required_argument, 0, 'l'},
4264 { "alt-autoscale-max", no_argument, 0, 'M'},
4265 { "zoom", required_argument, 0, 'm'},
4266 { "no-gridfit", no_argument, 0, 'N'},
4267 { "font", required_argument, 0, 'n'},
4268 { "logarithmic", no_argument, 0, 'o'},
4269 { "pango-markup", no_argument, 0, 'P'},
4270 { "font-render-mode", required_argument, 0, 'R'},
4271 { "rigid", no_argument, 0, 'r'},
4272 { "step", required_argument, 0, 'S'},
4273 { "start", required_argument, 0, 's'},
4274 { "tabwidth", required_argument, 0, 'T'},
4275 { "title", required_argument, 0, 't'},
4276 { "upper-limit", required_argument, 0, 'u'},
4277 { "vertical-label", required_argument, 0, 'v'},
4278 { "watermark", required_argument, 0, 'W'},
4279 { "width", required_argument, 0, 'w'},
4280 { "units-exponent", required_argument, 0, 'X'},
4281 { "x-grid", required_argument, 0, 'x'},
4282 { "alt-y-grid", no_argument, 0, 'Y'},
4283 { "y-grid", required_argument, 0, 'y'},
4284 { "lazy", no_argument, 0, 'z'},
4285 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4286 { "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 */
4287 { "disable-rrdtool-tag",no_argument, 0, 1001},
4288 { "right-axis", required_argument, 0, 1002},
4289 { "right-axis-label", required_argument, 0, 1003},
4290 { "right-axis-format", required_argument, 0, 1004},
4291 { "legend-position", required_argument, 0, 1005},
4292 { "legend-direction", required_argument, 0, 1006},
4293 { "border", required_argument, 0, 1007},
4294 { "grid-dash", required_argument, 0, 1008},
4295 { "dynamic-labels", no_argument, 0, 1009},
4301 opterr = 0; /* initialize getopt */
4302 rrd_parsetime("end-24h", &start_tv);
4303 rrd_parsetime("now", &end_tv);
4305 int option_index = 0;
4307 int col_start, col_end;
4309 opt = getopt_long(argc, argv,
4310 "Aa:B:b:c:Dd:Ee:Ff:G:gh:IiJjL:l:Mm:Nn:oPR:rS:s:T:t:u:v:W:w:X:x:Yy:z",
4311 long_options, &option_index);
4316 im->extra_flags |= NOMINOR;
4319 im->extra_flags |= ALTYGRID;
4322 im->extra_flags |= ALTAUTOSCALE;
4325 im->extra_flags |= ALTAUTOSCALE_MIN;
4328 im->extra_flags |= ALTAUTOSCALE_MAX;
4331 im->extra_flags |= ONLY_GRAPH;
4334 im->extra_flags |= NOLEGEND;
4337 if (strcmp(optarg, "north") == 0) {
4338 im->legendposition = NORTH;
4339 } else if (strcmp(optarg, "west") == 0) {
4340 im->legendposition = WEST;
4341 } else if (strcmp(optarg, "south") == 0) {
4342 im->legendposition = SOUTH;
4343 } else if (strcmp(optarg, "east") == 0) {
4344 im->legendposition = EAST;
4346 rrd_set_error("unknown legend-position '%s'", optarg);
4351 if (strcmp(optarg, "topdown") == 0) {
4352 im->legenddirection = TOP_DOWN;
4353 } else if (strcmp(optarg, "bottomup") == 0) {
4354 im->legenddirection = BOTTOM_UP;
4356 rrd_set_error("unknown legend-position '%s'", optarg);
4361 im->extra_flags |= FORCE_RULES_LEGEND;
4364 im->extra_flags |= NO_RRDTOOL_TAG;
4366 case LONGOPT_UNITS_SI:
4367 if (im->extra_flags & FORCE_UNITS) {
4368 rrd_set_error("--units can only be used once!");
4371 if (strcmp(optarg, "si") == 0)
4372 im->extra_flags |= FORCE_UNITS_SI;
4374 rrd_set_error("invalid argument for --units: %s", optarg);
4379 im->unitsexponent = atoi(optarg);
4382 im->unitslength = atoi(optarg);
4383 im->forceleftspace = 1;
4386 im->tabwidth = atof(optarg);
4389 im->step = atoi(optarg);
4395 im->with_markup = 1;
4398 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4399 rrd_set_error("start time: %s", parsetime_error);
4404 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4405 rrd_set_error("end time: %s", parsetime_error);
4410 if (strcmp(optarg, "none") == 0) {
4411 im->draw_x_grid = 0;
4415 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4417 &im->xlab_user.gridst,
4419 &im->xlab_user.mgridst,
4421 &im->xlab_user.labst,
4422 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4423 strncpy(im->xlab_form, optarg + stroff,
4424 sizeof(im->xlab_form) - 1);
4425 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4427 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4428 rrd_set_error("unknown keyword %s", scan_gtm);
4431 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4433 rrd_set_error("unknown keyword %s", scan_mtm);
4436 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4437 rrd_set_error("unknown keyword %s", scan_ltm);
4440 im->xlab_user.minsec = 1;
4441 im->xlab_user.stst = im->xlab_form;
4443 rrd_set_error("invalid x-grid format");
4449 if (strcmp(optarg, "none") == 0) {
4450 im->draw_y_grid = 0;
4453 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4454 if (im->ygridstep <= 0) {
4455 rrd_set_error("grid step must be > 0");
4457 } else if (im->ylabfact < 1) {
4458 rrd_set_error("label factor must be > 0");
4462 rrd_set_error("invalid y-grid format");
4467 im->draw_3d_border = atoi(optarg);
4469 case 1008: /* grid-dash */
4473 &im->grid_dash_off) != 2) {
4474 rrd_set_error("expected grid-dash format float:float");
4478 case 1009: /* enable dynamic labels */
4479 im->dynamic_labels = 1;
4481 case 1002: /* right y axis */
4485 &im->second_axis_scale,
4486 &im->second_axis_shift) == 2) {
4487 if(im->second_axis_scale==0){
4488 rrd_set_error("the second_axis_scale must not be 0");
4492 rrd_set_error("invalid right-axis format expected scale:shift");
4497 strncpy(im->second_axis_legend,optarg,150);
4498 im->second_axis_legend[150]='\0';
4501 if (bad_format(optarg)){
4502 rrd_set_error("use either %le or %lf formats");
4505 strncpy(im->second_axis_format,optarg,150);
4506 im->second_axis_format[150]='\0';
4509 strncpy(im->ylegend, optarg, 150);
4510 im->ylegend[150] = '\0';
4513 im->maxval = atof(optarg);
4516 im->minval = atof(optarg);
4519 im->base = atol(optarg);
4520 if (im->base != 1024 && im->base != 1000) {
4522 ("the only sensible value for base apart from 1000 is 1024");
4527 long_tmp = atol(optarg);
4528 if (long_tmp < 10) {
4529 rrd_set_error("width below 10 pixels");
4532 im->xsize = long_tmp;
4535 long_tmp = atol(optarg);
4536 if (long_tmp < 10) {
4537 rrd_set_error("height below 10 pixels");
4540 im->ysize = long_tmp;
4543 im->extra_flags |= FULL_SIZE_MODE;
4546 /* interlaced png not supported at the moment */
4552 im->imginfo = optarg;
4556 (im->imgformat = if_conv(optarg)) == -1) {
4557 rrd_set_error("unsupported graphics format '%s'", optarg);
4568 im->logarithmic = 1;
4572 "%10[A-Z]#%n%8lx%n",
4573 col_nam, &col_start, &color, &col_end) == 2) {
4575 int col_len = col_end - col_start;
4580 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4588 (((color & 0xF000) *
4589 0x11000) | ((color & 0x0F00) *
4590 0x01100) | ((color &
4593 ((color & 0x000F) * 0x00011)
4597 color = (color << 8) + 0xff /* shift left by 8 */ ;
4602 rrd_set_error("the color format is #RRGGBB[AA]");
4605 if ((ci = grc_conv(col_nam)) != -1) {
4606 im->graph_col[ci] = gfx_hex_to_col(color);
4608 rrd_set_error("invalid color name '%s'", col_nam);
4612 rrd_set_error("invalid color def format");
4621 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4622 int sindex, propidx;
4624 if ((sindex = text_prop_conv(prop)) != -1) {
4625 for (propidx = sindex;
4626 propidx < TEXT_PROP_LAST; propidx++) {
4628 rrd_set_font_desc(im,propidx,NULL,size);
4630 if ((int) strlen(optarg) > end+2) {
4631 if (optarg[end] == ':') {
4632 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4635 ("expected : after font size in '%s'",
4640 /* only run the for loop for DEFAULT (0) for
4641 all others, we break here. woodo programming */
4642 if (propidx == sindex && sindex != 0)
4646 rrd_set_error("invalid fonttag '%s'", prop);
4650 rrd_set_error("invalid text property format");
4656 im->zoom = atof(optarg);
4657 if (im->zoom <= 0.0) {
4658 rrd_set_error("zoom factor must be > 0");
4663 strncpy(im->title, optarg, 150);
4664 im->title[150] = '\0';
4667 if (strcmp(optarg, "normal") == 0) {
4668 cairo_font_options_set_antialias
4669 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4670 cairo_font_options_set_hint_style
4671 (im->font_options, CAIRO_HINT_STYLE_FULL);
4672 } else if (strcmp(optarg, "light") == 0) {
4673 cairo_font_options_set_antialias
4674 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4675 cairo_font_options_set_hint_style
4676 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4677 } else if (strcmp(optarg, "mono") == 0) {
4678 cairo_font_options_set_antialias
4679 (im->font_options, CAIRO_ANTIALIAS_NONE);
4680 cairo_font_options_set_hint_style
4681 (im->font_options, CAIRO_HINT_STYLE_FULL);
4683 rrd_set_error("unknown font-render-mode '%s'", optarg);
4688 if (strcmp(optarg, "normal") == 0)
4689 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4690 else if (strcmp(optarg, "mono") == 0)
4691 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4693 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4698 /* not supported curently */
4701 strncpy(im->watermark, optarg, 100);
4702 im->watermark[99] = '\0';
4706 if (im->daemon_addr != NULL)
4708 rrd_set_error ("You cannot specify --daemon "
4713 im->daemon_addr = strdup(optarg);
4714 if (im->daemon_addr == NULL)
4716 rrd_set_error("strdup failed");
4724 rrd_set_error("unknown option '%c'", optopt);
4726 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4731 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4732 pango_layout_context_changed(im->layout);
4736 if (im->logarithmic && im->minval <= 0) {
4738 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4742 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4743 /* error string is set in rrd_parsetime.c */
4747 if (start_tmp < 3600 * 24 * 365 * 10) {
4749 ("the first entry to fetch should be after 1980 (%ld)",
4754 if (end_tmp < start_tmp) {
4756 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4760 im->start = start_tmp;
4762 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4765 int rrd_graph_color(
4773 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4775 color = strstr(var, "#");
4776 if (color == NULL) {
4777 if (optional == 0) {
4778 rrd_set_error("Found no color in %s", err);
4785 long unsigned int col;
4787 rest = strstr(color, ":");
4794 sscanf(color, "#%6lx%n", &col, &n);
4795 col = (col << 8) + 0xff /* shift left by 8 */ ;
4797 rrd_set_error("Color problem in %s", err);
4800 sscanf(color, "#%8lx%n", &col, &n);
4804 rrd_set_error("Color problem in %s", err);
4806 if (rrd_test_error())
4808 gdp->col = gfx_hex_to_col(col);
4821 while (*ptr != '\0')
4822 if (*ptr++ == '%') {
4824 /* line cannot end with percent char */
4827 /* '%s', '%S' and '%%' are allowed */
4828 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4830 /* %c is allowed (but use only with vdef!) */
4831 else if (*ptr == 'c') {
4836 /* or else '% 6.2lf' and such are allowed */
4838 /* optional padding character */
4839 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4841 /* This should take care of 'm.n' with all three optional */
4842 while (*ptr >= '0' && *ptr <= '9')
4846 while (*ptr >= '0' && *ptr <= '9')
4848 /* Either 'le', 'lf' or 'lg' must follow here */
4851 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4866 const char *const str)
4868 /* A VDEF currently is either "func" or "param,func"
4869 * so the parsing is rather simple. Change if needed.
4876 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4877 if (n == (int) strlen(str)) { /* matched */
4881 sscanf(str, "%29[A-Z]%n", func, &n);
4882 if (n == (int) strlen(str)) { /* matched */
4886 ("Unknown function string '%s' in VDEF '%s'",
4891 if (!strcmp("PERCENT", func))
4892 gdes->vf.op = VDEF_PERCENT;
4893 else if (!strcmp("PERCENTNAN", func))
4894 gdes->vf.op = VDEF_PERCENTNAN;
4895 else if (!strcmp("MAXIMUM", func))
4896 gdes->vf.op = VDEF_MAXIMUM;
4897 else if (!strcmp("AVERAGE", func))
4898 gdes->vf.op = VDEF_AVERAGE;
4899 else if (!strcmp("STDEV", func))
4900 gdes->vf.op = VDEF_STDEV;
4901 else if (!strcmp("MINIMUM", func))
4902 gdes->vf.op = VDEF_MINIMUM;
4903 else if (!strcmp("TOTAL", func))
4904 gdes->vf.op = VDEF_TOTAL;
4905 else if (!strcmp("FIRST", func))
4906 gdes->vf.op = VDEF_FIRST;
4907 else if (!strcmp("LAST", func))
4908 gdes->vf.op = VDEF_LAST;
4909 else if (!strcmp("LSLSLOPE", func))
4910 gdes->vf.op = VDEF_LSLSLOPE;
4911 else if (!strcmp("LSLINT", func))
4912 gdes->vf.op = VDEF_LSLINT;
4913 else if (!strcmp("LSLCORREL", func))
4914 gdes->vf.op = VDEF_LSLCORREL;
4917 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4920 switch (gdes->vf.op) {
4922 case VDEF_PERCENTNAN:
4923 if (isnan(param)) { /* no parameter given */
4925 ("Function '%s' needs parameter in VDEF '%s'\n",
4929 if (param >= 0.0 && param <= 100.0) {
4930 gdes->vf.param = param;
4931 gdes->vf.val = DNAN; /* undefined */
4932 gdes->vf.when = 0; /* undefined */
4936 ("Parameter '%f' out of range in VDEF '%s'\n",
4937 param, gdes->vname);
4950 case VDEF_LSLCORREL:
4952 gdes->vf.param = DNAN;
4953 gdes->vf.val = DNAN;
4958 ("Function '%s' needs no parameter in VDEF '%s'\n",
4972 graph_desc_t *src, *dst;
4976 dst = &im->gdes[gdi];
4977 src = &im->gdes[dst->vidx];
4978 data = src->data + src->ds;
4980 steps = (src->end - src->start) / src->step;
4983 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4984 src->start, src->end, steps);
4986 switch (dst->vf.op) {
4990 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4991 rrd_set_error("malloc VDEV_PERCENT");
4994 for (step = 0; step < steps; step++) {
4995 array[step] = data[step * src->ds_cnt];
4997 qsort(array, step, sizeof(double), vdef_percent_compar);
4998 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4999 dst->vf.val = array[field];
5000 dst->vf.when = 0; /* no time component */
5004 for (step = 0; step < steps; step++)
5005 printf("DEBUG: %3li:%10.2f %c\n",
5006 step, array[step], step == field ? '*' : ' ');
5010 case VDEF_PERCENTNAN:{
5013 /* count number of "valid" values */
5015 for (step = 0; step < steps; step++) {
5016 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5018 /* and allocate it */
5019 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5020 rrd_set_error("malloc VDEV_PERCENT");
5023 /* and fill it in */
5025 for (step = 0; step < steps; step++) {
5026 if (!isnan(data[step * src->ds_cnt])) {
5027 array[field] = data[step * src->ds_cnt];
5031 qsort(array, nancount, sizeof(double), vdef_percent_compar);
5032 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5033 dst->vf.val = array[field];
5034 dst->vf.when = 0; /* no time component */
5041 while (step != steps && isnan(data[step * src->ds_cnt]))
5043 if (step == steps) {
5048 dst->vf.val = data[step * src->ds_cnt];
5049 dst->vf.when = src->start + (step + 1) * src->step;
5052 while (step != steps) {
5053 if (finite(data[step * src->ds_cnt])) {
5054 if (data[step * src->ds_cnt] > dst->vf.val) {
5055 dst->vf.val = data[step * src->ds_cnt];
5056 dst->vf.when = src->start + (step + 1) * src->step;
5068 double average = 0.0;
5070 for (step = 0; step < steps; step++) {
5071 if (finite(data[step * src->ds_cnt])) {
5072 sum += data[step * src->ds_cnt];
5077 if (dst->vf.op == VDEF_TOTAL) {
5078 dst->vf.val = sum * src->step;
5079 dst->vf.when = 0; /* no time component */
5081 } else if (dst->vf.op == VDEF_AVERAGE) {
5082 dst->vf.val = sum / cnt;
5083 dst->vf.when = 0; /* no time component */
5086 average = sum / cnt;
5088 for (step = 0; step < steps; step++) {
5089 if (finite(data[step * src->ds_cnt])) {
5090 sum += pow((data[step * src->ds_cnt] - average), 2.0);
5093 dst->vf.val = pow(sum / cnt, 0.5);
5094 dst->vf.when = 0; /* no time component */
5106 while (step != steps && isnan(data[step * src->ds_cnt]))
5108 if (step == steps) {
5113 dst->vf.val = data[step * src->ds_cnt];
5114 dst->vf.when = src->start + (step + 1) * src->step;
5117 while (step != steps) {
5118 if (finite(data[step * src->ds_cnt])) {
5119 if (data[step * src->ds_cnt] < dst->vf.val) {
5120 dst->vf.val = data[step * src->ds_cnt];
5121 dst->vf.when = src->start + (step + 1) * src->step;
5129 /* The time value returned here is one step before the
5130 * actual time value. This is the start of the first
5134 while (step != steps && isnan(data[step * src->ds_cnt]))
5136 if (step == steps) { /* all entries were NaN */
5141 dst->vf.val = data[step * src->ds_cnt];
5142 dst->vf.when = src->start + step * src->step;
5147 /* The time value returned here is the
5148 * actual time value. This is the end of the last
5152 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5154 if (step < 0) { /* all entries were NaN */
5159 dst->vf.val = data[step * src->ds_cnt];
5160 dst->vf.when = src->start + (step + 1) * src->step;
5166 case VDEF_LSLCORREL:{
5167 /* Bestfit line by linear least squares method */
5170 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5177 for (step = 0; step < steps; step++) {
5178 if (finite(data[step * src->ds_cnt])) {
5181 SUMxx += step * step;
5182 SUMxy += step * data[step * src->ds_cnt];
5183 SUMy += data[step * src->ds_cnt];
5184 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5188 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5189 y_intercept = (SUMy - slope * SUMx) / cnt;
5192 (SUMx * SUMy) / cnt) /
5194 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5196 if (dst->vf.op == VDEF_LSLSLOPE) {
5197 dst->vf.val = slope;
5200 } else if (dst->vf.op == VDEF_LSLINT) {
5201 dst->vf.val = y_intercept;
5204 } else if (dst->vf.op == VDEF_LSLCORREL) {
5205 dst->vf.val = correl;
5220 /* NaN < -INF < finite_values < INF */
5221 int vdef_percent_compar(
5227 /* Equality is not returned; this doesn't hurt except
5228 * (maybe) for a little performance.
5231 /* First catch NaN values. They are smallest */
5232 if (isnan(*(double *) a))
5234 if (isnan(*(double *) b))
5236 /* NaN doesn't reach this part so INF and -INF are extremes.
5237 * The sign from isinf() is compatible with the sign we return
5239 if (isinf(*(double *) a))
5240 return isinf(*(double *) a);
5241 if (isinf(*(double *) b))
5242 return isinf(*(double *) b);
5243 /* If we reach this, both values must be finite */
5244 if (*(double *) a < *(double *) b)
5253 rrd_info_type_t type,
5254 rrd_infoval_t value)
5256 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5257 if (im->grinfo == NULL) {
5258 im->grinfo = im->grinfo_current;
5269 /* Handling based on
5270 - ANSI C99 Specifications http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5271 - Single UNIX Specification version 2 http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
5272 - POSIX:2001/Single UNIX Specification version 3 http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5273 - POSIX:2008 Specifications http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5274 Specifications tells
5275 "If a conversion specifier is not one of the above, the behavior is undefined."
5278 "A conversion specifier consists of a % character, possibly followed by an E or O modifier character (described below), followed by a character that determines the behavior of the conversion specifier.
5281 "A conversion specification consists of a '%' character, possibly followed by an E or O modifier, and a terminating conversion specifier character that determines the conversion specification's behavior."
5283 POSIX:2008 introduce more complexe behavior that are not handled here.
5285 According to this, this code will replace:
5286 - % followed by @ by a %@
5287 - % followed by by a %SPACE
5288 - % followed by . by a %.
5289 - % followed by % by a %
5290 - % followed by t by a TAB
5291 - % followed by E then anything by '-'
5292 - % followed by O then anything by '-'
5293 - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5297 for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5298 if (format[j] == '%') {
5299 if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5301 j+=2; /* We skip next 2 following char */
5302 } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5303 (format[j+1] == 'g') || (format[j+1] == 'H') ||
5304 (format[j+1] == 'I') || (format[j+1] == 'm') ||
5305 (format[j+1] == 'M') || (format[j+1] == 'S') ||
5306 (format[j+1] == 'U') || (format[j+1] == 'V') ||
5307 (format[j+1] == 'W') || (format[j+1] == 'y')) {
5309 if (jj < FMT_LEG_LEN) {
5312 j++; /* We skip the following char */
5313 } else if (format[j+1] == 'j') {
5315 if (jj < FMT_LEG_LEN - 1) {
5319 j++; /* We skip the following char */
5320 } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5321 /* Assuming Year on 4 digit */
5323 if (jj < FMT_LEG_LEN - 2) {
5328 j++; /* We skip the following char */
5329 } else if (format[j+1] == 'R') {
5331 if (jj < FMT_LEG_LEN - 3) {
5337 j++; /* We skip the following char */
5338 } else if (format[j+1] == 'T') {
5340 if (jj < FMT_LEG_LEN - 6) {
5349 j++; /* We skip the following char */
5350 } else if (format[j+1] == 'F') {
5352 if (jj < FMT_LEG_LEN - 8) {
5363 j++; /* We skip the following char */
5364 } else if (format[j+1] == 'D') {
5366 if (jj < FMT_LEG_LEN - 6) {
5375 j++; /* We skip the following char */
5376 } else if (format[j+1] == 'n') {
5377 result[jj++] = '\r';
5378 result[jj++] = '\n';
5379 j++; /* We skip the following char */
5380 } else if (format[j+1] == 't') {
5381 result[jj++] = '\t';
5382 j++; /* We skip the following char */
5383 } else if (format[j+1] == '%') {
5385 j++; /* We skip the following char */
5386 } else if (format[j+1] == ' ') {
5387 if (jj < FMT_LEG_LEN - 1) {
5391 j++; /* We skip the following char */
5392 } else if (format[j+1] == '.') {
5393 if (jj < FMT_LEG_LEN - 1) {
5397 j++; /* We skip the following char */
5398 } else if (format[j+1] == '@') {
5399 if (jj < FMT_LEG_LEN - 1) {
5403 j++; /* We skip the following char */
5406 j++; /* We skip the following char */
5409 result[jj++] = format[j];
5412 result[jj] = '\0'; /* We must force the end of the string */