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);
1475 case TMT_SECOND: limit = 7200; break;
1476 case TMT_MINUTE: limit = 120; break;
1477 case TMT_HOUR: limit = 2; break;
1478 default: limit = 2; break;
1483 tm. tm_sec += basestep;
1487 tm. tm_min += basestep;
1491 tm. tm_hour += basestep;
1495 tm. tm_mday += basestep;
1499 tm. tm_mday += 7 * basestep;
1503 tm. tm_mon += basestep;
1507 tm. tm_year += basestep;
1509 madetime = mktime(&tm);
1510 } while (madetime == -1 && limit-- >= 0); /* this is necessary to skip impossible times
1511 like the daylight saving time skips */
1517 /* calculate values required for PRINT and GPRINT functions */
1522 long i, ii, validsteps;
1525 int graphelement = 0;
1528 double magfact = -1;
1533 /* wow initializing tmvdef is quite a task :-) */
1534 time_t now = time(NULL);
1536 localtime_r(&now, &tmvdef);
1537 for (i = 0; i < im->gdes_c; i++) {
1538 vidx = im->gdes[i].vidx;
1539 switch (im->gdes[i].gf) {
1542 /* PRINT and GPRINT can now print VDEF generated values.
1543 * There's no need to do any calculations on them as these
1544 * calculations were already made.
1546 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1547 printval = im->gdes[vidx].vf.val;
1548 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1549 } else { /* need to calculate max,min,avg etcetera */
1550 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1551 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1554 for (ii = im->gdes[vidx].ds;
1555 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1556 if (!finite(im->gdes[vidx].data[ii]))
1558 if (isnan(printval)) {
1559 printval = im->gdes[vidx].data[ii];
1564 switch (im->gdes[i].cf) {
1568 case CF_DEVSEASONAL:
1572 printval += im->gdes[vidx].data[ii];
1575 printval = min(printval, im->gdes[vidx].data[ii]);
1579 printval = max(printval, im->gdes[vidx].data[ii]);
1582 printval = im->gdes[vidx].data[ii];
1585 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1586 if (validsteps > 1) {
1587 printval = (printval / validsteps);
1590 } /* prepare printval */
1592 if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1593 /* Magfact is set to -1 upon entry to print_calc. If it
1594 * is still less than 0, then we need to run auto_scale.
1595 * Otherwise, put the value into the correct units. If
1596 * the value is 0, then do not set the symbol or magnification
1597 * so next the calculation will be performed again. */
1598 if (magfact < 0.0) {
1599 auto_scale(im, &printval, &si_symb, &magfact);
1600 if (printval == 0.0)
1603 printval /= magfact;
1605 *(++percent_s) = 's';
1606 } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1607 auto_scale(im, &printval, &si_symb, &magfact);
1610 if (im->gdes[i].gf == GF_PRINT) {
1611 rrd_infoval_t prline;
1613 if (im->gdes[i].strftm) {
1614 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1615 if (im->gdes[vidx].vf.never == 1) {
1616 time_clean(prline.u_str, im->gdes[i].format);
1618 strftime(prline.u_str,
1619 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1621 } else if (bad_format(im->gdes[i].format)) {
1623 ("bad format for PRINT in '%s'", im->gdes[i].format);
1627 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1631 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1636 if (im->gdes[i].strftm) {
1637 if (im->gdes[vidx].vf.never == 1) {
1638 time_clean(im->gdes[i].legend, im->gdes[i].format);
1640 strftime(im->gdes[i].legend,
1641 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1644 if (bad_format(im->gdes[i].format)) {
1646 ("bad format for GPRINT in '%s'",
1647 im->gdes[i].format);
1650 #ifdef HAVE_SNPRINTF
1651 snprintf(im->gdes[i].legend,
1653 im->gdes[i].format, printval, si_symb);
1655 sprintf(im->gdes[i].legend,
1656 im->gdes[i].format, printval, si_symb);
1669 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1670 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1675 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1676 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1685 #ifdef WITH_PIECHART
1693 ("STACK should already be turned into LINE or AREA here");
1698 return graphelement;
1703 /* place legends with color spots */
1709 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1710 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1711 int fill = 0, fill_last;
1712 double legendwidth; // = im->ximg - 2 * border;
1714 double leg_x = border;
1715 int leg_y = 0; //im->yimg;
1716 int leg_y_prev = 0; // im->yimg;
1719 int i, ii, mark = 0;
1720 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1723 char saved_legend[FMT_LEG_LEN + 5];
1729 legendwidth = im->legendwidth - 2 * border;
1733 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1734 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1735 rrd_set_error("malloc for legspace");
1739 for (i = 0; i < im->gdes_c; i++) {
1740 char prt_fctn; /*special printfunctions */
1742 strcpy(saved_legend, im->gdes[i].legend);
1746 /* hide legends for rules which are not displayed */
1747 if (im->gdes[i].gf == GF_TEXTALIGN) {
1748 default_txtalign = im->gdes[i].txtalign;
1751 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1752 if (im->gdes[i].gf == GF_HRULE
1753 && (im->gdes[i].yrule <
1754 im->minval || im->gdes[i].yrule > im->maxval))
1755 im->gdes[i].legend[0] = '\0';
1756 if (im->gdes[i].gf == GF_VRULE
1757 && (im->gdes[i].xrule <
1758 im->start || im->gdes[i].xrule > im->end))
1759 im->gdes[i].legend[0] = '\0';
1762 /* turn \\t into tab */
1763 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1764 memmove(tab, tab + 1, strlen(tab));
1768 leg_cc = strlen(im->gdes[i].legend);
1769 /* is there a controle code at the end of the legend string ? */
1770 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1771 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1773 im->gdes[i].legend[leg_cc] = '\0';
1777 /* only valid control codes */
1778 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1783 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1786 ("Unknown control code at the end of '%s\\%c'",
1787 im->gdes[i].legend, prt_fctn);
1791 if (prt_fctn == 'n') {
1795 /* remove exess space from the end of the legend for \g */
1796 while (prt_fctn == 'g' &&
1797 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1799 im->gdes[i].legend[leg_cc] = '\0';
1804 /* no interleg space if string ends in \g */
1805 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1807 fill += legspace[i];
1810 gfx_get_text_width(im,
1816 im->tabwidth, im->gdes[i].legend);
1821 /* who said there was a special tag ... ? */
1822 if (prt_fctn == 'g') {
1826 if (prt_fctn == '\0') {
1827 if(calc_width && (fill > legendwidth)){
1830 if (i == im->gdes_c - 1 || fill > legendwidth) {
1831 /* just one legend item is left right or center */
1832 switch (default_txtalign) {
1847 /* is it time to place the legends ? */
1848 if (fill > legendwidth) {
1856 if (leg_c == 1 && prt_fctn == 'j') {
1861 if (prt_fctn != '\0') {
1863 if (leg_c >= 2 && prt_fctn == 'j') {
1864 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1868 if (prt_fctn == 'c')
1869 leg_x = (double)(legendwidth - fill) / 2.0;
1870 if (prt_fctn == 'r')
1871 leg_x = legendwidth - fill + border;
1872 for (ii = mark; ii <= i; ii++) {
1873 if (im->gdes[ii].legend[0] == '\0')
1874 continue; /* skip empty legends */
1875 im->gdes[ii].leg_x = leg_x;
1876 im->gdes[ii].leg_y = leg_y + border;
1878 (double)gfx_get_text_width(im, leg_x,
1883 im->tabwidth, im->gdes[ii].legend)
1884 +(double)legspace[ii]
1888 if (leg_x > border || prt_fctn == 's')
1889 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1890 if (prt_fctn == 's')
1891 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1892 if (prt_fctn == 'u')
1893 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1895 if(calc_width && (fill > legendwidth)){
1904 strcpy(im->gdes[i].legend, saved_legend);
1909 im->legendwidth = legendwidth + 2 * border;
1912 im->legendheight = leg_y + border * 0.6;
1919 /* create a grid on the graph. it determines what to do
1920 from the values of xsize, start and end */
1922 /* the xaxis labels are determined from the number of seconds per pixel
1923 in the requested graph */
1925 int calc_horizontal_grid(
1933 int decimals, fractionals;
1935 im->ygrid_scale.labfact = 2;
1936 range = im->maxval - im->minval;
1937 scaledrange = range / im->magfact;
1938 /* does the scale of this graph make it impossible to put lines
1939 on it? If so, give up. */
1940 if (isnan(scaledrange)) {
1944 /* find grid spaceing */
1946 if (isnan(im->ygridstep)) {
1947 if (im->extra_flags & ALTYGRID) {
1948 /* find the value with max number of digits. Get number of digits */
1951 (max(fabs(im->maxval), fabs(im->minval)) *
1952 im->viewfactor / im->magfact));
1953 if (decimals <= 0) /* everything is small. make place for zero */
1955 im->ygrid_scale.gridstep =
1957 floor(log10(range * im->viewfactor / im->magfact))) /
1958 im->viewfactor * im->magfact;
1959 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1960 im->ygrid_scale.gridstep = 0.1;
1961 /* should have at least 5 lines but no more then 15 */
1962 if (range / im->ygrid_scale.gridstep < 5
1963 && im->ygrid_scale.gridstep >= 30)
1964 im->ygrid_scale.gridstep /= 10;
1965 if (range / im->ygrid_scale.gridstep > 15)
1966 im->ygrid_scale.gridstep *= 10;
1967 if (range / im->ygrid_scale.gridstep > 5) {
1968 im->ygrid_scale.labfact = 1;
1969 if (range / im->ygrid_scale.gridstep > 8
1970 || im->ygrid_scale.gridstep <
1971 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1972 im->ygrid_scale.labfact = 2;
1974 im->ygrid_scale.gridstep /= 5;
1975 im->ygrid_scale.labfact = 5;
1979 (im->ygrid_scale.gridstep *
1980 (double) im->ygrid_scale.labfact * im->viewfactor /
1982 if (fractionals < 0) { /* small amplitude. */
1983 int len = decimals - fractionals + 1;
1985 if (im->unitslength < len + 2)
1986 im->unitslength = len + 2;
1987 sprintf(im->ygrid_scale.labfmt,
1989 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1991 int len = decimals + 1;
1993 if (im->unitslength < len + 2)
1994 im->unitslength = len + 2;
1995 sprintf(im->ygrid_scale.labfmt,
1996 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1998 } else { /* classic rrd grid */
1999 for (i = 0; ylab[i].grid > 0; i++) {
2000 pixel = im->ysize / (scaledrange / ylab[i].grid);
2006 for (i = 0; i < 4; i++) {
2007 if (pixel * ylab[gridind].lfac[i] >=
2008 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2009 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2014 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2017 im->ygrid_scale.gridstep = im->ygridstep;
2018 im->ygrid_scale.labfact = im->ylabfact;
2023 int draw_horizontal_grid(
2029 char graph_label[100];
2031 double X0 = im->xorigin;
2032 double X1 = im->xorigin + im->xsize;
2033 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2034 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2036 double second_axis_magfact = 0;
2037 char *second_axis_symb = "";
2040 im->ygrid_scale.gridstep /
2041 (double) im->magfact * (double) im->viewfactor;
2042 MaxY = scaledstep * (double) egrid;
2043 for (i = sgrid; i <= egrid; i++) {
2045 im->ygrid_scale.gridstep * i);
2047 im->ygrid_scale.gridstep * (i + 1));
2049 if (floor(Y0 + 0.5) >=
2050 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2051 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2052 with the chosen settings. Add a label if required by settings, or if
2053 there is only one label so far and the next grid line is out of bounds. */
2054 if (i % im->ygrid_scale.labfact == 0
2056 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2057 if (im->symbol == ' ') {
2058 if (im->extra_flags & ALTYGRID) {
2059 sprintf(graph_label,
2060 im->ygrid_scale.labfmt,
2061 scaledstep * (double) i);
2064 sprintf(graph_label, "%4.1f",
2065 scaledstep * (double) i);
2067 sprintf(graph_label, "%4.0f",
2068 scaledstep * (double) i);
2072 char sisym = (i == 0 ? ' ' : im->symbol);
2074 if (im->extra_flags & ALTYGRID) {
2075 sprintf(graph_label,
2076 im->ygrid_scale.labfmt,
2077 scaledstep * (double) i, sisym);
2080 sprintf(graph_label, "%4.1f %c",
2081 scaledstep * (double) i, sisym);
2083 sprintf(graph_label, "%4.0f %c",
2084 scaledstep * (double) i, sisym);
2089 if (im->second_axis_scale != 0){
2090 char graph_label_right[100];
2091 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2092 if (im->second_axis_format[0] == '\0'){
2093 if (!second_axis_magfact){
2094 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2095 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2097 sval /= second_axis_magfact;
2100 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2102 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2106 sprintf(graph_label_right,im->second_axis_format,sval);
2110 im->graph_col[GRC_FONT],
2111 im->text_prop[TEXT_PROP_AXIS].font_desc,
2112 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2113 graph_label_right );
2119 text_prop[TEXT_PROP_AXIS].
2121 im->graph_col[GRC_FONT],
2123 text_prop[TEXT_PROP_AXIS].
2126 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2127 gfx_line(im, X0 - 2, Y0, X0, Y0,
2128 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2129 gfx_line(im, X1, Y0, X1 + 2, Y0,
2130 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2131 gfx_dashed_line(im, X0 - 2, Y0,
2137 im->grid_dash_on, im->grid_dash_off);
2138 } else if (!(im->extra_flags & NOMINOR)) {
2141 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2142 gfx_line(im, X1, Y0, X1 + 2, Y0,
2143 GRIDWIDTH, im->graph_col[GRC_GRID]);
2144 gfx_dashed_line(im, X0 - 1, Y0,
2148 graph_col[GRC_GRID],
2149 im->grid_dash_on, im->grid_dash_off);
2156 /* this is frexp for base 10 */
2167 iexp = floor(log((double)fabs(x)) / log((double)10));
2168 mnt = x / pow(10.0, iexp);
2171 mnt = x / pow(10.0, iexp);
2178 /* logaritmic horizontal grid */
2179 int horizontal_log_grid(
2183 double yloglab[][10] = {
2185 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2187 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2189 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2206 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2208 int i, j, val_exp, min_exp;
2209 double nex; /* number of decades in data */
2210 double logscale; /* scale in logarithmic space */
2211 int exfrac = 1; /* decade spacing */
2212 int mid = -1; /* row in yloglab for major grid */
2213 double mspac; /* smallest major grid spacing (pixels) */
2214 int flab; /* first value in yloglab to use */
2215 double value, tmp, pre_value;
2217 char graph_label[100];
2219 nex = log10(im->maxval / im->minval);
2220 logscale = im->ysize / nex;
2221 /* major spacing for data with high dynamic range */
2222 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2229 /* major spacing for less dynamic data */
2231 /* search best row in yloglab */
2233 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2234 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2237 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2240 /* find first value in yloglab */
2242 yloglab[mid][flab] < 10
2243 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2244 if (yloglab[mid][flab] == 10.0) {
2249 if (val_exp % exfrac)
2250 val_exp += abs(-val_exp % exfrac);
2252 X1 = im->xorigin + im->xsize;
2257 value = yloglab[mid][flab] * pow(10.0, val_exp);
2258 if (AlmostEqual2sComplement(value, pre_value, 4))
2259 break; /* it seems we are not converging */
2261 Y0 = ytr(im, value);
2262 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2264 /* major grid line */
2266 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2267 gfx_line(im, X1, Y0, X1 + 2, Y0,
2268 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2269 gfx_dashed_line(im, X0 - 2, Y0,
2274 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2276 if (im->extra_flags & FORCE_UNITS_SI) {
2281 scale = floor(val_exp / 3.0);
2283 pvalue = pow(10.0, val_exp % 3);
2285 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2286 pvalue *= yloglab[mid][flab];
2287 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2288 && ((scale + si_symbcenter) >= 0))
2289 symbol = si_symbol[scale + si_symbcenter];
2292 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2294 sprintf(graph_label, "%3.0e", value);
2296 if (im->second_axis_scale != 0){
2297 char graph_label_right[100];
2298 double sval = value*im->second_axis_scale+im->second_axis_shift;
2299 if (im->second_axis_format[0] == '\0'){
2300 if (im->extra_flags & FORCE_UNITS_SI) {
2303 auto_scale(im,&sval,&symb,&mfac);
2304 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2307 sprintf(graph_label_right,"%3.0e", sval);
2311 sprintf(graph_label_right,im->second_axis_format,sval,"");
2316 im->graph_col[GRC_FONT],
2317 im->text_prop[TEXT_PROP_AXIS].font_desc,
2318 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2319 graph_label_right );
2325 text_prop[TEXT_PROP_AXIS].
2327 im->graph_col[GRC_FONT],
2329 text_prop[TEXT_PROP_AXIS].
2332 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2334 if (mid < 4 && exfrac == 1) {
2335 /* find first and last minor line behind current major line
2336 * i is the first line and j tha last */
2338 min_exp = val_exp - 1;
2339 for (i = 1; yloglab[mid][i] < 10.0; i++);
2340 i = yloglab[mid][i - 1] + 1;
2344 i = yloglab[mid][flab - 1] + 1;
2345 j = yloglab[mid][flab];
2348 /* draw minor lines below current major line */
2349 for (; i < j; i++) {
2351 value = i * pow(10.0, min_exp);
2352 if (value < im->minval)
2354 Y0 = ytr(im, value);
2355 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2360 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2361 gfx_line(im, X1, Y0, X1 + 2, Y0,
2362 GRIDWIDTH, im->graph_col[GRC_GRID]);
2363 gfx_dashed_line(im, X0 - 1, Y0,
2367 graph_col[GRC_GRID],
2368 im->grid_dash_on, im->grid_dash_off);
2370 } else if (exfrac > 1) {
2371 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2372 value = pow(10.0, i);
2373 if (value < im->minval)
2375 Y0 = ytr(im, value);
2376 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2381 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2382 gfx_line(im, X1, Y0, X1 + 2, Y0,
2383 GRIDWIDTH, im->graph_col[GRC_GRID]);
2384 gfx_dashed_line(im, X0 - 1, Y0,
2388 graph_col[GRC_GRID],
2389 im->grid_dash_on, im->grid_dash_off);
2394 if (yloglab[mid][++flab] == 10.0) {
2400 /* draw minor lines after highest major line */
2401 if (mid < 4 && exfrac == 1) {
2402 /* find first and last minor line below current major line
2403 * i is the first line and j tha last */
2405 min_exp = val_exp - 1;
2406 for (i = 1; yloglab[mid][i] < 10.0; i++);
2407 i = yloglab[mid][i - 1] + 1;
2411 i = yloglab[mid][flab - 1] + 1;
2412 j = yloglab[mid][flab];
2415 /* draw minor lines below current major line */
2416 for (; i < j; i++) {
2418 value = i * pow(10.0, min_exp);
2419 if (value < im->minval)
2421 Y0 = ytr(im, value);
2422 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2426 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2427 gfx_line(im, X1, Y0, X1 + 2, Y0,
2428 GRIDWIDTH, im->graph_col[GRC_GRID]);
2429 gfx_dashed_line(im, X0 - 1, Y0,
2433 graph_col[GRC_GRID],
2434 im->grid_dash_on, im->grid_dash_off);
2437 /* fancy minor gridlines */
2438 else if (exfrac > 1) {
2439 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2440 value = pow(10.0, i);
2441 if (value < im->minval)
2443 Y0 = ytr(im, value);
2444 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2448 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2449 gfx_line(im, X1, Y0, X1 + 2, Y0,
2450 GRIDWIDTH, im->graph_col[GRC_GRID]);
2451 gfx_dashed_line(im, X0 - 1, Y0,
2455 graph_col[GRC_GRID],
2456 im->grid_dash_on, im->grid_dash_off);
2467 int xlab_sel; /* which sort of label and grid ? */
2468 time_t ti, tilab, timajor;
2470 char graph_label[100];
2471 double X0, Y0, Y1; /* points for filled graph and more */
2474 /* the type of time grid is determined by finding
2475 the number of seconds per pixel in the graph */
2476 if (im->xlab_user.minsec == -1) {
2477 factor = (im->end - im->start) / im->xsize;
2479 while (xlab[xlab_sel + 1].minsec !=
2480 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2482 } /* pick the last one */
2483 while (xlab[xlab_sel - 1].minsec ==
2484 xlab[xlab_sel].minsec
2485 && xlab[xlab_sel].length > (im->end - im->start)) {
2487 } /* go back to the smallest size */
2488 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2489 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2490 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2491 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2492 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2493 im->xlab_user.labst = xlab[xlab_sel].labst;
2494 im->xlab_user.precis = xlab[xlab_sel].precis;
2495 im->xlab_user.stst = xlab[xlab_sel].stst;
2498 /* y coords are the same for every line ... */
2500 Y1 = im->yorigin - im->ysize;
2501 /* paint the minor grid */
2502 if (!(im->extra_flags & NOMINOR)) {
2503 for (ti = find_first_time(im->start,
2511 find_first_time(im->start,
2516 ti < im->end && ti != -1;
2518 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2520 /* are we inside the graph ? */
2521 if (ti < im->start || ti > im->end)
2523 while (timajor < ti && timajor != -1) {
2524 timajor = find_next_time(timajor,
2527 mgridtm, im->xlab_user.mgridst);
2529 if (timajor == -1) break; /* fail in case of problems with time increments */
2531 continue; /* skip as falls on major grid line */
2533 gfx_line(im, X0, Y1 - 2, X0, Y1,
2534 GRIDWIDTH, im->graph_col[GRC_GRID]);
2535 gfx_line(im, X0, Y0, X0, Y0 + 2,
2536 GRIDWIDTH, im->graph_col[GRC_GRID]);
2537 gfx_dashed_line(im, X0, Y0 + 1, X0,
2540 graph_col[GRC_GRID],
2541 im->grid_dash_on, im->grid_dash_off);
2545 /* paint the major grid */
2546 for (ti = find_first_time(im->start,
2553 ti < im->end && ti != -1;
2554 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2556 /* are we inside the graph ? */
2557 if (ti < im->start || ti > im->end)
2560 gfx_line(im, X0, Y1 - 2, X0, Y1,
2561 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2562 gfx_line(im, X0, Y0, X0, Y0 + 3,
2563 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2564 gfx_dashed_line(im, X0, Y0 + 3, X0,
2568 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2570 /* paint the labels below the graph */
2572 find_first_time(im->start -
2581 im->xlab_user.precis / 2) && ti != -1;
2582 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2584 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2585 /* are we inside the graph ? */
2586 if (tilab < im->start || tilab > im->end)
2589 localtime_r(&tilab, &tm);
2590 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2592 # error "your libc has no strftime I guess we'll abort the exercise here."
2597 im->graph_col[GRC_FONT],
2599 text_prop[TEXT_PROP_AXIS].
2602 GFX_H_CENTER, GFX_V_TOP, graph_label);
2611 /* draw x and y axis */
2612 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2613 im->xorigin+im->xsize,im->yorigin-im->ysize,
2614 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2616 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2617 im->xorigin+im->xsize,im->yorigin-im->ysize,
2618 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2620 gfx_line(im, im->xorigin - 4,
2622 im->xorigin + im->xsize +
2623 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2624 gfx_line(im, im->xorigin,
2627 im->yorigin - im->ysize -
2628 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2629 /* arrow for X and Y axis direction */
2630 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 */
2631 im->graph_col[GRC_ARROW]);
2633 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 */
2634 im->graph_col[GRC_ARROW]);
2636 if (im->second_axis_scale != 0){
2637 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2638 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2639 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2641 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2642 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2643 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2644 im->graph_col[GRC_ARROW]);
2655 double X0, Y0; /* points for filled graph and more */
2656 struct gfx_color_t water_color;
2658 if (im->draw_3d_border > 0) {
2659 /* draw 3d border */
2660 i = im->draw_3d_border;
2661 gfx_new_area(im, 0, im->yimg,
2662 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2663 gfx_add_point(im, im->ximg - i, i);
2664 gfx_add_point(im, im->ximg, 0);
2665 gfx_add_point(im, 0, 0);
2667 gfx_new_area(im, i, im->yimg - i,
2669 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2670 gfx_add_point(im, im->ximg, 0);
2671 gfx_add_point(im, im->ximg, im->yimg);
2672 gfx_add_point(im, 0, im->yimg);
2675 if (im->draw_x_grid == 1)
2677 if (im->draw_y_grid == 1) {
2678 if (im->logarithmic) {
2679 res = horizontal_log_grid(im);
2681 res = draw_horizontal_grid(im);
2684 /* dont draw horizontal grid if there is no min and max val */
2686 char *nodata = "No Data found";
2688 gfx_text(im, im->ximg / 2,
2691 im->graph_col[GRC_FONT],
2693 text_prop[TEXT_PROP_AXIS].
2696 GFX_H_CENTER, GFX_V_CENTER, nodata);
2700 /* yaxis unit description */
2701 if (im->ylegend[0] != '\0'){
2703 im->xOriginLegendY+10,
2705 im->graph_col[GRC_FONT],
2707 text_prop[TEXT_PROP_UNIT].
2710 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2713 if (im->second_axis_legend[0] != '\0'){
2715 im->xOriginLegendY2+10,
2716 im->yOriginLegendY2,
2717 im->graph_col[GRC_FONT],
2718 im->text_prop[TEXT_PROP_UNIT].font_desc,
2720 RRDGRAPH_YLEGEND_ANGLE,
2721 GFX_H_CENTER, GFX_V_CENTER,
2722 im->second_axis_legend);
2727 im->xOriginTitle, im->yOriginTitle+6,
2728 im->graph_col[GRC_FONT],
2730 text_prop[TEXT_PROP_TITLE].
2732 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2733 /* rrdtool 'logo' */
2734 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2735 water_color = im->graph_col[GRC_FONT];
2736 water_color.alpha = 0.3;
2737 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2738 gfx_text(im, xpos, 5,
2741 text_prop[TEXT_PROP_WATERMARK].
2742 font_desc, im->tabwidth,
2743 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2745 /* graph watermark */
2746 if (im->watermark[0] != '\0') {
2747 water_color = im->graph_col[GRC_FONT];
2748 water_color.alpha = 0.3;
2750 im->ximg / 2, im->yimg - 6,
2753 text_prop[TEXT_PROP_WATERMARK].
2754 font_desc, im->tabwidth, 0,
2755 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2759 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2760 for (i = 0; i < im->gdes_c; i++) {
2761 if (im->gdes[i].legend[0] == '\0')
2763 /* im->gdes[i].leg_y is the bottom of the legend */
2764 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2765 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2766 gfx_text(im, X0, Y0,
2767 im->graph_col[GRC_FONT],
2770 [TEXT_PROP_LEGEND].font_desc,
2772 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2773 /* The legend for GRAPH items starts with "M " to have
2774 enough space for the box */
2775 if (im->gdes[i].gf != GF_PRINT &&
2776 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2780 boxH = gfx_get_text_width(im, 0,
2785 im->tabwidth, "o") * 1.2;
2787 /* shift the box up a bit */
2790 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2792 cairo_new_path(im->cr);
2793 cairo_set_line_width(im->cr, 1.0);
2796 X0 + boxH, Y0 - boxV / 2,
2797 1.0, im->gdes[i].col);
2799 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2801 cairo_new_path(im->cr);
2802 cairo_set_line_width(im->cr, 1.0);
2805 X0 + boxH / 2, Y0 - boxV,
2806 1.0, im->gdes[i].col);
2808 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2810 cairo_new_path(im->cr);
2811 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2814 X0 + boxH, Y0 - boxV,
2815 im->gdes[i].linewidth, im->gdes[i].col);
2818 /* make sure transparent colors show up the same way as in the graph */
2821 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2822 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2824 gfx_new_area(im, X0, Y0 - boxV, X0,
2825 Y0, X0 + boxH, Y0, im->gdes[i].col);
2826 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2829 cairo_new_path(im->cr);
2830 cairo_set_line_width(im->cr, 1.0);
2833 gfx_line_fit(im, &X0, &Y0);
2834 gfx_line_fit(im, &X1, &Y1);
2835 cairo_move_to(im->cr, X0, Y0);
2836 cairo_line_to(im->cr, X1, Y0);
2837 cairo_line_to(im->cr, X1, Y1);
2838 cairo_line_to(im->cr, X0, Y1);
2839 cairo_close_path(im->cr);
2840 cairo_set_source_rgba(im->cr,
2841 im->graph_col[GRC_FRAME].red,
2842 im->graph_col[GRC_FRAME].green,
2843 im->graph_col[GRC_FRAME].blue,
2844 im->graph_col[GRC_FRAME].alpha);
2846 if (im->gdes[i].dash) {
2847 /* make box borders in legend dashed if the graph is dashed */
2851 cairo_set_dash(im->cr, dashes, 1, 0.0);
2853 cairo_stroke(im->cr);
2854 cairo_restore(im->cr);
2861 /*****************************************************
2862 * lazy check make sure we rely need to create this graph
2863 *****************************************************/
2870 struct stat imgstat;
2873 return 0; /* no lazy option */
2874 if (strlen(im->graphfile) == 0)
2875 return 0; /* inmemory option */
2876 if (stat(im->graphfile, &imgstat) != 0)
2877 return 0; /* can't stat */
2878 /* one pixel in the existing graph is more then what we would
2880 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2882 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2883 return 0; /* the file does not exist */
2884 switch (im->imgformat) {
2886 size = PngSize(fd, &(im->ximg), &(im->yimg));
2896 int graph_size_location(
2901 /* The actual size of the image to draw is determined from
2902 ** several sources. The size given on the command line is
2903 ** the graph area but we need more as we have to draw labels
2904 ** and other things outside the graph area. If the option
2905 ** --full-size-mode is selected the size defines the total
2906 ** image size and the size available for the graph is
2910 /** +---+-----------------------------------+
2911 ** | y |...............graph title.........|
2912 ** | +---+-------------------------------+
2916 ** | s | x | main graph area |
2921 ** | l | b +-------------------------------+
2922 ** | e | l | x axis labels |
2923 ** +---+---+-------------------------------+
2924 ** |....................legends............|
2925 ** +---------------------------------------+
2927 ** +---------------------------------------+
2930 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2931 0, Xylabel = 0, Xmain = 0, Ymain =
2932 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2934 // no legends and no the shall be plotted it's easy
2935 if (im->extra_flags & ONLY_GRAPH) {
2937 im->ximg = im->xsize;
2938 im->yimg = im->ysize;
2939 im->yorigin = im->ysize;
2944 if(im->watermark[0] != '\0') {
2945 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2948 // calculate the width of the left vertical legend
2949 if (im->ylegend[0] != '\0') {
2950 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2953 // calculate the width of the right vertical legend
2954 if (im->second_axis_legend[0] != '\0') {
2955 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2958 Xvertical2 = Xspacing;
2961 if (im->title[0] != '\0') {
2962 /* The title is placed "inbetween" two text lines so it
2963 ** automatically has some vertical spacing. The horizontal
2964 ** spacing is added here, on each side.
2966 /* if necessary, reduce the font size of the title until it fits the image width */
2967 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2970 // we have no title; get a little clearing from the top
2975 if (im->draw_x_grid) {
2976 // calculate the height of the horizontal labelling
2977 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2979 if (im->draw_y_grid || im->forceleftspace) {
2980 // calculate the width of the vertical labelling
2982 gfx_get_text_width(im, 0,
2983 im->text_prop[TEXT_PROP_AXIS].font_desc,
2984 im->tabwidth, "0") * im->unitslength;
2988 // add some space to the labelling
2989 Xylabel += Xspacing;
2991 /* If the legend is printed besides the graph the width has to be
2992 ** calculated first. Placing the legend north or south of the
2993 ** graph requires the width calculation first, so the legend is
2994 ** skipped for the moment.
2996 im->legendheight = 0;
2997 im->legendwidth = 0;
2998 if (!(im->extra_flags & NOLEGEND)) {
2999 if(im->legendposition == WEST || im->legendposition == EAST){
3000 if (leg_place(im, 1) == -1){
3006 if (im->extra_flags & FULL_SIZE_MODE) {
3008 /* The actual size of the image to draw has been determined by the user.
3009 ** The graph area is the space remaining after accounting for the legend,
3010 ** the watermark, the axis labels, and the title.
3012 im->ximg = im->xsize;
3013 im->yimg = im->ysize;
3017 /* Now calculate the total size. Insert some spacing where
3018 desired. im->xorigin and im->yorigin need to correspond
3019 with the lower left corner of the main graph area or, if
3020 this one is not set, the imaginary box surrounding the
3022 /* Initial size calculation for the main graph area */
3024 Xmain -= Xylabel;// + Xspacing;
3025 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3026 Xmain -= im->legendwidth;// + Xspacing;
3028 if (im->second_axis_scale != 0){
3031 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3035 Xmain -= Xvertical + Xvertical2;
3037 /* limit the remaining space to 0 */
3043 /* Putting the legend north or south, the height can now be calculated */
3044 if (!(im->extra_flags & NOLEGEND)) {
3045 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3046 im->legendwidth = im->ximg;
3047 if (leg_place(im, 0) == -1){
3053 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3054 Ymain -= Yxlabel + im->legendheight;
3060 /* reserve space for the title *or* some padding above the graph */
3063 /* reserve space for padding below the graph */
3064 if (im->extra_flags & NOLEGEND) {
3065 Ymain -= 0.5*Yspacing;
3068 if (im->watermark[0] != '\0') {
3069 Ymain -= Ywatermark;
3071 /* limit the remaining height to 0 */
3076 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3078 /* The actual size of the image to draw is determined from
3079 ** several sources. The size given on the command line is
3080 ** the graph area but we need more as we have to draw labels
3081 ** and other things outside the graph area.
3085 Xmain = im->xsize; // + Xspacing;
3089 im->ximg = Xmain + Xylabel;
3090 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3091 im->ximg += Xspacing;
3094 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3095 im->ximg += im->legendwidth;// + Xspacing;
3097 if (im->second_axis_scale != 0){
3098 im->ximg += Xylabel;
3101 im->ximg += Xvertical + Xvertical2;
3103 if (!(im->extra_flags & NOLEGEND)) {
3104 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3105 im->legendwidth = im->ximg;
3106 if (leg_place(im, 0) == -1){
3112 im->yimg = Ymain + Yxlabel;
3113 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3114 im->yimg += im->legendheight;
3117 /* reserve space for the title *or* some padding above the graph */
3121 im->yimg += 1.5 * Yspacing;
3123 /* reserve space for padding below the graph */
3124 if (im->extra_flags & NOLEGEND) {
3125 im->yimg += 0.5*Yspacing;
3128 if (im->watermark[0] != '\0') {
3129 im->yimg += Ywatermark;
3134 /* In case of putting the legend in west or east position the first
3135 ** legend calculation might lead to wrong positions if some items
3136 ** are not aligned on the left hand side (e.g. centered) as the
3137 ** legendwidth wight have been increased after the item was placed.
3138 ** In this case the positions have to be recalculated.
3140 if (!(im->extra_flags & NOLEGEND)) {
3141 if(im->legendposition == WEST || im->legendposition == EAST){
3142 if (leg_place(im, 0) == -1){
3148 /* After calculating all dimensions
3149 ** it is now possible to calculate
3152 switch(im->legendposition){
3154 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3155 im->yOriginTitle = 0;
3157 im->xOriginLegend = 0;
3158 im->yOriginLegend = Ytitle;
3160 im->xOriginLegendY = 0;
3161 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3163 im->xorigin = Xvertical + Xylabel;
3164 im->yorigin = Ytitle + im->legendheight + Ymain;
3166 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3167 if (im->second_axis_scale != 0){
3168 im->xOriginLegendY2 += Xylabel;
3170 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3175 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3176 im->yOriginTitle = 0;
3178 im->xOriginLegend = 0;
3179 im->yOriginLegend = Ytitle;
3181 im->xOriginLegendY = im->legendwidth;
3182 im->yOriginLegendY = Ytitle + (Ymain / 2);
3184 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3185 im->yorigin = Ytitle + Ymain;
3187 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3188 if (im->second_axis_scale != 0){
3189 im->xOriginLegendY2 += Xylabel;
3191 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3196 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3197 im->yOriginTitle = 0;
3199 im->xOriginLegend = 0;
3200 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3202 im->xOriginLegendY = 0;
3203 im->yOriginLegendY = Ytitle + (Ymain / 2);
3205 im->xorigin = Xvertical + Xylabel;
3206 im->yorigin = Ytitle + Ymain;
3208 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3209 if (im->second_axis_scale != 0){
3210 im->xOriginLegendY2 += Xylabel;
3212 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3217 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3218 im->yOriginTitle = 0;
3220 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3221 if (im->second_axis_scale != 0){
3222 im->xOriginLegend += Xylabel;
3224 im->yOriginLegend = Ytitle;
3226 im->xOriginLegendY = 0;
3227 im->yOriginLegendY = Ytitle + (Ymain / 2);
3229 im->xorigin = Xvertical + Xylabel;
3230 im->yorigin = Ytitle + Ymain;
3232 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3233 if (im->second_axis_scale != 0){
3234 im->xOriginLegendY2 += Xylabel;
3236 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3238 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3239 im->xOriginTitle += Xspacing;
3240 im->xOriginLegend += Xspacing;
3241 im->xOriginLegendY += Xspacing;
3242 im->xorigin += Xspacing;
3243 im->xOriginLegendY2 += Xspacing;
3253 static cairo_status_t cairo_output(
3257 unsigned int length)
3259 image_desc_t *im = (image_desc_t*)closure;
3261 im->rendered_image =
3262 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3263 if (im->rendered_image == NULL)
3264 return CAIRO_STATUS_WRITE_ERROR;
3265 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3266 im->rendered_image_size += length;
3267 return CAIRO_STATUS_SUCCESS;
3270 /* draw that picture thing ... */
3275 int lazy = lazy_check(im);
3276 double areazero = 0.0;
3277 graph_desc_t *lastgdes = NULL;
3280 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3282 /* pull the data from the rrd files ... */
3283 if (data_fetch(im) == -1)
3285 /* evaluate VDEF and CDEF operations ... */
3286 if (data_calc(im) == -1)
3288 /* calculate and PRINT and GPRINT definitions. We have to do it at
3289 * this point because it will affect the length of the legends
3290 * if there are no graph elements (i==0) we stop here ...
3291 * if we are lazy, try to quit ...
3297 /* if we want and can be lazy ... quit now */
3301 /**************************************************************
3302 *** Calculating sizes and locations became a bit confusing ***
3303 *** so I moved this into a separate function. ***
3304 **************************************************************/
3305 if (graph_size_location(im, i) == -1)
3308 info.u_cnt = im->xorigin;
3309 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3310 info.u_cnt = im->yorigin - im->ysize;
3311 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3312 info.u_cnt = im->xsize;
3313 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3314 info.u_cnt = im->ysize;
3315 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3316 info.u_cnt = im->ximg;
3317 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3318 info.u_cnt = im->yimg;
3319 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3320 info.u_cnt = im->start;
3321 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3322 info.u_cnt = im->end;
3323 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3325 /* if we want and can be lazy ... quit now */
3329 /* get actual drawing data and find min and max values */
3330 if (data_proc(im) == -1)
3332 if (!im->logarithmic) {
3336 /* identify si magnitude Kilo, Mega Giga ? */
3337 if (!im->rigid && !im->logarithmic)
3338 expand_range(im); /* make sure the upper and lower limit are
3341 info.u_val = im->minval;
3342 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3343 info.u_val = im->maxval;
3344 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3347 if (!calc_horizontal_grid(im))
3352 apply_gridfit(im); */
3353 /* the actual graph is created by going through the individual
3354 graph elements and then drawing them */
3355 cairo_surface_destroy(im->surface);
3356 switch (im->imgformat) {
3359 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3360 im->ximg * im->zoom,
3361 im->yimg * im->zoom);
3365 im->surface = strlen(im->graphfile)
3366 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3367 im->yimg * im->zoom)
3368 : cairo_pdf_surface_create_for_stream
3369 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3373 im->surface = strlen(im->graphfile)
3375 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3376 im->yimg * im->zoom)
3377 : cairo_ps_surface_create_for_stream
3378 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3382 im->surface = strlen(im->graphfile)
3384 cairo_svg_surface_create(im->
3386 im->ximg * im->zoom, im->yimg * im->zoom)
3387 : cairo_svg_surface_create_for_stream
3388 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3389 cairo_svg_surface_restrict_to_version
3390 (im->surface, CAIRO_SVG_VERSION_1_1);
3393 cairo_destroy(im->cr);
3394 im->cr = cairo_create(im->surface);
3395 cairo_set_antialias(im->cr, im->graph_antialias);
3396 cairo_scale(im->cr, im->zoom, im->zoom);
3397 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3398 gfx_new_area(im, 0, 0, 0, im->yimg,
3399 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3400 gfx_add_point(im, im->ximg, 0);
3402 gfx_new_area(im, im->xorigin,
3405 im->xsize, im->yorigin,
3408 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3409 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3411 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3412 im->xsize, im->ysize + 2.0);
3414 if (im->minval > 0.0)
3415 areazero = im->minval;
3416 if (im->maxval < 0.0)
3417 areazero = im->maxval;
3418 for (i = 0; i < im->gdes_c; i++) {
3419 switch (im->gdes[i].gf) {
3433 for (ii = 0; ii < im->xsize; ii++) {
3434 if (!isnan(im->gdes[i].p_data[ii])
3435 && im->gdes[i].p_data[ii] != 0.0) {
3436 if (im->gdes[i].yrule > 0) {
3443 im->ysize, 1.0, im->gdes[i].col);
3444 } else if (im->gdes[i].yrule < 0) {
3447 im->yorigin - im->ysize - 1.0,
3449 im->yorigin - im->ysize -
3452 im->ysize, 1.0, im->gdes[i].col);
3460 rrd_value_t diffval = im->maxval - im->minval;
3461 rrd_value_t maxlimit = im->maxval + 9 * diffval;
3462 rrd_value_t minlimit = im->minval - 9 * diffval;
3463 for (ii = 0; ii < im->xsize; ii++) {
3464 /* fix data points at oo and -oo */
3465 if (isinf(im->gdes[i].p_data[ii])) {
3466 if (im->gdes[i].p_data[ii] > 0) {
3467 im->gdes[i].p_data[ii] = im->maxval;
3469 im->gdes[i].p_data[ii] = im->minval;
3472 /* some versions of cairo go unstable when trying
3473 to draw way out of the canvas ... lets not even try */
3474 if (im->gdes[i].p_data[ii] > maxlimit) {
3475 im->gdes[i].p_data[ii] = maxlimit;
3477 if (im->gdes[i].p_data[ii] < minlimit) {
3478 im->gdes[i].p_data[ii] = minlimit;
3482 /* *******************************************************
3487 -------|--t-1--t--------------------------------
3489 if we know the value at time t was a then
3490 we draw a square from t-1 to t with the value a.
3492 ********************************************************* */
3493 if (im->gdes[i].col.alpha != 0.0) {
3494 /* GF_LINE and friend */
3495 if (im->gdes[i].gf == GF_LINE) {
3496 double last_y = 0.0;
3500 cairo_new_path(im->cr);
3501 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3502 if (im->gdes[i].dash) {
3503 cairo_set_dash(im->cr,
3504 im->gdes[i].p_dashes,
3505 im->gdes[i].ndash, im->gdes[i].offset);
3508 for (ii = 1; ii < im->xsize; ii++) {
3509 if (isnan(im->gdes[i].p_data[ii])
3510 || (im->slopemode == 1
3511 && isnan(im->gdes[i].p_data[ii - 1]))) {
3516 last_y = ytr(im, im->gdes[i].p_data[ii]);
3517 if (im->slopemode == 0) {
3518 double x = ii - 1 + im->xorigin;
3521 gfx_line_fit(im, &x, &y);
3522 cairo_move_to(im->cr, x, y);
3523 x = ii + im->xorigin;
3525 gfx_line_fit(im, &x, &y);
3526 cairo_line_to(im->cr, x, y);
3528 double x = ii - 1 + im->xorigin;
3530 ytr(im, im->gdes[i].p_data[ii - 1]);
3531 gfx_line_fit(im, &x, &y);
3532 cairo_move_to(im->cr, x, y);
3533 x = ii + im->xorigin;
3535 gfx_line_fit(im, &x, &y);
3536 cairo_line_to(im->cr, x, y);
3540 double x1 = ii + im->xorigin;
3541 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3543 if (im->slopemode == 0
3544 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3545 double x = ii - 1 + im->xorigin;
3548 gfx_line_fit(im, &x, &y);
3549 cairo_line_to(im->cr, x, y);
3552 gfx_line_fit(im, &x1, &y1);
3553 cairo_line_to(im->cr, x1, y1);
3556 cairo_set_source_rgba(im->cr,
3562 col.blue, im->gdes[i].col.alpha);
3563 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3564 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3565 cairo_stroke(im->cr);
3566 cairo_restore(im->cr);
3572 (double *) malloc(sizeof(double) * im->xsize * 2);
3574 (double *) malloc(sizeof(double) * im->xsize * 2);
3576 (double *) malloc(sizeof(double) * im->xsize * 2);
3578 (double *) malloc(sizeof(double) * im->xsize * 2);
3581 for (ii = 0; ii <= im->xsize; ii++) {
3584 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3590 AlmostEqual2sComplement(foreY
3594 AlmostEqual2sComplement(foreY
3600 if (im->gdes[i].gf != GF_GRAD) {
3605 foreY[cntI], im->gdes[i].col);
3607 lastx = foreX[cntI];
3608 lasty = foreY[cntI];
3610 while (cntI < idxI) {
3615 AlmostEqual2sComplement(foreY
3619 AlmostEqual2sComplement(foreY
3626 if (im->gdes[i].gf != GF_GRAD) {
3627 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3629 gfx_add_rect_fadey(im,
3631 foreX[cntI], foreY[cntI], lasty,
3634 im->gdes[i].gradheight
3636 lastx = foreX[cntI];
3637 lasty = foreY[cntI];
3640 if (im->gdes[i].gf != GF_GRAD) {
3641 gfx_add_point(im, backX[idxI], backY[idxI]);
3643 gfx_add_rect_fadey(im,
3645 backX[idxI], backY[idxI], lasty,
3648 im->gdes[i].gradheight);
3649 lastx = backX[idxI];
3650 lasty = backY[idxI];
3657 AlmostEqual2sComplement(backY
3661 AlmostEqual2sComplement(backY
3668 if (im->gdes[i].gf != GF_GRAD) {
3669 gfx_add_point(im, backX[idxI], backY[idxI]);
3671 gfx_add_rect_fadey(im,
3673 backX[idxI], backY[idxI], lasty,
3676 im->gdes[i].gradheight);
3677 lastx = backX[idxI];
3678 lasty = backY[idxI];
3683 if (im->gdes[i].gf != GF_GRAD)
3690 if (ii == im->xsize)
3692 if (im->slopemode == 0 && ii == 0) {
3695 if (isnan(im->gdes[i].p_data[ii])) {
3699 ytop = ytr(im, im->gdes[i].p_data[ii]);
3700 if (lastgdes && im->gdes[i].stack) {
3701 ybase = ytr(im, lastgdes->p_data[ii]);
3703 ybase = ytr(im, areazero);
3705 if (ybase == ytop) {
3711 double extra = ytop;
3716 if (im->slopemode == 0) {
3717 backY[++idxI] = ybase - 0.2;
3718 backX[idxI] = ii + im->xorigin - 1;
3719 foreY[idxI] = ytop + 0.2;
3720 foreX[idxI] = ii + im->xorigin - 1;
3722 backY[++idxI] = ybase - 0.2;
3723 backX[idxI] = ii + im->xorigin;
3724 foreY[idxI] = ytop + 0.2;
3725 foreX[idxI] = ii + im->xorigin;
3727 /* close up any remaining area */
3732 } /* else GF_LINE */
3734 /* if color != 0x0 */
3735 /* make sure we do not run into trouble when stacking on NaN */
3736 for (ii = 0; ii < im->xsize; ii++) {
3737 if (isnan(im->gdes[i].p_data[ii])) {
3738 if (lastgdes && (im->gdes[i].stack)) {
3739 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3741 im->gdes[i].p_data[ii] = areazero;
3745 lastgdes = &(im->gdes[i]);
3747 } /* GF_AREA, GF_LINE, GF_GRAD */
3750 ("STACK should already be turned into LINE or AREA here");
3755 cairo_reset_clip(im->cr);
3757 /* grid_paint also does the text */
3758 if (!(im->extra_flags & ONLY_GRAPH))
3760 if (!(im->extra_flags & ONLY_GRAPH))
3762 /* the RULES are the last thing to paint ... */
3763 for (i = 0; i < im->gdes_c; i++) {
3765 switch (im->gdes[i].gf) {
3767 if (im->gdes[i].yrule >= im->minval
3768 && im->gdes[i].yrule <= im->maxval) {
3770 if (im->gdes[i].dash) {
3771 cairo_set_dash(im->cr,
3772 im->gdes[i].p_dashes,
3773 im->gdes[i].ndash, im->gdes[i].offset);
3775 gfx_line(im, im->xorigin,
3776 ytr(im, im->gdes[i].yrule),
3777 im->xorigin + im->xsize,
3778 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3779 cairo_stroke(im->cr);
3780 cairo_restore(im->cr);
3784 if (im->gdes[i].xrule >= im->start
3785 && im->gdes[i].xrule <= im->end) {
3787 if (im->gdes[i].dash) {
3788 cairo_set_dash(im->cr,
3789 im->gdes[i].p_dashes,
3790 im->gdes[i].ndash, im->gdes[i].offset);
3793 xtr(im, im->gdes[i].xrule),
3794 im->yorigin, xtr(im,
3798 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3799 cairo_stroke(im->cr);
3800 cairo_restore(im->cr);
3809 switch (im->imgformat) {
3812 cairo_status_t status;
3814 status = strlen(im->graphfile) ?
3815 cairo_surface_write_to_png(im->surface, im->graphfile)
3816 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3819 if (status != CAIRO_STATUS_SUCCESS) {
3820 rrd_set_error("Could not save png to '%s'", im->graphfile);
3826 if (strlen(im->graphfile)) {
3827 cairo_show_page(im->cr);
3829 cairo_surface_finish(im->surface);
3838 /*****************************************************
3840 *****************************************************/
3847 if ((im->gdes = (graph_desc_t *)
3848 rrd_realloc(im->gdes, (im->gdes_c)
3849 * sizeof(graph_desc_t))) == NULL) {
3850 rrd_set_error("realloc graph_descs");
3855 im->gdes[im->gdes_c - 1].step = im->step;
3856 im->gdes[im->gdes_c - 1].step_orig = im->step;
3857 im->gdes[im->gdes_c - 1].stack = 0;
3858 im->gdes[im->gdes_c - 1].linewidth = 0;
3859 im->gdes[im->gdes_c - 1].debug = 0;
3860 im->gdes[im->gdes_c - 1].start = im->start;
3861 im->gdes[im->gdes_c - 1].start_orig = im->start;
3862 im->gdes[im->gdes_c - 1].end = im->end;
3863 im->gdes[im->gdes_c - 1].end_orig = im->end;
3864 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3865 im->gdes[im->gdes_c - 1].data = NULL;
3866 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3867 im->gdes[im->gdes_c - 1].data_first = 0;
3868 im->gdes[im->gdes_c - 1].p_data = NULL;
3869 im->gdes[im->gdes_c - 1].rpnp = NULL;
3870 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3871 im->gdes[im->gdes_c - 1].shift = 0.0;
3872 im->gdes[im->gdes_c - 1].dash = 0;
3873 im->gdes[im->gdes_c - 1].ndash = 0;
3874 im->gdes[im->gdes_c - 1].offset = 0;
3875 im->gdes[im->gdes_c - 1].col.red = 0.0;
3876 im->gdes[im->gdes_c - 1].col.green = 0.0;
3877 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3878 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3879 im->gdes[im->gdes_c - 1].col2.red = 0.0;
3880 im->gdes[im->gdes_c - 1].col2.green = 0.0;
3881 im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3882 im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3883 im->gdes[im->gdes_c - 1].gradheight = 50.0;
3884 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3885 im->gdes[im->gdes_c - 1].format[0] = '\0';
3886 im->gdes[im->gdes_c - 1].strftm = 0;
3887 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3888 im->gdes[im->gdes_c - 1].ds = -1;
3889 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3890 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3891 im->gdes[im->gdes_c - 1].yrule = DNAN;
3892 im->gdes[im->gdes_c - 1].xrule = 0;
3893 im->gdes[im->gdes_c - 1].daemon[0] = 0;
3897 /* copies input untill the first unescaped colon is found
3898 or until input ends. backslashes have to be escaped as well */
3900 const char *const input,
3906 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3907 if (input[inp] == '\\'
3908 && input[inp + 1] != '\0'
3909 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3910 output[outp++] = input[++inp];
3912 output[outp++] = input[inp];
3915 output[outp] = '\0';
3919 /* Now just a wrapper around rrd_graph_v */
3931 rrd_info_t *grinfo = NULL;
3934 grinfo = rrd_graph_v(argc, argv);
3940 if (strcmp(walker->key, "image_info") == 0) {
3943 (char**)rrd_realloc((*prdata),
3944 (prlines + 1) * sizeof(char *))) == NULL) {
3945 rrd_set_error("realloc prdata");
3948 /* imginfo goes to position 0 in the prdata array */
3949 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3950 + 2) * sizeof(char));
3951 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3952 (*prdata)[prlines] = NULL;
3954 /* skip anything else */
3955 walker = walker->next;
3963 if (strcmp(walker->key, "image_width") == 0) {
3964 *xsize = walker->value.u_cnt;
3965 } else if (strcmp(walker->key, "image_height") == 0) {
3966 *ysize = walker->value.u_cnt;
3967 } else if (strcmp(walker->key, "value_min") == 0) {
3968 *ymin = walker->value.u_val;
3969 } else if (strcmp(walker->key, "value_max") == 0) {
3970 *ymax = walker->value.u_val;
3971 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3974 (char**)rrd_realloc((*prdata),
3975 (prlines + 1) * sizeof(char *))) == NULL) {
3976 rrd_set_error("realloc prdata");
3979 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3980 + 2) * sizeof(char));
3981 (*prdata)[prlines] = NULL;
3982 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3983 } else if (strcmp(walker->key, "image") == 0) {
3984 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3985 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3986 rrd_set_error("writing image");
3990 /* skip anything else */
3991 walker = walker->next;
3993 rrd_info_free(grinfo);
3998 /* Some surgery done on this function, it became ridiculously big.
4000 ** - initializing now in rrd_graph_init()
4001 ** - options parsing now in rrd_graph_options()
4002 ** - script parsing now in rrd_graph_script()
4004 rrd_info_t *rrd_graph_v(
4011 rrd_graph_init(&im);
4012 /* a dummy surface so that we can measure text sizes for placements */
4013 old_locale = setlocale(LC_NUMERIC, NULL);
4014 setlocale(LC_NUMERIC, "C");
4015 rrd_graph_options(argc, argv, &im);
4016 if (rrd_test_error()) {
4017 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4018 rrd_info_free(im.grinfo);
4023 if (optind >= argc) {
4024 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4025 rrd_info_free(im.grinfo);
4027 rrd_set_error("missing filename");
4031 if (strlen(argv[optind]) >= MAXPATH) {
4032 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4033 rrd_set_error("filename (including path) too long");
4034 rrd_info_free(im.grinfo);
4039 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4040 im.graphfile[MAXPATH - 1] = '\0';
4042 if (strcmp(im.graphfile, "-") == 0) {
4043 im.graphfile[0] = '\0';
4046 rrd_graph_script(argc, argv, &im, 1);
4047 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4049 if (rrd_test_error()) {
4050 rrd_info_free(im.grinfo);
4055 /* Everything is now read and the actual work can start */
4057 if (graph_paint(&im) == -1) {
4058 rrd_info_free(im.grinfo);
4064 /* The image is generated and needs to be output.
4065 ** Also, if needed, print a line with information about the image.
4073 path = strdup(im.graphfile);
4074 filename = basename(path);
4076 sprintf_alloc(im.imginfo,
4079 im.ximg), (long) (im.zoom * im.yimg));
4080 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4084 if (im.rendered_image) {
4087 img.u_blo.size = im.rendered_image_size;
4088 img.u_blo.ptr = im.rendered_image;
4089 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4098 image_desc_t *im,int prop,char *font, double size ){
4100 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4101 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4102 /* if we already got one, drop it first */
4103 pango_font_description_free(im->text_prop[prop].font_desc);
4104 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4107 im->text_prop[prop].size = size;
4109 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4110 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4114 void rrd_graph_init(
4119 char *deffont = getenv("RRD_DEFAULT_FONT");
4120 static PangoFontMap *fontmap = NULL;
4121 PangoContext *context;
4128 im->daemon_addr = NULL;
4129 im->draw_x_grid = 1;
4130 im->draw_y_grid = 1;
4131 im->draw_3d_border = 2;
4132 im->dynamic_labels = 0;
4133 im->extra_flags = 0;
4134 im->font_options = cairo_font_options_create();
4135 im->forceleftspace = 0;
4138 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4139 im->grid_dash_off = 1;
4140 im->grid_dash_on = 1;
4142 im->grinfo = (rrd_info_t *) NULL;
4143 im->grinfo_current = (rrd_info_t *) NULL;
4144 im->imgformat = IF_PNG;
4147 im->legenddirection = TOP_DOWN;
4148 im->legendheight = 0;
4149 im->legendposition = SOUTH;
4150 im->legendwidth = 0;
4151 im->logarithmic = 0;
4157 im->rendered_image_size = 0;
4158 im->rendered_image = NULL;
4162 im->tabwidth = 40.0;
4163 im->title[0] = '\0';
4164 im->unitsexponent = 9999;
4165 im->unitslength = 6;
4166 im->viewfactor = 1.0;
4167 im->watermark[0] = '\0';
4168 im->with_markup = 0;
4170 im->xlab_user.minsec = -1;
4172 im->xOriginLegend = 0;
4173 im->xOriginLegendY = 0;
4174 im->xOriginLegendY2 = 0;
4175 im->xOriginTitle = 0;
4177 im->ygridstep = DNAN;
4179 im->ylegend[0] = '\0';
4180 im->second_axis_scale = 0; /* 0 disables it */
4181 im->second_axis_shift = 0; /* no shift by default */
4182 im->second_axis_legend[0] = '\0';
4183 im->second_axis_format[0] = '\0';
4185 im->yOriginLegend = 0;
4186 im->yOriginLegendY = 0;
4187 im->yOriginLegendY2 = 0;
4188 im->yOriginTitle = 0;
4192 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4193 im->cr = cairo_create(im->surface);
4195 for (i = 0; i < DIM(text_prop); i++) {
4196 im->text_prop[i].size = -1;
4197 im->text_prop[i].font_desc = NULL;
4198 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4201 if (fontmap == NULL){
4202 fontmap = pango_cairo_font_map_get_default();
4205 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4207 pango_cairo_context_set_resolution(context, 100);
4209 pango_cairo_update_context(im->cr,context);
4211 im->layout = pango_layout_new(context);
4212 g_object_unref (context);
4214 // im->layout = pango_cairo_create_layout(im->cr);
4217 cairo_font_options_set_hint_style
4218 (im->font_options, CAIRO_HINT_STYLE_FULL);
4219 cairo_font_options_set_hint_metrics
4220 (im->font_options, CAIRO_HINT_METRICS_ON);
4221 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4225 for (i = 0; i < DIM(graph_col); i++)
4226 im->graph_col[i] = graph_col[i];
4232 void rrd_graph_options(
4239 char *parsetime_error = NULL;
4240 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4241 time_t start_tmp = 0, end_tmp = 0;
4243 rrd_time_value_t start_tv, end_tv;
4244 long unsigned int color;
4246 /* defines for long options without a short equivalent. should be bytes,
4247 and may not collide with (the ASCII value of) short options */
4248 #define LONGOPT_UNITS_SI 255
4251 struct option long_options[] = {
4252 { "alt-autoscale", no_argument, 0, 'A'},
4253 { "imgformat", required_argument, 0, 'a'},
4254 { "font-smoothing-threshold", required_argument, 0, 'B'},
4255 { "base", required_argument, 0, 'b'},
4256 { "color", required_argument, 0, 'c'},
4257 { "full-size-mode", no_argument, 0, 'D'},
4258 { "daemon", required_argument, 0, 'd'},
4259 { "slope-mode", no_argument, 0, 'E'},
4260 { "end", required_argument, 0, 'e'},
4261 { "force-rules-legend", no_argument, 0, 'F'},
4262 { "imginfo", required_argument, 0, 'f'},
4263 { "graph-render-mode", required_argument, 0, 'G'},
4264 { "no-legend", no_argument, 0, 'g'},
4265 { "height", required_argument, 0, 'h'},
4266 { "no-minor", no_argument, 0, 'I'},
4267 { "interlaced", no_argument, 0, 'i'},
4268 { "alt-autoscale-min", no_argument, 0, 'J'},
4269 { "only-graph", no_argument, 0, 'j'},
4270 { "units-length", required_argument, 0, 'L'},
4271 { "lower-limit", required_argument, 0, 'l'},
4272 { "alt-autoscale-max", no_argument, 0, 'M'},
4273 { "zoom", required_argument, 0, 'm'},
4274 { "no-gridfit", no_argument, 0, 'N'},
4275 { "font", required_argument, 0, 'n'},
4276 { "logarithmic", no_argument, 0, 'o'},
4277 { "pango-markup", no_argument, 0, 'P'},
4278 { "font-render-mode", required_argument, 0, 'R'},
4279 { "rigid", no_argument, 0, 'r'},
4280 { "step", required_argument, 0, 'S'},
4281 { "start", required_argument, 0, 's'},
4282 { "tabwidth", required_argument, 0, 'T'},
4283 { "title", required_argument, 0, 't'},
4284 { "upper-limit", required_argument, 0, 'u'},
4285 { "vertical-label", required_argument, 0, 'v'},
4286 { "watermark", required_argument, 0, 'W'},
4287 { "width", required_argument, 0, 'w'},
4288 { "units-exponent", required_argument, 0, 'X'},
4289 { "x-grid", required_argument, 0, 'x'},
4290 { "alt-y-grid", no_argument, 0, 'Y'},
4291 { "y-grid", required_argument, 0, 'y'},
4292 { "lazy", no_argument, 0, 'z'},
4293 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4294 { "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 */
4295 { "disable-rrdtool-tag",no_argument, 0, 1001},
4296 { "right-axis", required_argument, 0, 1002},
4297 { "right-axis-label", required_argument, 0, 1003},
4298 { "right-axis-format", required_argument, 0, 1004},
4299 { "legend-position", required_argument, 0, 1005},
4300 { "legend-direction", required_argument, 0, 1006},
4301 { "border", required_argument, 0, 1007},
4302 { "grid-dash", required_argument, 0, 1008},
4303 { "dynamic-labels", no_argument, 0, 1009},
4309 opterr = 0; /* initialize getopt */
4310 rrd_parsetime("end-24h", &start_tv);
4311 rrd_parsetime("now", &end_tv);
4313 int option_index = 0;
4315 int col_start, col_end;
4317 opt = getopt_long(argc, argv,
4318 "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",
4319 long_options, &option_index);
4324 im->extra_flags |= NOMINOR;
4327 im->extra_flags |= ALTYGRID;
4330 im->extra_flags |= ALTAUTOSCALE;
4333 im->extra_flags |= ALTAUTOSCALE_MIN;
4336 im->extra_flags |= ALTAUTOSCALE_MAX;
4339 im->extra_flags |= ONLY_GRAPH;
4342 im->extra_flags |= NOLEGEND;
4345 if (strcmp(optarg, "north") == 0) {
4346 im->legendposition = NORTH;
4347 } else if (strcmp(optarg, "west") == 0) {
4348 im->legendposition = WEST;
4349 } else if (strcmp(optarg, "south") == 0) {
4350 im->legendposition = SOUTH;
4351 } else if (strcmp(optarg, "east") == 0) {
4352 im->legendposition = EAST;
4354 rrd_set_error("unknown legend-position '%s'", optarg);
4359 if (strcmp(optarg, "topdown") == 0) {
4360 im->legenddirection = TOP_DOWN;
4361 } else if (strcmp(optarg, "bottomup") == 0) {
4362 im->legenddirection = BOTTOM_UP;
4364 rrd_set_error("unknown legend-position '%s'", optarg);
4369 im->extra_flags |= FORCE_RULES_LEGEND;
4372 im->extra_flags |= NO_RRDTOOL_TAG;
4374 case LONGOPT_UNITS_SI:
4375 if (im->extra_flags & FORCE_UNITS) {
4376 rrd_set_error("--units can only be used once!");
4379 if (strcmp(optarg, "si") == 0)
4380 im->extra_flags |= FORCE_UNITS_SI;
4382 rrd_set_error("invalid argument for --units: %s", optarg);
4387 im->unitsexponent = atoi(optarg);
4390 im->unitslength = atoi(optarg);
4391 im->forceleftspace = 1;
4394 im->tabwidth = atof(optarg);
4397 im->step = atoi(optarg);
4403 im->with_markup = 1;
4406 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4407 rrd_set_error("start time: %s", parsetime_error);
4412 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4413 rrd_set_error("end time: %s", parsetime_error);
4418 if (strcmp(optarg, "none") == 0) {
4419 im->draw_x_grid = 0;
4423 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4425 &im->xlab_user.gridst,
4427 &im->xlab_user.mgridst,
4429 &im->xlab_user.labst,
4430 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4431 strncpy(im->xlab_form, optarg + stroff,
4432 sizeof(im->xlab_form) - 1);
4433 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4435 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4436 rrd_set_error("unknown keyword %s", scan_gtm);
4439 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4441 rrd_set_error("unknown keyword %s", scan_mtm);
4444 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4445 rrd_set_error("unknown keyword %s", scan_ltm);
4448 im->xlab_user.minsec = 1;
4449 im->xlab_user.stst = im->xlab_form;
4451 rrd_set_error("invalid x-grid format");
4457 if (strcmp(optarg, "none") == 0) {
4458 im->draw_y_grid = 0;
4461 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4462 if (im->ygridstep <= 0) {
4463 rrd_set_error("grid step must be > 0");
4465 } else if (im->ylabfact < 1) {
4466 rrd_set_error("label factor must be > 0");
4470 rrd_set_error("invalid y-grid format");
4475 im->draw_3d_border = atoi(optarg);
4477 case 1008: /* grid-dash */
4481 &im->grid_dash_off) != 2) {
4482 rrd_set_error("expected grid-dash format float:float");
4486 case 1009: /* enable dynamic labels */
4487 im->dynamic_labels = 1;
4489 case 1002: /* right y axis */
4493 &im->second_axis_scale,
4494 &im->second_axis_shift) == 2) {
4495 if(im->second_axis_scale==0){
4496 rrd_set_error("the second_axis_scale must not be 0");
4500 rrd_set_error("invalid right-axis format expected scale:shift");
4505 strncpy(im->second_axis_legend,optarg,150);
4506 im->second_axis_legend[150]='\0';
4509 if (bad_format(optarg)){
4510 rrd_set_error("use either %le or %lf formats");
4513 strncpy(im->second_axis_format,optarg,150);
4514 im->second_axis_format[150]='\0';
4517 strncpy(im->ylegend, optarg, 150);
4518 im->ylegend[150] = '\0';
4521 im->maxval = atof(optarg);
4524 im->minval = atof(optarg);
4527 im->base = atol(optarg);
4528 if (im->base != 1024 && im->base != 1000) {
4530 ("the only sensible value for base apart from 1000 is 1024");
4535 long_tmp = atol(optarg);
4536 if (long_tmp < 10) {
4537 rrd_set_error("width below 10 pixels");
4540 im->xsize = long_tmp;
4543 long_tmp = atol(optarg);
4544 if (long_tmp < 10) {
4545 rrd_set_error("height below 10 pixels");
4548 im->ysize = long_tmp;
4551 im->extra_flags |= FULL_SIZE_MODE;
4554 /* interlaced png not supported at the moment */
4560 im->imginfo = optarg;
4564 (im->imgformat = if_conv(optarg)) == -1) {
4565 rrd_set_error("unsupported graphics format '%s'", optarg);
4576 im->logarithmic = 1;
4580 "%10[A-Z]#%n%8lx%n",
4581 col_nam, &col_start, &color, &col_end) == 2) {
4583 int col_len = col_end - col_start;
4588 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4596 (((color & 0xF000) *
4597 0x11000) | ((color & 0x0F00) *
4598 0x01100) | ((color &
4601 ((color & 0x000F) * 0x00011)
4605 color = (color << 8) + 0xff /* shift left by 8 */ ;
4610 rrd_set_error("the color format is #RRGGBB[AA]");
4613 if ((ci = grc_conv(col_nam)) != -1) {
4614 im->graph_col[ci] = gfx_hex_to_col(color);
4616 rrd_set_error("invalid color name '%s'", col_nam);
4620 rrd_set_error("invalid color def format");
4629 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4630 int sindex, propidx;
4632 if ((sindex = text_prop_conv(prop)) != -1) {
4633 for (propidx = sindex;
4634 propidx < TEXT_PROP_LAST; propidx++) {
4636 rrd_set_font_desc(im,propidx,NULL,size);
4638 if ((int) strlen(optarg) > end+2) {
4639 if (optarg[end] == ':') {
4640 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4643 ("expected : after font size in '%s'",
4648 /* only run the for loop for DEFAULT (0) for
4649 all others, we break here. woodo programming */
4650 if (propidx == sindex && sindex != 0)
4654 rrd_set_error("invalid fonttag '%s'", prop);
4658 rrd_set_error("invalid text property format");
4664 im->zoom = atof(optarg);
4665 if (im->zoom <= 0.0) {
4666 rrd_set_error("zoom factor must be > 0");
4671 strncpy(im->title, optarg, 150);
4672 im->title[150] = '\0';
4675 if (strcmp(optarg, "normal") == 0) {
4676 cairo_font_options_set_antialias
4677 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4678 cairo_font_options_set_hint_style
4679 (im->font_options, CAIRO_HINT_STYLE_FULL);
4680 } else if (strcmp(optarg, "light") == 0) {
4681 cairo_font_options_set_antialias
4682 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4683 cairo_font_options_set_hint_style
4684 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4685 } else if (strcmp(optarg, "mono") == 0) {
4686 cairo_font_options_set_antialias
4687 (im->font_options, CAIRO_ANTIALIAS_NONE);
4688 cairo_font_options_set_hint_style
4689 (im->font_options, CAIRO_HINT_STYLE_FULL);
4691 rrd_set_error("unknown font-render-mode '%s'", optarg);
4696 if (strcmp(optarg, "normal") == 0)
4697 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4698 else if (strcmp(optarg, "mono") == 0)
4699 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4701 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4706 /* not supported curently */
4709 strncpy(im->watermark, optarg, 100);
4710 im->watermark[99] = '\0';
4714 if (im->daemon_addr != NULL)
4716 rrd_set_error ("You cannot specify --daemon "
4721 im->daemon_addr = strdup(optarg);
4722 if (im->daemon_addr == NULL)
4724 rrd_set_error("strdup failed");
4732 rrd_set_error("unknown option '%c'", optopt);
4734 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4739 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4740 pango_layout_context_changed(im->layout);
4744 if (im->logarithmic && im->minval <= 0) {
4746 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4750 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4751 /* error string is set in rrd_parsetime.c */
4755 if (start_tmp < 3600 * 24 * 365 * 10) {
4757 ("the first entry to fetch should be after 1980 (%ld)",
4762 if (end_tmp < start_tmp) {
4764 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4768 im->start = start_tmp;
4770 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4773 int rrd_graph_color(
4781 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4783 color = strstr(var, "#");
4784 if (color == NULL) {
4785 if (optional == 0) {
4786 rrd_set_error("Found no color in %s", err);
4793 long unsigned int col;
4795 rest = strstr(color, ":");
4802 sscanf(color, "#%6lx%n", &col, &n);
4803 col = (col << 8) + 0xff /* shift left by 8 */ ;
4805 rrd_set_error("Color problem in %s", err);
4808 sscanf(color, "#%8lx%n", &col, &n);
4812 rrd_set_error("Color problem in %s", err);
4814 if (rrd_test_error())
4816 gdp->col = gfx_hex_to_col(col);
4829 while (*ptr != '\0')
4830 if (*ptr++ == '%') {
4832 /* line cannot end with percent char */
4835 /* '%s', '%S' and '%%' are allowed */
4836 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4838 /* %c is allowed (but use only with vdef!) */
4839 else if (*ptr == 'c') {
4844 /* or else '% 6.2lf' and such are allowed */
4846 /* optional padding character */
4847 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4849 /* This should take care of 'm.n' with all three optional */
4850 while (*ptr >= '0' && *ptr <= '9')
4854 while (*ptr >= '0' && *ptr <= '9')
4856 /* Either 'le', 'lf' or 'lg' must follow here */
4859 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4874 const char *const str)
4876 /* A VDEF currently is either "func" or "param,func"
4877 * so the parsing is rather simple. Change if needed.
4884 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4885 if (n == (int) strlen(str)) { /* matched */
4889 sscanf(str, "%29[A-Z]%n", func, &n);
4890 if (n == (int) strlen(str)) { /* matched */
4894 ("Unknown function string '%s' in VDEF '%s'",
4899 if (!strcmp("PERCENT", func))
4900 gdes->vf.op = VDEF_PERCENT;
4901 else if (!strcmp("PERCENTNAN", func))
4902 gdes->vf.op = VDEF_PERCENTNAN;
4903 else if (!strcmp("MAXIMUM", func))
4904 gdes->vf.op = VDEF_MAXIMUM;
4905 else if (!strcmp("AVERAGE", func))
4906 gdes->vf.op = VDEF_AVERAGE;
4907 else if (!strcmp("STDEV", func))
4908 gdes->vf.op = VDEF_STDEV;
4909 else if (!strcmp("MINIMUM", func))
4910 gdes->vf.op = VDEF_MINIMUM;
4911 else if (!strcmp("TOTAL", func))
4912 gdes->vf.op = VDEF_TOTAL;
4913 else if (!strcmp("FIRST", func))
4914 gdes->vf.op = VDEF_FIRST;
4915 else if (!strcmp("LAST", func))
4916 gdes->vf.op = VDEF_LAST;
4917 else if (!strcmp("LSLSLOPE", func))
4918 gdes->vf.op = VDEF_LSLSLOPE;
4919 else if (!strcmp("LSLINT", func))
4920 gdes->vf.op = VDEF_LSLINT;
4921 else if (!strcmp("LSLCORREL", func))
4922 gdes->vf.op = VDEF_LSLCORREL;
4925 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4928 switch (gdes->vf.op) {
4930 case VDEF_PERCENTNAN:
4931 if (isnan(param)) { /* no parameter given */
4933 ("Function '%s' needs parameter in VDEF '%s'\n",
4937 if (param >= 0.0 && param <= 100.0) {
4938 gdes->vf.param = param;
4939 gdes->vf.val = DNAN; /* undefined */
4940 gdes->vf.when = 0; /* undefined */
4944 ("Parameter '%f' out of range in VDEF '%s'\n",
4945 param, gdes->vname);
4958 case VDEF_LSLCORREL:
4960 gdes->vf.param = DNAN;
4961 gdes->vf.val = DNAN;
4966 ("Function '%s' needs no parameter in VDEF '%s'\n",
4980 graph_desc_t *src, *dst;
4984 dst = &im->gdes[gdi];
4985 src = &im->gdes[dst->vidx];
4986 data = src->data + src->ds;
4988 steps = (src->end - src->start) / src->step;
4991 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4992 src->start, src->end, steps);
4994 switch (dst->vf.op) {
4998 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4999 rrd_set_error("malloc VDEV_PERCENT");
5002 for (step = 0; step < steps; step++) {
5003 array[step] = data[step * src->ds_cnt];
5005 qsort(array, step, sizeof(double), vdef_percent_compar);
5006 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5007 dst->vf.val = array[field];
5008 dst->vf.when = 0; /* no time component */
5012 for (step = 0; step < steps; step++)
5013 printf("DEBUG: %3li:%10.2f %c\n",
5014 step, array[step], step == field ? '*' : ' ');
5018 case VDEF_PERCENTNAN:{
5021 /* count number of "valid" values */
5023 for (step = 0; step < steps; step++) {
5024 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5026 /* and allocate it */
5027 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5028 rrd_set_error("malloc VDEV_PERCENT");
5031 /* and fill it in */
5033 for (step = 0; step < steps; step++) {
5034 if (!isnan(data[step * src->ds_cnt])) {
5035 array[field] = data[step * src->ds_cnt];
5039 qsort(array, nancount, sizeof(double), vdef_percent_compar);
5040 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5041 dst->vf.val = array[field];
5042 dst->vf.when = 0; /* no time component */
5049 while (step != steps && isnan(data[step * src->ds_cnt]))
5051 if (step == steps) {
5056 dst->vf.val = data[step * src->ds_cnt];
5057 dst->vf.when = src->start + (step + 1) * src->step;
5060 while (step != steps) {
5061 if (finite(data[step * src->ds_cnt])) {
5062 if (data[step * src->ds_cnt] > dst->vf.val) {
5063 dst->vf.val = data[step * src->ds_cnt];
5064 dst->vf.when = src->start + (step + 1) * src->step;
5076 double average = 0.0;
5078 for (step = 0; step < steps; step++) {
5079 if (finite(data[step * src->ds_cnt])) {
5080 sum += data[step * src->ds_cnt];
5085 if (dst->vf.op == VDEF_TOTAL) {
5086 dst->vf.val = sum * src->step;
5087 dst->vf.when = 0; /* no time component */
5089 } else if (dst->vf.op == VDEF_AVERAGE) {
5090 dst->vf.val = sum / cnt;
5091 dst->vf.when = 0; /* no time component */
5094 average = sum / cnt;
5096 for (step = 0; step < steps; step++) {
5097 if (finite(data[step * src->ds_cnt])) {
5098 sum += pow((data[step * src->ds_cnt] - average), 2.0);
5101 dst->vf.val = pow(sum / cnt, 0.5);
5102 dst->vf.when = 0; /* no time component */
5114 while (step != steps && isnan(data[step * src->ds_cnt]))
5116 if (step == steps) {
5121 dst->vf.val = data[step * src->ds_cnt];
5122 dst->vf.when = src->start + (step + 1) * src->step;
5125 while (step != steps) {
5126 if (finite(data[step * src->ds_cnt])) {
5127 if (data[step * src->ds_cnt] < dst->vf.val) {
5128 dst->vf.val = data[step * src->ds_cnt];
5129 dst->vf.when = src->start + (step + 1) * src->step;
5137 /* The time value returned here is one step before the
5138 * actual time value. This is the start of the first
5142 while (step != steps && isnan(data[step * src->ds_cnt]))
5144 if (step == steps) { /* all entries were NaN */
5149 dst->vf.val = data[step * src->ds_cnt];
5150 dst->vf.when = src->start + step * src->step;
5155 /* The time value returned here is the
5156 * actual time value. This is the end of the last
5160 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5162 if (step < 0) { /* all entries were NaN */
5167 dst->vf.val = data[step * src->ds_cnt];
5168 dst->vf.when = src->start + (step + 1) * src->step;
5174 case VDEF_LSLCORREL:{
5175 /* Bestfit line by linear least squares method */
5178 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5185 for (step = 0; step < steps; step++) {
5186 if (finite(data[step * src->ds_cnt])) {
5189 SUMxx += step * step;
5190 SUMxy += step * data[step * src->ds_cnt];
5191 SUMy += data[step * src->ds_cnt];
5192 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5196 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5197 y_intercept = (SUMy - slope * SUMx) / cnt;
5200 (SUMx * SUMy) / cnt) /
5202 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5204 if (dst->vf.op == VDEF_LSLSLOPE) {
5205 dst->vf.val = slope;
5208 } else if (dst->vf.op == VDEF_LSLINT) {
5209 dst->vf.val = y_intercept;
5212 } else if (dst->vf.op == VDEF_LSLCORREL) {
5213 dst->vf.val = correl;
5228 /* NaN < -INF < finite_values < INF */
5229 int vdef_percent_compar(
5235 /* Equality is not returned; this doesn't hurt except
5236 * (maybe) for a little performance.
5239 /* First catch NaN values. They are smallest */
5240 if (isnan(*(double *) a))
5242 if (isnan(*(double *) b))
5244 /* NaN doesn't reach this part so INF and -INF are extremes.
5245 * The sign from isinf() is compatible with the sign we return
5247 if (isinf(*(double *) a))
5248 return isinf(*(double *) a);
5249 if (isinf(*(double *) b))
5250 return isinf(*(double *) b);
5251 /* If we reach this, both values must be finite */
5252 if (*(double *) a < *(double *) b)
5261 rrd_info_type_t type,
5262 rrd_infoval_t value)
5264 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5265 if (im->grinfo == NULL) {
5266 im->grinfo = im->grinfo_current;
5277 /* Handling based on
5278 - ANSI C99 Specifications http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5279 - Single UNIX Specification version 2 http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
5280 - POSIX:2001/Single UNIX Specification version 3 http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5281 - POSIX:2008 Specifications http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5282 Specifications tells
5283 "If a conversion specifier is not one of the above, the behavior is undefined."
5286 "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.
5289 "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."
5291 POSIX:2008 introduce more complexe behavior that are not handled here.
5293 According to this, this code will replace:
5294 - % followed by @ by a %@
5295 - % followed by by a %SPACE
5296 - % followed by . by a %.
5297 - % followed by % by a %
5298 - % followed by t by a TAB
5299 - % followed by E then anything by '-'
5300 - % followed by O then anything by '-'
5301 - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5305 for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5306 if (format[j] == '%') {
5307 if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5309 j+=2; /* We skip next 2 following char */
5310 } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5311 (format[j+1] == 'g') || (format[j+1] == 'H') ||
5312 (format[j+1] == 'I') || (format[j+1] == 'm') ||
5313 (format[j+1] == 'M') || (format[j+1] == 'S') ||
5314 (format[j+1] == 'U') || (format[j+1] == 'V') ||
5315 (format[j+1] == 'W') || (format[j+1] == 'y')) {
5317 if (jj < FMT_LEG_LEN) {
5320 j++; /* We skip the following char */
5321 } else if (format[j+1] == 'j') {
5323 if (jj < FMT_LEG_LEN - 1) {
5327 j++; /* We skip the following char */
5328 } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5329 /* Assuming Year on 4 digit */
5331 if (jj < FMT_LEG_LEN - 2) {
5336 j++; /* We skip the following char */
5337 } else if (format[j+1] == 'R') {
5339 if (jj < FMT_LEG_LEN - 3) {
5345 j++; /* We skip the following char */
5346 } else if (format[j+1] == 'T') {
5348 if (jj < FMT_LEG_LEN - 6) {
5357 j++; /* We skip the following char */
5358 } else if (format[j+1] == 'F') {
5360 if (jj < FMT_LEG_LEN - 8) {
5371 j++; /* We skip the following char */
5372 } else if (format[j+1] == 'D') {
5374 if (jj < FMT_LEG_LEN - 6) {
5383 j++; /* We skip the following char */
5384 } else if (format[j+1] == 'n') {
5385 result[jj++] = '\r';
5386 result[jj++] = '\n';
5387 j++; /* We skip the following char */
5388 } else if (format[j+1] == 't') {
5389 result[jj++] = '\t';
5390 j++; /* We skip the following char */
5391 } else if (format[j+1] == '%') {
5393 j++; /* We skip the following char */
5394 } else if (format[j+1] == ' ') {
5395 if (jj < FMT_LEG_LEN - 1) {
5399 j++; /* We skip the following char */
5400 } else if (format[j+1] == '.') {
5401 if (jj < FMT_LEG_LEN - 1) {
5405 j++; /* We skip the following char */
5406 } else if (format[j+1] == '@') {
5407 if (jj < FMT_LEG_LEN - 1) {
5411 j++; /* We skip the following char */
5414 j++; /* We skip the following char */
5417 result[jj++] = format[j];
5420 result[jj] = '\0'; /* We must force the end of the string */