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