1 /****************************************************************************
2 * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
13 #include "plbasename.h"
18 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
31 #include "rrd_graph.h"
32 #include "rrd_client.h"
34 /* some constant definitions */
38 #ifndef RRD_DEFAULT_FONT
39 /* there is special code later to pick Cour.ttf when running on windows */
40 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
43 text_prop_t text_prop[] = {
44 {8.0, RRD_DEFAULT_FONT,NULL}
46 {9.0, RRD_DEFAULT_FONT,NULL}
48 {7.0, RRD_DEFAULT_FONT,NULL}
50 {8.0, RRD_DEFAULT_FONT,NULL}
52 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
54 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
58 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
60 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
62 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
64 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
66 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
68 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
70 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
72 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
74 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
76 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
77 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
79 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
81 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
83 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
85 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
87 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
90 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
93 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
96 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
98 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
99 365 * 24 * 3600, "%y"}
101 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
104 /* sensible y label intervals ...*/
128 {20.0, {1, 5, 10, 20}
134 {100.0, {1, 2, 5, 10}
137 {200.0, {1, 5, 10, 20}
140 {500.0, {1, 2, 4, 10}
148 gfx_color_t graph_col[] = /* default colors */
150 {1.00, 1.00, 1.00, 1.00}, /* canvas */
151 {0.95, 0.95, 0.95, 1.00}, /* background */
152 {0.81, 0.81, 0.81, 1.00}, /* shade A */
153 {0.62, 0.62, 0.62, 1.00}, /* shade B */
154 {0.56, 0.56, 0.56, 0.75}, /* grid */
155 {0.87, 0.31, 0.31, 0.60}, /* major grid */
156 {0.00, 0.00, 0.00, 1.00}, /* font */
157 {0.50, 0.12, 0.12, 1.00}, /* arrow */
158 {0.12, 0.12, 0.12, 1.00}, /* axis */
159 {0.00, 0.00, 0.00, 1.00} /* frame */
166 # define DPRINT(x) (void)(printf x, printf("\n"))
172 /* initialize with xtr(im,0); */
180 pixie = (double) im->xsize / (double) (im->end - im->start);
183 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
186 /* translate data values into y coordinates */
195 if (!im->logarithmic)
196 pixie = (double) im->ysize / (im->maxval - im->minval);
199 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
201 } else if (!im->logarithmic) {
202 yval = im->yorigin - pixie * (value - im->minval);
204 if (value < im->minval) {
207 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
215 /* conversion function for symbolic entry names */
218 #define conv_if(VV,VVV) \
219 if (strcmp(#VV, string) == 0) return VVV ;
225 conv_if(PRINT, GF_PRINT);
226 conv_if(GPRINT, GF_GPRINT);
227 conv_if(COMMENT, GF_COMMENT);
228 conv_if(HRULE, GF_HRULE);
229 conv_if(VRULE, GF_VRULE);
230 conv_if(LINE, GF_LINE);
231 conv_if(AREA, GF_AREA);
232 conv_if(STACK, GF_STACK);
233 conv_if(TICK, GF_TICK);
234 conv_if(TEXTALIGN, GF_TEXTALIGN);
235 conv_if(DEF, GF_DEF);
236 conv_if(CDEF, GF_CDEF);
237 conv_if(VDEF, GF_VDEF);
238 conv_if(XPORT, GF_XPORT);
239 conv_if(SHIFT, GF_SHIFT);
241 return (enum gf_en)(-1);
244 enum gfx_if_en if_conv(
248 conv_if(PNG, IF_PNG);
249 conv_if(SVG, IF_SVG);
250 conv_if(EPS, IF_EPS);
251 conv_if(PDF, IF_PDF);
253 return (enum gfx_if_en)(-1);
256 enum tmt_en tmt_conv(
260 conv_if(SECOND, TMT_SECOND);
261 conv_if(MINUTE, TMT_MINUTE);
262 conv_if(HOUR, TMT_HOUR);
263 conv_if(DAY, TMT_DAY);
264 conv_if(WEEK, TMT_WEEK);
265 conv_if(MONTH, TMT_MONTH);
266 conv_if(YEAR, TMT_YEAR);
267 return (enum tmt_en)(-1);
270 enum grc_en grc_conv(
274 conv_if(BACK, GRC_BACK);
275 conv_if(CANVAS, GRC_CANVAS);
276 conv_if(SHADEA, GRC_SHADEA);
277 conv_if(SHADEB, GRC_SHADEB);
278 conv_if(GRID, GRC_GRID);
279 conv_if(MGRID, GRC_MGRID);
280 conv_if(FONT, GRC_FONT);
281 conv_if(ARROW, GRC_ARROW);
282 conv_if(AXIS, GRC_AXIS);
283 conv_if(FRAME, GRC_FRAME);
285 return (enum grc_en)(-1);
288 enum text_prop_en text_prop_conv(
292 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
293 conv_if(TITLE, TEXT_PROP_TITLE);
294 conv_if(AXIS, TEXT_PROP_AXIS);
295 conv_if(UNIT, TEXT_PROP_UNIT);
296 conv_if(LEGEND, TEXT_PROP_LEGEND);
297 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
298 return (enum text_prop_en)(-1);
308 cairo_status_t status = (cairo_status_t) 0;
313 if (im->daemon_addr != NULL)
314 free(im->daemon_addr);
316 for (i = 0; i < (unsigned) im->gdes_c; i++) {
317 if (im->gdes[i].data_first) {
318 /* careful here, because a single pointer can occur several times */
319 free(im->gdes[i].data);
320 if (im->gdes[i].ds_namv) {
321 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
322 free(im->gdes[i].ds_namv[ii]);
323 free(im->gdes[i].ds_namv);
326 /* free allocated memory used for dashed lines */
327 if (im->gdes[i].p_dashes != NULL)
328 free(im->gdes[i].p_dashes);
330 free(im->gdes[i].p_data);
331 free(im->gdes[i].rpnp);
334 if (im->font_options)
335 cairo_font_options_destroy(im->font_options);
338 status = cairo_status(im->cr);
339 cairo_destroy(im->cr);
341 if (im->rendered_image) {
342 free(im->rendered_image);
346 g_object_unref (im->layout);
350 cairo_surface_destroy(im->surface);
353 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
354 cairo_status_to_string(status));
359 /* find SI magnitude symbol for the given number*/
361 image_desc_t *im, /* image description */
367 char *symbol[] = { "a", /* 10e-18 Atto */
368 "f", /* 10e-15 Femto */
369 "p", /* 10e-12 Pico */
370 "n", /* 10e-9 Nano */
371 "u", /* 10e-6 Micro */
372 "m", /* 10e-3 Milli */
377 "T", /* 10e12 Tera */
378 "P", /* 10e15 Peta */
385 if (*value == 0.0 || isnan(*value)) {
389 sindex = floor(log(fabs(*value)) / log((double) im->base));
390 *magfact = pow((double) im->base, (double) sindex);
391 (*value) /= (*magfact);
393 if (sindex <= symbcenter && sindex >= -symbcenter) {
394 (*symb_ptr) = symbol[sindex + symbcenter];
401 static char si_symbol[] = {
402 'a', /* 10e-18 Atto */
403 'f', /* 10e-15 Femto */
404 'p', /* 10e-12 Pico */
405 'n', /* 10e-9 Nano */
406 'u', /* 10e-6 Micro */
407 'm', /* 10e-3 Milli */
412 'T', /* 10e12 Tera */
413 'P', /* 10e15 Peta */
416 static const int si_symbcenter = 6;
418 /* find SI magnitude symbol for the numbers on the y-axis*/
420 image_desc_t *im /* image description */
424 double digits, viewdigits = 0;
427 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
428 log((double) im->base));
430 if (im->unitsexponent != 9999) {
431 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
432 viewdigits = floor((double)(im->unitsexponent / 3));
437 im->magfact = pow((double) im->base, digits);
440 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
443 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
445 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
446 ((viewdigits + si_symbcenter) >= 0))
447 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
452 /* move min and max values around to become sensible */
457 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
458 600.0, 500.0, 400.0, 300.0, 250.0,
459 200.0, 125.0, 100.0, 90.0, 80.0,
460 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
461 25.0, 20.0, 10.0, 9.0, 8.0,
462 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
463 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
464 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
467 double scaled_min, scaled_max;
474 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
475 im->minval, im->maxval, im->magfact);
478 if (isnan(im->ygridstep)) {
479 if (im->extra_flags & ALTAUTOSCALE) {
480 /* measure the amplitude of the function. Make sure that
481 graph boundaries are slightly higher then max/min vals
482 so we can see amplitude on the graph */
485 delt = im->maxval - im->minval;
487 fact = 2.0 * pow(10.0,
489 (max(fabs(im->minval), fabs(im->maxval)) /
492 adj = (fact - delt) * 0.55;
495 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
496 im->minval, im->maxval, delt, fact, adj);
501 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
502 /* measure the amplitude of the function. Make sure that
503 graph boundaries are slightly lower than min vals
504 so we can see amplitude on the graph */
505 adj = (im->maxval - im->minval) * 0.1;
507 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
508 /* measure the amplitude of the function. Make sure that
509 graph boundaries are slightly higher than max vals
510 so we can see amplitude on the graph */
511 adj = (im->maxval - im->minval) * 0.1;
514 scaled_min = im->minval / im->magfact;
515 scaled_max = im->maxval / im->magfact;
517 for (i = 1; sensiblevalues[i] > 0; i++) {
518 if (sensiblevalues[i - 1] >= scaled_min &&
519 sensiblevalues[i] <= scaled_min)
520 im->minval = sensiblevalues[i] * (im->magfact);
522 if (-sensiblevalues[i - 1] <= scaled_min &&
523 -sensiblevalues[i] >= scaled_min)
524 im->minval = -sensiblevalues[i - 1] * (im->magfact);
526 if (sensiblevalues[i - 1] >= scaled_max &&
527 sensiblevalues[i] <= scaled_max)
528 im->maxval = sensiblevalues[i - 1] * (im->magfact);
530 if (-sensiblevalues[i - 1] <= scaled_max &&
531 -sensiblevalues[i] >= scaled_max)
532 im->maxval = -sensiblevalues[i] * (im->magfact);
536 /* adjust min and max to the grid definition if there is one */
537 im->minval = (double) im->ylabfact * im->ygridstep *
538 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
539 im->maxval = (double) im->ylabfact * im->ygridstep *
540 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
544 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
545 im->minval, im->maxval, im->magfact);
553 if (isnan(im->minval) || isnan(im->maxval))
556 if (im->logarithmic) {
557 double ya, yb, ypix, ypixfrac;
558 double log10_range = log10(im->maxval) - log10(im->minval);
560 ya = pow((double) 10, floor(log10(im->minval)));
561 while (ya < im->minval)
564 return; /* don't have y=10^x gridline */
566 if (yb <= im->maxval) {
567 /* we have at least 2 y=10^x gridlines.
568 Make sure distance between them in pixels
569 are an integer by expanding im->maxval */
570 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
571 double factor = y_pixel_delta / floor(y_pixel_delta);
572 double new_log10_range = factor * log10_range;
573 double new_ymax_log10 = log10(im->minval) + new_log10_range;
575 im->maxval = pow(10, new_ymax_log10);
576 ytr(im, DNAN); /* reset precalc */
577 log10_range = log10(im->maxval) - log10(im->minval);
579 /* make sure first y=10^x gridline is located on
580 integer pixel position by moving scale slightly
581 downwards (sub-pixel movement) */
582 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
583 ypixfrac = ypix - floor(ypix);
584 if (ypixfrac > 0 && ypixfrac < 1) {
585 double yfrac = ypixfrac / im->ysize;
587 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
588 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
589 ytr(im, DNAN); /* reset precalc */
592 /* Make sure we have an integer pixel distance between
593 each minor gridline */
594 double ypos1 = ytr(im, im->minval);
595 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
596 double y_pixel_delta = ypos1 - ypos2;
597 double factor = y_pixel_delta / floor(y_pixel_delta);
598 double new_range = factor * (im->maxval - im->minval);
599 double gridstep = im->ygrid_scale.gridstep;
600 double minor_y, minor_y_px, minor_y_px_frac;
602 if (im->maxval > 0.0)
603 im->maxval = im->minval + new_range;
605 im->minval = im->maxval - new_range;
606 ytr(im, DNAN); /* reset precalc */
607 /* make sure first minor gridline is on integer pixel y coord */
608 minor_y = gridstep * floor(im->minval / gridstep);
609 while (minor_y < im->minval)
611 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
612 minor_y_px_frac = minor_y_px - floor(minor_y_px);
613 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
614 double yfrac = minor_y_px_frac / im->ysize;
615 double range = im->maxval - im->minval;
617 im->minval = im->minval - yfrac * range;
618 im->maxval = im->maxval - yfrac * range;
619 ytr(im, DNAN); /* reset precalc */
621 calc_horizontal_grid(im); /* recalc with changed im->maxval */
625 /* reduce data reimplementation by Alex */
628 enum cf_en cf, /* which consolidation function ? */
629 unsigned long cur_step, /* step the data currently is in */
630 time_t *start, /* start, end and step as requested ... */
631 time_t *end, /* ... by the application will be ... */
632 unsigned long *step, /* ... adjusted to represent reality */
633 unsigned long *ds_cnt, /* number of data sources in file */
635 { /* two dimensional array containing the data */
636 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
637 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
639 rrd_value_t *srcptr, *dstptr;
641 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
644 row_cnt = ((*end) - (*start)) / cur_step;
650 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
651 row_cnt, reduce_factor, *start, *end, cur_step);
652 for (col = 0; col < row_cnt; col++) {
653 printf("time %10lu: ", *start + (col + 1) * cur_step);
654 for (i = 0; i < *ds_cnt; i++)
655 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
660 /* We have to combine [reduce_factor] rows of the source
661 ** into one row for the destination. Doing this we also
662 ** need to take care to combine the correct rows. First
663 ** alter the start and end time so that they are multiples
664 ** of the new step time. We cannot reduce the amount of
665 ** time so we have to move the end towards the future and
666 ** the start towards the past.
668 end_offset = (*end) % (*step);
669 start_offset = (*start) % (*step);
671 /* If there is a start offset (which cannot be more than
672 ** one destination row), skip the appropriate number of
673 ** source rows and one destination row. The appropriate
674 ** number is what we do know (start_offset/cur_step) of
675 ** the new interval (*step/cur_step aka reduce_factor).
678 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
679 printf("row_cnt before: %lu\n", row_cnt);
682 (*start) = (*start) - start_offset;
683 skiprows = reduce_factor - start_offset / cur_step;
684 srcptr += skiprows * *ds_cnt;
685 for (col = 0; col < (*ds_cnt); col++)
690 printf("row_cnt between: %lu\n", row_cnt);
693 /* At the end we have some rows that are not going to be
694 ** used, the amount is end_offset/cur_step
697 (*end) = (*end) - end_offset + (*step);
698 skiprows = end_offset / cur_step;
702 printf("row_cnt after: %lu\n", row_cnt);
705 /* Sanity check: row_cnt should be multiple of reduce_factor */
706 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
708 if (row_cnt % reduce_factor) {
709 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
710 row_cnt, reduce_factor);
711 printf("BUG in reduce_data()\n");
715 /* Now combine reduce_factor intervals at a time
716 ** into one interval for the destination.
719 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
720 for (col = 0; col < (*ds_cnt); col++) {
721 rrd_value_t newval = DNAN;
722 unsigned long validval = 0;
724 for (i = 0; i < reduce_factor; i++) {
725 if (isnan(srcptr[i * (*ds_cnt) + col])) {
730 newval = srcptr[i * (*ds_cnt) + col];
739 newval += srcptr[i * (*ds_cnt) + col];
742 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
745 /* an interval contains a failure if any subintervals contained a failure */
747 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
750 newval = srcptr[i * (*ds_cnt) + col];
776 srcptr += (*ds_cnt) * reduce_factor;
777 row_cnt -= reduce_factor;
779 /* If we had to alter the endtime, we didn't have enough
780 ** source rows to fill the last row. Fill it with NaN.
783 for (col = 0; col < (*ds_cnt); col++)
786 row_cnt = ((*end) - (*start)) / *step;
788 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
789 row_cnt, *start, *end, *step);
790 for (col = 0; col < row_cnt; col++) {
791 printf("time %10lu: ", *start + (col + 1) * (*step));
792 for (i = 0; i < *ds_cnt; i++)
793 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
800 /* get the data required for the graphs from the
809 /* pull the data from the rrd files ... */
810 for (i = 0; i < (int) im->gdes_c; i++) {
811 /* only GF_DEF elements fetch data */
812 if (im->gdes[i].gf != GF_DEF)
816 /* do we have it already ? */
817 for (ii = 0; ii < i; ii++) {
818 if (im->gdes[ii].gf != GF_DEF)
820 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
821 && (im->gdes[i].cf == im->gdes[ii].cf)
822 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
823 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
824 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
825 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
826 /* OK, the data is already there.
827 ** Just copy the header portion
829 im->gdes[i].start = im->gdes[ii].start;
830 im->gdes[i].end = im->gdes[ii].end;
831 im->gdes[i].step = im->gdes[ii].step;
832 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
833 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
834 im->gdes[i].data = im->gdes[ii].data;
835 im->gdes[i].data_first = 0;
842 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
845 * - a connection to the daemon has been established
846 * - this is the first occurrence of that RRD file
848 if (rrdc_is_connected(im->daemon_addr))
853 for (ii = 0; ii < i; ii++)
855 if (strcmp (im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
864 status = rrdc_flush (im->gdes[i].rrd);
867 rrd_set_error ("rrdc_flush (%s) failed with status %i.",
868 im->gdes[i].rrd, status);
872 } /* if (rrdc_is_connected()) */
874 if ((rrd_fetch_fn(im->gdes[i].rrd,
880 &im->gdes[i].ds_namv,
881 &im->gdes[i].data)) == -1) {
884 im->gdes[i].data_first = 1;
886 if (ft_step < im->gdes[i].step) {
887 reduce_data(im->gdes[i].cf_reduce,
892 &im->gdes[i].ds_cnt, &im->gdes[i].data);
894 im->gdes[i].step = ft_step;
898 /* lets see if the required data source is really there */
899 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
900 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
904 if (im->gdes[i].ds == -1) {
905 rrd_set_error("No DS called '%s' in '%s'",
906 im->gdes[i].ds_nam, im->gdes[i].rrd);
914 /* evaluate the expressions in the CDEF functions */
916 /*************************************************************
918 *************************************************************/
920 long find_var_wrapper(
924 return find_var((image_desc_t *) arg1, key);
927 /* find gdes containing var*/
934 for (ii = 0; ii < im->gdes_c - 1; ii++) {
935 if ((im->gdes[ii].gf == GF_DEF
936 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
937 && (strcmp(im->gdes[ii].vname, key) == 0)) {
944 /* find the greatest common divisor for all the numbers
945 in the 0 terminated num array */
952 for (i = 0; num[i + 1] != 0; i++) {
954 rest = num[i] % num[i + 1];
960 /* return i==0?num[i]:num[i-1]; */
964 /* run the rpn calculator on all the VDEF and CDEF arguments */
971 long *steparray, rpi;
976 rpnstack_init(&rpnstack);
978 for (gdi = 0; gdi < im->gdes_c; gdi++) {
979 /* Look for GF_VDEF and GF_CDEF in the same loop,
980 * so CDEFs can use VDEFs and vice versa
982 switch (im->gdes[gdi].gf) {
986 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
988 /* remove current shift */
989 vdp->start -= vdp->shift;
990 vdp->end -= vdp->shift;
993 if (im->gdes[gdi].shidx >= 0)
994 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
997 vdp->shift = im->gdes[gdi].shval;
999 /* normalize shift to multiple of consolidated step */
1000 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1003 vdp->start += vdp->shift;
1004 vdp->end += vdp->shift;
1008 /* A VDEF has no DS. This also signals other parts
1009 * of rrdtool that this is a VDEF value, not a CDEF.
1011 im->gdes[gdi].ds_cnt = 0;
1012 if (vdef_calc(im, gdi)) {
1013 rrd_set_error("Error processing VDEF '%s'",
1014 im->gdes[gdi].vname);
1015 rpnstack_free(&rpnstack);
1020 im->gdes[gdi].ds_cnt = 1;
1021 im->gdes[gdi].ds = 0;
1022 im->gdes[gdi].data_first = 1;
1023 im->gdes[gdi].start = 0;
1024 im->gdes[gdi].end = 0;
1029 /* Find the variables in the expression.
1030 * - VDEF variables are substituted by their values
1031 * and the opcode is changed into OP_NUMBER.
1032 * - CDEF variables are analized for their step size,
1033 * the lowest common denominator of all the step
1034 * sizes of the data sources involved is calculated
1035 * and the resulting number is the step size for the
1036 * resulting data source.
1038 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1039 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1040 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1041 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1043 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1046 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1047 im->gdes[gdi].vname, im->gdes[ptr].vname);
1048 printf("DEBUG: value from vdef is %f\n",
1049 im->gdes[ptr].vf.val);
1051 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1052 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1053 } else { /* normal variables and PREF(variables) */
1055 /* add one entry to the array that keeps track of the step sizes of the
1056 * data sources going into the CDEF. */
1058 (long*)rrd_realloc(steparray,
1060 1) * sizeof(*steparray))) == NULL) {
1061 rrd_set_error("realloc steparray");
1062 rpnstack_free(&rpnstack);
1066 steparray[stepcnt - 1] = im->gdes[ptr].step;
1068 /* adjust start and end of cdef (gdi) so
1069 * that it runs from the latest start point
1070 * to the earliest endpoint of any of the
1071 * rras involved (ptr)
1074 if (im->gdes[gdi].start < im->gdes[ptr].start)
1075 im->gdes[gdi].start = im->gdes[ptr].start;
1077 if (im->gdes[gdi].end == 0 ||
1078 im->gdes[gdi].end > im->gdes[ptr].end)
1079 im->gdes[gdi].end = im->gdes[ptr].end;
1081 /* store pointer to the first element of
1082 * the rra providing data for variable,
1083 * further save step size and data source
1086 im->gdes[gdi].rpnp[rpi].data =
1087 im->gdes[ptr].data + im->gdes[ptr].ds;
1088 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1089 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1091 /* backoff the *.data ptr; this is done so
1092 * rpncalc() function doesn't have to treat
1093 * the first case differently
1095 } /* if ds_cnt != 0 */
1096 } /* if OP_VARIABLE */
1097 } /* loop through all rpi */
1099 /* move the data pointers to the correct period */
1100 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1101 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1102 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1103 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1105 im->gdes[gdi].start - im->gdes[ptr].start;
1108 im->gdes[gdi].rpnp[rpi].data +=
1109 (diff / im->gdes[ptr].step) *
1110 im->gdes[ptr].ds_cnt;
1114 if (steparray == NULL) {
1115 rrd_set_error("rpn expressions without DEF"
1116 " or CDEF variables are not supported");
1117 rpnstack_free(&rpnstack);
1120 steparray[stepcnt] = 0;
1121 /* Now find the resulting step. All steps in all
1122 * used RRAs have to be visited
1124 im->gdes[gdi].step = lcd(steparray);
1126 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1127 im->gdes[gdi].start)
1128 / im->gdes[gdi].step)
1129 * sizeof(double))) == NULL) {
1130 rrd_set_error("malloc im->gdes[gdi].data");
1131 rpnstack_free(&rpnstack);
1135 /* Step through the new cdef results array and
1136 * calculate the values
1138 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1139 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1140 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1142 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1143 * in this case we are advancing by timesteps;
1144 * we use the fact that time_t is a synonym for long
1146 if (rpn_calc(rpnp, &rpnstack, (long) now,
1147 im->gdes[gdi].data, ++dataidx) == -1) {
1148 /* rpn_calc sets the error string */
1149 rpnstack_free(&rpnstack);
1152 } /* enumerate over time steps within a CDEF */
1157 } /* enumerate over CDEFs */
1158 rpnstack_free(&rpnstack);
1162 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1163 /* yes we are loosing precision by doing tos with floats instead of doubles
1164 but it seems more stable this way. */
1166 static int AlmostEqual2sComplement(
1172 int aInt = *(int *) &A;
1173 int bInt = *(int *) &B;
1176 /* Make sure maxUlps is non-negative and small enough that the
1177 default NAN won't compare as equal to anything. */
1179 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1181 /* Make aInt lexicographically ordered as a twos-complement int */
1184 aInt = 0x80000000l - aInt;
1186 /* Make bInt lexicographically ordered as a twos-complement int */
1189 bInt = 0x80000000l - bInt;
1191 intDiff = abs(aInt - bInt);
1193 if (intDiff <= maxUlps)
1199 /* massage data so, that we get one value for each x coordinate in the graph */
1204 double pixstep = (double) (im->end - im->start)
1205 / (double) im->xsize; /* how much time
1206 passes in one pixel */
1208 double minval = DNAN, maxval = DNAN;
1210 unsigned long gr_time;
1212 /* memory for the processed data */
1213 for (i = 0; i < im->gdes_c; i++) {
1214 if ((im->gdes[i].gf == GF_LINE) ||
1215 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1216 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1217 * sizeof(rrd_value_t))) == NULL) {
1218 rrd_set_error("malloc data_proc");
1224 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1227 gr_time = im->start + pixstep * i; /* time of the current step */
1230 for (ii = 0; ii < im->gdes_c; ii++) {
1233 switch (im->gdes[ii].gf) {
1237 if (!im->gdes[ii].stack)
1239 value = im->gdes[ii].yrule;
1240 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1241 /* The time of the data doesn't necessarily match
1242 ** the time of the graph. Beware.
1244 vidx = im->gdes[ii].vidx;
1245 if (im->gdes[vidx].gf == GF_VDEF) {
1246 value = im->gdes[vidx].vf.val;
1248 if (((long int) gr_time >=
1249 (long int) im->gdes[vidx].start)
1250 && ((long int) gr_time <=
1251 (long int) im->gdes[vidx].end)) {
1252 value = im->gdes[vidx].data[(unsigned long)
1258 im->gdes[vidx].step)
1259 * im->gdes[vidx].ds_cnt +
1266 if (!isnan(value)) {
1268 im->gdes[ii].p_data[i] = paintval;
1269 /* GF_TICK: the data values are not
1270 ** relevant for min and max
1272 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1273 if ((isnan(minval) || paintval < minval) &&
1274 !(im->logarithmic && paintval <= 0.0))
1276 if (isnan(maxval) || paintval > maxval)
1280 im->gdes[ii].p_data[i] = DNAN;
1285 ("STACK should already be turned into LINE or AREA here");
1294 /* if min or max have not been asigned a value this is because
1295 there was no data in the graph ... this is not good ...
1296 lets set these to dummy values then ... */
1298 if (im->logarithmic) {
1299 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1300 minval = 0.0; /* catching this right away below */
1303 /* in logarithm mode, where minval is smaller or equal
1304 to 0 make the beast just way smaller than maxval */
1306 minval = maxval / 10e8;
1309 if (isnan(minval) || isnan(maxval)) {
1315 /* adjust min and max values given by the user */
1316 /* for logscale we add something on top */
1317 if (isnan(im->minval)
1318 || ((!im->rigid) && im->minval > minval)
1320 if (im->logarithmic)
1321 im->minval = minval / 2.0;
1323 im->minval = minval;
1325 if (isnan(im->maxval)
1326 || (!im->rigid && im->maxval < maxval)
1328 if (im->logarithmic)
1329 im->maxval = maxval * 2.0;
1331 im->maxval = maxval;
1334 /* make sure min is smaller than max */
1335 if (im->minval > im->maxval) {
1337 im->minval = 0.99 * im->maxval;
1339 im->minval = 1.01 * im->maxval;
1342 /* make sure min and max are not equal */
1343 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1349 /* make sure min and max are not both zero */
1350 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1359 /* identify the point where the first gridline, label ... gets placed */
1361 time_t find_first_time(
1362 time_t start, /* what is the initial time */
1363 enum tmt_en baseint, /* what is the basic interval */
1364 long basestep /* how many if these do we jump a time */
1369 localtime_r(&start, &tm);
1373 tm. tm_sec -= tm.tm_sec % basestep;
1378 tm. tm_min -= tm.tm_min % basestep;
1384 tm. tm_hour -= tm.tm_hour % basestep;
1388 /* we do NOT look at the basestep for this ... */
1395 /* we do NOT look at the basestep for this ... */
1399 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1401 if (tm.tm_wday == 0)
1402 tm. tm_mday -= 7; /* we want the *previous* monday */
1410 tm. tm_mon -= tm.tm_mon % basestep;
1421 tm.tm_year + 1900) %basestep;
1427 /* identify the point where the next gridline, label ... gets placed */
1428 time_t find_next_time(
1429 time_t current, /* what is the initial time */
1430 enum tmt_en baseint, /* what is the basic interval */
1431 long basestep /* how many if these do we jump a time */
1437 localtime_r(¤t, &tm);
1442 tm. tm_sec += basestep;
1446 tm. tm_min += basestep;
1450 tm. tm_hour += basestep;
1454 tm. tm_mday += basestep;
1458 tm. tm_mday += 7 * basestep;
1462 tm. tm_mon += basestep;
1466 tm. tm_year += basestep;
1468 madetime = mktime(&tm);
1469 } while (madetime == -1); /* this is necessary to skip impssible times
1470 like the daylight saving time skips */
1476 /* calculate values required for PRINT and GPRINT functions */
1481 long i, ii, validsteps;
1484 int graphelement = 0;
1487 double magfact = -1;
1492 /* wow initializing tmvdef is quite a task :-) */
1493 time_t now = time(NULL);
1495 localtime_r(&now, &tmvdef);
1496 for (i = 0; i < im->gdes_c; i++) {
1497 vidx = im->gdes[i].vidx;
1498 switch (im->gdes[i].gf) {
1501 /* PRINT and GPRINT can now print VDEF generated values.
1502 * There's no need to do any calculations on them as these
1503 * calculations were already made.
1505 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1506 printval = im->gdes[vidx].vf.val;
1507 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1508 } else { /* need to calculate max,min,avg etcetera */
1509 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1510 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1513 for (ii = im->gdes[vidx].ds;
1514 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1515 if (!finite(im->gdes[vidx].data[ii]))
1517 if (isnan(printval)) {
1518 printval = im->gdes[vidx].data[ii];
1523 switch (im->gdes[i].cf) {
1527 case CF_DEVSEASONAL:
1531 printval += im->gdes[vidx].data[ii];
1534 printval = min(printval, im->gdes[vidx].data[ii]);
1538 printval = max(printval, im->gdes[vidx].data[ii]);
1541 printval = im->gdes[vidx].data[ii];
1544 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1545 if (validsteps > 1) {
1546 printval = (printval / validsteps);
1549 } /* prepare printval */
1551 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1552 /* Magfact is set to -1 upon entry to print_calc. If it
1553 * is still less than 0, then we need to run auto_scale.
1554 * Otherwise, put the value into the correct units. If
1555 * the value is 0, then do not set the symbol or magnification
1556 * so next the calculation will be performed again. */
1557 if (magfact < 0.0) {
1558 auto_scale(im, &printval, &si_symb, &magfact);
1559 if (printval == 0.0)
1562 printval /= magfact;
1564 *(++percent_s) = 's';
1565 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1566 auto_scale(im, &printval, &si_symb, &magfact);
1569 if (im->gdes[i].gf == GF_PRINT) {
1570 rrd_infoval_t prline;
1572 if (im->gdes[i].strftm) {
1573 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1574 strftime(prline.u_str,
1575 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1576 } else if (bad_format(im->gdes[i].format)) {
1578 ("bad format for PRINT in '%s'", im->gdes[i].format);
1582 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1586 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1591 if (im->gdes[i].strftm) {
1592 strftime(im->gdes[i].legend,
1593 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1595 if (bad_format(im->gdes[i].format)) {
1597 ("bad format for GPRINT in '%s'",
1598 im->gdes[i].format);
1601 #ifdef HAVE_SNPRINTF
1602 snprintf(im->gdes[i].legend,
1604 im->gdes[i].format, printval, si_symb);
1606 sprintf(im->gdes[i].legend,
1607 im->gdes[i].format, printval, si_symb);
1619 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1620 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1625 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1626 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1635 #ifdef WITH_PIECHART
1643 ("STACK should already be turned into LINE or AREA here");
1648 return graphelement;
1653 /* place legends with color spots */
1659 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1660 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1661 int fill = 0, fill_last;
1662 double legendwidth; // = im->ximg - 2 * border;
1664 double leg_x = border;
1665 int leg_y = 0; //im->yimg;
1666 int leg_y_prev = 0; // im->yimg;
1669 int i, ii, mark = 0;
1670 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1673 char saved_legend[FMT_LEG_LEN + 5];
1679 legendwidth = im->legendwidth - 2 * border;
1683 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1684 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1685 rrd_set_error("malloc for legspace");
1689 for (i = 0; i < im->gdes_c; i++) {
1690 char prt_fctn; /*special printfunctions */
1692 strcpy(saved_legend, im->gdes[i].legend);
1696 /* hide legends for rules which are not displayed */
1697 if (im->gdes[i].gf == GF_TEXTALIGN) {
1698 default_txtalign = im->gdes[i].txtalign;
1701 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1702 if (im->gdes[i].gf == GF_HRULE
1703 && (im->gdes[i].yrule <
1704 im->minval || im->gdes[i].yrule > im->maxval))
1705 im->gdes[i].legend[0] = '\0';
1706 if (im->gdes[i].gf == GF_VRULE
1707 && (im->gdes[i].xrule <
1708 im->start || im->gdes[i].xrule > im->end))
1709 im->gdes[i].legend[0] = '\0';
1712 /* turn \\t into tab */
1713 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1714 memmove(tab, tab + 1, strlen(tab));
1718 leg_cc = strlen(im->gdes[i].legend);
1719 /* is there a controle code at the end of the legend string ? */
1720 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1721 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1723 im->gdes[i].legend[leg_cc] = '\0';
1727 /* only valid control codes */
1728 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1732 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1735 ("Unknown control code at the end of '%s\\%c'",
1736 im->gdes[i].legend, prt_fctn);
1740 if (prt_fctn == 'n') {
1744 /* remove exess space from the end of the legend for \g */
1745 while (prt_fctn == 'g' &&
1746 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1748 im->gdes[i].legend[leg_cc] = '\0';
1753 /* no interleg space if string ends in \g */
1754 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1756 fill += legspace[i];
1759 gfx_get_text_width(im,
1765 im->tabwidth, im->gdes[i].legend);
1770 /* who said there was a special tag ... ? */
1771 if (prt_fctn == 'g') {
1775 if (prt_fctn == '\0') {
1776 if(calc_width && (fill > legendwidth)){
1779 if (i == im->gdes_c - 1 || fill > legendwidth) {
1780 /* just one legend item is left right or center */
1781 switch (default_txtalign) {
1796 /* is it time to place the legends ? */
1797 if (fill > legendwidth) {
1805 if (leg_c == 1 && prt_fctn == 'j') {
1810 if (prt_fctn != '\0') {
1812 if (leg_c >= 2 && prt_fctn == 'j') {
1813 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1817 if (prt_fctn == 'c')
1818 leg_x = (double)(legendwidth - fill) / 2.0;
1819 if (prt_fctn == 'r')
1820 leg_x = legendwidth - fill - border;
1821 for (ii = mark; ii <= i; ii++) {
1822 if (im->gdes[ii].legend[0] == '\0')
1823 continue; /* skip empty legends */
1824 im->gdes[ii].leg_x = leg_x;
1825 im->gdes[ii].leg_y = leg_y + border;
1827 (double)gfx_get_text_width(im, leg_x,
1832 im->tabwidth, im->gdes[ii].legend)
1833 +(double)legspace[ii]
1837 if (leg_x > border || prt_fctn == 's')
1838 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1839 if (prt_fctn == 's')
1840 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1842 if(calc_width && (fill > legendwidth)){
1851 strcpy(im->gdes[i].legend, saved_legend);
1856 im->legendwidth = legendwidth + 2 * border;
1859 im->legendheight = leg_y + border * 0.6;
1866 /* create a grid on the graph. it determines what to do
1867 from the values of xsize, start and end */
1869 /* the xaxis labels are determined from the number of seconds per pixel
1870 in the requested graph */
1872 int calc_horizontal_grid(
1880 int decimals, fractionals;
1882 im->ygrid_scale.labfact = 2;
1883 range = im->maxval - im->minval;
1884 scaledrange = range / im->magfact;
1885 /* does the scale of this graph make it impossible to put lines
1886 on it? If so, give up. */
1887 if (isnan(scaledrange)) {
1891 /* find grid spaceing */
1893 if (isnan(im->ygridstep)) {
1894 if (im->extra_flags & ALTYGRID) {
1895 /* find the value with max number of digits. Get number of digits */
1898 (max(fabs(im->maxval), fabs(im->minval)) *
1899 im->viewfactor / im->magfact));
1900 if (decimals <= 0) /* everything is small. make place for zero */
1902 im->ygrid_scale.gridstep =
1904 floor(log10(range * im->viewfactor / im->magfact))) /
1905 im->viewfactor * im->magfact;
1906 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1907 im->ygrid_scale.gridstep = 0.1;
1908 /* should have at least 5 lines but no more then 15 */
1909 if (range / im->ygrid_scale.gridstep < 5
1910 && im->ygrid_scale.gridstep >= 30)
1911 im->ygrid_scale.gridstep /= 10;
1912 if (range / im->ygrid_scale.gridstep > 15)
1913 im->ygrid_scale.gridstep *= 10;
1914 if (range / im->ygrid_scale.gridstep > 5) {
1915 im->ygrid_scale.labfact = 1;
1916 if (range / im->ygrid_scale.gridstep > 8
1917 || im->ygrid_scale.gridstep <
1918 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1919 im->ygrid_scale.labfact = 2;
1921 im->ygrid_scale.gridstep /= 5;
1922 im->ygrid_scale.labfact = 5;
1926 (im->ygrid_scale.gridstep *
1927 (double) im->ygrid_scale.labfact * im->viewfactor /
1929 if (fractionals < 0) { /* small amplitude. */
1930 int len = decimals - fractionals + 1;
1932 if (im->unitslength < len + 2)
1933 im->unitslength = len + 2;
1934 sprintf(im->ygrid_scale.labfmt,
1936 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1938 int len = decimals + 1;
1940 if (im->unitslength < len + 2)
1941 im->unitslength = len + 2;
1942 sprintf(im->ygrid_scale.labfmt,
1943 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1945 } else { /* classic rrd grid */
1946 for (i = 0; ylab[i].grid > 0; i++) {
1947 pixel = im->ysize / (scaledrange / ylab[i].grid);
1953 for (i = 0; i < 4; i++) {
1954 if (pixel * ylab[gridind].lfac[i] >=
1955 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1956 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1961 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1964 im->ygrid_scale.gridstep = im->ygridstep;
1965 im->ygrid_scale.labfact = im->ylabfact;
1970 int draw_horizontal_grid(
1976 char graph_label[100];
1978 double X0 = im->xorigin;
1979 double X1 = im->xorigin + im->xsize;
1980 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1981 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1983 double second_axis_magfact = 0;
1984 char *second_axis_symb = "";
1987 im->ygrid_scale.gridstep /
1988 (double) im->magfact * (double) im->viewfactor;
1989 MaxY = scaledstep * (double) egrid;
1990 for (i = sgrid; i <= egrid; i++) {
1992 im->ygrid_scale.gridstep * i);
1994 im->ygrid_scale.gridstep * (i + 1));
1996 if (floor(Y0 + 0.5) >=
1997 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1998 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1999 with the chosen settings. Add a label if required by settings, or if
2000 there is only one label so far and the next grid line is out of bounds. */
2001 if (i % im->ygrid_scale.labfact == 0
2003 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2004 if (im->symbol == ' ') {
2005 if (im->extra_flags & ALTYGRID) {
2006 sprintf(graph_label,
2007 im->ygrid_scale.labfmt,
2008 scaledstep * (double) i);
2011 sprintf(graph_label, "%4.1f",
2012 scaledstep * (double) i);
2014 sprintf(graph_label, "%4.0f",
2015 scaledstep * (double) i);
2019 char sisym = (i == 0 ? ' ' : im->symbol);
2021 if (im->extra_flags & ALTYGRID) {
2022 sprintf(graph_label,
2023 im->ygrid_scale.labfmt,
2024 scaledstep * (double) i, sisym);
2027 sprintf(graph_label, "%4.1f %c",
2028 scaledstep * (double) i, sisym);
2030 sprintf(graph_label, "%4.0f %c",
2031 scaledstep * (double) i, sisym);
2036 if (im->second_axis_scale != 0){
2037 char graph_label_right[100];
2038 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2039 if (im->second_axis_format[0] == '\0'){
2040 if (!second_axis_magfact){
2041 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2042 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2044 sval /= second_axis_magfact;
2047 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2049 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2053 sprintf(graph_label_right,im->second_axis_format,sval);
2057 im->graph_col[GRC_FONT],
2058 im->text_prop[TEXT_PROP_AXIS].font_desc,
2059 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2060 graph_label_right );
2066 text_prop[TEXT_PROP_AXIS].
2068 im->graph_col[GRC_FONT],
2070 text_prop[TEXT_PROP_AXIS].
2073 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2074 gfx_line(im, X0 - 2, Y0, X0, Y0,
2075 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2076 gfx_line(im, X1, Y0, X1 + 2, Y0,
2077 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2078 gfx_dashed_line(im, X0 - 2, Y0,
2084 im->grid_dash_on, im->grid_dash_off);
2085 } else if (!(im->extra_flags & NOMINOR)) {
2088 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2089 gfx_line(im, X1, Y0, X1 + 2, Y0,
2090 GRIDWIDTH, im->graph_col[GRC_GRID]);
2091 gfx_dashed_line(im, X0 - 1, Y0,
2095 graph_col[GRC_GRID],
2096 im->grid_dash_on, im->grid_dash_off);
2103 /* this is frexp for base 10 */
2114 iexp = floor(log((double)fabs(x)) / log((double)10));
2115 mnt = x / pow(10.0, iexp);
2118 mnt = x / pow(10.0, iexp);
2125 /* logaritmic horizontal grid */
2126 int horizontal_log_grid(
2130 double yloglab[][10] = {
2132 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2134 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2136 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2153 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2155 int i, j, val_exp, min_exp;
2156 double nex; /* number of decades in data */
2157 double logscale; /* scale in logarithmic space */
2158 int exfrac = 1; /* decade spacing */
2159 int mid = -1; /* row in yloglab for major grid */
2160 double mspac; /* smallest major grid spacing (pixels) */
2161 int flab; /* first value in yloglab to use */
2162 double value, tmp, pre_value;
2164 char graph_label[100];
2166 nex = log10(im->maxval / im->minval);
2167 logscale = im->ysize / nex;
2168 /* major spacing for data with high dynamic range */
2169 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2176 /* major spacing for less dynamic data */
2178 /* search best row in yloglab */
2180 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2181 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2184 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2187 /* find first value in yloglab */
2189 yloglab[mid][flab] < 10
2190 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2191 if (yloglab[mid][flab] == 10.0) {
2196 if (val_exp % exfrac)
2197 val_exp += abs(-val_exp % exfrac);
2199 X1 = im->xorigin + im->xsize;
2204 value = yloglab[mid][flab] * pow(10.0, val_exp);
2205 if (AlmostEqual2sComplement(value, pre_value, 4))
2206 break; /* it seems we are not converging */
2208 Y0 = ytr(im, value);
2209 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2211 /* major grid line */
2213 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2214 gfx_line(im, X1, Y0, X1 + 2, Y0,
2215 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2216 gfx_dashed_line(im, X0 - 2, Y0,
2221 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2223 if (im->extra_flags & FORCE_UNITS_SI) {
2228 scale = floor(val_exp / 3.0);
2230 pvalue = pow(10.0, val_exp % 3);
2232 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2233 pvalue *= yloglab[mid][flab];
2234 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2235 && ((scale + si_symbcenter) >= 0))
2236 symbol = si_symbol[scale + si_symbcenter];
2239 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2241 sprintf(graph_label, "%3.0e", value);
2243 if (im->second_axis_scale != 0){
2244 char graph_label_right[100];
2245 double sval = value*im->second_axis_scale+im->second_axis_shift;
2246 if (im->second_axis_format[0] == '\0'){
2247 if (im->extra_flags & FORCE_UNITS_SI) {
2250 auto_scale(im,&sval,&symb,&mfac);
2251 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2254 sprintf(graph_label_right,"%3.0e", sval);
2258 sprintf(graph_label_right,im->second_axis_format,sval);
2263 im->graph_col[GRC_FONT],
2264 im->text_prop[TEXT_PROP_AXIS].font_desc,
2265 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2266 graph_label_right );
2272 text_prop[TEXT_PROP_AXIS].
2274 im->graph_col[GRC_FONT],
2276 text_prop[TEXT_PROP_AXIS].
2279 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2281 if (mid < 4 && exfrac == 1) {
2282 /* find first and last minor line behind current major line
2283 * i is the first line and j tha last */
2285 min_exp = val_exp - 1;
2286 for (i = 1; yloglab[mid][i] < 10.0; i++);
2287 i = yloglab[mid][i - 1] + 1;
2291 i = yloglab[mid][flab - 1] + 1;
2292 j = yloglab[mid][flab];
2295 /* draw minor lines below current major line */
2296 for (; i < j; i++) {
2298 value = i * pow(10.0, min_exp);
2299 if (value < im->minval)
2301 Y0 = ytr(im, value);
2302 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2307 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2308 gfx_line(im, X1, Y0, X1 + 2, Y0,
2309 GRIDWIDTH, im->graph_col[GRC_GRID]);
2310 gfx_dashed_line(im, X0 - 1, Y0,
2314 graph_col[GRC_GRID],
2315 im->grid_dash_on, im->grid_dash_off);
2317 } else if (exfrac > 1) {
2318 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2319 value = pow(10.0, i);
2320 if (value < im->minval)
2322 Y0 = ytr(im, value);
2323 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2328 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2329 gfx_line(im, X1, Y0, X1 + 2, Y0,
2330 GRIDWIDTH, im->graph_col[GRC_GRID]);
2331 gfx_dashed_line(im, X0 - 1, Y0,
2335 graph_col[GRC_GRID],
2336 im->grid_dash_on, im->grid_dash_off);
2341 if (yloglab[mid][++flab] == 10.0) {
2347 /* draw minor lines after highest major line */
2348 if (mid < 4 && exfrac == 1) {
2349 /* find first and last minor line below current major line
2350 * i is the first line and j tha last */
2352 min_exp = val_exp - 1;
2353 for (i = 1; yloglab[mid][i] < 10.0; i++);
2354 i = yloglab[mid][i - 1] + 1;
2358 i = yloglab[mid][flab - 1] + 1;
2359 j = yloglab[mid][flab];
2362 /* draw minor lines below current major line */
2363 for (; i < j; i++) {
2365 value = i * pow(10.0, min_exp);
2366 if (value < im->minval)
2368 Y0 = ytr(im, value);
2369 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2373 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2374 gfx_line(im, X1, Y0, X1 + 2, Y0,
2375 GRIDWIDTH, im->graph_col[GRC_GRID]);
2376 gfx_dashed_line(im, X0 - 1, Y0,
2380 graph_col[GRC_GRID],
2381 im->grid_dash_on, im->grid_dash_off);
2384 /* fancy minor gridlines */
2385 else if (exfrac > 1) {
2386 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2387 value = pow(10.0, i);
2388 if (value < im->minval)
2390 Y0 = ytr(im, value);
2391 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2395 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2396 gfx_line(im, X1, Y0, X1 + 2, Y0,
2397 GRIDWIDTH, im->graph_col[GRC_GRID]);
2398 gfx_dashed_line(im, X0 - 1, Y0,
2402 graph_col[GRC_GRID],
2403 im->grid_dash_on, im->grid_dash_off);
2414 int xlab_sel; /* which sort of label and grid ? */
2415 time_t ti, tilab, timajor;
2417 char graph_label[100];
2418 double X0, Y0, Y1; /* points for filled graph and more */
2421 /* the type of time grid is determined by finding
2422 the number of seconds per pixel in the graph */
2423 if (im->xlab_user.minsec == -1) {
2424 factor = (im->end - im->start) / im->xsize;
2426 while (xlab[xlab_sel + 1].minsec !=
2427 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2429 } /* pick the last one */
2430 while (xlab[xlab_sel - 1].minsec ==
2431 xlab[xlab_sel].minsec
2432 && xlab[xlab_sel].length > (im->end - im->start)) {
2434 } /* go back to the smallest size */
2435 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2436 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2437 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2438 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2439 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2440 im->xlab_user.labst = xlab[xlab_sel].labst;
2441 im->xlab_user.precis = xlab[xlab_sel].precis;
2442 im->xlab_user.stst = xlab[xlab_sel].stst;
2445 /* y coords are the same for every line ... */
2447 Y1 = im->yorigin - im->ysize;
2448 /* paint the minor grid */
2449 if (!(im->extra_flags & NOMINOR)) {
2450 for (ti = find_first_time(im->start,
2458 find_first_time(im->start,
2465 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2467 /* are we inside the graph ? */
2468 if (ti < im->start || ti > im->end)
2470 while (timajor < ti) {
2471 timajor = find_next_time(timajor,
2474 mgridtm, im->xlab_user.mgridst);
2477 continue; /* skip as falls on major grid line */
2479 gfx_line(im, X0, Y1 - 2, X0, Y1,
2480 GRIDWIDTH, im->graph_col[GRC_GRID]);
2481 gfx_line(im, X0, Y0, X0, Y0 + 2,
2482 GRIDWIDTH, im->graph_col[GRC_GRID]);
2483 gfx_dashed_line(im, X0, Y0 + 1, X0,
2486 graph_col[GRC_GRID],
2487 im->grid_dash_on, im->grid_dash_off);
2491 /* paint the major grid */
2492 for (ti = find_first_time(im->start,
2500 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2502 /* are we inside the graph ? */
2503 if (ti < im->start || ti > im->end)
2506 gfx_line(im, X0, Y1 - 2, X0, Y1,
2507 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2508 gfx_line(im, X0, Y0, X0, Y0 + 3,
2509 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2510 gfx_dashed_line(im, X0, Y0 + 3, X0,
2514 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2516 /* paint the labels below the graph */
2518 find_first_time(im->start -
2527 im->xlab_user.precis / 2;
2528 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2530 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2531 /* are we inside the graph ? */
2532 if (tilab < im->start || tilab > im->end)
2535 localtime_r(&tilab, &tm);
2536 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2538 # error "your libc has no strftime I guess we'll abort the exercise here."
2543 im->graph_col[GRC_FONT],
2545 text_prop[TEXT_PROP_AXIS].
2548 GFX_H_CENTER, GFX_V_TOP, graph_label);
2557 /* draw x and y axis */
2558 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2559 im->xorigin+im->xsize,im->yorigin-im->ysize,
2560 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2562 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2563 im->xorigin+im->xsize,im->yorigin-im->ysize,
2564 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2566 gfx_line(im, im->xorigin - 4,
2568 im->xorigin + im->xsize +
2569 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2570 gfx_line(im, im->xorigin,
2573 im->yorigin - im->ysize -
2574 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2575 /* arrow for X and Y axis direction */
2576 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 */
2577 im->graph_col[GRC_ARROW]);
2579 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 */
2580 im->graph_col[GRC_ARROW]);
2582 if (im->second_axis_scale != 0){
2583 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2584 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2585 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2587 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2588 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2589 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2590 im->graph_col[GRC_ARROW]);
2601 double X0, Y0; /* points for filled graph and more */
2602 struct gfx_color_t water_color;
2604 /* draw 3d border */
2605 gfx_new_area(im, 0, im->yimg,
2606 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2607 gfx_add_point(im, im->ximg - 2, 2);
2608 gfx_add_point(im, im->ximg, 0);
2609 gfx_add_point(im, 0, 0);
2611 gfx_new_area(im, 2, im->yimg - 2,
2613 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2614 gfx_add_point(im, im->ximg, 0);
2615 gfx_add_point(im, im->ximg, im->yimg);
2616 gfx_add_point(im, 0, im->yimg);
2618 if (im->draw_x_grid == 1)
2620 if (im->draw_y_grid == 1) {
2621 if (im->logarithmic) {
2622 res = horizontal_log_grid(im);
2624 res = draw_horizontal_grid(im);
2627 /* dont draw horizontal grid if there is no min and max val */
2629 char *nodata = "No Data found";
2631 gfx_text(im, im->ximg / 2,
2634 im->graph_col[GRC_FONT],
2636 text_prop[TEXT_PROP_AXIS].
2639 GFX_H_CENTER, GFX_V_CENTER, nodata);
2643 /* yaxis unit description */
2644 if (im->ylegend[0] != '\0'){
2646 im->xOriginLegendY+10,
2648 im->graph_col[GRC_FONT],
2650 text_prop[TEXT_PROP_UNIT].
2653 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2656 if (im->second_axis_legend[0] != '\0'){
2658 im->xOriginLegendY2+10,
2659 im->yOriginLegendY2,
2660 im->graph_col[GRC_FONT],
2661 im->text_prop[TEXT_PROP_UNIT].font_desc,
2663 RRDGRAPH_YLEGEND_ANGLE,
2664 GFX_H_CENTER, GFX_V_CENTER,
2665 im->second_axis_legend);
2670 im->xOriginTitle, im->yOriginTitle+6,
2671 im->graph_col[GRC_FONT],
2673 text_prop[TEXT_PROP_TITLE].
2675 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2676 /* rrdtool 'logo' */
2677 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2678 water_color = im->graph_col[GRC_FONT];
2679 water_color.alpha = 0.3;
2680 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2681 gfx_text(im, xpos, 5,
2684 text_prop[TEXT_PROP_WATERMARK].
2685 font_desc, im->tabwidth,
2686 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2688 /* graph watermark */
2689 if (im->watermark[0] != '\0') {
2690 water_color = im->graph_col[GRC_FONT];
2691 water_color.alpha = 0.3;
2693 im->ximg / 2, im->yimg - 6,
2696 text_prop[TEXT_PROP_WATERMARK].
2697 font_desc, im->tabwidth, 0,
2698 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2702 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2703 for (i = 0; i < im->gdes_c; i++) {
2704 if (im->gdes[i].legend[0] == '\0')
2706 /* im->gdes[i].leg_y is the bottom of the legend */
2707 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2708 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2709 gfx_text(im, X0, Y0,
2710 im->graph_col[GRC_FONT],
2713 [TEXT_PROP_LEGEND].font_desc,
2715 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2716 /* The legend for GRAPH items starts with "M " to have
2717 enough space for the box */
2718 if (im->gdes[i].gf != GF_PRINT &&
2719 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2723 boxH = gfx_get_text_width(im, 0,
2728 im->tabwidth, "o") * 1.2;
2730 /* shift the box up a bit */
2732 /* make sure transparent colors show up the same way as in the graph */
2735 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2736 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2738 gfx_new_area(im, X0, Y0 - boxV, X0,
2739 Y0, X0 + boxH, Y0, im->gdes[i].col);
2740 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2743 cairo_new_path(im->cr);
2744 cairo_set_line_width(im->cr, 1.0);
2747 gfx_line_fit(im, &X0, &Y0);
2748 gfx_line_fit(im, &X1, &Y1);
2749 cairo_move_to(im->cr, X0, Y0);
2750 cairo_line_to(im->cr, X1, Y0);
2751 cairo_line_to(im->cr, X1, Y1);
2752 cairo_line_to(im->cr, X0, Y1);
2753 cairo_close_path(im->cr);
2754 cairo_set_source_rgba(im->cr,
2766 blue, im->graph_col[GRC_FRAME].alpha);
2767 if (im->gdes[i].dash) {
2768 /* make box borders in legend dashed if the graph is dashed */
2772 cairo_set_dash(im->cr, dashes, 1, 0.0);
2774 cairo_stroke(im->cr);
2775 cairo_restore(im->cr);
2782 /*****************************************************
2783 * lazy check make sure we rely need to create this graph
2784 *****************************************************/
2791 struct stat imgstat;
2794 return 0; /* no lazy option */
2795 if (strlen(im->graphfile) == 0)
2796 return 0; /* inmemory option */
2797 if (stat(im->graphfile, &imgstat) != 0)
2798 return 0; /* can't stat */
2799 /* one pixel in the existing graph is more then what we would
2801 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2803 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2804 return 0; /* the file does not exist */
2805 switch (im->imgformat) {
2807 size = PngSize(fd, &(im->ximg), &(im->yimg));
2817 int graph_size_location(
2822 /* The actual size of the image to draw is determined from
2823 ** several sources. The size given on the command line is
2824 ** the graph area but we need more as we have to draw labels
2825 ** and other things outside the graph area. If the option
2826 ** --full-size-mode is selected the size defines the total
2827 ** image size and the size available for the graph is
2831 /** +---+-----------------------------------+
2832 ** | y |...............graph title.........|
2833 ** | +---+-------------------------------+
2837 ** | s | x | main graph area |
2842 ** | l | b +-------------------------------+
2843 ** | e | l | x axis labels |
2844 ** +---+---+-------------------------------+
2845 ** |....................legends............|
2846 ** +---------------------------------------+
2848 ** +---------------------------------------+
2851 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2852 0, Xylabel = 0, Xmain = 0, Ymain =
2853 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2855 // no legends and no the shall be plotted it's easy
2856 if (im->extra_flags & ONLY_GRAPH) {
2858 im->ximg = im->xsize;
2859 im->yimg = im->ysize;
2860 im->yorigin = im->ysize;
2865 if(im->watermark[0] != '\0') {
2866 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2869 // calculate the width of the left vertical legend
2870 if (im->ylegend[0] != '\0') {
2871 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2874 // calculate the width of the right vertical legend
2875 if (im->second_axis_legend[0] != '\0') {
2876 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2879 Xvertical2 = Xspacing;
2882 if (im->title[0] != '\0') {
2883 /* The title is placed "inbetween" two text lines so it
2884 ** automatically has some vertical spacing. The horizontal
2885 ** spacing is added here, on each side.
2887 /* if necessary, reduce the font size of the title until it fits the image width */
2888 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2891 // we have no title; get a little clearing from the top
2892 Ytitle = 1.5 * Yspacing;
2896 if (im->draw_x_grid) {
2897 // calculate the height of the horizontal labelling
2898 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2900 if (im->draw_y_grid || im->forceleftspace) {
2901 // calculate the width of the vertical labelling
2903 gfx_get_text_width(im, 0,
2904 im->text_prop[TEXT_PROP_AXIS].font_desc,
2905 im->tabwidth, "0") * im->unitslength;
2909 // add some space to the labelling
2910 Xylabel += Xspacing;
2912 /* If the legend is printed besides the graph the width has to be
2913 ** calculated first. Placing the legend north or south of the
2914 ** graph requires the width calculation first, so the legend is
2915 ** skipped for the moment.
2917 im->legendheight = 0;
2918 im->legendwidth = 0;
2919 if (!(im->extra_flags & NOLEGEND)) {
2920 if(im->legendposition == WEST || im->legendposition == EAST){
2921 if (leg_place(im, 1) == -1){
2927 if (im->extra_flags & FULL_SIZE_MODE) {
2929 /* The actual size of the image to draw has been determined by the user.
2930 ** The graph area is the space remaining after accounting for the legend,
2931 ** the watermark, the axis labels, and the title.
2933 im->ximg = im->xsize;
2934 im->yimg = im->ysize;
2938 /* Now calculate the total size. Insert some spacing where
2939 desired. im->xorigin and im->yorigin need to correspond
2940 with the lower left corner of the main graph area or, if
2941 this one is not set, the imaginary box surrounding the
2943 /* Initial size calculation for the main graph area */
2945 Xmain -= Xylabel;// + Xspacing;
2946 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2947 Xmain -= im->legendwidth;// + Xspacing;
2949 if (im->second_axis_scale != 0){
2952 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2956 Xmain -= Xvertical + Xvertical2;
2958 /* limit the remaining space to 0 */
2964 /* Putting the legend north or south, the height can now be calculated */
2965 if (!(im->extra_flags & NOLEGEND)) {
2966 if(im->legendposition == NORTH || im->legendposition == SOUTH){
2967 im->legendwidth = im->ximg;
2968 if (leg_place(im, 0) == -1){
2974 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
2975 Ymain -= Yxlabel + im->legendheight;
2981 /* reserve space for the title *or* some padding above the graph */
2984 /* reserve space for padding below the graph */
2985 if (im->extra_flags & NOLEGEND) {
2989 if (im->watermark[0] != '\0') {
2990 Ymain -= Ywatermark;
2992 /* limit the remaining height to 0 */
2997 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2999 /* The actual size of the image to draw is determined from
3000 ** several sources. The size given on the command line is
3001 ** the graph area but we need more as we have to draw labels
3002 ** and other things outside the graph area.
3006 Xmain = im->xsize; // + Xspacing;
3010 im->ximg = Xmain + Xylabel;
3011 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3012 im->ximg += Xspacing;
3015 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3016 im->ximg += im->legendwidth;// + Xspacing;
3018 if (im->second_axis_scale != 0){
3019 im->ximg += Xylabel;
3022 im->ximg += Xvertical + Xvertical2;
3024 if (!(im->extra_flags & NOLEGEND)) {
3025 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3026 im->legendwidth = im->ximg;
3027 if (leg_place(im, 0) == -1){
3033 im->yimg = Ymain + Yxlabel;
3034 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3035 im->yimg += im->legendheight;
3038 /* reserve space for the title *or* some padding above the graph */
3042 im->yimg += 1.5 * Yspacing;
3044 /* reserve space for padding below the graph */
3045 if (im->extra_flags & NOLEGEND) {
3046 im->yimg += Yspacing;
3049 if (im->watermark[0] != '\0') {
3050 im->yimg += Ywatermark;
3055 /* In case of putting the legend in west or east position the first
3056 ** legend calculation might lead to wrong positions if some items
3057 ** are not aligned on the left hand side (e.g. centered) as the
3058 ** legendwidth wight have been increased after the item was placed.
3059 ** In this case the positions have to be recalculated.
3061 if (!(im->extra_flags & NOLEGEND)) {
3062 if(im->legendposition == WEST || im->legendposition == EAST){
3063 if (leg_place(im, 0) == -1){
3069 /* After calculating all dimensions
3070 ** it is now possible to calculate
3073 switch(im->legendposition){
3075 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3076 im->yOriginTitle = 0;
3078 im->xOriginLegend = 0;
3079 im->yOriginLegend = Ytitle;
3081 im->xOriginLegendY = 0;
3082 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3084 im->xorigin = Xvertical + Xylabel;
3085 im->yorigin = Ytitle + im->legendheight + Ymain;
3087 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3088 if (im->second_axis_scale != 0){
3089 im->xOriginLegendY2 += Xylabel;
3091 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3096 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3097 im->yOriginTitle = 0;
3099 im->xOriginLegend = 0;
3100 im->yOriginLegend = Ytitle;
3102 im->xOriginLegendY = im->legendwidth;
3103 im->yOriginLegendY = Ytitle + (Ymain / 2);
3105 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3106 im->yorigin = Ytitle + Ymain;
3108 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3109 if (im->second_axis_scale != 0){
3110 im->xOriginLegendY2 += Xylabel;
3112 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3117 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3118 im->yOriginTitle = 0;
3120 im->xOriginLegend = 0;
3121 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3123 im->xOriginLegendY = 0;
3124 im->yOriginLegendY = Ytitle + (Ymain / 2);
3126 im->xorigin = Xvertical + Xylabel;
3127 im->yorigin = Ytitle + Ymain;
3129 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3130 if (im->second_axis_scale != 0){
3131 im->xOriginLegendY2 += Xylabel;
3133 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3138 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3139 im->yOriginTitle = 0;
3141 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3142 if (im->second_axis_scale != 0){
3143 im->xOriginLegend += Xylabel;
3145 im->yOriginLegend = Ytitle;
3147 im->xOriginLegendY = 0;
3148 im->yOriginLegendY = Ytitle + (Ymain / 2);
3150 im->xorigin = Xvertical + Xylabel;
3151 im->yorigin = Ytitle + Ymain;
3153 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3154 if (im->second_axis_scale != 0){
3155 im->xOriginLegendY2 += Xylabel;
3157 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3159 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3160 im->xOriginTitle += Xspacing;
3161 im->xOriginLegend += Xspacing;
3162 im->xOriginLegendY += Xspacing;
3163 im->xorigin += Xspacing;
3164 im->xOriginLegendY2 += Xspacing;
3174 static cairo_status_t cairo_output(
3178 unsigned int length)
3180 image_desc_t *im = (image_desc_t*)closure;
3182 im->rendered_image =
3183 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3184 if (im->rendered_image == NULL)
3185 return CAIRO_STATUS_WRITE_ERROR;
3186 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3187 im->rendered_image_size += length;
3188 return CAIRO_STATUS_SUCCESS;
3191 /* draw that picture thing ... */
3196 int lazy = lazy_check(im);
3197 double areazero = 0.0;
3198 graph_desc_t *lastgdes = NULL;
3201 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3203 /* if we want and can be lazy ... quit now */
3205 info.u_cnt = im->ximg;
3206 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3207 info.u_cnt = im->yimg;
3208 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3211 /* pull the data from the rrd files ... */
3212 if (data_fetch(im) == -1)
3214 /* evaluate VDEF and CDEF operations ... */
3215 if (data_calc(im) == -1)
3217 /* calculate and PRINT and GPRINT definitions. We have to do it at
3218 * this point because it will affect the length of the legends
3219 * if there are no graph elements (i==0) we stop here ...
3220 * if we are lazy, try to quit ...
3226 if ((i == 0) || lazy)
3229 /**************************************************************
3230 *** Calculating sizes and locations became a bit confusing ***
3231 *** so I moved this into a separate function. ***
3232 **************************************************************/
3233 if (graph_size_location(im, i) == -1)
3236 info.u_cnt = im->xorigin;
3237 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3238 info.u_cnt = im->yorigin - im->ysize;
3239 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3240 info.u_cnt = im->xsize;
3241 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3242 info.u_cnt = im->ysize;
3243 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3244 info.u_cnt = im->ximg;
3245 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3246 info.u_cnt = im->yimg;
3247 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3248 info.u_cnt = im->start;
3249 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3250 info.u_cnt = im->end;
3251 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3253 /* get actual drawing data and find min and max values */
3254 if (data_proc(im) == -1)
3256 if (!im->logarithmic) {
3260 /* identify si magnitude Kilo, Mega Giga ? */
3261 if (!im->rigid && !im->logarithmic)
3262 expand_range(im); /* make sure the upper and lower limit are
3265 info.u_val = im->minval;
3266 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3267 info.u_val = im->maxval;
3268 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3270 if (!calc_horizontal_grid(im))
3275 apply_gridfit(im); */
3276 /* the actual graph is created by going through the individual
3277 graph elements and then drawing them */
3278 cairo_surface_destroy(im->surface);
3279 switch (im->imgformat) {
3282 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3283 im->ximg * im->zoom,
3284 im->yimg * im->zoom);
3288 im->surface = strlen(im->graphfile)
3289 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3290 im->yimg * im->zoom)
3291 : cairo_pdf_surface_create_for_stream
3292 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3296 im->surface = strlen(im->graphfile)
3298 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3299 im->yimg * im->zoom)
3300 : cairo_ps_surface_create_for_stream
3301 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3305 im->surface = strlen(im->graphfile)
3307 cairo_svg_surface_create(im->
3309 im->ximg * im->zoom, im->yimg * im->zoom)
3310 : cairo_svg_surface_create_for_stream
3311 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3312 cairo_svg_surface_restrict_to_version
3313 (im->surface, CAIRO_SVG_VERSION_1_1);
3316 cairo_destroy(im->cr);
3317 im->cr = cairo_create(im->surface);
3318 cairo_set_antialias(im->cr, im->graph_antialias);
3319 cairo_scale(im->cr, im->zoom, im->zoom);
3320 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3321 gfx_new_area(im, 0, 0, 0, im->yimg,
3322 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3323 gfx_add_point(im, im->ximg, 0);
3325 gfx_new_area(im, im->xorigin,
3328 im->xsize, im->yorigin,
3331 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3332 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3334 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3335 im->xsize, im->ysize + 2.0);
3337 if (im->minval > 0.0)
3338 areazero = im->minval;
3339 if (im->maxval < 0.0)
3340 areazero = im->maxval;
3341 for (i = 0; i < im->gdes_c; i++) {
3342 switch (im->gdes[i].gf) {
3356 for (ii = 0; ii < im->xsize; ii++) {
3357 if (!isnan(im->gdes[i].p_data[ii])
3358 && im->gdes[i].p_data[ii] != 0.0) {
3359 if (im->gdes[i].yrule > 0) {
3366 im->ysize, 1.0, im->gdes[i].col);
3367 } else if (im->gdes[i].yrule < 0) {
3370 im->yorigin - im->ysize - 1.0,
3372 im->yorigin - im->ysize -
3375 im->ysize, 1.0, im->gdes[i].col);
3382 /* fix data points at oo and -oo */
3383 for (ii = 0; ii < im->xsize; ii++) {
3384 if (isinf(im->gdes[i].p_data[ii])) {
3385 if (im->gdes[i].p_data[ii] > 0) {
3386 im->gdes[i].p_data[ii] = im->maxval;
3388 im->gdes[i].p_data[ii] = im->minval;
3394 /* *******************************************************
3399 -------|--t-1--t--------------------------------
3401 if we know the value at time t was a then
3402 we draw a square from t-1 to t with the value a.
3404 ********************************************************* */
3405 if (im->gdes[i].col.alpha != 0.0) {
3406 /* GF_LINE and friend */
3407 if (im->gdes[i].gf == GF_LINE) {
3408 double last_y = 0.0;
3412 cairo_new_path(im->cr);
3413 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3414 if (im->gdes[i].dash) {
3415 cairo_set_dash(im->cr,
3416 im->gdes[i].p_dashes,
3417 im->gdes[i].ndash, im->gdes[i].offset);
3420 for (ii = 1; ii < im->xsize; ii++) {
3421 if (isnan(im->gdes[i].p_data[ii])
3422 || (im->slopemode == 1
3423 && isnan(im->gdes[i].p_data[ii - 1]))) {
3428 last_y = ytr(im, im->gdes[i].p_data[ii]);
3429 if (im->slopemode == 0) {
3430 double x = ii - 1 + im->xorigin;
3433 gfx_line_fit(im, &x, &y);
3434 cairo_move_to(im->cr, x, y);
3435 x = ii + im->xorigin;
3437 gfx_line_fit(im, &x, &y);
3438 cairo_line_to(im->cr, x, y);
3440 double x = ii - 1 + im->xorigin;
3442 ytr(im, im->gdes[i].p_data[ii - 1]);
3443 gfx_line_fit(im, &x, &y);
3444 cairo_move_to(im->cr, x, y);
3445 x = ii + im->xorigin;
3447 gfx_line_fit(im, &x, &y);
3448 cairo_line_to(im->cr, x, y);
3452 double x1 = ii + im->xorigin;
3453 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3455 if (im->slopemode == 0
3456 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3457 double x = ii - 1 + im->xorigin;
3460 gfx_line_fit(im, &x, &y);
3461 cairo_line_to(im->cr, x, y);
3464 gfx_line_fit(im, &x1, &y1);
3465 cairo_line_to(im->cr, x1, y1);
3468 cairo_set_source_rgba(im->cr,
3474 col.blue, im->gdes[i].col.alpha);
3475 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3476 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3477 cairo_stroke(im->cr);
3478 cairo_restore(im->cr);
3482 (double *) malloc(sizeof(double) * im->xsize * 2);
3484 (double *) malloc(sizeof(double) * im->xsize * 2);
3486 (double *) malloc(sizeof(double) * im->xsize * 2);
3488 (double *) malloc(sizeof(double) * im->xsize * 2);
3491 for (ii = 0; ii <= im->xsize; ii++) {
3494 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3500 AlmostEqual2sComplement(foreY
3504 AlmostEqual2sComplement(foreY
3514 foreY[cntI], im->gdes[i].col);
3515 while (cntI < idxI) {
3520 AlmostEqual2sComplement(foreY
3524 AlmostEqual2sComplement(foreY
3531 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3533 gfx_add_point(im, backX[idxI], backY[idxI]);
3539 AlmostEqual2sComplement(backY
3543 AlmostEqual2sComplement(backY
3550 gfx_add_point(im, backX[idxI], backY[idxI]);
3560 if (ii == im->xsize)
3562 if (im->slopemode == 0 && ii == 0) {
3565 if (isnan(im->gdes[i].p_data[ii])) {
3569 ytop = ytr(im, im->gdes[i].p_data[ii]);
3570 if (lastgdes && im->gdes[i].stack) {
3571 ybase = ytr(im, lastgdes->p_data[ii]);
3573 ybase = ytr(im, areazero);
3575 if (ybase == ytop) {
3581 double extra = ytop;
3586 if (im->slopemode == 0) {
3587 backY[++idxI] = ybase - 0.2;
3588 backX[idxI] = ii + im->xorigin - 1;
3589 foreY[idxI] = ytop + 0.2;
3590 foreX[idxI] = ii + im->xorigin - 1;
3592 backY[++idxI] = ybase - 0.2;
3593 backX[idxI] = ii + im->xorigin;
3594 foreY[idxI] = ytop + 0.2;
3595 foreX[idxI] = ii + im->xorigin;
3597 /* close up any remaining area */
3602 } /* else GF_LINE */
3604 /* if color != 0x0 */
3605 /* make sure we do not run into trouble when stacking on NaN */
3606 for (ii = 0; ii < im->xsize; ii++) {
3607 if (isnan(im->gdes[i].p_data[ii])) {
3608 if (lastgdes && (im->gdes[i].stack)) {
3609 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3611 im->gdes[i].p_data[ii] = areazero;
3615 lastgdes = &(im->gdes[i]);
3619 ("STACK should already be turned into LINE or AREA here");
3624 cairo_reset_clip(im->cr);
3626 /* grid_paint also does the text */
3627 if (!(im->extra_flags & ONLY_GRAPH))
3629 if (!(im->extra_flags & ONLY_GRAPH))
3631 /* the RULES are the last thing to paint ... */
3632 for (i = 0; i < im->gdes_c; i++) {
3634 switch (im->gdes[i].gf) {
3636 if (im->gdes[i].yrule >= im->minval
3637 && im->gdes[i].yrule <= im->maxval) {
3639 if (im->gdes[i].dash) {
3640 cairo_set_dash(im->cr,
3641 im->gdes[i].p_dashes,
3642 im->gdes[i].ndash, im->gdes[i].offset);
3644 gfx_line(im, im->xorigin,
3645 ytr(im, im->gdes[i].yrule),
3646 im->xorigin + im->xsize,
3647 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3648 cairo_stroke(im->cr);
3649 cairo_restore(im->cr);
3653 if (im->gdes[i].xrule >= im->start
3654 && im->gdes[i].xrule <= im->end) {
3656 if (im->gdes[i].dash) {
3657 cairo_set_dash(im->cr,
3658 im->gdes[i].p_dashes,
3659 im->gdes[i].ndash, im->gdes[i].offset);
3662 xtr(im, im->gdes[i].xrule),
3663 im->yorigin, xtr(im,
3667 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3668 cairo_stroke(im->cr);
3669 cairo_restore(im->cr);
3678 switch (im->imgformat) {
3681 cairo_status_t status;
3683 status = strlen(im->graphfile) ?
3684 cairo_surface_write_to_png(im->surface, im->graphfile)
3685 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3688 if (status != CAIRO_STATUS_SUCCESS) {
3689 rrd_set_error("Could not save png to '%s'", im->graphfile);
3695 if (strlen(im->graphfile)) {
3696 cairo_show_page(im->cr);
3698 cairo_surface_finish(im->surface);
3707 /*****************************************************
3709 *****************************************************/
3716 if ((im->gdes = (graph_desc_t *)
3717 rrd_realloc(im->gdes, (im->gdes_c)
3718 * sizeof(graph_desc_t))) == NULL) {
3719 rrd_set_error("realloc graph_descs");
3724 im->gdes[im->gdes_c - 1].step = im->step;
3725 im->gdes[im->gdes_c - 1].step_orig = im->step;
3726 im->gdes[im->gdes_c - 1].stack = 0;
3727 im->gdes[im->gdes_c - 1].linewidth = 0;
3728 im->gdes[im->gdes_c - 1].debug = 0;
3729 im->gdes[im->gdes_c - 1].start = im->start;
3730 im->gdes[im->gdes_c - 1].start_orig = im->start;
3731 im->gdes[im->gdes_c - 1].end = im->end;
3732 im->gdes[im->gdes_c - 1].end_orig = im->end;
3733 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3734 im->gdes[im->gdes_c - 1].data = NULL;
3735 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3736 im->gdes[im->gdes_c - 1].data_first = 0;
3737 im->gdes[im->gdes_c - 1].p_data = NULL;
3738 im->gdes[im->gdes_c - 1].rpnp = NULL;
3739 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3740 im->gdes[im->gdes_c - 1].shift = 0.0;
3741 im->gdes[im->gdes_c - 1].dash = 0;
3742 im->gdes[im->gdes_c - 1].ndash = 0;
3743 im->gdes[im->gdes_c - 1].offset = 0;
3744 im->gdes[im->gdes_c - 1].col.red = 0.0;
3745 im->gdes[im->gdes_c - 1].col.green = 0.0;
3746 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3747 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3748 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3749 im->gdes[im->gdes_c - 1].format[0] = '\0';
3750 im->gdes[im->gdes_c - 1].strftm = 0;
3751 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3752 im->gdes[im->gdes_c - 1].ds = -1;
3753 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3754 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3755 im->gdes[im->gdes_c - 1].yrule = DNAN;
3756 im->gdes[im->gdes_c - 1].xrule = 0;
3760 /* copies input untill the first unescaped colon is found
3761 or until input ends. backslashes have to be escaped as well */
3763 const char *const input,
3769 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3770 if (input[inp] == '\\'
3771 && input[inp + 1] != '\0'
3772 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3773 output[outp++] = input[++inp];
3775 output[outp++] = input[inp];
3778 output[outp] = '\0';
3782 /* Now just a wrapper around rrd_graph_v */
3794 rrd_info_t *grinfo = NULL;
3797 grinfo = rrd_graph_v(argc, argv);
3803 if (strcmp(walker->key, "image_info") == 0) {
3806 (char**)rrd_realloc((*prdata),
3807 (prlines + 1) * sizeof(char *))) == NULL) {
3808 rrd_set_error("realloc prdata");
3811 /* imginfo goes to position 0 in the prdata array */
3812 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3813 + 2) * sizeof(char));
3814 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3815 (*prdata)[prlines] = NULL;
3817 /* skip anything else */
3818 walker = walker->next;
3826 if (strcmp(walker->key, "image_width") == 0) {
3827 *xsize = walker->value.u_cnt;
3828 } else if (strcmp(walker->key, "image_height") == 0) {
3829 *ysize = walker->value.u_cnt;
3830 } else if (strcmp(walker->key, "value_min") == 0) {
3831 *ymin = walker->value.u_val;
3832 } else if (strcmp(walker->key, "value_max") == 0) {
3833 *ymax = walker->value.u_val;
3834 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3837 (char**)rrd_realloc((*prdata),
3838 (prlines + 1) * sizeof(char *))) == NULL) {
3839 rrd_set_error("realloc prdata");
3842 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3843 + 2) * sizeof(char));
3844 (*prdata)[prlines] = NULL;
3845 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3846 } else if (strcmp(walker->key, "image") == 0) {
3847 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3848 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3849 rrd_set_error("writing image");
3853 /* skip anything else */
3854 walker = walker->next;
3856 rrd_info_free(grinfo);
3861 /* Some surgery done on this function, it became ridiculously big.
3863 ** - initializing now in rrd_graph_init()
3864 ** - options parsing now in rrd_graph_options()
3865 ** - script parsing now in rrd_graph_script()
3867 rrd_info_t *rrd_graph_v(
3873 rrd_graph_init(&im);
3874 /* a dummy surface so that we can measure text sizes for placements */
3876 rrd_graph_options(argc, argv, &im);
3877 if (rrd_test_error()) {
3878 rrd_info_free(im.grinfo);
3883 if (optind >= argc) {
3884 rrd_info_free(im.grinfo);
3886 rrd_set_error("missing filename");
3890 if (strlen(argv[optind]) >= MAXPATH) {
3891 rrd_set_error("filename (including path) too long");
3892 rrd_info_free(im.grinfo);
3897 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3898 im.graphfile[MAXPATH - 1] = '\0';
3900 if (strcmp(im.graphfile, "-") == 0) {
3901 im.graphfile[0] = '\0';
3904 rrd_graph_script(argc, argv, &im, 1);
3905 if (rrd_test_error()) {
3906 rrd_info_free(im.grinfo);
3911 /* Everything is now read and the actual work can start */
3913 if (graph_paint(&im) == -1) {
3914 rrd_info_free(im.grinfo);
3920 /* The image is generated and needs to be output.
3921 ** Also, if needed, print a line with information about the image.
3929 path = strdup(im.graphfile);
3930 filename = basename(path);
3932 sprintf_alloc(im.imginfo,
3935 im.ximg), (long) (im.zoom * im.yimg));
3936 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3940 if (im.rendered_image) {
3943 img.u_blo.size = im.rendered_image_size;
3944 img.u_blo.ptr = im.rendered_image;
3945 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3954 image_desc_t *im,int prop,char *font, double size ){
3956 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3957 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3958 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3961 im->text_prop[prop].size = size;
3963 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3964 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3968 void rrd_graph_init(
3973 char *deffont = getenv("RRD_DEFAULT_FONT");
3974 static PangoFontMap *fontmap = NULL;
3975 PangoContext *context;
3980 #ifdef HAVE_SETLOCALE
3981 setlocale(LC_TIME, "");
3982 #ifdef HAVE_MBSTOWCS
3983 setlocale(LC_CTYPE, "");
3987 im->daemon_addr = NULL;
3988 im->draw_x_grid = 1;
3989 im->draw_y_grid = 1;
3990 im->extra_flags = 0;
3991 im->font_options = cairo_font_options_create();
3992 im->forceleftspace = 0;
3995 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3996 im->grid_dash_off = 1;
3997 im->grid_dash_on = 1;
3999 im->grinfo = (rrd_info_t *) NULL;
4000 im->grinfo_current = (rrd_info_t *) NULL;
4001 im->imgformat = IF_PNG;
4004 im->legenddirection = TOP_DOWN;
4005 im->legendheight = 0;
4006 im->legendposition = SOUTH;
4007 im->legendwidth = 0;
4008 im->logarithmic = 0;
4014 im->rendered_image_size = 0;
4015 im->rendered_image = NULL;
4019 im->tabwidth = 40.0;
4020 im->title[0] = '\0';
4021 im->unitsexponent = 9999;
4022 im->unitslength = 6;
4023 im->viewfactor = 1.0;
4024 im->watermark[0] = '\0';
4025 im->with_markup = 0;
4027 im->xlab_user.minsec = -1;
4029 im->xOriginLegend = 0;
4030 im->xOriginLegendY = 0;
4031 im->xOriginLegendY2 = 0;
4032 im->xOriginTitle = 0;
4034 im->ygridstep = DNAN;
4036 im->ylegend[0] = '\0';
4037 im->second_axis_scale = 0; /* 0 disables it */
4038 im->second_axis_shift = 0; /* no shift by default */
4039 im->second_axis_legend[0] = '\0';
4040 im->second_axis_format[0] = '\0';
4042 im->yOriginLegend = 0;
4043 im->yOriginLegendY = 0;
4044 im->yOriginLegendY2 = 0;
4045 im->yOriginTitle = 0;
4049 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4050 im->cr = cairo_create(im->surface);
4052 for (i = 0; i < DIM(text_prop); i++) {
4053 im->text_prop[i].size = -1;
4054 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4057 if (fontmap == NULL){
4058 fontmap = pango_cairo_font_map_get_default();
4061 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4063 pango_cairo_context_set_resolution(context, 100);
4065 pango_cairo_update_context(im->cr,context);
4067 im->layout = pango_layout_new(context);
4069 // im->layout = pango_cairo_create_layout(im->cr);
4072 cairo_font_options_set_hint_style
4073 (im->font_options, CAIRO_HINT_STYLE_FULL);
4074 cairo_font_options_set_hint_metrics
4075 (im->font_options, CAIRO_HINT_METRICS_ON);
4076 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4080 for (i = 0; i < DIM(graph_col); i++)
4081 im->graph_col[i] = graph_col[i];
4087 void rrd_graph_options(
4094 char *parsetime_error = NULL;
4095 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4096 time_t start_tmp = 0, end_tmp = 0;
4098 rrd_time_value_t start_tv, end_tv;
4099 long unsigned int color;
4100 char *old_locale = "";
4102 /* defines for long options without a short equivalent. should be bytes,
4103 and may not collide with (the ASCII value of) short options */
4104 #define LONGOPT_UNITS_SI 255
4107 struct option long_options[] = {
4108 { "start", required_argument, 0, 's'},
4109 { "end", required_argument, 0, 'e'},
4110 { "x-grid", required_argument, 0, 'x'},
4111 { "y-grid", required_argument, 0, 'y'},
4112 { "vertical-label", required_argument, 0, 'v'},
4113 { "width", required_argument, 0, 'w'},
4114 { "height", required_argument, 0, 'h'},
4115 { "full-size-mode", no_argument, 0, 'D'},
4116 { "interlaced", no_argument, 0, 'i'},
4117 { "upper-limit", required_argument, 0, 'u'},
4118 { "lower-limit", required_argument, 0, 'l'},
4119 { "rigid", no_argument, 0, 'r'},
4120 { "base", required_argument, 0, 'b'},
4121 { "logarithmic", no_argument, 0, 'o'},
4122 { "color", required_argument, 0, 'c'},
4123 { "font", required_argument, 0, 'n'},
4124 { "title", required_argument, 0, 't'},
4125 { "imginfo", required_argument, 0, 'f'},
4126 { "imgformat", required_argument, 0, 'a'},
4127 { "lazy", no_argument, 0, 'z'},
4128 { "zoom", required_argument, 0, 'm'},
4129 { "no-legend", no_argument, 0, 'g'},
4130 { "legend-position", required_argument, 0, 1005},
4131 { "legend-direction", required_argument, 0, 1006},
4132 { "force-rules-legend", no_argument, 0, 'F'},
4133 { "only-graph", no_argument, 0, 'j'},
4134 { "alt-y-grid", no_argument, 0, 'Y'},
4135 {"disable-rrdtool-tag", no_argument, 0, 1001},
4136 {"right-axis", required_argument, 0, 1002},
4137 {"right-axis-label", required_argument, 0, 1003},
4138 {"right-axis-format", required_argument, 0, 1004},
4139 { "no-minor", no_argument, 0, 'I'},
4140 { "slope-mode", no_argument, 0, 'E'},
4141 { "alt-autoscale", no_argument, 0, 'A'},
4142 { "alt-autoscale-min", no_argument, 0, 'J'},
4143 { "alt-autoscale-max", no_argument, 0, 'M'},
4144 { "no-gridfit", no_argument, 0, 'N'},
4145 { "units-exponent", required_argument, 0, 'X'},
4146 { "units-length", required_argument, 0, 'L'},
4147 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4148 { "step", required_argument, 0, 'S'},
4149 { "tabwidth", required_argument, 0, 'T'},
4150 { "font-render-mode", required_argument, 0, 'R'},
4151 { "graph-render-mode", required_argument, 0, 'G'},
4152 { "font-smoothing-threshold", required_argument, 0, 'B'},
4153 { "watermark", required_argument, 0, 'W'},
4154 { "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 */
4155 { "pango-markup", no_argument, 0, 'P'},
4156 { "daemon", required_argument, 0, 'd'},
4162 opterr = 0; /* initialize getopt */
4163 rrd_parsetime("end-24h", &start_tv);
4164 rrd_parsetime("now", &end_tv);
4166 int option_index = 0;
4168 int col_start, col_end;
4170 opt = getopt_long(argc, argv,
4171 "s:e:x:y:v:w:h:D:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:kPd:",
4172 long_options, &option_index);
4177 im->extra_flags |= NOMINOR;
4180 im->extra_flags |= ALTYGRID;
4183 im->extra_flags |= ALTAUTOSCALE;
4186 im->extra_flags |= ALTAUTOSCALE_MIN;
4189 im->extra_flags |= ALTAUTOSCALE_MAX;
4192 im->extra_flags |= ONLY_GRAPH;
4195 im->extra_flags |= NOLEGEND;
4198 if (strcmp(optarg, "north") == 0) {
4199 im->legendposition = NORTH;
4200 } else if (strcmp(optarg, "west") == 0) {
4201 im->legendposition = WEST;
4202 } else if (strcmp(optarg, "south") == 0) {
4203 im->legendposition = SOUTH;
4204 } else if (strcmp(optarg, "east") == 0) {
4205 im->legendposition = EAST;
4207 rrd_set_error("unknown legend-position '%s'", optarg);
4212 if (strcmp(optarg, "topdown") == 0) {
4213 im->legenddirection = TOP_DOWN;
4214 } else if (strcmp(optarg, "bottomup") == 0) {
4215 im->legenddirection = BOTTOM_UP;
4217 rrd_set_error("unknown legend-position '%s'", optarg);
4222 im->extra_flags |= FORCE_RULES_LEGEND;
4225 im->extra_flags |= NO_RRDTOOL_TAG;
4227 case LONGOPT_UNITS_SI:
4228 if (im->extra_flags & FORCE_UNITS) {
4229 rrd_set_error("--units can only be used once!");
4230 setlocale(LC_NUMERIC, old_locale);
4233 if (strcmp(optarg, "si") == 0)
4234 im->extra_flags |= FORCE_UNITS_SI;
4236 rrd_set_error("invalid argument for --units: %s", optarg);
4241 im->unitsexponent = atoi(optarg);
4244 im->unitslength = atoi(optarg);
4245 im->forceleftspace = 1;
4248 old_locale = setlocale(LC_NUMERIC, "C");
4249 im->tabwidth = atof(optarg);
4250 setlocale(LC_NUMERIC, old_locale);
4253 old_locale = setlocale(LC_NUMERIC, "C");
4254 im->step = atoi(optarg);
4255 setlocale(LC_NUMERIC, old_locale);
4261 im->with_markup = 1;
4264 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4265 rrd_set_error("start time: %s", parsetime_error);
4270 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4271 rrd_set_error("end time: %s", parsetime_error);
4276 if (strcmp(optarg, "none") == 0) {
4277 im->draw_x_grid = 0;
4281 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4283 &im->xlab_user.gridst,
4285 &im->xlab_user.mgridst,
4287 &im->xlab_user.labst,
4288 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4289 strncpy(im->xlab_form, optarg + stroff,
4290 sizeof(im->xlab_form) - 1);
4291 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4293 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4294 rrd_set_error("unknown keyword %s", scan_gtm);
4297 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4299 rrd_set_error("unknown keyword %s", scan_mtm);
4302 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4303 rrd_set_error("unknown keyword %s", scan_ltm);
4306 im->xlab_user.minsec = 1;
4307 im->xlab_user.stst = im->xlab_form;
4309 rrd_set_error("invalid x-grid format");
4315 if (strcmp(optarg, "none") == 0) {
4316 im->draw_y_grid = 0;
4319 old_locale = setlocale(LC_NUMERIC, "C");
4320 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4321 setlocale(LC_NUMERIC, old_locale);
4322 if (im->ygridstep <= 0) {
4323 rrd_set_error("grid step must be > 0");
4325 } else if (im->ylabfact < 1) {
4326 rrd_set_error("label factor must be > 0");
4330 setlocale(LC_NUMERIC, old_locale);
4331 rrd_set_error("invalid y-grid format");
4335 case 1002: /* right y axis */
4339 &im->second_axis_scale,
4340 &im->second_axis_shift) == 2) {
4341 if(im->second_axis_scale==0){
4342 rrd_set_error("the second_axis_scale must not be 0");
4346 rrd_set_error("invalid right-axis format expected scale:shift");
4351 strncpy(im->second_axis_legend,optarg,150);
4352 im->second_axis_legend[150]='\0';
4355 if (bad_format(optarg)){
4356 rrd_set_error("use either %le or %lf formats");
4359 strncpy(im->second_axis_format,optarg,150);
4360 im->second_axis_format[150]='\0';
4363 strncpy(im->ylegend, optarg, 150);
4364 im->ylegend[150] = '\0';
4367 old_locale = setlocale(LC_NUMERIC, "C");
4368 im->maxval = atof(optarg);
4369 setlocale(LC_NUMERIC, old_locale);
4372 old_locale = setlocale(LC_NUMERIC, "C");
4373 im->minval = atof(optarg);
4374 setlocale(LC_NUMERIC, old_locale);
4377 im->base = atol(optarg);
4378 if (im->base != 1024 && im->base != 1000) {
4380 ("the only sensible value for base apart from 1000 is 1024");
4385 long_tmp = atol(optarg);
4386 if (long_tmp < 10) {
4387 rrd_set_error("width below 10 pixels");
4390 im->xsize = long_tmp;
4393 long_tmp = atol(optarg);
4394 if (long_tmp < 10) {
4395 rrd_set_error("height below 10 pixels");
4398 im->ysize = long_tmp;
4401 im->extra_flags |= FULL_SIZE_MODE;
4404 /* interlaced png not supported at the moment */
4410 im->imginfo = optarg;
4414 (im->imgformat = if_conv(optarg)) == -1) {
4415 rrd_set_error("unsupported graphics format '%s'", optarg);
4426 im->logarithmic = 1;
4430 "%10[A-Z]#%n%8lx%n",
4431 col_nam, &col_start, &color, &col_end) == 2) {
4433 int col_len = col_end - col_start;
4438 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4446 (((color & 0xF000) *
4447 0x11000) | ((color & 0x0F00) *
4448 0x01100) | ((color &
4451 ((color & 0x000F) * 0x00011)
4455 color = (color << 8) + 0xff /* shift left by 8 */ ;
4460 rrd_set_error("the color format is #RRGGBB[AA]");
4463 if ((ci = grc_conv(col_nam)) != -1) {
4464 im->graph_col[ci] = gfx_hex_to_col(color);
4466 rrd_set_error("invalid color name '%s'", col_nam);
4470 rrd_set_error("invalid color def format");
4479 old_locale = setlocale(LC_NUMERIC, "C");
4480 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4481 int sindex, propidx;
4483 setlocale(LC_NUMERIC, old_locale);
4484 if ((sindex = text_prop_conv(prop)) != -1) {
4485 for (propidx = sindex;
4486 propidx < TEXT_PROP_LAST; propidx++) {
4488 rrd_set_font_desc(im,propidx,NULL,size);
4490 if ((int) strlen(optarg) > end+2) {
4491 if (optarg[end] == ':') {
4492 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4495 ("expected : after font size in '%s'",
4500 /* only run the for loop for DEFAULT (0) for
4501 all others, we break here. woodo programming */
4502 if (propidx == sindex && sindex != 0)
4506 rrd_set_error("invalid fonttag '%s'", prop);
4510 setlocale(LC_NUMERIC, old_locale);
4511 rrd_set_error("invalid text property format");
4517 old_locale = setlocale(LC_NUMERIC, "C");
4518 im->zoom = atof(optarg);
4519 setlocale(LC_NUMERIC, old_locale);
4520 if (im->zoom <= 0.0) {
4521 rrd_set_error("zoom factor must be > 0");
4526 strncpy(im->title, optarg, 150);
4527 im->title[150] = '\0';
4530 if (strcmp(optarg, "normal") == 0) {
4531 cairo_font_options_set_antialias
4532 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4533 cairo_font_options_set_hint_style
4534 (im->font_options, CAIRO_HINT_STYLE_FULL);
4535 } else if (strcmp(optarg, "light") == 0) {
4536 cairo_font_options_set_antialias
4537 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4538 cairo_font_options_set_hint_style
4539 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4540 } else if (strcmp(optarg, "mono") == 0) {
4541 cairo_font_options_set_antialias
4542 (im->font_options, CAIRO_ANTIALIAS_NONE);
4543 cairo_font_options_set_hint_style
4544 (im->font_options, CAIRO_HINT_STYLE_FULL);
4546 rrd_set_error("unknown font-render-mode '%s'", optarg);
4551 if (strcmp(optarg, "normal") == 0)
4552 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4553 else if (strcmp(optarg, "mono") == 0)
4554 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4556 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4561 /* not supported curently */
4564 strncpy(im->watermark, optarg, 100);
4565 im->watermark[99] = '\0';
4569 if (im->daemon_addr != NULL)
4571 rrd_set_error ("You cannot specify --daemon "
4576 im->daemon_addr = strdup(optarg);
4577 if (im->daemon_addr == NULL)
4579 rrd_set_error("strdup failed");
4587 rrd_set_error("unknown option '%c'", optopt);
4589 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4594 { /* try to connect to rrdcached */
4595 int status = rrdc_connect(im->daemon_addr);
4596 if (status != 0) return;
4599 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4600 pango_layout_context_changed(im->layout);
4604 if (im->logarithmic && im->minval <= 0) {
4606 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4610 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4611 /* error string is set in rrd_parsetime.c */
4615 if (start_tmp < 3600 * 24 * 365 * 10) {
4617 ("the first entry to fetch should be after 1980 (%ld)",
4622 if (end_tmp < start_tmp) {
4624 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4628 im->start = start_tmp;
4630 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4633 int rrd_graph_color(
4641 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4643 color = strstr(var, "#");
4644 if (color == NULL) {
4645 if (optional == 0) {
4646 rrd_set_error("Found no color in %s", err);
4653 long unsigned int col;
4655 rest = strstr(color, ":");
4662 sscanf(color, "#%6lx%n", &col, &n);
4663 col = (col << 8) + 0xff /* shift left by 8 */ ;
4665 rrd_set_error("Color problem in %s", err);
4668 sscanf(color, "#%8lx%n", &col, &n);
4672 rrd_set_error("Color problem in %s", err);
4674 if (rrd_test_error())
4676 gdp->col = gfx_hex_to_col(col);
4689 while (*ptr != '\0')
4690 if (*ptr++ == '%') {
4692 /* line cannot end with percent char */
4695 /* '%s', '%S' and '%%' are allowed */
4696 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4698 /* %c is allowed (but use only with vdef!) */
4699 else if (*ptr == 'c') {
4704 /* or else '% 6.2lf' and such are allowed */
4706 /* optional padding character */
4707 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4709 /* This should take care of 'm.n' with all three optional */
4710 while (*ptr >= '0' && *ptr <= '9')
4714 while (*ptr >= '0' && *ptr <= '9')
4716 /* Either 'le', 'lf' or 'lg' must follow here */
4719 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4734 const char *const str)
4736 /* A VDEF currently is either "func" or "param,func"
4737 * so the parsing is rather simple. Change if needed.
4745 old_locale = setlocale(LC_NUMERIC, "C");
4746 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4747 setlocale(LC_NUMERIC, old_locale);
4748 if (n == (int) strlen(str)) { /* matched */
4752 sscanf(str, "%29[A-Z]%n", func, &n);
4753 if (n == (int) strlen(str)) { /* matched */
4757 ("Unknown function string '%s' in VDEF '%s'",
4762 if (!strcmp("PERCENT", func))
4763 gdes->vf.op = VDEF_PERCENT;
4764 else if (!strcmp("PERCENTNAN", func))
4765 gdes->vf.op = VDEF_PERCENTNAN;
4766 else if (!strcmp("MAXIMUM", func))
4767 gdes->vf.op = VDEF_MAXIMUM;
4768 else if (!strcmp("AVERAGE", func))
4769 gdes->vf.op = VDEF_AVERAGE;
4770 else if (!strcmp("STDEV", func))
4771 gdes->vf.op = VDEF_STDEV;
4772 else if (!strcmp("MINIMUM", func))
4773 gdes->vf.op = VDEF_MINIMUM;
4774 else if (!strcmp("TOTAL", func))
4775 gdes->vf.op = VDEF_TOTAL;
4776 else if (!strcmp("FIRST", func))
4777 gdes->vf.op = VDEF_FIRST;
4778 else if (!strcmp("LAST", func))
4779 gdes->vf.op = VDEF_LAST;
4780 else if (!strcmp("LSLSLOPE", func))
4781 gdes->vf.op = VDEF_LSLSLOPE;
4782 else if (!strcmp("LSLINT", func))
4783 gdes->vf.op = VDEF_LSLINT;
4784 else if (!strcmp("LSLCORREL", func))
4785 gdes->vf.op = VDEF_LSLCORREL;
4788 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4791 switch (gdes->vf.op) {
4793 case VDEF_PERCENTNAN:
4794 if (isnan(param)) { /* no parameter given */
4796 ("Function '%s' needs parameter in VDEF '%s'\n",
4800 if (param >= 0.0 && param <= 100.0) {
4801 gdes->vf.param = param;
4802 gdes->vf.val = DNAN; /* undefined */
4803 gdes->vf.when = 0; /* undefined */
4806 ("Parameter '%f' out of range in VDEF '%s'\n",
4807 param, gdes->vname);
4820 case VDEF_LSLCORREL:
4822 gdes->vf.param = DNAN;
4823 gdes->vf.val = DNAN;
4827 ("Function '%s' needs no parameter in VDEF '%s'\n",
4841 graph_desc_t *src, *dst;
4845 dst = &im->gdes[gdi];
4846 src = &im->gdes[dst->vidx];
4847 data = src->data + src->ds;
4849 steps = (src->end - src->start) / src->step;
4852 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4853 src->start, src->end, steps);
4855 switch (dst->vf.op) {
4859 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4860 rrd_set_error("malloc VDEV_PERCENT");
4863 for (step = 0; step < steps; step++) {
4864 array[step] = data[step * src->ds_cnt];
4866 qsort(array, step, sizeof(double), vdef_percent_compar);
4867 field = (steps - 1) * dst->vf.param / 100;
4868 dst->vf.val = array[field];
4869 dst->vf.when = 0; /* no time component */
4872 for (step = 0; step < steps; step++)
4873 printf("DEBUG: %3li:%10.2f %c\n",
4874 step, array[step], step == field ? '*' : ' ');
4878 case VDEF_PERCENTNAN:{
4881 /* count number of "valid" values */
4883 for (step = 0; step < steps; step++) {
4884 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4886 /* and allocate it */
4887 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4888 rrd_set_error("malloc VDEV_PERCENT");
4891 /* and fill it in */
4893 for (step = 0; step < steps; step++) {
4894 if (!isnan(data[step * src->ds_cnt])) {
4895 array[field] = data[step * src->ds_cnt];
4899 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4900 field = (nancount - 1) * dst->vf.param / 100;
4901 dst->vf.val = array[field];
4902 dst->vf.when = 0; /* no time component */
4908 while (step != steps && isnan(data[step * src->ds_cnt]))
4910 if (step == steps) {
4914 dst->vf.val = data[step * src->ds_cnt];
4915 dst->vf.when = src->start + (step + 1) * src->step;
4917 while (step != steps) {
4918 if (finite(data[step * src->ds_cnt])) {
4919 if (data[step * src->ds_cnt] > dst->vf.val) {
4920 dst->vf.val = data[step * src->ds_cnt];
4921 dst->vf.when = src->start + (step + 1) * src->step;
4932 double average = 0.0;
4934 for (step = 0; step < steps; step++) {
4935 if (finite(data[step * src->ds_cnt])) {
4936 sum += data[step * src->ds_cnt];
4941 if (dst->vf.op == VDEF_TOTAL) {
4942 dst->vf.val = sum * src->step;
4943 dst->vf.when = 0; /* no time component */
4944 } else if (dst->vf.op == VDEF_AVERAGE) {
4945 dst->vf.val = sum / cnt;
4946 dst->vf.when = 0; /* no time component */
4948 average = sum / cnt;
4950 for (step = 0; step < steps; step++) {
4951 if (finite(data[step * src->ds_cnt])) {
4952 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4955 dst->vf.val = pow(sum / cnt, 0.5);
4956 dst->vf.when = 0; /* no time component */
4966 while (step != steps && isnan(data[step * src->ds_cnt]))
4968 if (step == steps) {
4972 dst->vf.val = data[step * src->ds_cnt];
4973 dst->vf.when = src->start + (step + 1) * src->step;
4975 while (step != steps) {
4976 if (finite(data[step * src->ds_cnt])) {
4977 if (data[step * src->ds_cnt] < dst->vf.val) {
4978 dst->vf.val = data[step * src->ds_cnt];
4979 dst->vf.when = src->start + (step + 1) * src->step;
4986 /* The time value returned here is one step before the
4987 * actual time value. This is the start of the first
4991 while (step != steps && isnan(data[step * src->ds_cnt]))
4993 if (step == steps) { /* all entries were NaN */
4997 dst->vf.val = data[step * src->ds_cnt];
4998 dst->vf.when = src->start + step * src->step;
5002 /* The time value returned here is the
5003 * actual time value. This is the end of the last
5007 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5009 if (step < 0) { /* all entries were NaN */
5013 dst->vf.val = data[step * src->ds_cnt];
5014 dst->vf.when = src->start + (step + 1) * src->step;
5019 case VDEF_LSLCORREL:{
5020 /* Bestfit line by linear least squares method */
5023 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5030 for (step = 0; step < steps; step++) {
5031 if (finite(data[step * src->ds_cnt])) {
5034 SUMxx += step * step;
5035 SUMxy += step * data[step * src->ds_cnt];
5036 SUMy += data[step * src->ds_cnt];
5037 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5041 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5042 y_intercept = (SUMy - slope * SUMx) / cnt;
5045 (SUMx * SUMy) / cnt) /
5047 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5049 if (dst->vf.op == VDEF_LSLSLOPE) {
5050 dst->vf.val = slope;
5052 } else if (dst->vf.op == VDEF_LSLINT) {
5053 dst->vf.val = y_intercept;
5055 } else if (dst->vf.op == VDEF_LSLCORREL) {
5056 dst->vf.val = correl;
5069 /* NaN < -INF < finite_values < INF */
5070 int vdef_percent_compar(
5076 /* Equality is not returned; this doesn't hurt except
5077 * (maybe) for a little performance.
5080 /* First catch NaN values. They are smallest */
5081 if (isnan(*(double *) a))
5083 if (isnan(*(double *) b))
5085 /* NaN doesn't reach this part so INF and -INF are extremes.
5086 * The sign from isinf() is compatible with the sign we return
5088 if (isinf(*(double *) a))
5089 return isinf(*(double *) a);
5090 if (isinf(*(double *) b))
5091 return isinf(*(double *) b);
5092 /* If we reach this, both values must be finite */
5093 if (*(double *) a < *(double *) b)
5102 rrd_info_type_t type,
5103 rrd_infoval_t value)
5105 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5106 if (im->grinfo == NULL) {
5107 im->grinfo = im->grinfo_current;