1 /****************************************************************************
2 * RRDtool 1.4.2 Copyright by Tobi Oetiker, 1997-2009
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
20 #include "plbasename.h"
23 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
32 #ifdef HAVE_LANGINFO_H
36 #include "rrd_graph.h"
37 #include "rrd_client.h"
39 /* some constant definitions */
43 #ifndef RRD_DEFAULT_FONT
44 /* there is special code later to pick Cour.ttf when running on windows */
45 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
48 text_prop_t text_prop[] = {
49 {8.0, RRD_DEFAULT_FONT,NULL}
51 {9.0, RRD_DEFAULT_FONT,NULL}
53 {7.0, RRD_DEFAULT_FONT,NULL}
55 {8.0, RRD_DEFAULT_FONT,NULL}
57 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
59 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
63 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
65 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
67 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
69 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
71 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
73 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
75 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
77 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
79 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
81 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
82 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
84 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
86 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
88 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
90 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
92 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
95 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
98 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
101 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
103 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
104 365 * 24 * 3600, "%y"}
106 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
109 /* sensible y label intervals ...*/
133 {20.0, {1, 5, 10, 20}
139 {100.0, {1, 2, 5, 10}
142 {200.0, {1, 5, 10, 20}
145 {500.0, {1, 2, 4, 10}
153 gfx_color_t graph_col[] = /* default colors */
155 {1.00, 1.00, 1.00, 1.00}, /* canvas */
156 {0.95, 0.95, 0.95, 1.00}, /* background */
157 {0.81, 0.81, 0.81, 1.00}, /* shade A */
158 {0.62, 0.62, 0.62, 1.00}, /* shade B */
159 {0.56, 0.56, 0.56, 0.75}, /* grid */
160 {0.87, 0.31, 0.31, 0.60}, /* major grid */
161 {0.00, 0.00, 0.00, 1.00}, /* font */
162 {0.50, 0.12, 0.12, 1.00}, /* arrow */
163 {0.12, 0.12, 0.12, 1.00}, /* axis */
164 {0.00, 0.00, 0.00, 1.00} /* frame */
171 # define DPRINT(x) (void)(printf x, printf("\n"))
177 /* initialize with xtr(im,0); */
185 pixie = (double) im->xsize / (double) (im->end - im->start);
188 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
191 /* translate data values into y coordinates */
200 if (!im->logarithmic)
201 pixie = (double) im->ysize / (im->maxval - im->minval);
204 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
206 } else if (!im->logarithmic) {
207 yval = im->yorigin - pixie * (value - im->minval);
209 if (value < im->minval) {
212 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
220 /* conversion function for symbolic entry names */
223 #define conv_if(VV,VVV) \
224 if (strcmp(#VV, string) == 0) return VVV ;
230 conv_if(PRINT, GF_PRINT);
231 conv_if(GPRINT, GF_GPRINT);
232 conv_if(COMMENT, GF_COMMENT);
233 conv_if(HRULE, GF_HRULE);
234 conv_if(VRULE, GF_VRULE);
235 conv_if(LINE, GF_LINE);
236 conv_if(AREA, GF_AREA);
237 conv_if(STACK, GF_STACK);
238 conv_if(TICK, GF_TICK);
239 conv_if(TEXTALIGN, GF_TEXTALIGN);
240 conv_if(DEF, GF_DEF);
241 conv_if(CDEF, GF_CDEF);
242 conv_if(VDEF, GF_VDEF);
243 conv_if(XPORT, GF_XPORT);
244 conv_if(SHIFT, GF_SHIFT);
246 return (enum gf_en)(-1);
249 enum gfx_if_en if_conv(
253 conv_if(PNG, IF_PNG);
254 conv_if(SVG, IF_SVG);
255 conv_if(EPS, IF_EPS);
256 conv_if(PDF, IF_PDF);
258 return (enum gfx_if_en)(-1);
261 enum tmt_en tmt_conv(
265 conv_if(SECOND, TMT_SECOND);
266 conv_if(MINUTE, TMT_MINUTE);
267 conv_if(HOUR, TMT_HOUR);
268 conv_if(DAY, TMT_DAY);
269 conv_if(WEEK, TMT_WEEK);
270 conv_if(MONTH, TMT_MONTH);
271 conv_if(YEAR, TMT_YEAR);
272 return (enum tmt_en)(-1);
275 enum grc_en grc_conv(
279 conv_if(BACK, GRC_BACK);
280 conv_if(CANVAS, GRC_CANVAS);
281 conv_if(SHADEA, GRC_SHADEA);
282 conv_if(SHADEB, GRC_SHADEB);
283 conv_if(GRID, GRC_GRID);
284 conv_if(MGRID, GRC_MGRID);
285 conv_if(FONT, GRC_FONT);
286 conv_if(ARROW, GRC_ARROW);
287 conv_if(AXIS, GRC_AXIS);
288 conv_if(FRAME, GRC_FRAME);
290 return (enum grc_en)(-1);
293 enum text_prop_en text_prop_conv(
297 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
298 conv_if(TITLE, TEXT_PROP_TITLE);
299 conv_if(AXIS, TEXT_PROP_AXIS);
300 conv_if(UNIT, TEXT_PROP_UNIT);
301 conv_if(LEGEND, TEXT_PROP_LEGEND);
302 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
303 return (enum text_prop_en)(-1);
313 cairo_status_t status = (cairo_status_t) 0;
318 if (im->daemon_addr != NULL)
319 free(im->daemon_addr);
321 for (i = 0; i < (unsigned) im->gdes_c; i++) {
322 if (im->gdes[i].data_first) {
323 /* careful here, because a single pointer can occur several times */
324 free(im->gdes[i].data);
325 if (im->gdes[i].ds_namv) {
326 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
327 free(im->gdes[i].ds_namv[ii]);
328 free(im->gdes[i].ds_namv);
331 /* free allocated memory used for dashed lines */
332 if (im->gdes[i].p_dashes != NULL)
333 free(im->gdes[i].p_dashes);
335 free(im->gdes[i].p_data);
336 free(im->gdes[i].rpnp);
339 if (im->font_options)
340 cairo_font_options_destroy(im->font_options);
343 status = cairo_status(im->cr);
344 cairo_destroy(im->cr);
346 if (im->rendered_image) {
347 free(im->rendered_image);
351 g_object_unref (im->layout);
355 cairo_surface_destroy(im->surface);
358 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
359 cairo_status_to_string(status));
364 /* find SI magnitude symbol for the given number*/
366 image_desc_t *im, /* image description */
372 char *symbol[] = { "a", /* 10e-18 Atto */
373 "f", /* 10e-15 Femto */
374 "p", /* 10e-12 Pico */
375 "n", /* 10e-9 Nano */
376 "u", /* 10e-6 Micro */
377 "m", /* 10e-3 Milli */
382 "T", /* 10e12 Tera */
383 "P", /* 10e15 Peta */
390 if (*value == 0.0 || isnan(*value)) {
394 sindex = floor(log(fabs(*value)) / log((double) im->base));
395 *magfact = pow((double) im->base, (double) sindex);
396 (*value) /= (*magfact);
398 if (sindex <= symbcenter && sindex >= -symbcenter) {
399 (*symb_ptr) = symbol[sindex + symbcenter];
406 static char si_symbol[] = {
407 'a', /* 10e-18 Atto */
408 'f', /* 10e-15 Femto */
409 'p', /* 10e-12 Pico */
410 'n', /* 10e-9 Nano */
411 'u', /* 10e-6 Micro */
412 'm', /* 10e-3 Milli */
417 'T', /* 10e12 Tera */
418 'P', /* 10e15 Peta */
421 static const int si_symbcenter = 6;
423 /* find SI magnitude symbol for the numbers on the y-axis*/
425 image_desc_t *im /* image description */
429 double digits, viewdigits = 0;
432 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
433 log((double) im->base));
435 if (im->unitsexponent != 9999) {
436 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
437 viewdigits = floor((double)(im->unitsexponent / 3));
442 im->magfact = pow((double) im->base, digits);
445 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
448 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
450 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
451 ((viewdigits + si_symbcenter) >= 0))
452 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
457 /* move min and max values around to become sensible */
462 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
463 600.0, 500.0, 400.0, 300.0, 250.0,
464 200.0, 125.0, 100.0, 90.0, 80.0,
465 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
466 25.0, 20.0, 10.0, 9.0, 8.0,
467 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
468 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
469 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
472 double scaled_min, scaled_max;
479 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
480 im->minval, im->maxval, im->magfact);
483 if (isnan(im->ygridstep)) {
484 if (im->extra_flags & ALTAUTOSCALE) {
485 /* measure the amplitude of the function. Make sure that
486 graph boundaries are slightly higher then max/min vals
487 so we can see amplitude on the graph */
490 delt = im->maxval - im->minval;
492 fact = 2.0 * pow(10.0,
494 (max(fabs(im->minval), fabs(im->maxval)) /
497 adj = (fact - delt) * 0.55;
500 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
501 im->minval, im->maxval, delt, fact, adj);
506 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
507 /* measure the amplitude of the function. Make sure that
508 graph boundaries are slightly lower than min vals
509 so we can see amplitude on the graph */
510 adj = (im->maxval - im->minval) * 0.1;
512 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
513 /* measure the amplitude of the function. Make sure that
514 graph boundaries are slightly higher than max vals
515 so we can see amplitude on the graph */
516 adj = (im->maxval - im->minval) * 0.1;
519 scaled_min = im->minval / im->magfact;
520 scaled_max = im->maxval / im->magfact;
522 for (i = 1; sensiblevalues[i] > 0; i++) {
523 if (sensiblevalues[i - 1] >= scaled_min &&
524 sensiblevalues[i] <= scaled_min)
525 im->minval = sensiblevalues[i] * (im->magfact);
527 if (-sensiblevalues[i - 1] <= scaled_min &&
528 -sensiblevalues[i] >= scaled_min)
529 im->minval = -sensiblevalues[i - 1] * (im->magfact);
531 if (sensiblevalues[i - 1] >= scaled_max &&
532 sensiblevalues[i] <= scaled_max)
533 im->maxval = sensiblevalues[i - 1] * (im->magfact);
535 if (-sensiblevalues[i - 1] <= scaled_max &&
536 -sensiblevalues[i] >= scaled_max)
537 im->maxval = -sensiblevalues[i] * (im->magfact);
541 /* adjust min and max to the grid definition if there is one */
542 im->minval = (double) im->ylabfact * im->ygridstep *
543 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
544 im->maxval = (double) im->ylabfact * im->ygridstep *
545 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
549 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
550 im->minval, im->maxval, im->magfact);
558 if (isnan(im->minval) || isnan(im->maxval))
561 if (im->logarithmic) {
562 double ya, yb, ypix, ypixfrac;
563 double log10_range = log10(im->maxval) - log10(im->minval);
565 ya = pow((double) 10, floor(log10(im->minval)));
566 while (ya < im->minval)
569 return; /* don't have y=10^x gridline */
571 if (yb <= im->maxval) {
572 /* we have at least 2 y=10^x gridlines.
573 Make sure distance between them in pixels
574 are an integer by expanding im->maxval */
575 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
576 double factor = y_pixel_delta / floor(y_pixel_delta);
577 double new_log10_range = factor * log10_range;
578 double new_ymax_log10 = log10(im->minval) + new_log10_range;
580 im->maxval = pow(10, new_ymax_log10);
581 ytr(im, DNAN); /* reset precalc */
582 log10_range = log10(im->maxval) - log10(im->minval);
584 /* make sure first y=10^x gridline is located on
585 integer pixel position by moving scale slightly
586 downwards (sub-pixel movement) */
587 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
588 ypixfrac = ypix - floor(ypix);
589 if (ypixfrac > 0 && ypixfrac < 1) {
590 double yfrac = ypixfrac / im->ysize;
592 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
593 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
594 ytr(im, DNAN); /* reset precalc */
597 /* Make sure we have an integer pixel distance between
598 each minor gridline */
599 double ypos1 = ytr(im, im->minval);
600 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
601 double y_pixel_delta = ypos1 - ypos2;
602 double factor = y_pixel_delta / floor(y_pixel_delta);
603 double new_range = factor * (im->maxval - im->minval);
604 double gridstep = im->ygrid_scale.gridstep;
605 double minor_y, minor_y_px, minor_y_px_frac;
607 if (im->maxval > 0.0)
608 im->maxval = im->minval + new_range;
610 im->minval = im->maxval - new_range;
611 ytr(im, DNAN); /* reset precalc */
612 /* make sure first minor gridline is on integer pixel y coord */
613 minor_y = gridstep * floor(im->minval / gridstep);
614 while (minor_y < im->minval)
616 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
617 minor_y_px_frac = minor_y_px - floor(minor_y_px);
618 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
619 double yfrac = minor_y_px_frac / im->ysize;
620 double range = im->maxval - im->minval;
622 im->minval = im->minval - yfrac * range;
623 im->maxval = im->maxval - yfrac * range;
624 ytr(im, DNAN); /* reset precalc */
626 calc_horizontal_grid(im); /* recalc with changed im->maxval */
630 /* reduce data reimplementation by Alex */
633 enum cf_en cf, /* which consolidation function ? */
634 unsigned long cur_step, /* step the data currently is in */
635 time_t *start, /* start, end and step as requested ... */
636 time_t *end, /* ... by the application will be ... */
637 unsigned long *step, /* ... adjusted to represent reality */
638 unsigned long *ds_cnt, /* number of data sources in file */
640 { /* two dimensional array containing the data */
641 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
642 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
644 rrd_value_t *srcptr, *dstptr;
646 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
649 row_cnt = ((*end) - (*start)) / cur_step;
655 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
656 row_cnt, reduce_factor, *start, *end, cur_step);
657 for (col = 0; col < row_cnt; col++) {
658 printf("time %10lu: ", *start + (col + 1) * cur_step);
659 for (i = 0; i < *ds_cnt; i++)
660 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
665 /* We have to combine [reduce_factor] rows of the source
666 ** into one row for the destination. Doing this we also
667 ** need to take care to combine the correct rows. First
668 ** alter the start and end time so that they are multiples
669 ** of the new step time. We cannot reduce the amount of
670 ** time so we have to move the end towards the future and
671 ** the start towards the past.
673 end_offset = (*end) % (*step);
674 start_offset = (*start) % (*step);
676 /* If there is a start offset (which cannot be more than
677 ** one destination row), skip the appropriate number of
678 ** source rows and one destination row. The appropriate
679 ** number is what we do know (start_offset/cur_step) of
680 ** the new interval (*step/cur_step aka reduce_factor).
683 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
684 printf("row_cnt before: %lu\n", row_cnt);
687 (*start) = (*start) - start_offset;
688 skiprows = reduce_factor - start_offset / cur_step;
689 srcptr += skiprows * *ds_cnt;
690 for (col = 0; col < (*ds_cnt); col++)
695 printf("row_cnt between: %lu\n", row_cnt);
698 /* At the end we have some rows that are not going to be
699 ** used, the amount is end_offset/cur_step
702 (*end) = (*end) - end_offset + (*step);
703 skiprows = end_offset / cur_step;
707 printf("row_cnt after: %lu\n", row_cnt);
710 /* Sanity check: row_cnt should be multiple of reduce_factor */
711 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
713 if (row_cnt % reduce_factor) {
714 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
715 row_cnt, reduce_factor);
716 printf("BUG in reduce_data()\n");
720 /* Now combine reduce_factor intervals at a time
721 ** into one interval for the destination.
724 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
725 for (col = 0; col < (*ds_cnt); col++) {
726 rrd_value_t newval = DNAN;
727 unsigned long validval = 0;
729 for (i = 0; i < reduce_factor; i++) {
730 if (isnan(srcptr[i * (*ds_cnt) + col])) {
735 newval = srcptr[i * (*ds_cnt) + col];
744 newval += srcptr[i * (*ds_cnt) + col];
747 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
750 /* an interval contains a failure if any subintervals contained a failure */
752 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
755 newval = srcptr[i * (*ds_cnt) + col];
781 srcptr += (*ds_cnt) * reduce_factor;
782 row_cnt -= reduce_factor;
784 /* If we had to alter the endtime, we didn't have enough
785 ** source rows to fill the last row. Fill it with NaN.
788 for (col = 0; col < (*ds_cnt); col++)
791 row_cnt = ((*end) - (*start)) / *step;
793 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
794 row_cnt, *start, *end, *step);
795 for (col = 0; col < row_cnt; col++) {
796 printf("time %10lu: ", *start + (col + 1) * (*step));
797 for (i = 0; i < *ds_cnt; i++)
798 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
805 /* get the data required for the graphs from the
814 /* pull the data from the rrd files ... */
815 for (i = 0; i < (int) im->gdes_c; i++) {
816 /* only GF_DEF elements fetch data */
817 if (im->gdes[i].gf != GF_DEF)
821 /* do we have it already ? */
822 for (ii = 0; ii < i; ii++) {
823 if (im->gdes[ii].gf != GF_DEF)
825 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
826 && (im->gdes[i].cf == im->gdes[ii].cf)
827 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
828 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
829 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
830 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
831 /* OK, the data is already there.
832 ** Just copy the header portion
834 im->gdes[i].start = im->gdes[ii].start;
835 im->gdes[i].end = im->gdes[ii].end;
836 im->gdes[i].step = im->gdes[ii].step;
837 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
838 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
839 im->gdes[i].data = im->gdes[ii].data;
840 im->gdes[i].data_first = 0;
847 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
851 if (im->gdes[i].daemon[0] != 0)
852 daemon = im->gdes[i].daemon;
854 daemon = im->daemon_addr;
856 /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
857 * case. If "daemon" holds the same value as in the previous
858 * iteration, no actual new connection is established - the
859 * existing connection is re-used. */
860 rrdc_connect (daemon);
862 /* If connecting was successfull, use the daemon to query the data.
863 * If there is no connection, for example because no daemon address
864 * was specified, (try to) use the local file directly. */
865 if (rrdc_is_connected (daemon))
867 status = rrdc_fetch (im->gdes[i].rrd,
868 cf_to_string (im->gdes[i].cf),
873 &im->gdes[i].ds_namv,
880 if ((rrd_fetch_fn(im->gdes[i].rrd,
886 &im->gdes[i].ds_namv,
887 &im->gdes[i].data)) == -1) {
891 im->gdes[i].data_first = 1;
893 if (ft_step < im->gdes[i].step) {
894 reduce_data(im->gdes[i].cf_reduce,
899 &im->gdes[i].ds_cnt, &im->gdes[i].data);
901 im->gdes[i].step = ft_step;
905 /* lets see if the required data source is really there */
906 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
907 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
911 if (im->gdes[i].ds == -1) {
912 rrd_set_error("No DS called '%s' in '%s'",
913 im->gdes[i].ds_nam, im->gdes[i].rrd);
921 /* evaluate the expressions in the CDEF functions */
923 /*************************************************************
925 *************************************************************/
927 long find_var_wrapper(
931 return find_var((image_desc_t *) arg1, key);
934 /* find gdes containing var*/
941 for (ii = 0; ii < im->gdes_c - 1; ii++) {
942 if ((im->gdes[ii].gf == GF_DEF
943 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
944 && (strcmp(im->gdes[ii].vname, key) == 0)) {
951 /* find the greatest common divisor for all the numbers
952 in the 0 terminated num array */
959 for (i = 0; num[i + 1] != 0; i++) {
961 rest = num[i] % num[i + 1];
967 /* return i==0?num[i]:num[i-1]; */
971 /* run the rpn calculator on all the VDEF and CDEF arguments */
978 long *steparray, rpi;
983 rpnstack_init(&rpnstack);
985 for (gdi = 0; gdi < im->gdes_c; gdi++) {
986 /* Look for GF_VDEF and GF_CDEF in the same loop,
987 * so CDEFs can use VDEFs and vice versa
989 switch (im->gdes[gdi].gf) {
993 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
995 /* remove current shift */
996 vdp->start -= vdp->shift;
997 vdp->end -= vdp->shift;
1000 if (im->gdes[gdi].shidx >= 0)
1001 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1004 vdp->shift = im->gdes[gdi].shval;
1006 /* normalize shift to multiple of consolidated step */
1007 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1010 vdp->start += vdp->shift;
1011 vdp->end += vdp->shift;
1015 /* A VDEF has no DS. This also signals other parts
1016 * of rrdtool that this is a VDEF value, not a CDEF.
1018 im->gdes[gdi].ds_cnt = 0;
1019 if (vdef_calc(im, gdi)) {
1020 rrd_set_error("Error processing VDEF '%s'",
1021 im->gdes[gdi].vname);
1022 rpnstack_free(&rpnstack);
1027 im->gdes[gdi].ds_cnt = 1;
1028 im->gdes[gdi].ds = 0;
1029 im->gdes[gdi].data_first = 1;
1030 im->gdes[gdi].start = 0;
1031 im->gdes[gdi].end = 0;
1036 /* Find the variables in the expression.
1037 * - VDEF variables are substituted by their values
1038 * and the opcode is changed into OP_NUMBER.
1039 * - CDEF variables are analized for their step size,
1040 * the lowest common denominator of all the step
1041 * sizes of the data sources involved is calculated
1042 * and the resulting number is the step size for the
1043 * resulting data source.
1045 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1046 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1047 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1048 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1050 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1053 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1054 im->gdes[gdi].vname, im->gdes[ptr].vname);
1055 printf("DEBUG: value from vdef is %f\n",
1056 im->gdes[ptr].vf.val);
1058 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1059 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1060 } else { /* normal variables and PREF(variables) */
1062 /* add one entry to the array that keeps track of the step sizes of the
1063 * data sources going into the CDEF. */
1065 (long*)rrd_realloc(steparray,
1067 1) * sizeof(*steparray))) == NULL) {
1068 rrd_set_error("realloc steparray");
1069 rpnstack_free(&rpnstack);
1073 steparray[stepcnt - 1] = im->gdes[ptr].step;
1075 /* adjust start and end of cdef (gdi) so
1076 * that it runs from the latest start point
1077 * to the earliest endpoint of any of the
1078 * rras involved (ptr)
1081 if (im->gdes[gdi].start < im->gdes[ptr].start)
1082 im->gdes[gdi].start = im->gdes[ptr].start;
1084 if (im->gdes[gdi].end == 0 ||
1085 im->gdes[gdi].end > im->gdes[ptr].end)
1086 im->gdes[gdi].end = im->gdes[ptr].end;
1088 /* store pointer to the first element of
1089 * the rra providing data for variable,
1090 * further save step size and data source
1093 im->gdes[gdi].rpnp[rpi].data =
1094 im->gdes[ptr].data + im->gdes[ptr].ds;
1095 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1096 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1098 /* backoff the *.data ptr; this is done so
1099 * rpncalc() function doesn't have to treat
1100 * the first case differently
1102 } /* if ds_cnt != 0 */
1103 } /* if OP_VARIABLE */
1104 } /* loop through all rpi */
1106 /* move the data pointers to the correct period */
1107 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1108 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1109 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1110 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1112 im->gdes[gdi].start - im->gdes[ptr].start;
1115 im->gdes[gdi].rpnp[rpi].data +=
1116 (diff / im->gdes[ptr].step) *
1117 im->gdes[ptr].ds_cnt;
1121 if (steparray == NULL) {
1122 rrd_set_error("rpn expressions without DEF"
1123 " or CDEF variables are not supported");
1124 rpnstack_free(&rpnstack);
1127 steparray[stepcnt] = 0;
1128 /* Now find the resulting step. All steps in all
1129 * used RRAs have to be visited
1131 im->gdes[gdi].step = lcd(steparray);
1133 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1134 im->gdes[gdi].start)
1135 / im->gdes[gdi].step)
1136 * sizeof(double))) == NULL) {
1137 rrd_set_error("malloc im->gdes[gdi].data");
1138 rpnstack_free(&rpnstack);
1142 /* Step through the new cdef results array and
1143 * calculate the values
1145 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1146 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1147 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1149 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1150 * in this case we are advancing by timesteps;
1151 * we use the fact that time_t is a synonym for long
1153 if (rpn_calc(rpnp, &rpnstack, (long) now,
1154 im->gdes[gdi].data, ++dataidx) == -1) {
1155 /* rpn_calc sets the error string */
1156 rpnstack_free(&rpnstack);
1159 } /* enumerate over time steps within a CDEF */
1164 } /* enumerate over CDEFs */
1165 rpnstack_free(&rpnstack);
1169 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1170 /* yes we are loosing precision by doing tos with floats instead of doubles
1171 but it seems more stable this way. */
1173 static int AlmostEqual2sComplement(
1179 int aInt = *(int *) &A;
1180 int bInt = *(int *) &B;
1183 /* Make sure maxUlps is non-negative and small enough that the
1184 default NAN won't compare as equal to anything. */
1186 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1188 /* Make aInt lexicographically ordered as a twos-complement int */
1191 aInt = 0x80000000l - aInt;
1193 /* Make bInt lexicographically ordered as a twos-complement int */
1196 bInt = 0x80000000l - bInt;
1198 intDiff = abs(aInt - bInt);
1200 if (intDiff <= maxUlps)
1206 /* massage data so, that we get one value for each x coordinate in the graph */
1211 double pixstep = (double) (im->end - im->start)
1212 / (double) im->xsize; /* how much time
1213 passes in one pixel */
1215 double minval = DNAN, maxval = DNAN;
1217 unsigned long gr_time;
1219 /* memory for the processed data */
1220 for (i = 0; i < im->gdes_c; i++) {
1221 if ((im->gdes[i].gf == GF_LINE) ||
1222 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1223 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1224 * sizeof(rrd_value_t))) == NULL) {
1225 rrd_set_error("malloc data_proc");
1231 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1234 gr_time = im->start + pixstep * i; /* time of the current step */
1237 for (ii = 0; ii < im->gdes_c; ii++) {
1240 switch (im->gdes[ii].gf) {
1244 if (!im->gdes[ii].stack)
1246 value = im->gdes[ii].yrule;
1247 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1248 /* The time of the data doesn't necessarily match
1249 ** the time of the graph. Beware.
1251 vidx = im->gdes[ii].vidx;
1252 if (im->gdes[vidx].gf == GF_VDEF) {
1253 value = im->gdes[vidx].vf.val;
1255 if (((long int) gr_time >=
1256 (long int) im->gdes[vidx].start)
1257 && ((long int) gr_time <
1258 (long int) im->gdes[vidx].end)) {
1259 value = im->gdes[vidx].data[(unsigned long)
1265 im->gdes[vidx].step)
1266 * im->gdes[vidx].ds_cnt +
1273 if (!isnan(value)) {
1275 im->gdes[ii].p_data[i] = paintval;
1276 /* GF_TICK: the data values are not
1277 ** relevant for min and max
1279 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1280 if ((isnan(minval) || paintval < minval) &&
1281 !(im->logarithmic && paintval <= 0.0))
1283 if (isnan(maxval) || paintval > maxval)
1287 im->gdes[ii].p_data[i] = DNAN;
1292 ("STACK should already be turned into LINE or AREA here");
1301 /* if min or max have not been asigned a value this is because
1302 there was no data in the graph ... this is not good ...
1303 lets set these to dummy values then ... */
1305 if (im->logarithmic) {
1306 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1307 minval = 0.0; /* catching this right away below */
1310 /* in logarithm mode, where minval is smaller or equal
1311 to 0 make the beast just way smaller than maxval */
1313 minval = maxval / 10e8;
1316 if (isnan(minval) || isnan(maxval)) {
1322 /* adjust min and max values given by the user */
1323 /* for logscale we add something on top */
1324 if (isnan(im->minval)
1325 || ((!im->rigid) && im->minval > minval)
1327 if (im->logarithmic)
1328 im->minval = minval / 2.0;
1330 im->minval = minval;
1332 if (isnan(im->maxval)
1333 || (!im->rigid && im->maxval < maxval)
1335 if (im->logarithmic)
1336 im->maxval = maxval * 2.0;
1338 im->maxval = maxval;
1341 /* make sure min is smaller than max */
1342 if (im->minval > im->maxval) {
1344 im->minval = 0.99 * im->maxval;
1346 im->minval = 1.01 * im->maxval;
1349 /* make sure min and max are not equal */
1350 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1356 /* make sure min and max are not both zero */
1357 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1364 static int find_first_weekday(void){
1365 static int first_weekday = -1;
1366 if (first_weekday == -1){
1367 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1368 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1369 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1370 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1371 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1372 else first_weekday = 1; /* we go for a monday default */
1377 return first_weekday;
1380 /* identify the point where the first gridline, label ... gets placed */
1382 time_t find_first_time(
1383 time_t start, /* what is the initial time */
1384 enum tmt_en baseint, /* what is the basic interval */
1385 long basestep /* how many if these do we jump a time */
1390 localtime_r(&start, &tm);
1394 tm. tm_sec -= tm.tm_sec % basestep;
1399 tm. tm_min -= tm.tm_min % basestep;
1405 tm. tm_hour -= tm.tm_hour % basestep;
1409 /* we do NOT look at the basestep for this ... */
1416 /* we do NOT look at the basestep for this ... */
1420 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1422 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1423 tm. tm_mday -= 7; /* we want the *previous* week */
1431 tm. tm_mon -= tm.tm_mon % basestep;
1442 tm.tm_year + 1900) %basestep;
1448 /* identify the point where the next gridline, label ... gets placed */
1449 time_t find_next_time(
1450 time_t current, /* what is the initial time */
1451 enum tmt_en baseint, /* what is the basic interval */
1452 long basestep /* how many if these do we jump a time */
1458 localtime_r(¤t, &tm);
1463 tm. tm_sec += basestep;
1467 tm. tm_min += basestep;
1471 tm. tm_hour += basestep;
1475 tm. tm_mday += basestep;
1479 tm. tm_mday += 7 * basestep;
1483 tm. tm_mon += basestep;
1487 tm. tm_year += basestep;
1489 madetime = mktime(&tm);
1490 } while (madetime == -1); /* this is necessary to skip impssible times
1491 like the daylight saving time skips */
1497 /* calculate values required for PRINT and GPRINT functions */
1502 long i, ii, validsteps;
1505 int graphelement = 0;
1508 double magfact = -1;
1513 /* wow initializing tmvdef is quite a task :-) */
1514 time_t now = time(NULL);
1516 localtime_r(&now, &tmvdef);
1517 for (i = 0; i < im->gdes_c; i++) {
1518 vidx = im->gdes[i].vidx;
1519 switch (im->gdes[i].gf) {
1522 /* PRINT and GPRINT can now print VDEF generated values.
1523 * There's no need to do any calculations on them as these
1524 * calculations were already made.
1526 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1527 printval = im->gdes[vidx].vf.val;
1528 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1529 } else { /* need to calculate max,min,avg etcetera */
1530 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1531 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1534 for (ii = im->gdes[vidx].ds;
1535 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1536 if (!finite(im->gdes[vidx].data[ii]))
1538 if (isnan(printval)) {
1539 printval = im->gdes[vidx].data[ii];
1544 switch (im->gdes[i].cf) {
1548 case CF_DEVSEASONAL:
1552 printval += im->gdes[vidx].data[ii];
1555 printval = min(printval, im->gdes[vidx].data[ii]);
1559 printval = max(printval, im->gdes[vidx].data[ii]);
1562 printval = im->gdes[vidx].data[ii];
1565 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1566 if (validsteps > 1) {
1567 printval = (printval / validsteps);
1570 } /* prepare printval */
1572 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1573 /* Magfact is set to -1 upon entry to print_calc. If it
1574 * is still less than 0, then we need to run auto_scale.
1575 * Otherwise, put the value into the correct units. If
1576 * the value is 0, then do not set the symbol or magnification
1577 * so next the calculation will be performed again. */
1578 if (magfact < 0.0) {
1579 auto_scale(im, &printval, &si_symb, &magfact);
1580 if (printval == 0.0)
1583 printval /= magfact;
1585 *(++percent_s) = 's';
1586 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1587 auto_scale(im, &printval, &si_symb, &magfact);
1590 if (im->gdes[i].gf == GF_PRINT) {
1591 rrd_infoval_t prline;
1593 if (im->gdes[i].strftm) {
1594 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1595 strftime(prline.u_str,
1596 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1597 } else if (bad_format(im->gdes[i].format)) {
1599 ("bad format for PRINT in '%s'", im->gdes[i].format);
1603 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1607 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1612 if (im->gdes[i].strftm) {
1613 strftime(im->gdes[i].legend,
1614 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1616 if (bad_format(im->gdes[i].format)) {
1618 ("bad format for GPRINT in '%s'",
1619 im->gdes[i].format);
1622 #ifdef HAVE_SNPRINTF
1623 snprintf(im->gdes[i].legend,
1625 im->gdes[i].format, printval, si_symb);
1627 sprintf(im->gdes[i].legend,
1628 im->gdes[i].format, printval, si_symb);
1640 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1641 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1646 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1647 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1656 #ifdef WITH_PIECHART
1664 ("STACK should already be turned into LINE or AREA here");
1669 return graphelement;
1674 /* place legends with color spots */
1680 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1681 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1682 int fill = 0, fill_last;
1683 double legendwidth; // = im->ximg - 2 * border;
1685 double leg_x = border;
1686 int leg_y = 0; //im->yimg;
1687 int leg_y_prev = 0; // im->yimg;
1690 int i, ii, mark = 0;
1691 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1694 char saved_legend[FMT_LEG_LEN + 5];
1700 legendwidth = im->legendwidth - 2 * border;
1704 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1705 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1706 rrd_set_error("malloc for legspace");
1710 for (i = 0; i < im->gdes_c; i++) {
1711 char prt_fctn; /*special printfunctions */
1713 strcpy(saved_legend, im->gdes[i].legend);
1717 /* hide legends for rules which are not displayed */
1718 if (im->gdes[i].gf == GF_TEXTALIGN) {
1719 default_txtalign = im->gdes[i].txtalign;
1722 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1723 if (im->gdes[i].gf == GF_HRULE
1724 && (im->gdes[i].yrule <
1725 im->minval || im->gdes[i].yrule > im->maxval))
1726 im->gdes[i].legend[0] = '\0';
1727 if (im->gdes[i].gf == GF_VRULE
1728 && (im->gdes[i].xrule <
1729 im->start || im->gdes[i].xrule > im->end))
1730 im->gdes[i].legend[0] = '\0';
1733 /* turn \\t into tab */
1734 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1735 memmove(tab, tab + 1, strlen(tab));
1739 leg_cc = strlen(im->gdes[i].legend);
1740 /* is there a controle code at the end of the legend string ? */
1741 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1742 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1744 im->gdes[i].legend[leg_cc] = '\0';
1748 /* only valid control codes */
1749 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1754 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1757 ("Unknown control code at the end of '%s\\%c'",
1758 im->gdes[i].legend, prt_fctn);
1762 if (prt_fctn == 'n') {
1766 /* remove exess space from the end of the legend for \g */
1767 while (prt_fctn == 'g' &&
1768 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1770 im->gdes[i].legend[leg_cc] = '\0';
1775 /* no interleg space if string ends in \g */
1776 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1778 fill += legspace[i];
1781 gfx_get_text_width(im,
1787 im->tabwidth, im->gdes[i].legend);
1792 /* who said there was a special tag ... ? */
1793 if (prt_fctn == 'g') {
1797 if (prt_fctn == '\0') {
1798 if(calc_width && (fill > legendwidth)){
1801 if (i == im->gdes_c - 1 || fill > legendwidth) {
1802 /* just one legend item is left right or center */
1803 switch (default_txtalign) {
1818 /* is it time to place the legends ? */
1819 if (fill > legendwidth) {
1827 if (leg_c == 1 && prt_fctn == 'j') {
1832 if (prt_fctn != '\0') {
1834 if (leg_c >= 2 && prt_fctn == 'j') {
1835 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1839 if (prt_fctn == 'c')
1840 leg_x = (double)(legendwidth - fill) / 2.0;
1841 if (prt_fctn == 'r')
1842 leg_x = legendwidth - fill + border;
1843 for (ii = mark; ii <= i; ii++) {
1844 if (im->gdes[ii].legend[0] == '\0')
1845 continue; /* skip empty legends */
1846 im->gdes[ii].leg_x = leg_x;
1847 im->gdes[ii].leg_y = leg_y + border;
1849 (double)gfx_get_text_width(im, leg_x,
1854 im->tabwidth, im->gdes[ii].legend)
1855 +(double)legspace[ii]
1859 if (leg_x > border || prt_fctn == 's')
1860 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1861 if (prt_fctn == 's')
1862 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1863 if (prt_fctn == 'u')
1864 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1866 if(calc_width && (fill > legendwidth)){
1875 strcpy(im->gdes[i].legend, saved_legend);
1880 im->legendwidth = legendwidth + 2 * border;
1883 im->legendheight = leg_y + border * 0.6;
1890 /* create a grid on the graph. it determines what to do
1891 from the values of xsize, start and end */
1893 /* the xaxis labels are determined from the number of seconds per pixel
1894 in the requested graph */
1896 int calc_horizontal_grid(
1904 int decimals, fractionals;
1906 im->ygrid_scale.labfact = 2;
1907 range = im->maxval - im->minval;
1908 scaledrange = range / im->magfact;
1909 /* does the scale of this graph make it impossible to put lines
1910 on it? If so, give up. */
1911 if (isnan(scaledrange)) {
1915 /* find grid spaceing */
1917 if (isnan(im->ygridstep)) {
1918 if (im->extra_flags & ALTYGRID) {
1919 /* find the value with max number of digits. Get number of digits */
1922 (max(fabs(im->maxval), fabs(im->minval)) *
1923 im->viewfactor / im->magfact));
1924 if (decimals <= 0) /* everything is small. make place for zero */
1926 im->ygrid_scale.gridstep =
1928 floor(log10(range * im->viewfactor / im->magfact))) /
1929 im->viewfactor * im->magfact;
1930 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1931 im->ygrid_scale.gridstep = 0.1;
1932 /* should have at least 5 lines but no more then 15 */
1933 if (range / im->ygrid_scale.gridstep < 5
1934 && im->ygrid_scale.gridstep >= 30)
1935 im->ygrid_scale.gridstep /= 10;
1936 if (range / im->ygrid_scale.gridstep > 15)
1937 im->ygrid_scale.gridstep *= 10;
1938 if (range / im->ygrid_scale.gridstep > 5) {
1939 im->ygrid_scale.labfact = 1;
1940 if (range / im->ygrid_scale.gridstep > 8
1941 || im->ygrid_scale.gridstep <
1942 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1943 im->ygrid_scale.labfact = 2;
1945 im->ygrid_scale.gridstep /= 5;
1946 im->ygrid_scale.labfact = 5;
1950 (im->ygrid_scale.gridstep *
1951 (double) im->ygrid_scale.labfact * im->viewfactor /
1953 if (fractionals < 0) { /* small amplitude. */
1954 int len = decimals - fractionals + 1;
1956 if (im->unitslength < len + 2)
1957 im->unitslength = len + 2;
1958 sprintf(im->ygrid_scale.labfmt,
1960 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1962 int len = decimals + 1;
1964 if (im->unitslength < len + 2)
1965 im->unitslength = len + 2;
1966 sprintf(im->ygrid_scale.labfmt,
1967 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1969 } else { /* classic rrd grid */
1970 for (i = 0; ylab[i].grid > 0; i++) {
1971 pixel = im->ysize / (scaledrange / ylab[i].grid);
1977 for (i = 0; i < 4; i++) {
1978 if (pixel * ylab[gridind].lfac[i] >=
1979 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1980 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1985 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1988 im->ygrid_scale.gridstep = im->ygridstep;
1989 im->ygrid_scale.labfact = im->ylabfact;
1994 int draw_horizontal_grid(
2000 char graph_label[100];
2002 double X0 = im->xorigin;
2003 double X1 = im->xorigin + im->xsize;
2004 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2005 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2007 double second_axis_magfact = 0;
2008 char *second_axis_symb = "";
2011 im->ygrid_scale.gridstep /
2012 (double) im->magfact * (double) im->viewfactor;
2013 MaxY = scaledstep * (double) egrid;
2014 for (i = sgrid; i <= egrid; i++) {
2016 im->ygrid_scale.gridstep * i);
2018 im->ygrid_scale.gridstep * (i + 1));
2020 if (floor(Y0 + 0.5) >=
2021 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2022 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2023 with the chosen settings. Add a label if required by settings, or if
2024 there is only one label so far and the next grid line is out of bounds. */
2025 if (i % im->ygrid_scale.labfact == 0
2027 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2028 if (im->symbol == ' ') {
2029 if (im->extra_flags & ALTYGRID) {
2030 sprintf(graph_label,
2031 im->ygrid_scale.labfmt,
2032 scaledstep * (double) i);
2035 sprintf(graph_label, "%4.1f",
2036 scaledstep * (double) i);
2038 sprintf(graph_label, "%4.0f",
2039 scaledstep * (double) i);
2043 char sisym = (i == 0 ? ' ' : im->symbol);
2045 if (im->extra_flags & ALTYGRID) {
2046 sprintf(graph_label,
2047 im->ygrid_scale.labfmt,
2048 scaledstep * (double) i, sisym);
2051 sprintf(graph_label, "%4.1f %c",
2052 scaledstep * (double) i, sisym);
2054 sprintf(graph_label, "%4.0f %c",
2055 scaledstep * (double) i, sisym);
2060 if (im->second_axis_scale != 0){
2061 char graph_label_right[100];
2062 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2063 if (im->second_axis_format[0] == '\0'){
2064 if (!second_axis_magfact){
2065 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2066 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2068 sval /= second_axis_magfact;
2071 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2073 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2077 sprintf(graph_label_right,im->second_axis_format,sval);
2081 im->graph_col[GRC_FONT],
2082 im->text_prop[TEXT_PROP_AXIS].font_desc,
2083 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2084 graph_label_right );
2090 text_prop[TEXT_PROP_AXIS].
2092 im->graph_col[GRC_FONT],
2094 text_prop[TEXT_PROP_AXIS].
2097 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2098 gfx_line(im, X0 - 2, Y0, X0, Y0,
2099 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2100 gfx_line(im, X1, Y0, X1 + 2, Y0,
2101 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2102 gfx_dashed_line(im, X0 - 2, Y0,
2108 im->grid_dash_on, im->grid_dash_off);
2109 } else if (!(im->extra_flags & NOMINOR)) {
2112 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2113 gfx_line(im, X1, Y0, X1 + 2, Y0,
2114 GRIDWIDTH, im->graph_col[GRC_GRID]);
2115 gfx_dashed_line(im, X0 - 1, Y0,
2119 graph_col[GRC_GRID],
2120 im->grid_dash_on, im->grid_dash_off);
2127 /* this is frexp for base 10 */
2138 iexp = floor(log((double)fabs(x)) / log((double)10));
2139 mnt = x / pow(10.0, iexp);
2142 mnt = x / pow(10.0, iexp);
2149 /* logaritmic horizontal grid */
2150 int horizontal_log_grid(
2154 double yloglab[][10] = {
2156 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2158 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2160 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2177 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2179 int i, j, val_exp, min_exp;
2180 double nex; /* number of decades in data */
2181 double logscale; /* scale in logarithmic space */
2182 int exfrac = 1; /* decade spacing */
2183 int mid = -1; /* row in yloglab for major grid */
2184 double mspac; /* smallest major grid spacing (pixels) */
2185 int flab; /* first value in yloglab to use */
2186 double value, tmp, pre_value;
2188 char graph_label[100];
2190 nex = log10(im->maxval / im->minval);
2191 logscale = im->ysize / nex;
2192 /* major spacing for data with high dynamic range */
2193 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2200 /* major spacing for less dynamic data */
2202 /* search best row in yloglab */
2204 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2205 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2208 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2211 /* find first value in yloglab */
2213 yloglab[mid][flab] < 10
2214 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2215 if (yloglab[mid][flab] == 10.0) {
2220 if (val_exp % exfrac)
2221 val_exp += abs(-val_exp % exfrac);
2223 X1 = im->xorigin + im->xsize;
2228 value = yloglab[mid][flab] * pow(10.0, val_exp);
2229 if (AlmostEqual2sComplement(value, pre_value, 4))
2230 break; /* it seems we are not converging */
2232 Y0 = ytr(im, value);
2233 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2235 /* major grid line */
2237 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2238 gfx_line(im, X1, Y0, X1 + 2, Y0,
2239 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2240 gfx_dashed_line(im, X0 - 2, Y0,
2245 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2247 if (im->extra_flags & FORCE_UNITS_SI) {
2252 scale = floor(val_exp / 3.0);
2254 pvalue = pow(10.0, val_exp % 3);
2256 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2257 pvalue *= yloglab[mid][flab];
2258 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2259 && ((scale + si_symbcenter) >= 0))
2260 symbol = si_symbol[scale + si_symbcenter];
2263 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2265 sprintf(graph_label, "%3.0e", value);
2267 if (im->second_axis_scale != 0){
2268 char graph_label_right[100];
2269 double sval = value*im->second_axis_scale+im->second_axis_shift;
2270 if (im->second_axis_format[0] == '\0'){
2271 if (im->extra_flags & FORCE_UNITS_SI) {
2274 auto_scale(im,&sval,&symb,&mfac);
2275 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2278 sprintf(graph_label_right,"%3.0e", sval);
2282 sprintf(graph_label_right,im->second_axis_format,sval);
2287 im->graph_col[GRC_FONT],
2288 im->text_prop[TEXT_PROP_AXIS].font_desc,
2289 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2290 graph_label_right );
2296 text_prop[TEXT_PROP_AXIS].
2298 im->graph_col[GRC_FONT],
2300 text_prop[TEXT_PROP_AXIS].
2303 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2305 if (mid < 4 && exfrac == 1) {
2306 /* find first and last minor line behind current major line
2307 * i is the first line and j tha last */
2309 min_exp = val_exp - 1;
2310 for (i = 1; yloglab[mid][i] < 10.0; i++);
2311 i = yloglab[mid][i - 1] + 1;
2315 i = yloglab[mid][flab - 1] + 1;
2316 j = yloglab[mid][flab];
2319 /* draw minor lines below current major line */
2320 for (; i < j; i++) {
2322 value = i * pow(10.0, min_exp);
2323 if (value < im->minval)
2325 Y0 = ytr(im, value);
2326 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2331 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2332 gfx_line(im, X1, Y0, X1 + 2, Y0,
2333 GRIDWIDTH, im->graph_col[GRC_GRID]);
2334 gfx_dashed_line(im, X0 - 1, Y0,
2338 graph_col[GRC_GRID],
2339 im->grid_dash_on, im->grid_dash_off);
2341 } else if (exfrac > 1) {
2342 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2343 value = pow(10.0, i);
2344 if (value < im->minval)
2346 Y0 = ytr(im, value);
2347 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2352 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2353 gfx_line(im, X1, Y0, X1 + 2, Y0,
2354 GRIDWIDTH, im->graph_col[GRC_GRID]);
2355 gfx_dashed_line(im, X0 - 1, Y0,
2359 graph_col[GRC_GRID],
2360 im->grid_dash_on, im->grid_dash_off);
2365 if (yloglab[mid][++flab] == 10.0) {
2371 /* draw minor lines after highest major line */
2372 if (mid < 4 && exfrac == 1) {
2373 /* find first and last minor line below current major line
2374 * i is the first line and j tha last */
2376 min_exp = val_exp - 1;
2377 for (i = 1; yloglab[mid][i] < 10.0; i++);
2378 i = yloglab[mid][i - 1] + 1;
2382 i = yloglab[mid][flab - 1] + 1;
2383 j = yloglab[mid][flab];
2386 /* draw minor lines below current major line */
2387 for (; i < j; i++) {
2389 value = i * pow(10.0, min_exp);
2390 if (value < im->minval)
2392 Y0 = ytr(im, value);
2393 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2397 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2398 gfx_line(im, X1, Y0, X1 + 2, Y0,
2399 GRIDWIDTH, im->graph_col[GRC_GRID]);
2400 gfx_dashed_line(im, X0 - 1, Y0,
2404 graph_col[GRC_GRID],
2405 im->grid_dash_on, im->grid_dash_off);
2408 /* fancy minor gridlines */
2409 else if (exfrac > 1) {
2410 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2411 value = pow(10.0, i);
2412 if (value < im->minval)
2414 Y0 = ytr(im, value);
2415 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2419 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2420 gfx_line(im, X1, Y0, X1 + 2, Y0,
2421 GRIDWIDTH, im->graph_col[GRC_GRID]);
2422 gfx_dashed_line(im, X0 - 1, Y0,
2426 graph_col[GRC_GRID],
2427 im->grid_dash_on, im->grid_dash_off);
2438 int xlab_sel; /* which sort of label and grid ? */
2439 time_t ti, tilab, timajor;
2441 char graph_label[100];
2442 double X0, Y0, Y1; /* points for filled graph and more */
2445 /* the type of time grid is determined by finding
2446 the number of seconds per pixel in the graph */
2447 if (im->xlab_user.minsec == -1) {
2448 factor = (im->end - im->start) / im->xsize;
2450 while (xlab[xlab_sel + 1].minsec !=
2451 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2453 } /* pick the last one */
2454 while (xlab[xlab_sel - 1].minsec ==
2455 xlab[xlab_sel].minsec
2456 && xlab[xlab_sel].length > (im->end - im->start)) {
2458 } /* go back to the smallest size */
2459 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2460 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2461 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2462 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2463 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2464 im->xlab_user.labst = xlab[xlab_sel].labst;
2465 im->xlab_user.precis = xlab[xlab_sel].precis;
2466 im->xlab_user.stst = xlab[xlab_sel].stst;
2469 /* y coords are the same for every line ... */
2471 Y1 = im->yorigin - im->ysize;
2472 /* paint the minor grid */
2473 if (!(im->extra_flags & NOMINOR)) {
2474 for (ti = find_first_time(im->start,
2482 find_first_time(im->start,
2489 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2491 /* are we inside the graph ? */
2492 if (ti < im->start || ti > im->end)
2494 while (timajor < ti) {
2495 timajor = find_next_time(timajor,
2498 mgridtm, im->xlab_user.mgridst);
2501 continue; /* skip as falls on major grid line */
2503 gfx_line(im, X0, Y1 - 2, X0, Y1,
2504 GRIDWIDTH, im->graph_col[GRC_GRID]);
2505 gfx_line(im, X0, Y0, X0, Y0 + 2,
2506 GRIDWIDTH, im->graph_col[GRC_GRID]);
2507 gfx_dashed_line(im, X0, Y0 + 1, X0,
2510 graph_col[GRC_GRID],
2511 im->grid_dash_on, im->grid_dash_off);
2515 /* paint the major grid */
2516 for (ti = find_first_time(im->start,
2524 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2526 /* are we inside the graph ? */
2527 if (ti < im->start || ti > im->end)
2530 gfx_line(im, X0, Y1 - 2, X0, Y1,
2531 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2532 gfx_line(im, X0, Y0, X0, Y0 + 3,
2533 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2534 gfx_dashed_line(im, X0, Y0 + 3, X0,
2538 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2540 /* paint the labels below the graph */
2542 find_first_time(im->start -
2551 im->xlab_user.precis / 2;
2552 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2554 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2555 /* are we inside the graph ? */
2556 if (tilab < im->start || tilab > im->end)
2559 localtime_r(&tilab, &tm);
2560 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2562 # error "your libc has no strftime I guess we'll abort the exercise here."
2567 im->graph_col[GRC_FONT],
2569 text_prop[TEXT_PROP_AXIS].
2572 GFX_H_CENTER, GFX_V_TOP, graph_label);
2581 /* draw x and y axis */
2582 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2583 im->xorigin+im->xsize,im->yorigin-im->ysize,
2584 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2586 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2587 im->xorigin+im->xsize,im->yorigin-im->ysize,
2588 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2590 gfx_line(im, im->xorigin - 4,
2592 im->xorigin + im->xsize +
2593 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2594 gfx_line(im, im->xorigin,
2597 im->yorigin - im->ysize -
2598 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2599 /* arrow for X and Y axis direction */
2600 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 */
2601 im->graph_col[GRC_ARROW]);
2603 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 */
2604 im->graph_col[GRC_ARROW]);
2606 if (im->second_axis_scale != 0){
2607 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2608 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2609 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2611 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2612 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2613 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2614 im->graph_col[GRC_ARROW]);
2625 double X0, Y0; /* points for filled graph and more */
2626 struct gfx_color_t water_color;
2628 if (im->draw_3d_border > 0) {
2629 /* draw 3d border */
2630 i = im->draw_3d_border;
2631 gfx_new_area(im, 0, im->yimg,
2632 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2633 gfx_add_point(im, im->ximg - i, i);
2634 gfx_add_point(im, im->ximg, 0);
2635 gfx_add_point(im, 0, 0);
2637 gfx_new_area(im, i, im->yimg - i,
2639 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2640 gfx_add_point(im, im->ximg, 0);
2641 gfx_add_point(im, im->ximg, im->yimg);
2642 gfx_add_point(im, 0, im->yimg);
2645 if (im->draw_x_grid == 1)
2647 if (im->draw_y_grid == 1) {
2648 if (im->logarithmic) {
2649 res = horizontal_log_grid(im);
2651 res = draw_horizontal_grid(im);
2654 /* dont draw horizontal grid if there is no min and max val */
2656 char *nodata = "No Data found";
2658 gfx_text(im, im->ximg / 2,
2661 im->graph_col[GRC_FONT],
2663 text_prop[TEXT_PROP_AXIS].
2666 GFX_H_CENTER, GFX_V_CENTER, nodata);
2670 /* yaxis unit description */
2671 if (im->ylegend[0] != '\0'){
2673 im->xOriginLegendY+10,
2675 im->graph_col[GRC_FONT],
2677 text_prop[TEXT_PROP_UNIT].
2680 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2683 if (im->second_axis_legend[0] != '\0'){
2685 im->xOriginLegendY2+10,
2686 im->yOriginLegendY2,
2687 im->graph_col[GRC_FONT],
2688 im->text_prop[TEXT_PROP_UNIT].font_desc,
2690 RRDGRAPH_YLEGEND_ANGLE,
2691 GFX_H_CENTER, GFX_V_CENTER,
2692 im->second_axis_legend);
2697 im->xOriginTitle, im->yOriginTitle+6,
2698 im->graph_col[GRC_FONT],
2700 text_prop[TEXT_PROP_TITLE].
2702 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2703 /* rrdtool 'logo' */
2704 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2705 water_color = im->graph_col[GRC_FONT];
2706 water_color.alpha = 0.3;
2707 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2708 gfx_text(im, xpos, 5,
2711 text_prop[TEXT_PROP_WATERMARK].
2712 font_desc, im->tabwidth,
2713 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2715 /* graph watermark */
2716 if (im->watermark[0] != '\0') {
2717 water_color = im->graph_col[GRC_FONT];
2718 water_color.alpha = 0.3;
2720 im->ximg / 2, im->yimg - 6,
2723 text_prop[TEXT_PROP_WATERMARK].
2724 font_desc, im->tabwidth, 0,
2725 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2729 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2730 for (i = 0; i < im->gdes_c; i++) {
2731 if (im->gdes[i].legend[0] == '\0')
2733 /* im->gdes[i].leg_y is the bottom of the legend */
2734 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2735 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2736 gfx_text(im, X0, Y0,
2737 im->graph_col[GRC_FONT],
2740 [TEXT_PROP_LEGEND].font_desc,
2742 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2743 /* The legend for GRAPH items starts with "M " to have
2744 enough space for the box */
2745 if (im->gdes[i].gf != GF_PRINT &&
2746 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2750 boxH = gfx_get_text_width(im, 0,
2755 im->tabwidth, "o") * 1.2;
2757 /* shift the box up a bit */
2760 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2762 cairo_new_path(im->cr);
2763 cairo_set_line_width(im->cr, 1.0);
2766 X0 + boxH, Y0 - boxV / 2,
2767 1.0, im->gdes[i].col);
2769 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2771 cairo_new_path(im->cr);
2772 cairo_set_line_width(im->cr, 1.0);
2775 X0 + boxH / 2, Y0 - boxV,
2776 1.0, im->gdes[i].col);
2778 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2780 cairo_new_path(im->cr);
2781 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2784 X0 + boxH, Y0 - boxV,
2785 im->gdes[i].linewidth, im->gdes[i].col);
2788 /* make sure transparent colors show up the same way as in the graph */
2791 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2792 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2794 gfx_new_area(im, X0, Y0 - boxV, X0,
2795 Y0, X0 + boxH, Y0, im->gdes[i].col);
2796 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2799 cairo_new_path(im->cr);
2800 cairo_set_line_width(im->cr, 1.0);
2803 gfx_line_fit(im, &X0, &Y0);
2804 gfx_line_fit(im, &X1, &Y1);
2805 cairo_move_to(im->cr, X0, Y0);
2806 cairo_line_to(im->cr, X1, Y0);
2807 cairo_line_to(im->cr, X1, Y1);
2808 cairo_line_to(im->cr, X0, Y1);
2809 cairo_close_path(im->cr);
2810 cairo_set_source_rgba(im->cr,
2811 im->graph_col[GRC_FRAME].red,
2812 im->graph_col[GRC_FRAME].green,
2813 im->graph_col[GRC_FRAME].blue,
2814 im->graph_col[GRC_FRAME].alpha);
2816 if (im->gdes[i].dash) {
2817 /* make box borders in legend dashed if the graph is dashed */
2821 cairo_set_dash(im->cr, dashes, 1, 0.0);
2823 cairo_stroke(im->cr);
2824 cairo_restore(im->cr);
2831 /*****************************************************
2832 * lazy check make sure we rely need to create this graph
2833 *****************************************************/
2840 struct stat imgstat;
2843 return 0; /* no lazy option */
2844 if (strlen(im->graphfile) == 0)
2845 return 0; /* inmemory option */
2846 if (stat(im->graphfile, &imgstat) != 0)
2847 return 0; /* can't stat */
2848 /* one pixel in the existing graph is more then what we would
2850 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2852 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2853 return 0; /* the file does not exist */
2854 switch (im->imgformat) {
2856 size = PngSize(fd, &(im->ximg), &(im->yimg));
2866 int graph_size_location(
2871 /* The actual size of the image to draw is determined from
2872 ** several sources. The size given on the command line is
2873 ** the graph area but we need more as we have to draw labels
2874 ** and other things outside the graph area. If the option
2875 ** --full-size-mode is selected the size defines the total
2876 ** image size and the size available for the graph is
2880 /** +---+-----------------------------------+
2881 ** | y |...............graph title.........|
2882 ** | +---+-------------------------------+
2886 ** | s | x | main graph area |
2891 ** | l | b +-------------------------------+
2892 ** | e | l | x axis labels |
2893 ** +---+---+-------------------------------+
2894 ** |....................legends............|
2895 ** +---------------------------------------+
2897 ** +---------------------------------------+
2900 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2901 0, Xylabel = 0, Xmain = 0, Ymain =
2902 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2904 // no legends and no the shall be plotted it's easy
2905 if (im->extra_flags & ONLY_GRAPH) {
2907 im->ximg = im->xsize;
2908 im->yimg = im->ysize;
2909 im->yorigin = im->ysize;
2914 if(im->watermark[0] != '\0') {
2915 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2918 // calculate the width of the left vertical legend
2919 if (im->ylegend[0] != '\0') {
2920 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2923 // calculate the width of the right vertical legend
2924 if (im->second_axis_legend[0] != '\0') {
2925 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2928 Xvertical2 = Xspacing;
2931 if (im->title[0] != '\0') {
2932 /* The title is placed "inbetween" two text lines so it
2933 ** automatically has some vertical spacing. The horizontal
2934 ** spacing is added here, on each side.
2936 /* if necessary, reduce the font size of the title until it fits the image width */
2937 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2940 // we have no title; get a little clearing from the top
2941 Ytitle = 1.5 * Yspacing;
2945 if (im->draw_x_grid) {
2946 // calculate the height of the horizontal labelling
2947 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2949 if (im->draw_y_grid || im->forceleftspace) {
2950 // calculate the width of the vertical labelling
2952 gfx_get_text_width(im, 0,
2953 im->text_prop[TEXT_PROP_AXIS].font_desc,
2954 im->tabwidth, "0") * im->unitslength;
2958 // add some space to the labelling
2959 Xylabel += Xspacing;
2961 /* If the legend is printed besides the graph the width has to be
2962 ** calculated first. Placing the legend north or south of the
2963 ** graph requires the width calculation first, so the legend is
2964 ** skipped for the moment.
2966 im->legendheight = 0;
2967 im->legendwidth = 0;
2968 if (!(im->extra_flags & NOLEGEND)) {
2969 if(im->legendposition == WEST || im->legendposition == EAST){
2970 if (leg_place(im, 1) == -1){
2976 if (im->extra_flags & FULL_SIZE_MODE) {
2978 /* The actual size of the image to draw has been determined by the user.
2979 ** The graph area is the space remaining after accounting for the legend,
2980 ** the watermark, the axis labels, and the title.
2982 im->ximg = im->xsize;
2983 im->yimg = im->ysize;
2987 /* Now calculate the total size. Insert some spacing where
2988 desired. im->xorigin and im->yorigin need to correspond
2989 with the lower left corner of the main graph area or, if
2990 this one is not set, the imaginary box surrounding the
2992 /* Initial size calculation for the main graph area */
2994 Xmain -= Xylabel;// + Xspacing;
2995 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2996 Xmain -= im->legendwidth;// + Xspacing;
2998 if (im->second_axis_scale != 0){
3001 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3005 Xmain -= Xvertical + Xvertical2;
3007 /* limit the remaining space to 0 */
3013 /* Putting the legend north or south, the height can now be calculated */
3014 if (!(im->extra_flags & NOLEGEND)) {
3015 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3016 im->legendwidth = im->ximg;
3017 if (leg_place(im, 0) == -1){
3023 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3024 Ymain -= Yxlabel + im->legendheight;
3030 /* reserve space for the title *or* some padding above the graph */
3033 /* reserve space for padding below the graph */
3034 if (im->extra_flags & NOLEGEND) {
3038 if (im->watermark[0] != '\0') {
3039 Ymain -= Ywatermark;
3041 /* limit the remaining height to 0 */
3046 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3048 /* The actual size of the image to draw is determined from
3049 ** several sources. The size given on the command line is
3050 ** the graph area but we need more as we have to draw labels
3051 ** and other things outside the graph area.
3055 Xmain = im->xsize; // + Xspacing;
3059 im->ximg = Xmain + Xylabel;
3060 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3061 im->ximg += Xspacing;
3064 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3065 im->ximg += im->legendwidth;// + Xspacing;
3067 if (im->second_axis_scale != 0){
3068 im->ximg += Xylabel;
3071 im->ximg += Xvertical + Xvertical2;
3073 if (!(im->extra_flags & NOLEGEND)) {
3074 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3075 im->legendwidth = im->ximg;
3076 if (leg_place(im, 0) == -1){
3082 im->yimg = Ymain + Yxlabel;
3083 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3084 im->yimg += im->legendheight;
3087 /* reserve space for the title *or* some padding above the graph */
3091 im->yimg += 1.5 * Yspacing;
3093 /* reserve space for padding below the graph */
3094 if (im->extra_flags & NOLEGEND) {
3095 im->yimg += Yspacing;
3098 if (im->watermark[0] != '\0') {
3099 im->yimg += Ywatermark;
3104 /* In case of putting the legend in west or east position the first
3105 ** legend calculation might lead to wrong positions if some items
3106 ** are not aligned on the left hand side (e.g. centered) as the
3107 ** legendwidth wight have been increased after the item was placed.
3108 ** In this case the positions have to be recalculated.
3110 if (!(im->extra_flags & NOLEGEND)) {
3111 if(im->legendposition == WEST || im->legendposition == EAST){
3112 if (leg_place(im, 0) == -1){
3118 /* After calculating all dimensions
3119 ** it is now possible to calculate
3122 switch(im->legendposition){
3124 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3125 im->yOriginTitle = 0;
3127 im->xOriginLegend = 0;
3128 im->yOriginLegend = Ytitle;
3130 im->xOriginLegendY = 0;
3131 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3133 im->xorigin = Xvertical + Xylabel;
3134 im->yorigin = Ytitle + im->legendheight + Ymain;
3136 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3137 if (im->second_axis_scale != 0){
3138 im->xOriginLegendY2 += Xylabel;
3140 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3145 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3146 im->yOriginTitle = 0;
3148 im->xOriginLegend = 0;
3149 im->yOriginLegend = Ytitle;
3151 im->xOriginLegendY = im->legendwidth;
3152 im->yOriginLegendY = Ytitle + (Ymain / 2);
3154 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3155 im->yorigin = Ytitle + Ymain;
3157 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3158 if (im->second_axis_scale != 0){
3159 im->xOriginLegendY2 += Xylabel;
3161 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3166 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3167 im->yOriginTitle = 0;
3169 im->xOriginLegend = 0;
3170 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3172 im->xOriginLegendY = 0;
3173 im->yOriginLegendY = Ytitle + (Ymain / 2);
3175 im->xorigin = Xvertical + Xylabel;
3176 im->yorigin = Ytitle + Ymain;
3178 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3179 if (im->second_axis_scale != 0){
3180 im->xOriginLegendY2 += Xylabel;
3182 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3187 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3188 im->yOriginTitle = 0;
3190 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3191 if (im->second_axis_scale != 0){
3192 im->xOriginLegend += Xylabel;
3194 im->yOriginLegend = Ytitle;
3196 im->xOriginLegendY = 0;
3197 im->yOriginLegendY = Ytitle + (Ymain / 2);
3199 im->xorigin = Xvertical + Xylabel;
3200 im->yorigin = Ytitle + Ymain;
3202 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3203 if (im->second_axis_scale != 0){
3204 im->xOriginLegendY2 += Xylabel;
3206 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3208 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3209 im->xOriginTitle += Xspacing;
3210 im->xOriginLegend += Xspacing;
3211 im->xOriginLegendY += Xspacing;
3212 im->xorigin += Xspacing;
3213 im->xOriginLegendY2 += Xspacing;
3223 static cairo_status_t cairo_output(
3227 unsigned int length)
3229 image_desc_t *im = (image_desc_t*)closure;
3231 im->rendered_image =
3232 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3233 if (im->rendered_image == NULL)
3234 return CAIRO_STATUS_WRITE_ERROR;
3235 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3236 im->rendered_image_size += length;
3237 return CAIRO_STATUS_SUCCESS;
3240 /* draw that picture thing ... */
3245 int lazy = lazy_check(im);
3246 double areazero = 0.0;
3247 graph_desc_t *lastgdes = NULL;
3250 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3252 /* pull the data from the rrd files ... */
3253 if (data_fetch(im) == -1)
3255 /* evaluate VDEF and CDEF operations ... */
3256 if (data_calc(im) == -1)
3258 /* calculate and PRINT and GPRINT definitions. We have to do it at
3259 * this point because it will affect the length of the legends
3260 * if there are no graph elements (i==0) we stop here ...
3261 * if we are lazy, try to quit ...
3267 /* if we want and can be lazy ... quit now */
3271 /**************************************************************
3272 *** Calculating sizes and locations became a bit confusing ***
3273 *** so I moved this into a separate function. ***
3274 **************************************************************/
3275 if (graph_size_location(im, i) == -1)
3278 info.u_cnt = im->xorigin;
3279 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3280 info.u_cnt = im->yorigin - im->ysize;
3281 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3282 info.u_cnt = im->xsize;
3283 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3284 info.u_cnt = im->ysize;
3285 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3286 info.u_cnt = im->ximg;
3287 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3288 info.u_cnt = im->yimg;
3289 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3290 info.u_cnt = im->start;
3291 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3292 info.u_cnt = im->end;
3293 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3295 /* if we want and can be lazy ... quit now */
3299 /* get actual drawing data and find min and max values */
3300 if (data_proc(im) == -1)
3302 if (!im->logarithmic) {
3306 /* identify si magnitude Kilo, Mega Giga ? */
3307 if (!im->rigid && !im->logarithmic)
3308 expand_range(im); /* make sure the upper and lower limit are
3311 info.u_val = im->minval;
3312 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3313 info.u_val = im->maxval;
3314 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3317 if (!calc_horizontal_grid(im))
3322 apply_gridfit(im); */
3323 /* the actual graph is created by going through the individual
3324 graph elements and then drawing them */
3325 cairo_surface_destroy(im->surface);
3326 switch (im->imgformat) {
3329 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3330 im->ximg * im->zoom,
3331 im->yimg * im->zoom);
3335 im->surface = strlen(im->graphfile)
3336 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3337 im->yimg * im->zoom)
3338 : cairo_pdf_surface_create_for_stream
3339 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3343 im->surface = strlen(im->graphfile)
3345 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3346 im->yimg * im->zoom)
3347 : cairo_ps_surface_create_for_stream
3348 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3352 im->surface = strlen(im->graphfile)
3354 cairo_svg_surface_create(im->
3356 im->ximg * im->zoom, im->yimg * im->zoom)
3357 : cairo_svg_surface_create_for_stream
3358 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3359 cairo_svg_surface_restrict_to_version
3360 (im->surface, CAIRO_SVG_VERSION_1_1);
3363 cairo_destroy(im->cr);
3364 im->cr = cairo_create(im->surface);
3365 cairo_set_antialias(im->cr, im->graph_antialias);
3366 cairo_scale(im->cr, im->zoom, im->zoom);
3367 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3368 gfx_new_area(im, 0, 0, 0, im->yimg,
3369 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3370 gfx_add_point(im, im->ximg, 0);
3372 gfx_new_area(im, im->xorigin,
3375 im->xsize, im->yorigin,
3378 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3379 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3381 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3382 im->xsize, im->ysize + 2.0);
3384 if (im->minval > 0.0)
3385 areazero = im->minval;
3386 if (im->maxval < 0.0)
3387 areazero = im->maxval;
3388 for (i = 0; i < im->gdes_c; i++) {
3389 switch (im->gdes[i].gf) {
3403 for (ii = 0; ii < im->xsize; ii++) {
3404 if (!isnan(im->gdes[i].p_data[ii])
3405 && im->gdes[i].p_data[ii] != 0.0) {
3406 if (im->gdes[i].yrule > 0) {
3413 im->ysize, 1.0, im->gdes[i].col);
3414 } else if (im->gdes[i].yrule < 0) {
3417 im->yorigin - im->ysize - 1.0,
3419 im->yorigin - im->ysize -
3422 im->ysize, 1.0, im->gdes[i].col);
3429 /* fix data points at oo and -oo */
3430 for (ii = 0; ii < im->xsize; ii++) {
3431 if (isinf(im->gdes[i].p_data[ii])) {
3432 if (im->gdes[i].p_data[ii] > 0) {
3433 im->gdes[i].p_data[ii] = im->maxval;
3435 im->gdes[i].p_data[ii] = im->minval;
3441 /* *******************************************************
3446 -------|--t-1--t--------------------------------
3448 if we know the value at time t was a then
3449 we draw a square from t-1 to t with the value a.
3451 ********************************************************* */
3452 if (im->gdes[i].col.alpha != 0.0) {
3453 /* GF_LINE and friend */
3454 if (im->gdes[i].gf == GF_LINE) {
3455 double last_y = 0.0;
3459 cairo_new_path(im->cr);
3460 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3461 if (im->gdes[i].dash) {
3462 cairo_set_dash(im->cr,
3463 im->gdes[i].p_dashes,
3464 im->gdes[i].ndash, im->gdes[i].offset);
3467 for (ii = 1; ii < im->xsize; ii++) {
3468 if (isnan(im->gdes[i].p_data[ii])
3469 || (im->slopemode == 1
3470 && isnan(im->gdes[i].p_data[ii - 1]))) {
3475 last_y = ytr(im, im->gdes[i].p_data[ii]);
3476 if (im->slopemode == 0) {
3477 double x = ii - 1 + im->xorigin;
3480 gfx_line_fit(im, &x, &y);
3481 cairo_move_to(im->cr, x, y);
3482 x = ii + im->xorigin;
3484 gfx_line_fit(im, &x, &y);
3485 cairo_line_to(im->cr, x, y);
3487 double x = ii - 1 + im->xorigin;
3489 ytr(im, im->gdes[i].p_data[ii - 1]);
3490 gfx_line_fit(im, &x, &y);
3491 cairo_move_to(im->cr, x, y);
3492 x = ii + im->xorigin;
3494 gfx_line_fit(im, &x, &y);
3495 cairo_line_to(im->cr, x, y);
3499 double x1 = ii + im->xorigin;
3500 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3502 if (im->slopemode == 0
3503 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3504 double x = ii - 1 + im->xorigin;
3507 gfx_line_fit(im, &x, &y);
3508 cairo_line_to(im->cr, x, y);
3511 gfx_line_fit(im, &x1, &y1);
3512 cairo_line_to(im->cr, x1, y1);
3515 cairo_set_source_rgba(im->cr,
3521 col.blue, im->gdes[i].col.alpha);
3522 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3523 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3524 cairo_stroke(im->cr);
3525 cairo_restore(im->cr);
3529 (double *) malloc(sizeof(double) * im->xsize * 2);
3531 (double *) malloc(sizeof(double) * im->xsize * 2);
3533 (double *) malloc(sizeof(double) * im->xsize * 2);
3535 (double *) malloc(sizeof(double) * im->xsize * 2);
3538 for (ii = 0; ii <= im->xsize; ii++) {
3541 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3547 AlmostEqual2sComplement(foreY
3551 AlmostEqual2sComplement(foreY
3561 foreY[cntI], im->gdes[i].col);
3562 while (cntI < idxI) {
3567 AlmostEqual2sComplement(foreY
3571 AlmostEqual2sComplement(foreY
3578 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3580 gfx_add_point(im, backX[idxI], backY[idxI]);
3586 AlmostEqual2sComplement(backY
3590 AlmostEqual2sComplement(backY
3597 gfx_add_point(im, backX[idxI], backY[idxI]);
3607 if (ii == im->xsize)
3609 if (im->slopemode == 0 && ii == 0) {
3612 if (isnan(im->gdes[i].p_data[ii])) {
3616 ytop = ytr(im, im->gdes[i].p_data[ii]);
3617 if (lastgdes && im->gdes[i].stack) {
3618 ybase = ytr(im, lastgdes->p_data[ii]);
3620 ybase = ytr(im, areazero);
3622 if (ybase == ytop) {
3628 double extra = ytop;
3633 if (im->slopemode == 0) {
3634 backY[++idxI] = ybase - 0.2;
3635 backX[idxI] = ii + im->xorigin - 1;
3636 foreY[idxI] = ytop + 0.2;
3637 foreX[idxI] = ii + im->xorigin - 1;
3639 backY[++idxI] = ybase - 0.2;
3640 backX[idxI] = ii + im->xorigin;
3641 foreY[idxI] = ytop + 0.2;
3642 foreX[idxI] = ii + im->xorigin;
3644 /* close up any remaining area */
3649 } /* else GF_LINE */
3651 /* if color != 0x0 */
3652 /* make sure we do not run into trouble when stacking on NaN */
3653 for (ii = 0; ii < im->xsize; ii++) {
3654 if (isnan(im->gdes[i].p_data[ii])) {
3655 if (lastgdes && (im->gdes[i].stack)) {
3656 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3658 im->gdes[i].p_data[ii] = areazero;
3662 lastgdes = &(im->gdes[i]);
3666 ("STACK should already be turned into LINE or AREA here");
3671 cairo_reset_clip(im->cr);
3673 /* grid_paint also does the text */
3674 if (!(im->extra_flags & ONLY_GRAPH))
3676 if (!(im->extra_flags & ONLY_GRAPH))
3678 /* the RULES are the last thing to paint ... */
3679 for (i = 0; i < im->gdes_c; i++) {
3681 switch (im->gdes[i].gf) {
3683 if (im->gdes[i].yrule >= im->minval
3684 && im->gdes[i].yrule <= im->maxval) {
3686 if (im->gdes[i].dash) {
3687 cairo_set_dash(im->cr,
3688 im->gdes[i].p_dashes,
3689 im->gdes[i].ndash, im->gdes[i].offset);
3691 gfx_line(im, im->xorigin,
3692 ytr(im, im->gdes[i].yrule),
3693 im->xorigin + im->xsize,
3694 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3695 cairo_stroke(im->cr);
3696 cairo_restore(im->cr);
3700 if (im->gdes[i].xrule >= im->start
3701 && im->gdes[i].xrule <= im->end) {
3703 if (im->gdes[i].dash) {
3704 cairo_set_dash(im->cr,
3705 im->gdes[i].p_dashes,
3706 im->gdes[i].ndash, im->gdes[i].offset);
3709 xtr(im, im->gdes[i].xrule),
3710 im->yorigin, xtr(im,
3714 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3715 cairo_stroke(im->cr);
3716 cairo_restore(im->cr);
3725 switch (im->imgformat) {
3728 cairo_status_t status;
3730 status = strlen(im->graphfile) ?
3731 cairo_surface_write_to_png(im->surface, im->graphfile)
3732 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3735 if (status != CAIRO_STATUS_SUCCESS) {
3736 rrd_set_error("Could not save png to '%s'", im->graphfile);
3742 if (strlen(im->graphfile)) {
3743 cairo_show_page(im->cr);
3745 cairo_surface_finish(im->surface);
3754 /*****************************************************
3756 *****************************************************/
3763 if ((im->gdes = (graph_desc_t *)
3764 rrd_realloc(im->gdes, (im->gdes_c)
3765 * sizeof(graph_desc_t))) == NULL) {
3766 rrd_set_error("realloc graph_descs");
3771 im->gdes[im->gdes_c - 1].step = im->step;
3772 im->gdes[im->gdes_c - 1].step_orig = im->step;
3773 im->gdes[im->gdes_c - 1].stack = 0;
3774 im->gdes[im->gdes_c - 1].linewidth = 0;
3775 im->gdes[im->gdes_c - 1].debug = 0;
3776 im->gdes[im->gdes_c - 1].start = im->start;
3777 im->gdes[im->gdes_c - 1].start_orig = im->start;
3778 im->gdes[im->gdes_c - 1].end = im->end;
3779 im->gdes[im->gdes_c - 1].end_orig = im->end;
3780 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3781 im->gdes[im->gdes_c - 1].data = NULL;
3782 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3783 im->gdes[im->gdes_c - 1].data_first = 0;
3784 im->gdes[im->gdes_c - 1].p_data = NULL;
3785 im->gdes[im->gdes_c - 1].rpnp = NULL;
3786 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3787 im->gdes[im->gdes_c - 1].shift = 0.0;
3788 im->gdes[im->gdes_c - 1].dash = 0;
3789 im->gdes[im->gdes_c - 1].ndash = 0;
3790 im->gdes[im->gdes_c - 1].offset = 0;
3791 im->gdes[im->gdes_c - 1].col.red = 0.0;
3792 im->gdes[im->gdes_c - 1].col.green = 0.0;
3793 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3794 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3795 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3796 im->gdes[im->gdes_c - 1].format[0] = '\0';
3797 im->gdes[im->gdes_c - 1].strftm = 0;
3798 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3799 im->gdes[im->gdes_c - 1].ds = -1;
3800 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3801 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3802 im->gdes[im->gdes_c - 1].yrule = DNAN;
3803 im->gdes[im->gdes_c - 1].xrule = 0;
3804 im->gdes[im->gdes_c - 1].daemon[0] = 0;
3808 /* copies input untill the first unescaped colon is found
3809 or until input ends. backslashes have to be escaped as well */
3811 const char *const input,
3817 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3818 if (input[inp] == '\\'
3819 && input[inp + 1] != '\0'
3820 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3821 output[outp++] = input[++inp];
3823 output[outp++] = input[inp];
3826 output[outp] = '\0';
3830 /* Now just a wrapper around rrd_graph_v */
3842 rrd_info_t *grinfo = NULL;
3845 grinfo = rrd_graph_v(argc, argv);
3851 if (strcmp(walker->key, "image_info") == 0) {
3854 (char**)rrd_realloc((*prdata),
3855 (prlines + 1) * sizeof(char *))) == NULL) {
3856 rrd_set_error("realloc prdata");
3859 /* imginfo goes to position 0 in the prdata array */
3860 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3861 + 2) * sizeof(char));
3862 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3863 (*prdata)[prlines] = NULL;
3865 /* skip anything else */
3866 walker = walker->next;
3874 if (strcmp(walker->key, "image_width") == 0) {
3875 *xsize = walker->value.u_cnt;
3876 } else if (strcmp(walker->key, "image_height") == 0) {
3877 *ysize = walker->value.u_cnt;
3878 } else if (strcmp(walker->key, "value_min") == 0) {
3879 *ymin = walker->value.u_val;
3880 } else if (strcmp(walker->key, "value_max") == 0) {
3881 *ymax = walker->value.u_val;
3882 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3885 (char**)rrd_realloc((*prdata),
3886 (prlines + 1) * sizeof(char *))) == NULL) {
3887 rrd_set_error("realloc prdata");
3890 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3891 + 2) * sizeof(char));
3892 (*prdata)[prlines] = NULL;
3893 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3894 } else if (strcmp(walker->key, "image") == 0) {
3895 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3896 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3897 rrd_set_error("writing image");
3901 /* skip anything else */
3902 walker = walker->next;
3904 rrd_info_free(grinfo);
3909 /* Some surgery done on this function, it became ridiculously big.
3911 ** - initializing now in rrd_graph_init()
3912 ** - options parsing now in rrd_graph_options()
3913 ** - script parsing now in rrd_graph_script()
3915 rrd_info_t *rrd_graph_v(
3922 rrd_graph_init(&im);
3923 /* a dummy surface so that we can measure text sizes for placements */
3924 old_locale = setlocale(LC_NUMERIC, "C");
3925 rrd_graph_options(argc, argv, &im);
3926 if (rrd_test_error()) {
3927 rrd_info_free(im.grinfo);
3932 if (optind >= argc) {
3933 rrd_info_free(im.grinfo);
3935 rrd_set_error("missing filename");
3939 if (strlen(argv[optind]) >= MAXPATH) {
3940 rrd_set_error("filename (including path) too long");
3941 rrd_info_free(im.grinfo);
3946 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3947 im.graphfile[MAXPATH - 1] = '\0';
3949 if (strcmp(im.graphfile, "-") == 0) {
3950 im.graphfile[0] = '\0';
3953 rrd_graph_script(argc, argv, &im, 1);
3954 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3956 if (rrd_test_error()) {
3957 rrd_info_free(im.grinfo);
3962 /* Everything is now read and the actual work can start */
3964 if (graph_paint(&im) == -1) {
3965 rrd_info_free(im.grinfo);
3971 /* The image is generated and needs to be output.
3972 ** Also, if needed, print a line with information about the image.
3980 path = strdup(im.graphfile);
3981 filename = basename(path);
3983 sprintf_alloc(im.imginfo,
3986 im.ximg), (long) (im.zoom * im.yimg));
3987 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3991 if (im.rendered_image) {
3994 img.u_blo.size = im.rendered_image_size;
3995 img.u_blo.ptr = im.rendered_image;
3996 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4005 image_desc_t *im,int prop,char *font, double size ){
4007 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4008 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4009 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4012 im->text_prop[prop].size = size;
4014 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4015 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4019 void rrd_graph_init(
4024 char *deffont = getenv("RRD_DEFAULT_FONT");
4025 static PangoFontMap *fontmap = NULL;
4026 PangoContext *context;
4033 im->daemon_addr = NULL;
4034 im->draw_x_grid = 1;
4035 im->draw_y_grid = 1;
4036 im->draw_3d_border = 2;
4037 im->dynamic_labels = 0;
4038 im->extra_flags = 0;
4039 im->font_options = cairo_font_options_create();
4040 im->forceleftspace = 0;
4043 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4044 im->grid_dash_off = 1;
4045 im->grid_dash_on = 1;
4047 im->grinfo = (rrd_info_t *) NULL;
4048 im->grinfo_current = (rrd_info_t *) NULL;
4049 im->imgformat = IF_PNG;
4052 im->legenddirection = TOP_DOWN;
4053 im->legendheight = 0;
4054 im->legendposition = SOUTH;
4055 im->legendwidth = 0;
4056 im->logarithmic = 0;
4062 im->rendered_image_size = 0;
4063 im->rendered_image = NULL;
4067 im->tabwidth = 40.0;
4068 im->title[0] = '\0';
4069 im->unitsexponent = 9999;
4070 im->unitslength = 6;
4071 im->viewfactor = 1.0;
4072 im->watermark[0] = '\0';
4073 im->with_markup = 0;
4075 im->xlab_user.minsec = -1;
4077 im->xOriginLegend = 0;
4078 im->xOriginLegendY = 0;
4079 im->xOriginLegendY2 = 0;
4080 im->xOriginTitle = 0;
4082 im->ygridstep = DNAN;
4084 im->ylegend[0] = '\0';
4085 im->second_axis_scale = 0; /* 0 disables it */
4086 im->second_axis_shift = 0; /* no shift by default */
4087 im->second_axis_legend[0] = '\0';
4088 im->second_axis_format[0] = '\0';
4090 im->yOriginLegend = 0;
4091 im->yOriginLegendY = 0;
4092 im->yOriginLegendY2 = 0;
4093 im->yOriginTitle = 0;
4097 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4098 im->cr = cairo_create(im->surface);
4100 for (i = 0; i < DIM(text_prop); i++) {
4101 im->text_prop[i].size = -1;
4102 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4105 if (fontmap == NULL){
4106 fontmap = pango_cairo_font_map_get_default();
4109 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4111 pango_cairo_context_set_resolution(context, 100);
4113 pango_cairo_update_context(im->cr,context);
4115 im->layout = pango_layout_new(context);
4117 // im->layout = pango_cairo_create_layout(im->cr);
4120 cairo_font_options_set_hint_style
4121 (im->font_options, CAIRO_HINT_STYLE_FULL);
4122 cairo_font_options_set_hint_metrics
4123 (im->font_options, CAIRO_HINT_METRICS_ON);
4124 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4128 for (i = 0; i < DIM(graph_col); i++)
4129 im->graph_col[i] = graph_col[i];
4135 void rrd_graph_options(
4142 char *parsetime_error = NULL;
4143 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4144 time_t start_tmp = 0, end_tmp = 0;
4146 rrd_time_value_t start_tv, end_tv;
4147 long unsigned int color;
4149 /* defines for long options without a short equivalent. should be bytes,
4150 and may not collide with (the ASCII value of) short options */
4151 #define LONGOPT_UNITS_SI 255
4154 struct option long_options[] = {
4155 { "alt-autoscale", no_argument, 0, 'A'},
4156 { "imgformat", required_argument, 0, 'a'},
4157 { "font-smoothing-threshold", required_argument, 0, 'B'},
4158 { "base", required_argument, 0, 'b'},
4159 { "color", required_argument, 0, 'c'},
4160 { "full-size-mode", no_argument, 0, 'D'},
4161 { "daemon", required_argument, 0, 'd'},
4162 { "slope-mode", no_argument, 0, 'E'},
4163 { "end", required_argument, 0, 'e'},
4164 { "force-rules-legend", no_argument, 0, 'F'},
4165 { "imginfo", required_argument, 0, 'f'},
4166 { "graph-render-mode", required_argument, 0, 'G'},
4167 { "no-legend", no_argument, 0, 'g'},
4168 { "height", required_argument, 0, 'h'},
4169 { "no-minor", no_argument, 0, 'I'},
4170 { "interlaced", no_argument, 0, 'i'},
4171 { "alt-autoscale-min", no_argument, 0, 'J'},
4172 { "only-graph", no_argument, 0, 'j'},
4173 { "units-length", required_argument, 0, 'L'},
4174 { "lower-limit", required_argument, 0, 'l'},
4175 { "alt-autoscale-max", no_argument, 0, 'M'},
4176 { "zoom", required_argument, 0, 'm'},
4177 { "no-gridfit", no_argument, 0, 'N'},
4178 { "font", required_argument, 0, 'n'},
4179 { "logarithmic", no_argument, 0, 'o'},
4180 { "pango-markup", no_argument, 0, 'P'},
4181 { "font-render-mode", required_argument, 0, 'R'},
4182 { "rigid", no_argument, 0, 'r'},
4183 { "step", required_argument, 0, 'S'},
4184 { "start", required_argument, 0, 's'},
4185 { "tabwidth", required_argument, 0, 'T'},
4186 { "title", required_argument, 0, 't'},
4187 { "upper-limit", required_argument, 0, 'u'},
4188 { "vertical-label", required_argument, 0, 'v'},
4189 { "watermark", required_argument, 0, 'W'},
4190 { "width", required_argument, 0, 'w'},
4191 { "units-exponent", required_argument, 0, 'X'},
4192 { "x-grid", required_argument, 0, 'x'},
4193 { "alt-y-grid", no_argument, 0, 'Y'},
4194 { "y-grid", required_argument, 0, 'y'},
4195 { "lazy", no_argument, 0, 'z'},
4196 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4197 { "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 */
4198 { "disable-rrdtool-tag",no_argument, 0, 1001},
4199 { "right-axis", required_argument, 0, 1002},
4200 { "right-axis-label", required_argument, 0, 1003},
4201 { "right-axis-format", required_argument, 0, 1004},
4202 { "legend-position", required_argument, 0, 1005},
4203 { "legend-direction", required_argument, 0, 1006},
4204 { "border", required_argument, 0, 1007},
4205 { "grid-dash", required_argument, 0, 1008},
4206 { "dynamic-labels", no_argument, 0, 1009},
4212 opterr = 0; /* initialize getopt */
4213 rrd_parsetime("end-24h", &start_tv);
4214 rrd_parsetime("now", &end_tv);
4216 int option_index = 0;
4218 int col_start, col_end;
4220 opt = getopt_long(argc, argv,
4221 "Aa:B:b:c:Dd:Ee:Ff:G:gh:IiJjL:l:Nn:Bb:oPR:rS:s:T:t:u:v:W:w:X:x:Yy:z",
4222 long_options, &option_index);
4227 im->extra_flags |= NOMINOR;
4230 im->extra_flags |= ALTYGRID;
4233 im->extra_flags |= ALTAUTOSCALE;
4236 im->extra_flags |= ALTAUTOSCALE_MIN;
4239 im->extra_flags |= ALTAUTOSCALE_MAX;
4242 im->extra_flags |= ONLY_GRAPH;
4245 im->extra_flags |= NOLEGEND;
4248 if (strcmp(optarg, "north") == 0) {
4249 im->legendposition = NORTH;
4250 } else if (strcmp(optarg, "west") == 0) {
4251 im->legendposition = WEST;
4252 } else if (strcmp(optarg, "south") == 0) {
4253 im->legendposition = SOUTH;
4254 } else if (strcmp(optarg, "east") == 0) {
4255 im->legendposition = EAST;
4257 rrd_set_error("unknown legend-position '%s'", optarg);
4262 if (strcmp(optarg, "topdown") == 0) {
4263 im->legenddirection = TOP_DOWN;
4264 } else if (strcmp(optarg, "bottomup") == 0) {
4265 im->legenddirection = BOTTOM_UP;
4267 rrd_set_error("unknown legend-position '%s'", optarg);
4272 im->extra_flags |= FORCE_RULES_LEGEND;
4275 im->extra_flags |= NO_RRDTOOL_TAG;
4277 case LONGOPT_UNITS_SI:
4278 if (im->extra_flags & FORCE_UNITS) {
4279 rrd_set_error("--units can only be used once!");
4282 if (strcmp(optarg, "si") == 0)
4283 im->extra_flags |= FORCE_UNITS_SI;
4285 rrd_set_error("invalid argument for --units: %s", optarg);
4290 im->unitsexponent = atoi(optarg);
4293 im->unitslength = atoi(optarg);
4294 im->forceleftspace = 1;
4297 im->tabwidth = atof(optarg);
4300 im->step = atoi(optarg);
4306 im->with_markup = 1;
4309 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4310 rrd_set_error("start time: %s", parsetime_error);
4315 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4316 rrd_set_error("end time: %s", parsetime_error);
4321 if (strcmp(optarg, "none") == 0) {
4322 im->draw_x_grid = 0;
4326 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4328 &im->xlab_user.gridst,
4330 &im->xlab_user.mgridst,
4332 &im->xlab_user.labst,
4333 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4334 strncpy(im->xlab_form, optarg + stroff,
4335 sizeof(im->xlab_form) - 1);
4336 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4338 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4339 rrd_set_error("unknown keyword %s", scan_gtm);
4342 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4344 rrd_set_error("unknown keyword %s", scan_mtm);
4347 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4348 rrd_set_error("unknown keyword %s", scan_ltm);
4351 im->xlab_user.minsec = 1;
4352 im->xlab_user.stst = im->xlab_form;
4354 rrd_set_error("invalid x-grid format");
4360 if (strcmp(optarg, "none") == 0) {
4361 im->draw_y_grid = 0;
4364 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4365 if (im->ygridstep <= 0) {
4366 rrd_set_error("grid step must be > 0");
4368 } else if (im->ylabfact < 1) {
4369 rrd_set_error("label factor must be > 0");
4373 rrd_set_error("invalid y-grid format");
4378 im->draw_3d_border = atoi(optarg);
4380 case 1008: /* grid-dash */
4384 &im->grid_dash_off) != 2) {
4385 rrd_set_error("expected grid-dash format float:float");
4389 case 1009: /* enable dynamic labels */
4390 im->dynamic_labels = 1;
4392 case 1002: /* right y axis */
4396 &im->second_axis_scale,
4397 &im->second_axis_shift) == 2) {
4398 if(im->second_axis_scale==0){
4399 rrd_set_error("the second_axis_scale must not be 0");
4403 rrd_set_error("invalid right-axis format expected scale:shift");
4408 strncpy(im->second_axis_legend,optarg,150);
4409 im->second_axis_legend[150]='\0';
4412 if (bad_format(optarg)){
4413 rrd_set_error("use either %le or %lf formats");
4416 strncpy(im->second_axis_format,optarg,150);
4417 im->second_axis_format[150]='\0';
4420 strncpy(im->ylegend, optarg, 150);
4421 im->ylegend[150] = '\0';
4424 im->maxval = atof(optarg);
4427 im->minval = atof(optarg);
4430 im->base = atol(optarg);
4431 if (im->base != 1024 && im->base != 1000) {
4433 ("the only sensible value for base apart from 1000 is 1024");
4438 long_tmp = atol(optarg);
4439 if (long_tmp < 10) {
4440 rrd_set_error("width below 10 pixels");
4443 im->xsize = long_tmp;
4446 long_tmp = atol(optarg);
4447 if (long_tmp < 10) {
4448 rrd_set_error("height below 10 pixels");
4451 im->ysize = long_tmp;
4454 im->extra_flags |= FULL_SIZE_MODE;
4457 /* interlaced png not supported at the moment */
4463 im->imginfo = optarg;
4467 (im->imgformat = if_conv(optarg)) == -1) {
4468 rrd_set_error("unsupported graphics format '%s'", optarg);
4479 im->logarithmic = 1;
4483 "%10[A-Z]#%n%8lx%n",
4484 col_nam, &col_start, &color, &col_end) == 2) {
4486 int col_len = col_end - col_start;
4491 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4499 (((color & 0xF000) *
4500 0x11000) | ((color & 0x0F00) *
4501 0x01100) | ((color &
4504 ((color & 0x000F) * 0x00011)
4508 color = (color << 8) + 0xff /* shift left by 8 */ ;
4513 rrd_set_error("the color format is #RRGGBB[AA]");
4516 if ((ci = grc_conv(col_nam)) != -1) {
4517 im->graph_col[ci] = gfx_hex_to_col(color);
4519 rrd_set_error("invalid color name '%s'", col_nam);
4523 rrd_set_error("invalid color def format");
4532 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4533 int sindex, propidx;
4535 if ((sindex = text_prop_conv(prop)) != -1) {
4536 for (propidx = sindex;
4537 propidx < TEXT_PROP_LAST; propidx++) {
4539 rrd_set_font_desc(im,propidx,NULL,size);
4541 if ((int) strlen(optarg) > end+2) {
4542 if (optarg[end] == ':') {
4543 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4546 ("expected : after font size in '%s'",
4551 /* only run the for loop for DEFAULT (0) for
4552 all others, we break here. woodo programming */
4553 if (propidx == sindex && sindex != 0)
4557 rrd_set_error("invalid fonttag '%s'", prop);
4561 rrd_set_error("invalid text property format");
4567 im->zoom = atof(optarg);
4568 if (im->zoom <= 0.0) {
4569 rrd_set_error("zoom factor must be > 0");
4574 strncpy(im->title, optarg, 150);
4575 im->title[150] = '\0';
4578 if (strcmp(optarg, "normal") == 0) {
4579 cairo_font_options_set_antialias
4580 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4581 cairo_font_options_set_hint_style
4582 (im->font_options, CAIRO_HINT_STYLE_FULL);
4583 } else if (strcmp(optarg, "light") == 0) {
4584 cairo_font_options_set_antialias
4585 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4586 cairo_font_options_set_hint_style
4587 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4588 } else if (strcmp(optarg, "mono") == 0) {
4589 cairo_font_options_set_antialias
4590 (im->font_options, CAIRO_ANTIALIAS_NONE);
4591 cairo_font_options_set_hint_style
4592 (im->font_options, CAIRO_HINT_STYLE_FULL);
4594 rrd_set_error("unknown font-render-mode '%s'", optarg);
4599 if (strcmp(optarg, "normal") == 0)
4600 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4601 else if (strcmp(optarg, "mono") == 0)
4602 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4604 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4609 /* not supported curently */
4612 strncpy(im->watermark, optarg, 100);
4613 im->watermark[99] = '\0';
4617 if (im->daemon_addr != NULL)
4619 rrd_set_error ("You cannot specify --daemon "
4624 im->daemon_addr = strdup(optarg);
4625 if (im->daemon_addr == NULL)
4627 rrd_set_error("strdup failed");
4635 rrd_set_error("unknown option '%c'", optopt);
4637 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4642 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4643 pango_layout_context_changed(im->layout);
4647 if (im->logarithmic && im->minval <= 0) {
4649 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4653 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4654 /* error string is set in rrd_parsetime.c */
4658 if (start_tmp < 3600 * 24 * 365 * 10) {
4660 ("the first entry to fetch should be after 1980 (%ld)",
4665 if (end_tmp < start_tmp) {
4667 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4671 im->start = start_tmp;
4673 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4676 int rrd_graph_color(
4684 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4686 color = strstr(var, "#");
4687 if (color == NULL) {
4688 if (optional == 0) {
4689 rrd_set_error("Found no color in %s", err);
4696 long unsigned int col;
4698 rest = strstr(color, ":");
4705 sscanf(color, "#%6lx%n", &col, &n);
4706 col = (col << 8) + 0xff /* shift left by 8 */ ;
4708 rrd_set_error("Color problem in %s", err);
4711 sscanf(color, "#%8lx%n", &col, &n);
4715 rrd_set_error("Color problem in %s", err);
4717 if (rrd_test_error())
4719 gdp->col = gfx_hex_to_col(col);
4732 while (*ptr != '\0')
4733 if (*ptr++ == '%') {
4735 /* line cannot end with percent char */
4738 /* '%s', '%S' and '%%' are allowed */
4739 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4741 /* %c is allowed (but use only with vdef!) */
4742 else if (*ptr == 'c') {
4747 /* or else '% 6.2lf' and such are allowed */
4749 /* optional padding character */
4750 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4752 /* This should take care of 'm.n' with all three optional */
4753 while (*ptr >= '0' && *ptr <= '9')
4757 while (*ptr >= '0' && *ptr <= '9')
4759 /* Either 'le', 'lf' or 'lg' must follow here */
4762 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4777 const char *const str)
4779 /* A VDEF currently is either "func" or "param,func"
4780 * so the parsing is rather simple. Change if needed.
4787 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4788 if (n == (int) strlen(str)) { /* matched */
4792 sscanf(str, "%29[A-Z]%n", func, &n);
4793 if (n == (int) strlen(str)) { /* matched */
4797 ("Unknown function string '%s' in VDEF '%s'",
4802 if (!strcmp("PERCENT", func))
4803 gdes->vf.op = VDEF_PERCENT;
4804 else if (!strcmp("PERCENTNAN", func))
4805 gdes->vf.op = VDEF_PERCENTNAN;
4806 else if (!strcmp("MAXIMUM", func))
4807 gdes->vf.op = VDEF_MAXIMUM;
4808 else if (!strcmp("AVERAGE", func))
4809 gdes->vf.op = VDEF_AVERAGE;
4810 else if (!strcmp("STDEV", func))
4811 gdes->vf.op = VDEF_STDEV;
4812 else if (!strcmp("MINIMUM", func))
4813 gdes->vf.op = VDEF_MINIMUM;
4814 else if (!strcmp("TOTAL", func))
4815 gdes->vf.op = VDEF_TOTAL;
4816 else if (!strcmp("FIRST", func))
4817 gdes->vf.op = VDEF_FIRST;
4818 else if (!strcmp("LAST", func))
4819 gdes->vf.op = VDEF_LAST;
4820 else if (!strcmp("LSLSLOPE", func))
4821 gdes->vf.op = VDEF_LSLSLOPE;
4822 else if (!strcmp("LSLINT", func))
4823 gdes->vf.op = VDEF_LSLINT;
4824 else if (!strcmp("LSLCORREL", func))
4825 gdes->vf.op = VDEF_LSLCORREL;
4828 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4831 switch (gdes->vf.op) {
4833 case VDEF_PERCENTNAN:
4834 if (isnan(param)) { /* no parameter given */
4836 ("Function '%s' needs parameter in VDEF '%s'\n",
4840 if (param >= 0.0 && param <= 100.0) {
4841 gdes->vf.param = param;
4842 gdes->vf.val = DNAN; /* undefined */
4843 gdes->vf.when = 0; /* undefined */
4846 ("Parameter '%f' out of range in VDEF '%s'\n",
4847 param, gdes->vname);
4860 case VDEF_LSLCORREL:
4862 gdes->vf.param = DNAN;
4863 gdes->vf.val = DNAN;
4867 ("Function '%s' needs no parameter in VDEF '%s'\n",
4881 graph_desc_t *src, *dst;
4885 dst = &im->gdes[gdi];
4886 src = &im->gdes[dst->vidx];
4887 data = src->data + src->ds;
4889 steps = (src->end - src->start) / src->step;
4892 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4893 src->start, src->end, steps);
4895 switch (dst->vf.op) {
4899 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4900 rrd_set_error("malloc VDEV_PERCENT");
4903 for (step = 0; step < steps; step++) {
4904 array[step] = data[step * src->ds_cnt];
4906 qsort(array, step, sizeof(double), vdef_percent_compar);
4907 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4908 dst->vf.val = array[field];
4909 dst->vf.when = 0; /* no time component */
4912 for (step = 0; step < steps; step++)
4913 printf("DEBUG: %3li:%10.2f %c\n",
4914 step, array[step], step == field ? '*' : ' ');
4918 case VDEF_PERCENTNAN:{
4921 /* count number of "valid" values */
4923 for (step = 0; step < steps; step++) {
4924 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4926 /* and allocate it */
4927 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4928 rrd_set_error("malloc VDEV_PERCENT");
4931 /* and fill it in */
4933 for (step = 0; step < steps; step++) {
4934 if (!isnan(data[step * src->ds_cnt])) {
4935 array[field] = data[step * src->ds_cnt];
4939 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4940 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4941 dst->vf.val = array[field];
4942 dst->vf.when = 0; /* no time component */
4948 while (step != steps && isnan(data[step * src->ds_cnt]))
4950 if (step == steps) {
4954 dst->vf.val = data[step * src->ds_cnt];
4955 dst->vf.when = src->start + (step + 1) * src->step;
4957 while (step != steps) {
4958 if (finite(data[step * src->ds_cnt])) {
4959 if (data[step * src->ds_cnt] > dst->vf.val) {
4960 dst->vf.val = data[step * src->ds_cnt];
4961 dst->vf.when = src->start + (step + 1) * src->step;
4972 double average = 0.0;
4974 for (step = 0; step < steps; step++) {
4975 if (finite(data[step * src->ds_cnt])) {
4976 sum += data[step * src->ds_cnt];
4981 if (dst->vf.op == VDEF_TOTAL) {
4982 dst->vf.val = sum * src->step;
4983 dst->vf.when = 0; /* no time component */
4984 } else if (dst->vf.op == VDEF_AVERAGE) {
4985 dst->vf.val = sum / cnt;
4986 dst->vf.when = 0; /* no time component */
4988 average = sum / cnt;
4990 for (step = 0; step < steps; step++) {
4991 if (finite(data[step * src->ds_cnt])) {
4992 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4995 dst->vf.val = pow(sum / cnt, 0.5);
4996 dst->vf.when = 0; /* no time component */
5006 while (step != steps && isnan(data[step * src->ds_cnt]))
5008 if (step == steps) {
5012 dst->vf.val = data[step * src->ds_cnt];
5013 dst->vf.when = src->start + (step + 1) * src->step;
5015 while (step != steps) {
5016 if (finite(data[step * src->ds_cnt])) {
5017 if (data[step * src->ds_cnt] < dst->vf.val) {
5018 dst->vf.val = data[step * src->ds_cnt];
5019 dst->vf.when = src->start + (step + 1) * src->step;
5026 /* The time value returned here is one step before the
5027 * actual time value. This is the start of the first
5031 while (step != steps && isnan(data[step * src->ds_cnt]))
5033 if (step == steps) { /* all entries were NaN */
5037 dst->vf.val = data[step * src->ds_cnt];
5038 dst->vf.when = src->start + step * src->step;
5042 /* The time value returned here is the
5043 * actual time value. This is the end of the last
5047 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5049 if (step < 0) { /* all entries were NaN */
5053 dst->vf.val = data[step * src->ds_cnt];
5054 dst->vf.when = src->start + (step + 1) * src->step;
5059 case VDEF_LSLCORREL:{
5060 /* Bestfit line by linear least squares method */
5063 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5070 for (step = 0; step < steps; step++) {
5071 if (finite(data[step * src->ds_cnt])) {
5074 SUMxx += step * step;
5075 SUMxy += step * data[step * src->ds_cnt];
5076 SUMy += data[step * src->ds_cnt];
5077 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5081 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5082 y_intercept = (SUMy - slope * SUMx) / cnt;
5085 (SUMx * SUMy) / cnt) /
5087 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5089 if (dst->vf.op == VDEF_LSLSLOPE) {
5090 dst->vf.val = slope;
5092 } else if (dst->vf.op == VDEF_LSLINT) {
5093 dst->vf.val = y_intercept;
5095 } else if (dst->vf.op == VDEF_LSLCORREL) {
5096 dst->vf.val = correl;
5109 /* NaN < -INF < finite_values < INF */
5110 int vdef_percent_compar(
5116 /* Equality is not returned; this doesn't hurt except
5117 * (maybe) for a little performance.
5120 /* First catch NaN values. They are smallest */
5121 if (isnan(*(double *) a))
5123 if (isnan(*(double *) b))
5125 /* NaN doesn't reach this part so INF and -INF are extremes.
5126 * The sign from isinf() is compatible with the sign we return
5128 if (isinf(*(double *) a))
5129 return isinf(*(double *) a);
5130 if (isinf(*(double *) b))
5131 return isinf(*(double *) b);
5132 /* If we reach this, both values must be finite */
5133 if (*(double *) a < *(double *) b)
5142 rrd_info_type_t type,
5143 rrd_infoval_t value)
5145 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5146 if (im->grinfo == NULL) {
5147 im->grinfo = im->grinfo_current;