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 /* pull the data from the rrd files ... */
3204 if (data_fetch(im) == -1)
3206 /* evaluate VDEF and CDEF operations ... */
3207 if (data_calc(im) == -1)
3209 /* calculate and PRINT and GPRINT definitions. We have to do it at
3210 * this point because it will affect the length of the legends
3211 * if there are no graph elements (i==0) we stop here ...
3212 * if we are lazy, try to quit ...
3218 /* if we want and can be lazy ... quit now */
3222 /**************************************************************
3223 *** Calculating sizes and locations became a bit confusing ***
3224 *** so I moved this into a separate function. ***
3225 **************************************************************/
3226 if (graph_size_location(im, i) == -1)
3229 info.u_cnt = im->xorigin;
3230 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3231 info.u_cnt = im->yorigin - im->ysize;
3232 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3233 info.u_cnt = im->xsize;
3234 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3235 info.u_cnt = im->ysize;
3236 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3237 info.u_cnt = im->ximg;
3238 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3239 info.u_cnt = im->yimg;
3240 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3241 info.u_cnt = im->start;
3242 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3243 info.u_cnt = im->end;
3244 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3246 /* if we want and can be lazy ... quit now */
3250 /* get actual drawing data and find min and max values */
3251 if (data_proc(im) == -1)
3253 if (!im->logarithmic) {
3257 /* identify si magnitude Kilo, Mega Giga ? */
3258 if (!im->rigid && !im->logarithmic)
3259 expand_range(im); /* make sure the upper and lower limit are
3262 info.u_val = im->minval;
3263 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3264 info.u_val = im->maxval;
3265 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3268 if (!calc_horizontal_grid(im))
3273 apply_gridfit(im); */
3274 /* the actual graph is created by going through the individual
3275 graph elements and then drawing them */
3276 cairo_surface_destroy(im->surface);
3277 switch (im->imgformat) {
3280 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3281 im->ximg * im->zoom,
3282 im->yimg * im->zoom);
3286 im->surface = strlen(im->graphfile)
3287 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3288 im->yimg * im->zoom)
3289 : cairo_pdf_surface_create_for_stream
3290 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3294 im->surface = strlen(im->graphfile)
3296 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3297 im->yimg * im->zoom)
3298 : cairo_ps_surface_create_for_stream
3299 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3303 im->surface = strlen(im->graphfile)
3305 cairo_svg_surface_create(im->
3307 im->ximg * im->zoom, im->yimg * im->zoom)
3308 : cairo_svg_surface_create_for_stream
3309 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3310 cairo_svg_surface_restrict_to_version
3311 (im->surface, CAIRO_SVG_VERSION_1_1);
3314 cairo_destroy(im->cr);
3315 im->cr = cairo_create(im->surface);
3316 cairo_set_antialias(im->cr, im->graph_antialias);
3317 cairo_scale(im->cr, im->zoom, im->zoom);
3318 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3319 gfx_new_area(im, 0, 0, 0, im->yimg,
3320 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3321 gfx_add_point(im, im->ximg, 0);
3323 gfx_new_area(im, im->xorigin,
3326 im->xsize, im->yorigin,
3329 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3330 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3332 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3333 im->xsize, im->ysize + 2.0);
3335 if (im->minval > 0.0)
3336 areazero = im->minval;
3337 if (im->maxval < 0.0)
3338 areazero = im->maxval;
3339 for (i = 0; i < im->gdes_c; i++) {
3340 switch (im->gdes[i].gf) {
3354 for (ii = 0; ii < im->xsize; ii++) {
3355 if (!isnan(im->gdes[i].p_data[ii])
3356 && im->gdes[i].p_data[ii] != 0.0) {
3357 if (im->gdes[i].yrule > 0) {
3364 im->ysize, 1.0, im->gdes[i].col);
3365 } else if (im->gdes[i].yrule < 0) {
3368 im->yorigin - im->ysize - 1.0,
3370 im->yorigin - im->ysize -
3373 im->ysize, 1.0, im->gdes[i].col);
3380 /* fix data points at oo and -oo */
3381 for (ii = 0; ii < im->xsize; ii++) {
3382 if (isinf(im->gdes[i].p_data[ii])) {
3383 if (im->gdes[i].p_data[ii] > 0) {
3384 im->gdes[i].p_data[ii] = im->maxval;
3386 im->gdes[i].p_data[ii] = im->minval;
3392 /* *******************************************************
3397 -------|--t-1--t--------------------------------
3399 if we know the value at time t was a then
3400 we draw a square from t-1 to t with the value a.
3402 ********************************************************* */
3403 if (im->gdes[i].col.alpha != 0.0) {
3404 /* GF_LINE and friend */
3405 if (im->gdes[i].gf == GF_LINE) {
3406 double last_y = 0.0;
3410 cairo_new_path(im->cr);
3411 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3412 if (im->gdes[i].dash) {
3413 cairo_set_dash(im->cr,
3414 im->gdes[i].p_dashes,
3415 im->gdes[i].ndash, im->gdes[i].offset);
3418 for (ii = 1; ii < im->xsize; ii++) {
3419 if (isnan(im->gdes[i].p_data[ii])
3420 || (im->slopemode == 1
3421 && isnan(im->gdes[i].p_data[ii - 1]))) {
3426 last_y = ytr(im, im->gdes[i].p_data[ii]);
3427 if (im->slopemode == 0) {
3428 double x = ii - 1 + im->xorigin;
3431 gfx_line_fit(im, &x, &y);
3432 cairo_move_to(im->cr, x, y);
3433 x = ii + im->xorigin;
3435 gfx_line_fit(im, &x, &y);
3436 cairo_line_to(im->cr, x, y);
3438 double x = ii - 1 + im->xorigin;
3440 ytr(im, im->gdes[i].p_data[ii - 1]);
3441 gfx_line_fit(im, &x, &y);
3442 cairo_move_to(im->cr, x, y);
3443 x = ii + im->xorigin;
3445 gfx_line_fit(im, &x, &y);
3446 cairo_line_to(im->cr, x, y);
3450 double x1 = ii + im->xorigin;
3451 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3453 if (im->slopemode == 0
3454 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3455 double x = ii - 1 + im->xorigin;
3458 gfx_line_fit(im, &x, &y);
3459 cairo_line_to(im->cr, x, y);
3462 gfx_line_fit(im, &x1, &y1);
3463 cairo_line_to(im->cr, x1, y1);
3466 cairo_set_source_rgba(im->cr,
3472 col.blue, im->gdes[i].col.alpha);
3473 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3474 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3475 cairo_stroke(im->cr);
3476 cairo_restore(im->cr);
3480 (double *) malloc(sizeof(double) * im->xsize * 2);
3482 (double *) malloc(sizeof(double) * im->xsize * 2);
3484 (double *) malloc(sizeof(double) * im->xsize * 2);
3486 (double *) malloc(sizeof(double) * im->xsize * 2);
3489 for (ii = 0; ii <= im->xsize; ii++) {
3492 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3498 AlmostEqual2sComplement(foreY
3502 AlmostEqual2sComplement(foreY
3512 foreY[cntI], im->gdes[i].col);
3513 while (cntI < idxI) {
3518 AlmostEqual2sComplement(foreY
3522 AlmostEqual2sComplement(foreY
3529 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3531 gfx_add_point(im, backX[idxI], backY[idxI]);
3537 AlmostEqual2sComplement(backY
3541 AlmostEqual2sComplement(backY
3548 gfx_add_point(im, backX[idxI], backY[idxI]);
3558 if (ii == im->xsize)
3560 if (im->slopemode == 0 && ii == 0) {
3563 if (isnan(im->gdes[i].p_data[ii])) {
3567 ytop = ytr(im, im->gdes[i].p_data[ii]);
3568 if (lastgdes && im->gdes[i].stack) {
3569 ybase = ytr(im, lastgdes->p_data[ii]);
3571 ybase = ytr(im, areazero);
3573 if (ybase == ytop) {
3579 double extra = ytop;
3584 if (im->slopemode == 0) {
3585 backY[++idxI] = ybase - 0.2;
3586 backX[idxI] = ii + im->xorigin - 1;
3587 foreY[idxI] = ytop + 0.2;
3588 foreX[idxI] = ii + im->xorigin - 1;
3590 backY[++idxI] = ybase - 0.2;
3591 backX[idxI] = ii + im->xorigin;
3592 foreY[idxI] = ytop + 0.2;
3593 foreX[idxI] = ii + im->xorigin;
3595 /* close up any remaining area */
3600 } /* else GF_LINE */
3602 /* if color != 0x0 */
3603 /* make sure we do not run into trouble when stacking on NaN */
3604 for (ii = 0; ii < im->xsize; ii++) {
3605 if (isnan(im->gdes[i].p_data[ii])) {
3606 if (lastgdes && (im->gdes[i].stack)) {
3607 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3609 im->gdes[i].p_data[ii] = areazero;
3613 lastgdes = &(im->gdes[i]);
3617 ("STACK should already be turned into LINE or AREA here");
3622 cairo_reset_clip(im->cr);
3624 /* grid_paint also does the text */
3625 if (!(im->extra_flags & ONLY_GRAPH))
3627 if (!(im->extra_flags & ONLY_GRAPH))
3629 /* the RULES are the last thing to paint ... */
3630 for (i = 0; i < im->gdes_c; i++) {
3632 switch (im->gdes[i].gf) {
3634 if (im->gdes[i].yrule >= im->minval
3635 && im->gdes[i].yrule <= im->maxval) {
3637 if (im->gdes[i].dash) {
3638 cairo_set_dash(im->cr,
3639 im->gdes[i].p_dashes,
3640 im->gdes[i].ndash, im->gdes[i].offset);
3642 gfx_line(im, im->xorigin,
3643 ytr(im, im->gdes[i].yrule),
3644 im->xorigin + im->xsize,
3645 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3646 cairo_stroke(im->cr);
3647 cairo_restore(im->cr);
3651 if (im->gdes[i].xrule >= im->start
3652 && im->gdes[i].xrule <= im->end) {
3654 if (im->gdes[i].dash) {
3655 cairo_set_dash(im->cr,
3656 im->gdes[i].p_dashes,
3657 im->gdes[i].ndash, im->gdes[i].offset);
3660 xtr(im, im->gdes[i].xrule),
3661 im->yorigin, xtr(im,
3665 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3666 cairo_stroke(im->cr);
3667 cairo_restore(im->cr);
3676 switch (im->imgformat) {
3679 cairo_status_t status;
3681 status = strlen(im->graphfile) ?
3682 cairo_surface_write_to_png(im->surface, im->graphfile)
3683 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3686 if (status != CAIRO_STATUS_SUCCESS) {
3687 rrd_set_error("Could not save png to '%s'", im->graphfile);
3693 if (strlen(im->graphfile)) {
3694 cairo_show_page(im->cr);
3696 cairo_surface_finish(im->surface);
3705 /*****************************************************
3707 *****************************************************/
3714 if ((im->gdes = (graph_desc_t *)
3715 rrd_realloc(im->gdes, (im->gdes_c)
3716 * sizeof(graph_desc_t))) == NULL) {
3717 rrd_set_error("realloc graph_descs");
3722 im->gdes[im->gdes_c - 1].step = im->step;
3723 im->gdes[im->gdes_c - 1].step_orig = im->step;
3724 im->gdes[im->gdes_c - 1].stack = 0;
3725 im->gdes[im->gdes_c - 1].linewidth = 0;
3726 im->gdes[im->gdes_c - 1].debug = 0;
3727 im->gdes[im->gdes_c - 1].start = im->start;
3728 im->gdes[im->gdes_c - 1].start_orig = im->start;
3729 im->gdes[im->gdes_c - 1].end = im->end;
3730 im->gdes[im->gdes_c - 1].end_orig = im->end;
3731 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3732 im->gdes[im->gdes_c - 1].data = NULL;
3733 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3734 im->gdes[im->gdes_c - 1].data_first = 0;
3735 im->gdes[im->gdes_c - 1].p_data = NULL;
3736 im->gdes[im->gdes_c - 1].rpnp = NULL;
3737 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3738 im->gdes[im->gdes_c - 1].shift = 0.0;
3739 im->gdes[im->gdes_c - 1].dash = 0;
3740 im->gdes[im->gdes_c - 1].ndash = 0;
3741 im->gdes[im->gdes_c - 1].offset = 0;
3742 im->gdes[im->gdes_c - 1].col.red = 0.0;
3743 im->gdes[im->gdes_c - 1].col.green = 0.0;
3744 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3745 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3746 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3747 im->gdes[im->gdes_c - 1].format[0] = '\0';
3748 im->gdes[im->gdes_c - 1].strftm = 0;
3749 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3750 im->gdes[im->gdes_c - 1].ds = -1;
3751 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3752 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3753 im->gdes[im->gdes_c - 1].yrule = DNAN;
3754 im->gdes[im->gdes_c - 1].xrule = 0;
3758 /* copies input untill the first unescaped colon is found
3759 or until input ends. backslashes have to be escaped as well */
3761 const char *const input,
3767 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3768 if (input[inp] == '\\'
3769 && input[inp + 1] != '\0'
3770 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3771 output[outp++] = input[++inp];
3773 output[outp++] = input[inp];
3776 output[outp] = '\0';
3780 /* Now just a wrapper around rrd_graph_v */
3792 rrd_info_t *grinfo = NULL;
3795 grinfo = rrd_graph_v(argc, argv);
3801 if (strcmp(walker->key, "image_info") == 0) {
3804 (char**)rrd_realloc((*prdata),
3805 (prlines + 1) * sizeof(char *))) == NULL) {
3806 rrd_set_error("realloc prdata");
3809 /* imginfo goes to position 0 in the prdata array */
3810 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3811 + 2) * sizeof(char));
3812 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3813 (*prdata)[prlines] = NULL;
3815 /* skip anything else */
3816 walker = walker->next;
3824 if (strcmp(walker->key, "image_width") == 0) {
3825 *xsize = walker->value.u_cnt;
3826 } else if (strcmp(walker->key, "image_height") == 0) {
3827 *ysize = walker->value.u_cnt;
3828 } else if (strcmp(walker->key, "value_min") == 0) {
3829 *ymin = walker->value.u_val;
3830 } else if (strcmp(walker->key, "value_max") == 0) {
3831 *ymax = walker->value.u_val;
3832 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3835 (char**)rrd_realloc((*prdata),
3836 (prlines + 1) * sizeof(char *))) == NULL) {
3837 rrd_set_error("realloc prdata");
3840 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3841 + 2) * sizeof(char));
3842 (*prdata)[prlines] = NULL;
3843 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3844 } else if (strcmp(walker->key, "image") == 0) {
3845 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3846 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3847 rrd_set_error("writing image");
3851 /* skip anything else */
3852 walker = walker->next;
3854 rrd_info_free(grinfo);
3859 /* Some surgery done on this function, it became ridiculously big.
3861 ** - initializing now in rrd_graph_init()
3862 ** - options parsing now in rrd_graph_options()
3863 ** - script parsing now in rrd_graph_script()
3865 rrd_info_t *rrd_graph_v(
3871 rrd_graph_init(&im);
3872 /* a dummy surface so that we can measure text sizes for placements */
3874 rrd_graph_options(argc, argv, &im);
3875 if (rrd_test_error()) {
3876 rrd_info_free(im.grinfo);
3881 if (optind >= argc) {
3882 rrd_info_free(im.grinfo);
3884 rrd_set_error("missing filename");
3888 if (strlen(argv[optind]) >= MAXPATH) {
3889 rrd_set_error("filename (including path) too long");
3890 rrd_info_free(im.grinfo);
3895 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3896 im.graphfile[MAXPATH - 1] = '\0';
3898 if (strcmp(im.graphfile, "-") == 0) {
3899 im.graphfile[0] = '\0';
3902 rrd_graph_script(argc, argv, &im, 1);
3903 if (rrd_test_error()) {
3904 rrd_info_free(im.grinfo);
3909 /* Everything is now read and the actual work can start */
3911 if (graph_paint(&im) == -1) {
3912 rrd_info_free(im.grinfo);
3918 /* The image is generated and needs to be output.
3919 ** Also, if needed, print a line with information about the image.
3927 path = strdup(im.graphfile);
3928 filename = basename(path);
3930 sprintf_alloc(im.imginfo,
3933 im.ximg), (long) (im.zoom * im.yimg));
3934 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3938 if (im.rendered_image) {
3941 img.u_blo.size = im.rendered_image_size;
3942 img.u_blo.ptr = im.rendered_image;
3943 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3952 image_desc_t *im,int prop,char *font, double size ){
3954 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3955 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3956 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3959 im->text_prop[prop].size = size;
3961 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3962 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3966 void rrd_graph_init(
3971 char *deffont = getenv("RRD_DEFAULT_FONT");
3972 static PangoFontMap *fontmap = NULL;
3973 PangoContext *context;
3978 #ifdef HAVE_SETLOCALE
3979 setlocale(LC_TIME, "");
3980 #ifdef HAVE_MBSTOWCS
3981 setlocale(LC_CTYPE, "");
3985 im->daemon_addr = NULL;
3986 im->draw_x_grid = 1;
3987 im->draw_y_grid = 1;
3988 im->extra_flags = 0;
3989 im->font_options = cairo_font_options_create();
3990 im->forceleftspace = 0;
3993 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3994 im->grid_dash_off = 1;
3995 im->grid_dash_on = 1;
3997 im->grinfo = (rrd_info_t *) NULL;
3998 im->grinfo_current = (rrd_info_t *) NULL;
3999 im->imgformat = IF_PNG;
4002 im->legenddirection = TOP_DOWN;
4003 im->legendheight = 0;
4004 im->legendposition = SOUTH;
4005 im->legendwidth = 0;
4006 im->logarithmic = 0;
4012 im->rendered_image_size = 0;
4013 im->rendered_image = NULL;
4017 im->tabwidth = 40.0;
4018 im->title[0] = '\0';
4019 im->unitsexponent = 9999;
4020 im->unitslength = 6;
4021 im->viewfactor = 1.0;
4022 im->watermark[0] = '\0';
4023 im->with_markup = 0;
4025 im->xlab_user.minsec = -1;
4027 im->xOriginLegend = 0;
4028 im->xOriginLegendY = 0;
4029 im->xOriginLegendY2 = 0;
4030 im->xOriginTitle = 0;
4032 im->ygridstep = DNAN;
4034 im->ylegend[0] = '\0';
4035 im->second_axis_scale = 0; /* 0 disables it */
4036 im->second_axis_shift = 0; /* no shift by default */
4037 im->second_axis_legend[0] = '\0';
4038 im->second_axis_format[0] = '\0';
4040 im->yOriginLegend = 0;
4041 im->yOriginLegendY = 0;
4042 im->yOriginLegendY2 = 0;
4043 im->yOriginTitle = 0;
4047 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4048 im->cr = cairo_create(im->surface);
4050 for (i = 0; i < DIM(text_prop); i++) {
4051 im->text_prop[i].size = -1;
4052 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4055 if (fontmap == NULL){
4056 fontmap = pango_cairo_font_map_get_default();
4059 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4061 pango_cairo_context_set_resolution(context, 100);
4063 pango_cairo_update_context(im->cr,context);
4065 im->layout = pango_layout_new(context);
4067 // im->layout = pango_cairo_create_layout(im->cr);
4070 cairo_font_options_set_hint_style
4071 (im->font_options, CAIRO_HINT_STYLE_FULL);
4072 cairo_font_options_set_hint_metrics
4073 (im->font_options, CAIRO_HINT_METRICS_ON);
4074 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4078 for (i = 0; i < DIM(graph_col); i++)
4079 im->graph_col[i] = graph_col[i];
4085 void rrd_graph_options(
4092 char *parsetime_error = NULL;
4093 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4094 time_t start_tmp = 0, end_tmp = 0;
4096 rrd_time_value_t start_tv, end_tv;
4097 long unsigned int color;
4098 char *old_locale = "";
4100 /* defines for long options without a short equivalent. should be bytes,
4101 and may not collide with (the ASCII value of) short options */
4102 #define LONGOPT_UNITS_SI 255
4105 struct option long_options[] = {
4106 { "start", required_argument, 0, 's'},
4107 { "end", required_argument, 0, 'e'},
4108 { "x-grid", required_argument, 0, 'x'},
4109 { "y-grid", required_argument, 0, 'y'},
4110 { "vertical-label", required_argument, 0, 'v'},
4111 { "width", required_argument, 0, 'w'},
4112 { "height", required_argument, 0, 'h'},
4113 { "full-size-mode", no_argument, 0, 'D'},
4114 { "interlaced", no_argument, 0, 'i'},
4115 { "upper-limit", required_argument, 0, 'u'},
4116 { "lower-limit", required_argument, 0, 'l'},
4117 { "rigid", no_argument, 0, 'r'},
4118 { "base", required_argument, 0, 'b'},
4119 { "logarithmic", no_argument, 0, 'o'},
4120 { "color", required_argument, 0, 'c'},
4121 { "font", required_argument, 0, 'n'},
4122 { "title", required_argument, 0, 't'},
4123 { "imginfo", required_argument, 0, 'f'},
4124 { "imgformat", required_argument, 0, 'a'},
4125 { "lazy", no_argument, 0, 'z'},
4126 { "zoom", required_argument, 0, 'm'},
4127 { "no-legend", no_argument, 0, 'g'},
4128 { "legend-position", required_argument, 0, 1005},
4129 { "legend-direction", required_argument, 0, 1006},
4130 { "force-rules-legend", no_argument, 0, 'F'},
4131 { "only-graph", no_argument, 0, 'j'},
4132 { "alt-y-grid", no_argument, 0, 'Y'},
4133 {"disable-rrdtool-tag", no_argument, 0, 1001},
4134 {"right-axis", required_argument, 0, 1002},
4135 {"right-axis-label", required_argument, 0, 1003},
4136 {"right-axis-format", required_argument, 0, 1004},
4137 { "no-minor", no_argument, 0, 'I'},
4138 { "slope-mode", no_argument, 0, 'E'},
4139 { "alt-autoscale", no_argument, 0, 'A'},
4140 { "alt-autoscale-min", no_argument, 0, 'J'},
4141 { "alt-autoscale-max", no_argument, 0, 'M'},
4142 { "no-gridfit", no_argument, 0, 'N'},
4143 { "units-exponent", required_argument, 0, 'X'},
4144 { "units-length", required_argument, 0, 'L'},
4145 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4146 { "step", required_argument, 0, 'S'},
4147 { "tabwidth", required_argument, 0, 'T'},
4148 { "font-render-mode", required_argument, 0, 'R'},
4149 { "graph-render-mode", required_argument, 0, 'G'},
4150 { "font-smoothing-threshold", required_argument, 0, 'B'},
4151 { "watermark", required_argument, 0, 'W'},
4152 { "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 */
4153 { "pango-markup", no_argument, 0, 'P'},
4154 { "daemon", required_argument, 0, 'd'},
4160 opterr = 0; /* initialize getopt */
4161 rrd_parsetime("end-24h", &start_tv);
4162 rrd_parsetime("now", &end_tv);
4164 int option_index = 0;
4166 int col_start, col_end;
4168 opt = getopt_long(argc, argv,
4169 "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:",
4170 long_options, &option_index);
4175 im->extra_flags |= NOMINOR;
4178 im->extra_flags |= ALTYGRID;
4181 im->extra_flags |= ALTAUTOSCALE;
4184 im->extra_flags |= ALTAUTOSCALE_MIN;
4187 im->extra_flags |= ALTAUTOSCALE_MAX;
4190 im->extra_flags |= ONLY_GRAPH;
4193 im->extra_flags |= NOLEGEND;
4196 if (strcmp(optarg, "north") == 0) {
4197 im->legendposition = NORTH;
4198 } else if (strcmp(optarg, "west") == 0) {
4199 im->legendposition = WEST;
4200 } else if (strcmp(optarg, "south") == 0) {
4201 im->legendposition = SOUTH;
4202 } else if (strcmp(optarg, "east") == 0) {
4203 im->legendposition = EAST;
4205 rrd_set_error("unknown legend-position '%s'", optarg);
4210 if (strcmp(optarg, "topdown") == 0) {
4211 im->legenddirection = TOP_DOWN;
4212 } else if (strcmp(optarg, "bottomup") == 0) {
4213 im->legenddirection = BOTTOM_UP;
4215 rrd_set_error("unknown legend-position '%s'", optarg);
4220 im->extra_flags |= FORCE_RULES_LEGEND;
4223 im->extra_flags |= NO_RRDTOOL_TAG;
4225 case LONGOPT_UNITS_SI:
4226 if (im->extra_flags & FORCE_UNITS) {
4227 rrd_set_error("--units can only be used once!");
4228 setlocale(LC_NUMERIC, old_locale);
4231 if (strcmp(optarg, "si") == 0)
4232 im->extra_flags |= FORCE_UNITS_SI;
4234 rrd_set_error("invalid argument for --units: %s", optarg);
4239 im->unitsexponent = atoi(optarg);
4242 im->unitslength = atoi(optarg);
4243 im->forceleftspace = 1;
4246 old_locale = setlocale(LC_NUMERIC, "C");
4247 im->tabwidth = atof(optarg);
4248 setlocale(LC_NUMERIC, old_locale);
4251 old_locale = setlocale(LC_NUMERIC, "C");
4252 im->step = atoi(optarg);
4253 setlocale(LC_NUMERIC, old_locale);
4259 im->with_markup = 1;
4262 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4263 rrd_set_error("start time: %s", parsetime_error);
4268 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4269 rrd_set_error("end time: %s", parsetime_error);
4274 if (strcmp(optarg, "none") == 0) {
4275 im->draw_x_grid = 0;
4279 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4281 &im->xlab_user.gridst,
4283 &im->xlab_user.mgridst,
4285 &im->xlab_user.labst,
4286 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4287 strncpy(im->xlab_form, optarg + stroff,
4288 sizeof(im->xlab_form) - 1);
4289 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4291 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4292 rrd_set_error("unknown keyword %s", scan_gtm);
4295 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4297 rrd_set_error("unknown keyword %s", scan_mtm);
4300 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4301 rrd_set_error("unknown keyword %s", scan_ltm);
4304 im->xlab_user.minsec = 1;
4305 im->xlab_user.stst = im->xlab_form;
4307 rrd_set_error("invalid x-grid format");
4313 if (strcmp(optarg, "none") == 0) {
4314 im->draw_y_grid = 0;
4317 old_locale = setlocale(LC_NUMERIC, "C");
4318 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4319 setlocale(LC_NUMERIC, old_locale);
4320 if (im->ygridstep <= 0) {
4321 rrd_set_error("grid step must be > 0");
4323 } else if (im->ylabfact < 1) {
4324 rrd_set_error("label factor must be > 0");
4328 setlocale(LC_NUMERIC, old_locale);
4329 rrd_set_error("invalid y-grid format");
4333 case 1002: /* right y axis */
4337 &im->second_axis_scale,
4338 &im->second_axis_shift) == 2) {
4339 if(im->second_axis_scale==0){
4340 rrd_set_error("the second_axis_scale must not be 0");
4344 rrd_set_error("invalid right-axis format expected scale:shift");
4349 strncpy(im->second_axis_legend,optarg,150);
4350 im->second_axis_legend[150]='\0';
4353 if (bad_format(optarg)){
4354 rrd_set_error("use either %le or %lf formats");
4357 strncpy(im->second_axis_format,optarg,150);
4358 im->second_axis_format[150]='\0';
4361 strncpy(im->ylegend, optarg, 150);
4362 im->ylegend[150] = '\0';
4365 old_locale = setlocale(LC_NUMERIC, "C");
4366 im->maxval = atof(optarg);
4367 setlocale(LC_NUMERIC, old_locale);
4370 old_locale = setlocale(LC_NUMERIC, "C");
4371 im->minval = atof(optarg);
4372 setlocale(LC_NUMERIC, old_locale);
4375 im->base = atol(optarg);
4376 if (im->base != 1024 && im->base != 1000) {
4378 ("the only sensible value for base apart from 1000 is 1024");
4383 long_tmp = atol(optarg);
4384 if (long_tmp < 10) {
4385 rrd_set_error("width below 10 pixels");
4388 im->xsize = long_tmp;
4391 long_tmp = atol(optarg);
4392 if (long_tmp < 10) {
4393 rrd_set_error("height below 10 pixels");
4396 im->ysize = long_tmp;
4399 im->extra_flags |= FULL_SIZE_MODE;
4402 /* interlaced png not supported at the moment */
4408 im->imginfo = optarg;
4412 (im->imgformat = if_conv(optarg)) == -1) {
4413 rrd_set_error("unsupported graphics format '%s'", optarg);
4424 im->logarithmic = 1;
4428 "%10[A-Z]#%n%8lx%n",
4429 col_nam, &col_start, &color, &col_end) == 2) {
4431 int col_len = col_end - col_start;
4436 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4444 (((color & 0xF000) *
4445 0x11000) | ((color & 0x0F00) *
4446 0x01100) | ((color &
4449 ((color & 0x000F) * 0x00011)
4453 color = (color << 8) + 0xff /* shift left by 8 */ ;
4458 rrd_set_error("the color format is #RRGGBB[AA]");
4461 if ((ci = grc_conv(col_nam)) != -1) {
4462 im->graph_col[ci] = gfx_hex_to_col(color);
4464 rrd_set_error("invalid color name '%s'", col_nam);
4468 rrd_set_error("invalid color def format");
4477 old_locale = setlocale(LC_NUMERIC, "C");
4478 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4479 int sindex, propidx;
4481 setlocale(LC_NUMERIC, old_locale);
4482 if ((sindex = text_prop_conv(prop)) != -1) {
4483 for (propidx = sindex;
4484 propidx < TEXT_PROP_LAST; propidx++) {
4486 rrd_set_font_desc(im,propidx,NULL,size);
4488 if ((int) strlen(optarg) > end+2) {
4489 if (optarg[end] == ':') {
4490 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4493 ("expected : after font size in '%s'",
4498 /* only run the for loop for DEFAULT (0) for
4499 all others, we break here. woodo programming */
4500 if (propidx == sindex && sindex != 0)
4504 rrd_set_error("invalid fonttag '%s'", prop);
4508 setlocale(LC_NUMERIC, old_locale);
4509 rrd_set_error("invalid text property format");
4515 old_locale = setlocale(LC_NUMERIC, "C");
4516 im->zoom = atof(optarg);
4517 setlocale(LC_NUMERIC, old_locale);
4518 if (im->zoom <= 0.0) {
4519 rrd_set_error("zoom factor must be > 0");
4524 strncpy(im->title, optarg, 150);
4525 im->title[150] = '\0';
4528 if (strcmp(optarg, "normal") == 0) {
4529 cairo_font_options_set_antialias
4530 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4531 cairo_font_options_set_hint_style
4532 (im->font_options, CAIRO_HINT_STYLE_FULL);
4533 } else if (strcmp(optarg, "light") == 0) {
4534 cairo_font_options_set_antialias
4535 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4536 cairo_font_options_set_hint_style
4537 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4538 } else if (strcmp(optarg, "mono") == 0) {
4539 cairo_font_options_set_antialias
4540 (im->font_options, CAIRO_ANTIALIAS_NONE);
4541 cairo_font_options_set_hint_style
4542 (im->font_options, CAIRO_HINT_STYLE_FULL);
4544 rrd_set_error("unknown font-render-mode '%s'", optarg);
4549 if (strcmp(optarg, "normal") == 0)
4550 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4551 else if (strcmp(optarg, "mono") == 0)
4552 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4554 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4559 /* not supported curently */
4562 strncpy(im->watermark, optarg, 100);
4563 im->watermark[99] = '\0';
4567 if (im->daemon_addr != NULL)
4569 rrd_set_error ("You cannot specify --daemon "
4574 im->daemon_addr = strdup(optarg);
4575 if (im->daemon_addr == NULL)
4577 rrd_set_error("strdup failed");
4585 rrd_set_error("unknown option '%c'", optopt);
4587 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4592 { /* try to connect to rrdcached */
4593 int status = rrdc_connect(im->daemon_addr);
4594 if (status != 0) return;
4597 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4598 pango_layout_context_changed(im->layout);
4602 if (im->logarithmic && im->minval <= 0) {
4604 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4608 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4609 /* error string is set in rrd_parsetime.c */
4613 if (start_tmp < 3600 * 24 * 365 * 10) {
4615 ("the first entry to fetch should be after 1980 (%ld)",
4620 if (end_tmp < start_tmp) {
4622 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4626 im->start = start_tmp;
4628 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4631 int rrd_graph_color(
4639 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4641 color = strstr(var, "#");
4642 if (color == NULL) {
4643 if (optional == 0) {
4644 rrd_set_error("Found no color in %s", err);
4651 long unsigned int col;
4653 rest = strstr(color, ":");
4660 sscanf(color, "#%6lx%n", &col, &n);
4661 col = (col << 8) + 0xff /* shift left by 8 */ ;
4663 rrd_set_error("Color problem in %s", err);
4666 sscanf(color, "#%8lx%n", &col, &n);
4670 rrd_set_error("Color problem in %s", err);
4672 if (rrd_test_error())
4674 gdp->col = gfx_hex_to_col(col);
4687 while (*ptr != '\0')
4688 if (*ptr++ == '%') {
4690 /* line cannot end with percent char */
4693 /* '%s', '%S' and '%%' are allowed */
4694 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4696 /* %c is allowed (but use only with vdef!) */
4697 else if (*ptr == 'c') {
4702 /* or else '% 6.2lf' and such are allowed */
4704 /* optional padding character */
4705 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4707 /* This should take care of 'm.n' with all three optional */
4708 while (*ptr >= '0' && *ptr <= '9')
4712 while (*ptr >= '0' && *ptr <= '9')
4714 /* Either 'le', 'lf' or 'lg' must follow here */
4717 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4732 const char *const str)
4734 /* A VDEF currently is either "func" or "param,func"
4735 * so the parsing is rather simple. Change if needed.
4743 old_locale = setlocale(LC_NUMERIC, "C");
4744 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4745 setlocale(LC_NUMERIC, old_locale);
4746 if (n == (int) strlen(str)) { /* matched */
4750 sscanf(str, "%29[A-Z]%n", func, &n);
4751 if (n == (int) strlen(str)) { /* matched */
4755 ("Unknown function string '%s' in VDEF '%s'",
4760 if (!strcmp("PERCENT", func))
4761 gdes->vf.op = VDEF_PERCENT;
4762 else if (!strcmp("PERCENTNAN", func))
4763 gdes->vf.op = VDEF_PERCENTNAN;
4764 else if (!strcmp("MAXIMUM", func))
4765 gdes->vf.op = VDEF_MAXIMUM;
4766 else if (!strcmp("AVERAGE", func))
4767 gdes->vf.op = VDEF_AVERAGE;
4768 else if (!strcmp("STDEV", func))
4769 gdes->vf.op = VDEF_STDEV;
4770 else if (!strcmp("MINIMUM", func))
4771 gdes->vf.op = VDEF_MINIMUM;
4772 else if (!strcmp("TOTAL", func))
4773 gdes->vf.op = VDEF_TOTAL;
4774 else if (!strcmp("FIRST", func))
4775 gdes->vf.op = VDEF_FIRST;
4776 else if (!strcmp("LAST", func))
4777 gdes->vf.op = VDEF_LAST;
4778 else if (!strcmp("LSLSLOPE", func))
4779 gdes->vf.op = VDEF_LSLSLOPE;
4780 else if (!strcmp("LSLINT", func))
4781 gdes->vf.op = VDEF_LSLINT;
4782 else if (!strcmp("LSLCORREL", func))
4783 gdes->vf.op = VDEF_LSLCORREL;
4786 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4789 switch (gdes->vf.op) {
4791 case VDEF_PERCENTNAN:
4792 if (isnan(param)) { /* no parameter given */
4794 ("Function '%s' needs parameter in VDEF '%s'\n",
4798 if (param >= 0.0 && param <= 100.0) {
4799 gdes->vf.param = param;
4800 gdes->vf.val = DNAN; /* undefined */
4801 gdes->vf.when = 0; /* undefined */
4804 ("Parameter '%f' out of range in VDEF '%s'\n",
4805 param, gdes->vname);
4818 case VDEF_LSLCORREL:
4820 gdes->vf.param = DNAN;
4821 gdes->vf.val = DNAN;
4825 ("Function '%s' needs no parameter in VDEF '%s'\n",
4839 graph_desc_t *src, *dst;
4843 dst = &im->gdes[gdi];
4844 src = &im->gdes[dst->vidx];
4845 data = src->data + src->ds;
4847 steps = (src->end - src->start) / src->step;
4850 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4851 src->start, src->end, steps);
4853 switch (dst->vf.op) {
4857 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4858 rrd_set_error("malloc VDEV_PERCENT");
4861 for (step = 0; step < steps; step++) {
4862 array[step] = data[step * src->ds_cnt];
4864 qsort(array, step, sizeof(double), vdef_percent_compar);
4865 field = (steps - 1) * dst->vf.param / 100;
4866 dst->vf.val = array[field];
4867 dst->vf.when = 0; /* no time component */
4870 for (step = 0; step < steps; step++)
4871 printf("DEBUG: %3li:%10.2f %c\n",
4872 step, array[step], step == field ? '*' : ' ');
4876 case VDEF_PERCENTNAN:{
4879 /* count number of "valid" values */
4881 for (step = 0; step < steps; step++) {
4882 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4884 /* and allocate it */
4885 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4886 rrd_set_error("malloc VDEV_PERCENT");
4889 /* and fill it in */
4891 for (step = 0; step < steps; step++) {
4892 if (!isnan(data[step * src->ds_cnt])) {
4893 array[field] = data[step * src->ds_cnt];
4897 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4898 field = (nancount - 1) * dst->vf.param / 100;
4899 dst->vf.val = array[field];
4900 dst->vf.when = 0; /* no time component */
4906 while (step != steps && isnan(data[step * src->ds_cnt]))
4908 if (step == steps) {
4912 dst->vf.val = data[step * src->ds_cnt];
4913 dst->vf.when = src->start + (step + 1) * src->step;
4915 while (step != steps) {
4916 if (finite(data[step * src->ds_cnt])) {
4917 if (data[step * src->ds_cnt] > dst->vf.val) {
4918 dst->vf.val = data[step * src->ds_cnt];
4919 dst->vf.when = src->start + (step + 1) * src->step;
4930 double average = 0.0;
4932 for (step = 0; step < steps; step++) {
4933 if (finite(data[step * src->ds_cnt])) {
4934 sum += data[step * src->ds_cnt];
4939 if (dst->vf.op == VDEF_TOTAL) {
4940 dst->vf.val = sum * src->step;
4941 dst->vf.when = 0; /* no time component */
4942 } else if (dst->vf.op == VDEF_AVERAGE) {
4943 dst->vf.val = sum / cnt;
4944 dst->vf.when = 0; /* no time component */
4946 average = sum / cnt;
4948 for (step = 0; step < steps; step++) {
4949 if (finite(data[step * src->ds_cnt])) {
4950 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4953 dst->vf.val = pow(sum / cnt, 0.5);
4954 dst->vf.when = 0; /* no time component */
4964 while (step != steps && isnan(data[step * src->ds_cnt]))
4966 if (step == steps) {
4970 dst->vf.val = data[step * src->ds_cnt];
4971 dst->vf.when = src->start + (step + 1) * src->step;
4973 while (step != steps) {
4974 if (finite(data[step * src->ds_cnt])) {
4975 if (data[step * src->ds_cnt] < dst->vf.val) {
4976 dst->vf.val = data[step * src->ds_cnt];
4977 dst->vf.when = src->start + (step + 1) * src->step;
4984 /* The time value returned here is one step before the
4985 * actual time value. This is the start of the first
4989 while (step != steps && isnan(data[step * src->ds_cnt]))
4991 if (step == steps) { /* all entries were NaN */
4995 dst->vf.val = data[step * src->ds_cnt];
4996 dst->vf.when = src->start + step * src->step;
5000 /* The time value returned here is the
5001 * actual time value. This is the end of the last
5005 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5007 if (step < 0) { /* all entries were NaN */
5011 dst->vf.val = data[step * src->ds_cnt];
5012 dst->vf.when = src->start + (step + 1) * src->step;
5017 case VDEF_LSLCORREL:{
5018 /* Bestfit line by linear least squares method */
5021 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5028 for (step = 0; step < steps; step++) {
5029 if (finite(data[step * src->ds_cnt])) {
5032 SUMxx += step * step;
5033 SUMxy += step * data[step * src->ds_cnt];
5034 SUMy += data[step * src->ds_cnt];
5035 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5039 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5040 y_intercept = (SUMy - slope * SUMx) / cnt;
5043 (SUMx * SUMy) / cnt) /
5045 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5047 if (dst->vf.op == VDEF_LSLSLOPE) {
5048 dst->vf.val = slope;
5050 } else if (dst->vf.op == VDEF_LSLINT) {
5051 dst->vf.val = y_intercept;
5053 } else if (dst->vf.op == VDEF_LSLCORREL) {
5054 dst->vf.val = correl;
5067 /* NaN < -INF < finite_values < INF */
5068 int vdef_percent_compar(
5074 /* Equality is not returned; this doesn't hurt except
5075 * (maybe) for a little performance.
5078 /* First catch NaN values. They are smallest */
5079 if (isnan(*(double *) a))
5081 if (isnan(*(double *) b))
5083 /* NaN doesn't reach this part so INF and -INF are extremes.
5084 * The sign from isinf() is compatible with the sign we return
5086 if (isinf(*(double *) a))
5087 return isinf(*(double *) a);
5088 if (isinf(*(double *) b))
5089 return isinf(*(double *) b);
5090 /* If we reach this, both values must be finite */
5091 if (*(double *) a < *(double *) b)
5100 rrd_info_type_t type,
5101 rrd_infoval_t value)
5103 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5104 if (im->grinfo == NULL) {
5105 im->grinfo = im->grinfo_current;