1 /****************************************************************************
2 * RRDtool 1.3.2 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
20 #include "plbasename.h"
23 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
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 */
850 * - a connection to the daemon has been established
851 * - this is the first occurrence of that RRD file
853 if (rrdc_is_connected(im->daemon_addr))
858 for (ii = 0; ii < i; ii++)
860 if (strcmp (im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
869 status = rrdc_flush (im->gdes[i].rrd);
872 rrd_set_error ("rrdc_flush (%s) failed with status %i.",
873 im->gdes[i].rrd, status);
877 } /* if (rrdc_is_connected()) */
879 if ((rrd_fetch_fn(im->gdes[i].rrd,
885 &im->gdes[i].ds_namv,
886 &im->gdes[i].data)) == -1) {
889 im->gdes[i].data_first = 1;
891 if (ft_step < im->gdes[i].step) {
892 reduce_data(im->gdes[i].cf_reduce,
897 &im->gdes[i].ds_cnt, &im->gdes[i].data);
899 im->gdes[i].step = ft_step;
903 /* lets see if the required data source is really there */
904 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
905 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
909 if (im->gdes[i].ds == -1) {
910 rrd_set_error("No DS called '%s' in '%s'",
911 im->gdes[i].ds_nam, im->gdes[i].rrd);
919 /* evaluate the expressions in the CDEF functions */
921 /*************************************************************
923 *************************************************************/
925 long find_var_wrapper(
929 return find_var((image_desc_t *) arg1, key);
932 /* find gdes containing var*/
939 for (ii = 0; ii < im->gdes_c - 1; ii++) {
940 if ((im->gdes[ii].gf == GF_DEF
941 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
942 && (strcmp(im->gdes[ii].vname, key) == 0)) {
949 /* find the greatest common divisor for all the numbers
950 in the 0 terminated num array */
957 for (i = 0; num[i + 1] != 0; i++) {
959 rest = num[i] % num[i + 1];
965 /* return i==0?num[i]:num[i-1]; */
969 /* run the rpn calculator on all the VDEF and CDEF arguments */
976 long *steparray, rpi;
981 rpnstack_init(&rpnstack);
983 for (gdi = 0; gdi < im->gdes_c; gdi++) {
984 /* Look for GF_VDEF and GF_CDEF in the same loop,
985 * so CDEFs can use VDEFs and vice versa
987 switch (im->gdes[gdi].gf) {
991 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
993 /* remove current shift */
994 vdp->start -= vdp->shift;
995 vdp->end -= vdp->shift;
998 if (im->gdes[gdi].shidx >= 0)
999 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1002 vdp->shift = im->gdes[gdi].shval;
1004 /* normalize shift to multiple of consolidated step */
1005 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1008 vdp->start += vdp->shift;
1009 vdp->end += vdp->shift;
1013 /* A VDEF has no DS. This also signals other parts
1014 * of rrdtool that this is a VDEF value, not a CDEF.
1016 im->gdes[gdi].ds_cnt = 0;
1017 if (vdef_calc(im, gdi)) {
1018 rrd_set_error("Error processing VDEF '%s'",
1019 im->gdes[gdi].vname);
1020 rpnstack_free(&rpnstack);
1025 im->gdes[gdi].ds_cnt = 1;
1026 im->gdes[gdi].ds = 0;
1027 im->gdes[gdi].data_first = 1;
1028 im->gdes[gdi].start = 0;
1029 im->gdes[gdi].end = 0;
1034 /* Find the variables in the expression.
1035 * - VDEF variables are substituted by their values
1036 * and the opcode is changed into OP_NUMBER.
1037 * - CDEF variables are analized for their step size,
1038 * the lowest common denominator of all the step
1039 * sizes of the data sources involved is calculated
1040 * and the resulting number is the step size for the
1041 * resulting data source.
1043 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1044 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1045 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1046 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1048 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1051 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1052 im->gdes[gdi].vname, im->gdes[ptr].vname);
1053 printf("DEBUG: value from vdef is %f\n",
1054 im->gdes[ptr].vf.val);
1056 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1057 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1058 } else { /* normal variables and PREF(variables) */
1060 /* add one entry to the array that keeps track of the step sizes of the
1061 * data sources going into the CDEF. */
1063 (long*)rrd_realloc(steparray,
1065 1) * sizeof(*steparray))) == NULL) {
1066 rrd_set_error("realloc steparray");
1067 rpnstack_free(&rpnstack);
1071 steparray[stepcnt - 1] = im->gdes[ptr].step;
1073 /* adjust start and end of cdef (gdi) so
1074 * that it runs from the latest start point
1075 * to the earliest endpoint of any of the
1076 * rras involved (ptr)
1079 if (im->gdes[gdi].start < im->gdes[ptr].start)
1080 im->gdes[gdi].start = im->gdes[ptr].start;
1082 if (im->gdes[gdi].end == 0 ||
1083 im->gdes[gdi].end > im->gdes[ptr].end)
1084 im->gdes[gdi].end = im->gdes[ptr].end;
1086 /* store pointer to the first element of
1087 * the rra providing data for variable,
1088 * further save step size and data source
1091 im->gdes[gdi].rpnp[rpi].data =
1092 im->gdes[ptr].data + im->gdes[ptr].ds;
1093 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1094 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1096 /* backoff the *.data ptr; this is done so
1097 * rpncalc() function doesn't have to treat
1098 * the first case differently
1100 } /* if ds_cnt != 0 */
1101 } /* if OP_VARIABLE */
1102 } /* loop through all rpi */
1104 /* move the data pointers to the correct period */
1105 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1106 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1107 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1108 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1110 im->gdes[gdi].start - im->gdes[ptr].start;
1113 im->gdes[gdi].rpnp[rpi].data +=
1114 (diff / im->gdes[ptr].step) *
1115 im->gdes[ptr].ds_cnt;
1119 if (steparray == NULL) {
1120 rrd_set_error("rpn expressions without DEF"
1121 " or CDEF variables are not supported");
1122 rpnstack_free(&rpnstack);
1125 steparray[stepcnt] = 0;
1126 /* Now find the resulting step. All steps in all
1127 * used RRAs have to be visited
1129 im->gdes[gdi].step = lcd(steparray);
1131 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1132 im->gdes[gdi].start)
1133 / im->gdes[gdi].step)
1134 * sizeof(double))) == NULL) {
1135 rrd_set_error("malloc im->gdes[gdi].data");
1136 rpnstack_free(&rpnstack);
1140 /* Step through the new cdef results array and
1141 * calculate the values
1143 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1144 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1145 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1147 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1148 * in this case we are advancing by timesteps;
1149 * we use the fact that time_t is a synonym for long
1151 if (rpn_calc(rpnp, &rpnstack, (long) now,
1152 im->gdes[gdi].data, ++dataidx) == -1) {
1153 /* rpn_calc sets the error string */
1154 rpnstack_free(&rpnstack);
1157 } /* enumerate over time steps within a CDEF */
1162 } /* enumerate over CDEFs */
1163 rpnstack_free(&rpnstack);
1167 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1168 /* yes we are loosing precision by doing tos with floats instead of doubles
1169 but it seems more stable this way. */
1171 static int AlmostEqual2sComplement(
1177 int aInt = *(int *) &A;
1178 int bInt = *(int *) &B;
1181 /* Make sure maxUlps is non-negative and small enough that the
1182 default NAN won't compare as equal to anything. */
1184 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1186 /* Make aInt lexicographically ordered as a twos-complement int */
1189 aInt = 0x80000000l - aInt;
1191 /* Make bInt lexicographically ordered as a twos-complement int */
1194 bInt = 0x80000000l - bInt;
1196 intDiff = abs(aInt - bInt);
1198 if (intDiff <= maxUlps)
1204 /* massage data so, that we get one value for each x coordinate in the graph */
1209 double pixstep = (double) (im->end - im->start)
1210 / (double) im->xsize; /* how much time
1211 passes in one pixel */
1213 double minval = DNAN, maxval = DNAN;
1215 unsigned long gr_time;
1217 /* memory for the processed data */
1218 for (i = 0; i < im->gdes_c; i++) {
1219 if ((im->gdes[i].gf == GF_LINE) ||
1220 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1221 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1222 * sizeof(rrd_value_t))) == NULL) {
1223 rrd_set_error("malloc data_proc");
1229 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1232 gr_time = im->start + pixstep * i; /* time of the current step */
1235 for (ii = 0; ii < im->gdes_c; ii++) {
1238 switch (im->gdes[ii].gf) {
1242 if (!im->gdes[ii].stack)
1244 value = im->gdes[ii].yrule;
1245 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1246 /* The time of the data doesn't necessarily match
1247 ** the time of the graph. Beware.
1249 vidx = im->gdes[ii].vidx;
1250 if (im->gdes[vidx].gf == GF_VDEF) {
1251 value = im->gdes[vidx].vf.val;
1253 if (((long int) gr_time >=
1254 (long int) im->gdes[vidx].start)
1255 && ((long int) gr_time <=
1256 (long int) im->gdes[vidx].end)) {
1257 value = im->gdes[vidx].data[(unsigned long)
1263 im->gdes[vidx].step)
1264 * im->gdes[vidx].ds_cnt +
1271 if (!isnan(value)) {
1273 im->gdes[ii].p_data[i] = paintval;
1274 /* GF_TICK: the data values are not
1275 ** relevant for min and max
1277 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1278 if ((isnan(minval) || paintval < minval) &&
1279 !(im->logarithmic && paintval <= 0.0))
1281 if (isnan(maxval) || paintval > maxval)
1285 im->gdes[ii].p_data[i] = DNAN;
1290 ("STACK should already be turned into LINE or AREA here");
1299 /* if min or max have not been asigned a value this is because
1300 there was no data in the graph ... this is not good ...
1301 lets set these to dummy values then ... */
1303 if (im->logarithmic) {
1304 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1305 minval = 0.0; /* catching this right away below */
1308 /* in logarithm mode, where minval is smaller or equal
1309 to 0 make the beast just way smaller than maxval */
1311 minval = maxval / 10e8;
1314 if (isnan(minval) || isnan(maxval)) {
1320 /* adjust min and max values given by the user */
1321 /* for logscale we add something on top */
1322 if (isnan(im->minval)
1323 || ((!im->rigid) && im->minval > minval)
1325 if (im->logarithmic)
1326 im->minval = minval / 2.0;
1328 im->minval = minval;
1330 if (isnan(im->maxval)
1331 || (!im->rigid && im->maxval < maxval)
1333 if (im->logarithmic)
1334 im->maxval = maxval * 2.0;
1336 im->maxval = maxval;
1339 /* make sure min is smaller than max */
1340 if (im->minval > im->maxval) {
1342 im->minval = 0.99 * im->maxval;
1344 im->minval = 1.01 * im->maxval;
1347 /* make sure min and max are not equal */
1348 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1354 /* make sure min and max are not both zero */
1355 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1364 /* identify the point where the first gridline, label ... gets placed */
1366 time_t find_first_time(
1367 time_t start, /* what is the initial time */
1368 enum tmt_en baseint, /* what is the basic interval */
1369 long basestep /* how many if these do we jump a time */
1374 localtime_r(&start, &tm);
1378 tm. tm_sec -= tm.tm_sec % basestep;
1383 tm. tm_min -= tm.tm_min % basestep;
1389 tm. tm_hour -= tm.tm_hour % basestep;
1393 /* we do NOT look at the basestep for this ... */
1400 /* we do NOT look at the basestep for this ... */
1404 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1406 if (tm.tm_wday == 0)
1407 tm. tm_mday -= 7; /* we want the *previous* monday */
1415 tm. tm_mon -= tm.tm_mon % basestep;
1426 tm.tm_year + 1900) %basestep;
1432 /* identify the point where the next gridline, label ... gets placed */
1433 time_t find_next_time(
1434 time_t current, /* what is the initial time */
1435 enum tmt_en baseint, /* what is the basic interval */
1436 long basestep /* how many if these do we jump a time */
1442 localtime_r(¤t, &tm);
1447 tm. tm_sec += basestep;
1451 tm. tm_min += basestep;
1455 tm. tm_hour += basestep;
1459 tm. tm_mday += basestep;
1463 tm. tm_mday += 7 * basestep;
1467 tm. tm_mon += basestep;
1471 tm. tm_year += basestep;
1473 madetime = mktime(&tm);
1474 } while (madetime == -1); /* this is necessary to skip impssible times
1475 like the daylight saving time skips */
1481 /* calculate values required for PRINT and GPRINT functions */
1486 long i, ii, validsteps;
1489 int graphelement = 0;
1492 double magfact = -1;
1497 /* wow initializing tmvdef is quite a task :-) */
1498 time_t now = time(NULL);
1500 localtime_r(&now, &tmvdef);
1501 for (i = 0; i < im->gdes_c; i++) {
1502 vidx = im->gdes[i].vidx;
1503 switch (im->gdes[i].gf) {
1506 /* PRINT and GPRINT can now print VDEF generated values.
1507 * There's no need to do any calculations on them as these
1508 * calculations were already made.
1510 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1511 printval = im->gdes[vidx].vf.val;
1512 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1513 } else { /* need to calculate max,min,avg etcetera */
1514 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1515 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1518 for (ii = im->gdes[vidx].ds;
1519 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1520 if (!finite(im->gdes[vidx].data[ii]))
1522 if (isnan(printval)) {
1523 printval = im->gdes[vidx].data[ii];
1528 switch (im->gdes[i].cf) {
1532 case CF_DEVSEASONAL:
1536 printval += im->gdes[vidx].data[ii];
1539 printval = min(printval, im->gdes[vidx].data[ii]);
1543 printval = max(printval, im->gdes[vidx].data[ii]);
1546 printval = im->gdes[vidx].data[ii];
1549 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1550 if (validsteps > 1) {
1551 printval = (printval / validsteps);
1554 } /* prepare printval */
1556 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1557 /* Magfact is set to -1 upon entry to print_calc. If it
1558 * is still less than 0, then we need to run auto_scale.
1559 * Otherwise, put the value into the correct units. If
1560 * the value is 0, then do not set the symbol or magnification
1561 * so next the calculation will be performed again. */
1562 if (magfact < 0.0) {
1563 auto_scale(im, &printval, &si_symb, &magfact);
1564 if (printval == 0.0)
1567 printval /= magfact;
1569 *(++percent_s) = 's';
1570 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1571 auto_scale(im, &printval, &si_symb, &magfact);
1574 if (im->gdes[i].gf == GF_PRINT) {
1575 rrd_infoval_t prline;
1577 if (im->gdes[i].strftm) {
1578 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1579 strftime(prline.u_str,
1580 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1581 } else if (bad_format(im->gdes[i].format)) {
1583 ("bad format for PRINT in '%s'", im->gdes[i].format);
1587 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1591 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1596 if (im->gdes[i].strftm) {
1597 strftime(im->gdes[i].legend,
1598 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1600 if (bad_format(im->gdes[i].format)) {
1602 ("bad format for GPRINT in '%s'",
1603 im->gdes[i].format);
1606 #ifdef HAVE_SNPRINTF
1607 snprintf(im->gdes[i].legend,
1609 im->gdes[i].format, printval, si_symb);
1611 sprintf(im->gdes[i].legend,
1612 im->gdes[i].format, printval, si_symb);
1624 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1625 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1630 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1631 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1640 #ifdef WITH_PIECHART
1648 ("STACK should already be turned into LINE or AREA here");
1653 return graphelement;
1658 /* place legends with color spots */
1664 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1665 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1666 int fill = 0, fill_last;
1667 double legendwidth; // = im->ximg - 2 * border;
1669 double leg_x = border;
1670 int leg_y = 0; //im->yimg;
1671 int leg_y_prev = 0; // im->yimg;
1674 int i, ii, mark = 0;
1675 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1678 char saved_legend[FMT_LEG_LEN + 5];
1684 legendwidth = im->legendwidth - 2 * border;
1688 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1689 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1690 rrd_set_error("malloc for legspace");
1694 for (i = 0; i < im->gdes_c; i++) {
1695 char prt_fctn; /*special printfunctions */
1697 strcpy(saved_legend, im->gdes[i].legend);
1701 /* hide legends for rules which are not displayed */
1702 if (im->gdes[i].gf == GF_TEXTALIGN) {
1703 default_txtalign = im->gdes[i].txtalign;
1706 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1707 if (im->gdes[i].gf == GF_HRULE
1708 && (im->gdes[i].yrule <
1709 im->minval || im->gdes[i].yrule > im->maxval))
1710 im->gdes[i].legend[0] = '\0';
1711 if (im->gdes[i].gf == GF_VRULE
1712 && (im->gdes[i].xrule <
1713 im->start || im->gdes[i].xrule > im->end))
1714 im->gdes[i].legend[0] = '\0';
1717 /* turn \\t into tab */
1718 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1719 memmove(tab, tab + 1, strlen(tab));
1723 leg_cc = strlen(im->gdes[i].legend);
1724 /* is there a controle code at the end of the legend string ? */
1725 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1726 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1728 im->gdes[i].legend[leg_cc] = '\0';
1732 /* only valid control codes */
1733 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1737 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1740 ("Unknown control code at the end of '%s\\%c'",
1741 im->gdes[i].legend, prt_fctn);
1745 if (prt_fctn == 'n') {
1749 /* remove exess space from the end of the legend for \g */
1750 while (prt_fctn == 'g' &&
1751 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1753 im->gdes[i].legend[leg_cc] = '\0';
1758 /* no interleg space if string ends in \g */
1759 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1761 fill += legspace[i];
1764 gfx_get_text_width(im,
1770 im->tabwidth, im->gdes[i].legend);
1775 /* who said there was a special tag ... ? */
1776 if (prt_fctn == 'g') {
1780 if (prt_fctn == '\0') {
1781 if(calc_width && (fill > legendwidth)){
1784 if (i == im->gdes_c - 1 || fill > legendwidth) {
1785 /* just one legend item is left right or center */
1786 switch (default_txtalign) {
1801 /* is it time to place the legends ? */
1802 if (fill > legendwidth) {
1810 if (leg_c == 1 && prt_fctn == 'j') {
1815 if (prt_fctn != '\0') {
1817 if (leg_c >= 2 && prt_fctn == 'j') {
1818 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1822 if (prt_fctn == 'c')
1823 leg_x = (double)(legendwidth - fill) / 2.0;
1824 if (prt_fctn == 'r')
1825 leg_x = legendwidth - fill - border;
1826 for (ii = mark; ii <= i; ii++) {
1827 if (im->gdes[ii].legend[0] == '\0')
1828 continue; /* skip empty legends */
1829 im->gdes[ii].leg_x = leg_x;
1830 im->gdes[ii].leg_y = leg_y + border;
1832 (double)gfx_get_text_width(im, leg_x,
1837 im->tabwidth, im->gdes[ii].legend)
1838 +(double)legspace[ii]
1842 if (leg_x > border || prt_fctn == 's')
1843 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1844 if (prt_fctn == 's')
1845 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1847 if(calc_width && (fill > legendwidth)){
1856 strcpy(im->gdes[i].legend, saved_legend);
1861 im->legendwidth = legendwidth + 2 * border;
1864 im->legendheight = leg_y + border * 0.6;
1871 /* create a grid on the graph. it determines what to do
1872 from the values of xsize, start and end */
1874 /* the xaxis labels are determined from the number of seconds per pixel
1875 in the requested graph */
1877 int calc_horizontal_grid(
1885 int decimals, fractionals;
1887 im->ygrid_scale.labfact = 2;
1888 range = im->maxval - im->minval;
1889 scaledrange = range / im->magfact;
1890 /* does the scale of this graph make it impossible to put lines
1891 on it? If so, give up. */
1892 if (isnan(scaledrange)) {
1896 /* find grid spaceing */
1898 if (isnan(im->ygridstep)) {
1899 if (im->extra_flags & ALTYGRID) {
1900 /* find the value with max number of digits. Get number of digits */
1903 (max(fabs(im->maxval), fabs(im->minval)) *
1904 im->viewfactor / im->magfact));
1905 if (decimals <= 0) /* everything is small. make place for zero */
1907 im->ygrid_scale.gridstep =
1909 floor(log10(range * im->viewfactor / im->magfact))) /
1910 im->viewfactor * im->magfact;
1911 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1912 im->ygrid_scale.gridstep = 0.1;
1913 /* should have at least 5 lines but no more then 15 */
1914 if (range / im->ygrid_scale.gridstep < 5
1915 && im->ygrid_scale.gridstep >= 30)
1916 im->ygrid_scale.gridstep /= 10;
1917 if (range / im->ygrid_scale.gridstep > 15)
1918 im->ygrid_scale.gridstep *= 10;
1919 if (range / im->ygrid_scale.gridstep > 5) {
1920 im->ygrid_scale.labfact = 1;
1921 if (range / im->ygrid_scale.gridstep > 8
1922 || im->ygrid_scale.gridstep <
1923 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1924 im->ygrid_scale.labfact = 2;
1926 im->ygrid_scale.gridstep /= 5;
1927 im->ygrid_scale.labfact = 5;
1931 (im->ygrid_scale.gridstep *
1932 (double) im->ygrid_scale.labfact * im->viewfactor /
1934 if (fractionals < 0) { /* small amplitude. */
1935 int len = decimals - fractionals + 1;
1937 if (im->unitslength < len + 2)
1938 im->unitslength = len + 2;
1939 sprintf(im->ygrid_scale.labfmt,
1941 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1943 int len = decimals + 1;
1945 if (im->unitslength < len + 2)
1946 im->unitslength = len + 2;
1947 sprintf(im->ygrid_scale.labfmt,
1948 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1950 } else { /* classic rrd grid */
1951 for (i = 0; ylab[i].grid > 0; i++) {
1952 pixel = im->ysize / (scaledrange / ylab[i].grid);
1958 for (i = 0; i < 4; i++) {
1959 if (pixel * ylab[gridind].lfac[i] >=
1960 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1961 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1966 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1969 im->ygrid_scale.gridstep = im->ygridstep;
1970 im->ygrid_scale.labfact = im->ylabfact;
1975 int draw_horizontal_grid(
1981 char graph_label[100];
1983 double X0 = im->xorigin;
1984 double X1 = im->xorigin + im->xsize;
1985 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1986 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1988 double second_axis_magfact = 0;
1989 char *second_axis_symb = "";
1992 im->ygrid_scale.gridstep /
1993 (double) im->magfact * (double) im->viewfactor;
1994 MaxY = scaledstep * (double) egrid;
1995 for (i = sgrid; i <= egrid; i++) {
1997 im->ygrid_scale.gridstep * i);
1999 im->ygrid_scale.gridstep * (i + 1));
2001 if (floor(Y0 + 0.5) >=
2002 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2003 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2004 with the chosen settings. Add a label if required by settings, or if
2005 there is only one label so far and the next grid line is out of bounds. */
2006 if (i % im->ygrid_scale.labfact == 0
2008 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2009 if (im->symbol == ' ') {
2010 if (im->extra_flags & ALTYGRID) {
2011 sprintf(graph_label,
2012 im->ygrid_scale.labfmt,
2013 scaledstep * (double) i);
2016 sprintf(graph_label, "%4.1f",
2017 scaledstep * (double) i);
2019 sprintf(graph_label, "%4.0f",
2020 scaledstep * (double) i);
2024 char sisym = (i == 0 ? ' ' : im->symbol);
2026 if (im->extra_flags & ALTYGRID) {
2027 sprintf(graph_label,
2028 im->ygrid_scale.labfmt,
2029 scaledstep * (double) i, sisym);
2032 sprintf(graph_label, "%4.1f %c",
2033 scaledstep * (double) i, sisym);
2035 sprintf(graph_label, "%4.0f %c",
2036 scaledstep * (double) i, sisym);
2041 if (im->second_axis_scale != 0){
2042 char graph_label_right[100];
2043 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2044 if (im->second_axis_format[0] == '\0'){
2045 if (!second_axis_magfact){
2046 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2047 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2049 sval /= second_axis_magfact;
2052 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2054 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2058 sprintf(graph_label_right,im->second_axis_format,sval);
2062 im->graph_col[GRC_FONT],
2063 im->text_prop[TEXT_PROP_AXIS].font_desc,
2064 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2065 graph_label_right );
2071 text_prop[TEXT_PROP_AXIS].
2073 im->graph_col[GRC_FONT],
2075 text_prop[TEXT_PROP_AXIS].
2078 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2079 gfx_line(im, X0 - 2, Y0, X0, Y0,
2080 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2081 gfx_line(im, X1, Y0, X1 + 2, Y0,
2082 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2083 gfx_dashed_line(im, X0 - 2, Y0,
2089 im->grid_dash_on, im->grid_dash_off);
2090 } else if (!(im->extra_flags & NOMINOR)) {
2093 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2094 gfx_line(im, X1, Y0, X1 + 2, Y0,
2095 GRIDWIDTH, im->graph_col[GRC_GRID]);
2096 gfx_dashed_line(im, X0 - 1, Y0,
2100 graph_col[GRC_GRID],
2101 im->grid_dash_on, im->grid_dash_off);
2108 /* this is frexp for base 10 */
2119 iexp = floor(log((double)fabs(x)) / log((double)10));
2120 mnt = x / pow(10.0, iexp);
2123 mnt = x / pow(10.0, iexp);
2130 /* logaritmic horizontal grid */
2131 int horizontal_log_grid(
2135 double yloglab[][10] = {
2137 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2139 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2141 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2158 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2160 int i, j, val_exp, min_exp;
2161 double nex; /* number of decades in data */
2162 double logscale; /* scale in logarithmic space */
2163 int exfrac = 1; /* decade spacing */
2164 int mid = -1; /* row in yloglab for major grid */
2165 double mspac; /* smallest major grid spacing (pixels) */
2166 int flab; /* first value in yloglab to use */
2167 double value, tmp, pre_value;
2169 char graph_label[100];
2171 nex = log10(im->maxval / im->minval);
2172 logscale = im->ysize / nex;
2173 /* major spacing for data with high dynamic range */
2174 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2181 /* major spacing for less dynamic data */
2183 /* search best row in yloglab */
2185 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2186 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2189 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2192 /* find first value in yloglab */
2194 yloglab[mid][flab] < 10
2195 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2196 if (yloglab[mid][flab] == 10.0) {
2201 if (val_exp % exfrac)
2202 val_exp += abs(-val_exp % exfrac);
2204 X1 = im->xorigin + im->xsize;
2209 value = yloglab[mid][flab] * pow(10.0, val_exp);
2210 if (AlmostEqual2sComplement(value, pre_value, 4))
2211 break; /* it seems we are not converging */
2213 Y0 = ytr(im, value);
2214 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2216 /* major grid line */
2218 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2219 gfx_line(im, X1, Y0, X1 + 2, Y0,
2220 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2221 gfx_dashed_line(im, X0 - 2, Y0,
2226 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2228 if (im->extra_flags & FORCE_UNITS_SI) {
2233 scale = floor(val_exp / 3.0);
2235 pvalue = pow(10.0, val_exp % 3);
2237 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2238 pvalue *= yloglab[mid][flab];
2239 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2240 && ((scale + si_symbcenter) >= 0))
2241 symbol = si_symbol[scale + si_symbcenter];
2244 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2246 sprintf(graph_label, "%3.0e", value);
2248 if (im->second_axis_scale != 0){
2249 char graph_label_right[100];
2250 double sval = value*im->second_axis_scale+im->second_axis_shift;
2251 if (im->second_axis_format[0] == '\0'){
2252 if (im->extra_flags & FORCE_UNITS_SI) {
2255 auto_scale(im,&sval,&symb,&mfac);
2256 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2259 sprintf(graph_label_right,"%3.0e", sval);
2263 sprintf(graph_label_right,im->second_axis_format,sval);
2268 im->graph_col[GRC_FONT],
2269 im->text_prop[TEXT_PROP_AXIS].font_desc,
2270 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2271 graph_label_right );
2277 text_prop[TEXT_PROP_AXIS].
2279 im->graph_col[GRC_FONT],
2281 text_prop[TEXT_PROP_AXIS].
2284 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2286 if (mid < 4 && exfrac == 1) {
2287 /* find first and last minor line behind current major line
2288 * i is the first line and j tha last */
2290 min_exp = val_exp - 1;
2291 for (i = 1; yloglab[mid][i] < 10.0; i++);
2292 i = yloglab[mid][i - 1] + 1;
2296 i = yloglab[mid][flab - 1] + 1;
2297 j = yloglab[mid][flab];
2300 /* draw minor lines below current major line */
2301 for (; i < j; i++) {
2303 value = i * pow(10.0, min_exp);
2304 if (value < im->minval)
2306 Y0 = ytr(im, value);
2307 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2312 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2313 gfx_line(im, X1, Y0, X1 + 2, Y0,
2314 GRIDWIDTH, im->graph_col[GRC_GRID]);
2315 gfx_dashed_line(im, X0 - 1, Y0,
2319 graph_col[GRC_GRID],
2320 im->grid_dash_on, im->grid_dash_off);
2322 } else if (exfrac > 1) {
2323 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2324 value = pow(10.0, i);
2325 if (value < im->minval)
2327 Y0 = ytr(im, value);
2328 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2333 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2334 gfx_line(im, X1, Y0, X1 + 2, Y0,
2335 GRIDWIDTH, im->graph_col[GRC_GRID]);
2336 gfx_dashed_line(im, X0 - 1, Y0,
2340 graph_col[GRC_GRID],
2341 im->grid_dash_on, im->grid_dash_off);
2346 if (yloglab[mid][++flab] == 10.0) {
2352 /* draw minor lines after highest major line */
2353 if (mid < 4 && exfrac == 1) {
2354 /* find first and last minor line below current major line
2355 * i is the first line and j tha last */
2357 min_exp = val_exp - 1;
2358 for (i = 1; yloglab[mid][i] < 10.0; i++);
2359 i = yloglab[mid][i - 1] + 1;
2363 i = yloglab[mid][flab - 1] + 1;
2364 j = yloglab[mid][flab];
2367 /* draw minor lines below current major line */
2368 for (; i < j; i++) {
2370 value = i * pow(10.0, min_exp);
2371 if (value < im->minval)
2373 Y0 = ytr(im, value);
2374 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2378 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2379 gfx_line(im, X1, Y0, X1 + 2, Y0,
2380 GRIDWIDTH, im->graph_col[GRC_GRID]);
2381 gfx_dashed_line(im, X0 - 1, Y0,
2385 graph_col[GRC_GRID],
2386 im->grid_dash_on, im->grid_dash_off);
2389 /* fancy minor gridlines */
2390 else if (exfrac > 1) {
2391 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2392 value = pow(10.0, i);
2393 if (value < im->minval)
2395 Y0 = ytr(im, value);
2396 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2400 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2401 gfx_line(im, X1, Y0, X1 + 2, Y0,
2402 GRIDWIDTH, im->graph_col[GRC_GRID]);
2403 gfx_dashed_line(im, X0 - 1, Y0,
2407 graph_col[GRC_GRID],
2408 im->grid_dash_on, im->grid_dash_off);
2419 int xlab_sel; /* which sort of label and grid ? */
2420 time_t ti, tilab, timajor;
2422 char graph_label[100];
2423 double X0, Y0, Y1; /* points for filled graph and more */
2426 /* the type of time grid is determined by finding
2427 the number of seconds per pixel in the graph */
2428 if (im->xlab_user.minsec == -1) {
2429 factor = (im->end - im->start) / im->xsize;
2431 while (xlab[xlab_sel + 1].minsec !=
2432 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2434 } /* pick the last one */
2435 while (xlab[xlab_sel - 1].minsec ==
2436 xlab[xlab_sel].minsec
2437 && xlab[xlab_sel].length > (im->end - im->start)) {
2439 } /* go back to the smallest size */
2440 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2441 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2442 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2443 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2444 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2445 im->xlab_user.labst = xlab[xlab_sel].labst;
2446 im->xlab_user.precis = xlab[xlab_sel].precis;
2447 im->xlab_user.stst = xlab[xlab_sel].stst;
2450 /* y coords are the same for every line ... */
2452 Y1 = im->yorigin - im->ysize;
2453 /* paint the minor grid */
2454 if (!(im->extra_flags & NOMINOR)) {
2455 for (ti = find_first_time(im->start,
2463 find_first_time(im->start,
2470 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2472 /* are we inside the graph ? */
2473 if (ti < im->start || ti > im->end)
2475 while (timajor < ti) {
2476 timajor = find_next_time(timajor,
2479 mgridtm, im->xlab_user.mgridst);
2482 continue; /* skip as falls on major grid line */
2484 gfx_line(im, X0, Y1 - 2, X0, Y1,
2485 GRIDWIDTH, im->graph_col[GRC_GRID]);
2486 gfx_line(im, X0, Y0, X0, Y0 + 2,
2487 GRIDWIDTH, im->graph_col[GRC_GRID]);
2488 gfx_dashed_line(im, X0, Y0 + 1, X0,
2491 graph_col[GRC_GRID],
2492 im->grid_dash_on, im->grid_dash_off);
2496 /* paint the major grid */
2497 for (ti = find_first_time(im->start,
2505 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2507 /* are we inside the graph ? */
2508 if (ti < im->start || ti > im->end)
2511 gfx_line(im, X0, Y1 - 2, X0, Y1,
2512 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2513 gfx_line(im, X0, Y0, X0, Y0 + 3,
2514 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2515 gfx_dashed_line(im, X0, Y0 + 3, X0,
2519 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2521 /* paint the labels below the graph */
2523 find_first_time(im->start -
2532 im->xlab_user.precis / 2;
2533 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2535 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2536 /* are we inside the graph ? */
2537 if (tilab < im->start || tilab > im->end)
2540 localtime_r(&tilab, &tm);
2541 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2543 # error "your libc has no strftime I guess we'll abort the exercise here."
2548 im->graph_col[GRC_FONT],
2550 text_prop[TEXT_PROP_AXIS].
2553 GFX_H_CENTER, GFX_V_TOP, graph_label);
2562 /* draw x and y axis */
2563 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2564 im->xorigin+im->xsize,im->yorigin-im->ysize,
2565 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2567 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2568 im->xorigin+im->xsize,im->yorigin-im->ysize,
2569 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2571 gfx_line(im, im->xorigin - 4,
2573 im->xorigin + im->xsize +
2574 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2575 gfx_line(im, im->xorigin,
2578 im->yorigin - im->ysize -
2579 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2580 /* arrow for X and Y axis direction */
2581 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 */
2582 im->graph_col[GRC_ARROW]);
2584 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 */
2585 im->graph_col[GRC_ARROW]);
2587 if (im->second_axis_scale != 0){
2588 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2589 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2590 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2592 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2593 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2594 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2595 im->graph_col[GRC_ARROW]);
2606 double X0, Y0; /* points for filled graph and more */
2607 struct gfx_color_t water_color;
2609 if (im->draw_3d_border > 0) {
2610 /* draw 3d border */
2611 i = im->draw_3d_border;
2612 gfx_new_area(im, 0, im->yimg,
2613 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2614 gfx_add_point(im, im->ximg - i, i);
2615 gfx_add_point(im, im->ximg, 0);
2616 gfx_add_point(im, 0, 0);
2618 gfx_new_area(im, i, im->yimg - i,
2620 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2621 gfx_add_point(im, im->ximg, 0);
2622 gfx_add_point(im, im->ximg, im->yimg);
2623 gfx_add_point(im, 0, im->yimg);
2626 if (im->draw_x_grid == 1)
2628 if (im->draw_y_grid == 1) {
2629 if (im->logarithmic) {
2630 res = horizontal_log_grid(im);
2632 res = draw_horizontal_grid(im);
2635 /* dont draw horizontal grid if there is no min and max val */
2637 char *nodata = "No Data found";
2639 gfx_text(im, im->ximg / 2,
2642 im->graph_col[GRC_FONT],
2644 text_prop[TEXT_PROP_AXIS].
2647 GFX_H_CENTER, GFX_V_CENTER, nodata);
2651 /* yaxis unit description */
2652 if (im->ylegend[0] != '\0'){
2654 im->xOriginLegendY+10,
2656 im->graph_col[GRC_FONT],
2658 text_prop[TEXT_PROP_UNIT].
2661 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2664 if (im->second_axis_legend[0] != '\0'){
2666 im->xOriginLegendY2+10,
2667 im->yOriginLegendY2,
2668 im->graph_col[GRC_FONT],
2669 im->text_prop[TEXT_PROP_UNIT].font_desc,
2671 RRDGRAPH_YLEGEND_ANGLE,
2672 GFX_H_CENTER, GFX_V_CENTER,
2673 im->second_axis_legend);
2678 im->xOriginTitle, im->yOriginTitle+6,
2679 im->graph_col[GRC_FONT],
2681 text_prop[TEXT_PROP_TITLE].
2683 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2684 /* rrdtool 'logo' */
2685 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2686 water_color = im->graph_col[GRC_FONT];
2687 water_color.alpha = 0.3;
2688 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2689 gfx_text(im, xpos, 5,
2692 text_prop[TEXT_PROP_WATERMARK].
2693 font_desc, im->tabwidth,
2694 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2696 /* graph watermark */
2697 if (im->watermark[0] != '\0') {
2698 water_color = im->graph_col[GRC_FONT];
2699 water_color.alpha = 0.3;
2701 im->ximg / 2, im->yimg - 6,
2704 text_prop[TEXT_PROP_WATERMARK].
2705 font_desc, im->tabwidth, 0,
2706 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2710 /* didn't look closely, nor think.. but did you mean ') && !(' below? */
2711 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2712 for (i = 0; i < im->gdes_c; i++) {
2713 if (im->gdes[i].legend[0] == '\0')
2715 /* im->gdes[i].leg_y is the bottom of the legend */
2716 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2717 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2718 gfx_text(im, X0, Y0,
2719 im->graph_col[GRC_FONT],
2722 [TEXT_PROP_LEGEND].font_desc,
2724 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2725 /* The legend for GRAPH items starts with "M " to have
2726 enough space for the box */
2727 if (im->gdes[i].gf != GF_PRINT &&
2728 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2732 boxH = gfx_get_text_width(im, 0,
2737 im->tabwidth, "o") * 1.2;
2739 /* shift the box up a bit */
2741 /* make sure transparent colors show up the same way as in the graph */
2744 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2745 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2747 gfx_new_area(im, X0, Y0 - boxV, X0,
2748 Y0, X0 + boxH, Y0, im->gdes[i].col);
2749 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2752 cairo_new_path(im->cr);
2753 cairo_set_line_width(im->cr, 1.0);
2756 gfx_line_fit(im, &X0, &Y0);
2757 gfx_line_fit(im, &X1, &Y1);
2758 cairo_move_to(im->cr, X0, Y0);
2759 cairo_line_to(im->cr, X1, Y0);
2760 cairo_line_to(im->cr, X1, Y1);
2761 cairo_line_to(im->cr, X0, Y1);
2762 cairo_close_path(im->cr);
2763 cairo_set_source_rgba(im->cr,
2775 blue, im->graph_col[GRC_FRAME].alpha);
2776 if (im->gdes[i].dash) {
2777 /* make box borders in legend dashed if the graph is dashed */
2781 cairo_set_dash(im->cr, dashes, 1, 0.0);
2783 cairo_stroke(im->cr);
2784 cairo_restore(im->cr);
2791 /*****************************************************
2792 * lazy check make sure we rely need to create this graph
2793 *****************************************************/
2800 struct stat imgstat;
2803 return 0; /* no lazy option */
2804 if (strlen(im->graphfile) == 0)
2805 return 0; /* inmemory option */
2806 if (stat(im->graphfile, &imgstat) != 0)
2807 return 0; /* can't stat */
2808 /* one pixel in the existing graph is more then what we would
2810 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2812 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2813 return 0; /* the file does not exist */
2814 switch (im->imgformat) {
2816 size = PngSize(fd, &(im->ximg), &(im->yimg));
2826 int graph_size_location(
2831 /* The actual size of the image to draw is determined from
2832 ** several sources. The size given on the command line is
2833 ** the graph area but we need more as we have to draw labels
2834 ** and other things outside the graph area. If the option
2835 ** --full-size-mode is selected the size defines the total
2836 ** image size and the size available for the graph is
2840 /** +---+-----------------------------------+
2841 ** | y |...............graph title.........|
2842 ** | +---+-------------------------------+
2846 ** | s | x | main graph area |
2851 ** | l | b +-------------------------------+
2852 ** | e | l | x axis labels |
2853 ** +---+---+-------------------------------+
2854 ** |....................legends............|
2855 ** +---------------------------------------+
2857 ** +---------------------------------------+
2860 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2861 0, Xylabel = 0, Xmain = 0, Ymain =
2862 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2864 // no legends and no the shall be plotted it's easy
2865 if (im->extra_flags & ONLY_GRAPH) {
2867 im->ximg = im->xsize;
2868 im->yimg = im->ysize;
2869 im->yorigin = im->ysize;
2874 if(im->watermark[0] != '\0') {
2875 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2878 // calculate the width of the left vertical legend
2879 if (im->ylegend[0] != '\0') {
2880 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2883 // calculate the width of the right vertical legend
2884 if (im->second_axis_legend[0] != '\0') {
2885 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2888 Xvertical2 = Xspacing;
2891 if (im->title[0] != '\0') {
2892 /* The title is placed "inbetween" two text lines so it
2893 ** automatically has some vertical spacing. The horizontal
2894 ** spacing is added here, on each side.
2896 /* if necessary, reduce the font size of the title until it fits the image width */
2897 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2900 // we have no title; get a little clearing from the top
2901 Ytitle = 1.5 * Yspacing;
2905 if (im->draw_x_grid) {
2906 // calculate the height of the horizontal labelling
2907 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2909 if (im->draw_y_grid || im->forceleftspace) {
2910 // calculate the width of the vertical labelling
2912 gfx_get_text_width(im, 0,
2913 im->text_prop[TEXT_PROP_AXIS].font_desc,
2914 im->tabwidth, "0") * im->unitslength;
2918 // add some space to the labelling
2919 Xylabel += Xspacing;
2921 /* If the legend is printed besides the graph the width has to be
2922 ** calculated first. Placing the legend north or south of the
2923 ** graph requires the width calculation first, so the legend is
2924 ** skipped for the moment.
2926 im->legendheight = 0;
2927 im->legendwidth = 0;
2928 if (!(im->extra_flags & NOLEGEND)) {
2929 if(im->legendposition == WEST || im->legendposition == EAST){
2930 if (leg_place(im, 1) == -1){
2936 if (im->extra_flags & FULL_SIZE_MODE) {
2938 /* The actual size of the image to draw has been determined by the user.
2939 ** The graph area is the space remaining after accounting for the legend,
2940 ** the watermark, the axis labels, and the title.
2942 im->ximg = im->xsize;
2943 im->yimg = im->ysize;
2947 /* Now calculate the total size. Insert some spacing where
2948 desired. im->xorigin and im->yorigin need to correspond
2949 with the lower left corner of the main graph area or, if
2950 this one is not set, the imaginary box surrounding the
2952 /* Initial size calculation for the main graph area */
2954 Xmain -= Xylabel;// + Xspacing;
2955 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2956 Xmain -= im->legendwidth;// + Xspacing;
2958 if (im->second_axis_scale != 0){
2961 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2965 Xmain -= Xvertical + Xvertical2;
2967 /* limit the remaining space to 0 */
2973 /* Putting the legend north or south, the height can now be calculated */
2974 if (!(im->extra_flags & NOLEGEND)) {
2975 if(im->legendposition == NORTH || im->legendposition == SOUTH){
2976 im->legendwidth = im->ximg;
2977 if (leg_place(im, 0) == -1){
2983 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
2984 Ymain -= Yxlabel + im->legendheight;
2990 /* reserve space for the title *or* some padding above the graph */
2993 /* reserve space for padding below the graph */
2994 if (im->extra_flags & NOLEGEND) {
2998 if (im->watermark[0] != '\0') {
2999 Ymain -= Ywatermark;
3001 /* limit the remaining height to 0 */
3006 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3008 /* The actual size of the image to draw is determined from
3009 ** several sources. The size given on the command line is
3010 ** the graph area but we need more as we have to draw labels
3011 ** and other things outside the graph area.
3015 Xmain = im->xsize; // + Xspacing;
3019 im->ximg = Xmain + Xylabel;
3020 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3021 im->ximg += Xspacing;
3024 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3025 im->ximg += im->legendwidth;// + Xspacing;
3027 if (im->second_axis_scale != 0){
3028 im->ximg += Xylabel;
3031 im->ximg += Xvertical + Xvertical2;
3033 if (!(im->extra_flags & NOLEGEND)) {
3034 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3035 im->legendwidth = im->ximg;
3036 if (leg_place(im, 0) == -1){
3042 im->yimg = Ymain + Yxlabel;
3043 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3044 im->yimg += im->legendheight;
3047 /* reserve space for the title *or* some padding above the graph */
3051 im->yimg += 1.5 * Yspacing;
3053 /* reserve space for padding below the graph */
3054 if (im->extra_flags & NOLEGEND) {
3055 im->yimg += Yspacing;
3058 if (im->watermark[0] != '\0') {
3059 im->yimg += Ywatermark;
3064 /* In case of putting the legend in west or east position the first
3065 ** legend calculation might lead to wrong positions if some items
3066 ** are not aligned on the left hand side (e.g. centered) as the
3067 ** legendwidth wight have been increased after the item was placed.
3068 ** In this case the positions have to be recalculated.
3070 if (!(im->extra_flags & NOLEGEND)) {
3071 if(im->legendposition == WEST || im->legendposition == EAST){
3072 if (leg_place(im, 0) == -1){
3078 /* After calculating all dimensions
3079 ** it is now possible to calculate
3082 switch(im->legendposition){
3084 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3085 im->yOriginTitle = 0;
3087 im->xOriginLegend = 0;
3088 im->yOriginLegend = Ytitle;
3090 im->xOriginLegendY = 0;
3091 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3093 im->xorigin = Xvertical + Xylabel;
3094 im->yorigin = Ytitle + im->legendheight + Ymain;
3096 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3097 if (im->second_axis_scale != 0){
3098 im->xOriginLegendY2 += Xylabel;
3100 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3105 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3106 im->yOriginTitle = 0;
3108 im->xOriginLegend = 0;
3109 im->yOriginLegend = Ytitle;
3111 im->xOriginLegendY = im->legendwidth;
3112 im->yOriginLegendY = Ytitle + (Ymain / 2);
3114 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3115 im->yorigin = Ytitle + Ymain;
3117 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3118 if (im->second_axis_scale != 0){
3119 im->xOriginLegendY2 += Xylabel;
3121 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3126 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3127 im->yOriginTitle = 0;
3129 im->xOriginLegend = 0;
3130 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3132 im->xOriginLegendY = 0;
3133 im->yOriginLegendY = Ytitle + (Ymain / 2);
3135 im->xorigin = Xvertical + Xylabel;
3136 im->yorigin = Ytitle + Ymain;
3138 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3139 if (im->second_axis_scale != 0){
3140 im->xOriginLegendY2 += Xylabel;
3142 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3147 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3148 im->yOriginTitle = 0;
3150 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3151 if (im->second_axis_scale != 0){
3152 im->xOriginLegend += Xylabel;
3154 im->yOriginLegend = Ytitle;
3156 im->xOriginLegendY = 0;
3157 im->yOriginLegendY = Ytitle + (Ymain / 2);
3159 im->xorigin = Xvertical + Xylabel;
3160 im->yorigin = Ytitle + Ymain;
3162 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3163 if (im->second_axis_scale != 0){
3164 im->xOriginLegendY2 += Xylabel;
3166 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3168 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3169 im->xOriginTitle += Xspacing;
3170 im->xOriginLegend += Xspacing;
3171 im->xOriginLegendY += Xspacing;
3172 im->xorigin += Xspacing;
3173 im->xOriginLegendY2 += Xspacing;
3183 static cairo_status_t cairo_output(
3187 unsigned int length)
3189 image_desc_t *im = (image_desc_t*)closure;
3191 im->rendered_image =
3192 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3193 if (im->rendered_image == NULL)
3194 return CAIRO_STATUS_WRITE_ERROR;
3195 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3196 im->rendered_image_size += length;
3197 return CAIRO_STATUS_SUCCESS;
3200 /* draw that picture thing ... */
3205 int lazy = lazy_check(im);
3206 double areazero = 0.0;
3207 graph_desc_t *lastgdes = NULL;
3210 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3212 /* pull the data from the rrd files ... */
3213 if (data_fetch(im) == -1)
3215 /* evaluate VDEF and CDEF operations ... */
3216 if (data_calc(im) == -1)
3218 /* calculate and PRINT and GPRINT definitions. We have to do it at
3219 * this point because it will affect the length of the legends
3220 * if there are no graph elements (i==0) we stop here ...
3221 * if we are lazy, try to quit ...
3227 /* if we want and can be lazy ... quit now */
3231 /**************************************************************
3232 *** Calculating sizes and locations became a bit confusing ***
3233 *** so I moved this into a separate function. ***
3234 **************************************************************/
3235 if (graph_size_location(im, i) == -1)
3238 info.u_cnt = im->xorigin;
3239 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3240 info.u_cnt = im->yorigin - im->ysize;
3241 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3242 info.u_cnt = im->xsize;
3243 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3244 info.u_cnt = im->ysize;
3245 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3246 info.u_cnt = im->ximg;
3247 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3248 info.u_cnt = im->yimg;
3249 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3250 info.u_cnt = im->start;
3251 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3252 info.u_cnt = im->end;
3253 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3255 /* if we want and can be lazy ... quit now */
3259 /* get actual drawing data and find min and max values */
3260 if (data_proc(im) == -1)
3262 if (!im->logarithmic) {
3266 /* identify si magnitude Kilo, Mega Giga ? */
3267 if (!im->rigid && !im->logarithmic)
3268 expand_range(im); /* make sure the upper and lower limit are
3271 info.u_val = im->minval;
3272 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3273 info.u_val = im->maxval;
3274 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3277 if (!calc_horizontal_grid(im))
3282 apply_gridfit(im); */
3283 /* the actual graph is created by going through the individual
3284 graph elements and then drawing them */
3285 cairo_surface_destroy(im->surface);
3286 switch (im->imgformat) {
3289 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3290 im->ximg * im->zoom,
3291 im->yimg * im->zoom);
3295 im->surface = strlen(im->graphfile)
3296 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3297 im->yimg * im->zoom)
3298 : cairo_pdf_surface_create_for_stream
3299 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3303 im->surface = strlen(im->graphfile)
3305 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3306 im->yimg * im->zoom)
3307 : cairo_ps_surface_create_for_stream
3308 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3312 im->surface = strlen(im->graphfile)
3314 cairo_svg_surface_create(im->
3316 im->ximg * im->zoom, im->yimg * im->zoom)
3317 : cairo_svg_surface_create_for_stream
3318 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3319 cairo_svg_surface_restrict_to_version
3320 (im->surface, CAIRO_SVG_VERSION_1_1);
3323 cairo_destroy(im->cr);
3324 im->cr = cairo_create(im->surface);
3325 cairo_set_antialias(im->cr, im->graph_antialias);
3326 cairo_scale(im->cr, im->zoom, im->zoom);
3327 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3328 gfx_new_area(im, 0, 0, 0, im->yimg,
3329 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3330 gfx_add_point(im, im->ximg, 0);
3332 gfx_new_area(im, im->xorigin,
3335 im->xsize, im->yorigin,
3338 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3339 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3341 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3342 im->xsize, im->ysize + 2.0);
3344 if (im->minval > 0.0)
3345 areazero = im->minval;
3346 if (im->maxval < 0.0)
3347 areazero = im->maxval;
3348 for (i = 0; i < im->gdes_c; i++) {
3349 switch (im->gdes[i].gf) {
3363 for (ii = 0; ii < im->xsize; ii++) {
3364 if (!isnan(im->gdes[i].p_data[ii])
3365 && im->gdes[i].p_data[ii] != 0.0) {
3366 if (im->gdes[i].yrule > 0) {
3373 im->ysize, 1.0, im->gdes[i].col);
3374 } else if (im->gdes[i].yrule < 0) {
3377 im->yorigin - im->ysize - 1.0,
3379 im->yorigin - im->ysize -
3382 im->ysize, 1.0, im->gdes[i].col);
3389 /* fix data points at oo and -oo */
3390 for (ii = 0; ii < im->xsize; ii++) {
3391 if (isinf(im->gdes[i].p_data[ii])) {
3392 if (im->gdes[i].p_data[ii] > 0) {
3393 im->gdes[i].p_data[ii] = im->maxval;
3395 im->gdes[i].p_data[ii] = im->minval;
3401 /* *******************************************************
3406 -------|--t-1--t--------------------------------
3408 if we know the value at time t was a then
3409 we draw a square from t-1 to t with the value a.
3411 ********************************************************* */
3412 if (im->gdes[i].col.alpha != 0.0) {
3413 /* GF_LINE and friend */
3414 if (im->gdes[i].gf == GF_LINE) {
3415 double last_y = 0.0;
3419 cairo_new_path(im->cr);
3420 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3421 if (im->gdes[i].dash) {
3422 cairo_set_dash(im->cr,
3423 im->gdes[i].p_dashes,
3424 im->gdes[i].ndash, im->gdes[i].offset);
3427 for (ii = 1; ii < im->xsize; ii++) {
3428 if (isnan(im->gdes[i].p_data[ii])
3429 || (im->slopemode == 1
3430 && isnan(im->gdes[i].p_data[ii - 1]))) {
3435 last_y = ytr(im, im->gdes[i].p_data[ii]);
3436 if (im->slopemode == 0) {
3437 double x = ii - 1 + im->xorigin;
3440 gfx_line_fit(im, &x, &y);
3441 cairo_move_to(im->cr, x, y);
3442 x = ii + im->xorigin;
3444 gfx_line_fit(im, &x, &y);
3445 cairo_line_to(im->cr, x, y);
3447 double x = ii - 1 + im->xorigin;
3449 ytr(im, im->gdes[i].p_data[ii - 1]);
3450 gfx_line_fit(im, &x, &y);
3451 cairo_move_to(im->cr, x, y);
3452 x = ii + im->xorigin;
3454 gfx_line_fit(im, &x, &y);
3455 cairo_line_to(im->cr, x, y);
3459 double x1 = ii + im->xorigin;
3460 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3462 if (im->slopemode == 0
3463 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3464 double x = ii - 1 + im->xorigin;
3467 gfx_line_fit(im, &x, &y);
3468 cairo_line_to(im->cr, x, y);
3471 gfx_line_fit(im, &x1, &y1);
3472 cairo_line_to(im->cr, x1, y1);
3475 cairo_set_source_rgba(im->cr,
3481 col.blue, im->gdes[i].col.alpha);
3482 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3483 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3484 cairo_stroke(im->cr);
3485 cairo_restore(im->cr);
3489 (double *) malloc(sizeof(double) * im->xsize * 2);
3491 (double *) malloc(sizeof(double) * im->xsize * 2);
3493 (double *) malloc(sizeof(double) * im->xsize * 2);
3495 (double *) malloc(sizeof(double) * im->xsize * 2);
3498 for (ii = 0; ii <= im->xsize; ii++) {
3501 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3507 AlmostEqual2sComplement(foreY
3511 AlmostEqual2sComplement(foreY
3521 foreY[cntI], im->gdes[i].col);
3522 while (cntI < idxI) {
3527 AlmostEqual2sComplement(foreY
3531 AlmostEqual2sComplement(foreY
3538 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3540 gfx_add_point(im, backX[idxI], backY[idxI]);
3546 AlmostEqual2sComplement(backY
3550 AlmostEqual2sComplement(backY
3557 gfx_add_point(im, backX[idxI], backY[idxI]);
3567 if (ii == im->xsize)
3569 if (im->slopemode == 0 && ii == 0) {
3572 if (isnan(im->gdes[i].p_data[ii])) {
3576 ytop = ytr(im, im->gdes[i].p_data[ii]);
3577 if (lastgdes && im->gdes[i].stack) {
3578 ybase = ytr(im, lastgdes->p_data[ii]);
3580 ybase = ytr(im, areazero);
3582 if (ybase == ytop) {
3588 double extra = ytop;
3593 if (im->slopemode == 0) {
3594 backY[++idxI] = ybase - 0.2;
3595 backX[idxI] = ii + im->xorigin - 1;
3596 foreY[idxI] = ytop + 0.2;
3597 foreX[idxI] = ii + im->xorigin - 1;
3599 backY[++idxI] = ybase - 0.2;
3600 backX[idxI] = ii + im->xorigin;
3601 foreY[idxI] = ytop + 0.2;
3602 foreX[idxI] = ii + im->xorigin;
3604 /* close up any remaining area */
3609 } /* else GF_LINE */
3611 /* if color != 0x0 */
3612 /* make sure we do not run into trouble when stacking on NaN */
3613 for (ii = 0; ii < im->xsize; ii++) {
3614 if (isnan(im->gdes[i].p_data[ii])) {
3615 if (lastgdes && (im->gdes[i].stack)) {
3616 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3618 im->gdes[i].p_data[ii] = areazero;
3622 lastgdes = &(im->gdes[i]);
3626 ("STACK should already be turned into LINE or AREA here");
3631 cairo_reset_clip(im->cr);
3633 /* grid_paint also does the text */
3634 if (!(im->extra_flags & ONLY_GRAPH))
3636 if (!(im->extra_flags & ONLY_GRAPH))
3638 /* the RULES are the last thing to paint ... */
3639 for (i = 0; i < im->gdes_c; i++) {
3641 switch (im->gdes[i].gf) {
3643 if (im->gdes[i].yrule >= im->minval
3644 && im->gdes[i].yrule <= im->maxval) {
3646 if (im->gdes[i].dash) {
3647 cairo_set_dash(im->cr,
3648 im->gdes[i].p_dashes,
3649 im->gdes[i].ndash, im->gdes[i].offset);
3651 gfx_line(im, im->xorigin,
3652 ytr(im, im->gdes[i].yrule),
3653 im->xorigin + im->xsize,
3654 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3655 cairo_stroke(im->cr);
3656 cairo_restore(im->cr);
3660 if (im->gdes[i].xrule >= im->start
3661 && im->gdes[i].xrule <= im->end) {
3663 if (im->gdes[i].dash) {
3664 cairo_set_dash(im->cr,
3665 im->gdes[i].p_dashes,
3666 im->gdes[i].ndash, im->gdes[i].offset);
3669 xtr(im, im->gdes[i].xrule),
3670 im->yorigin, xtr(im,
3674 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3675 cairo_stroke(im->cr);
3676 cairo_restore(im->cr);
3685 switch (im->imgformat) {
3688 cairo_status_t status;
3690 status = strlen(im->graphfile) ?
3691 cairo_surface_write_to_png(im->surface, im->graphfile)
3692 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3695 if (status != CAIRO_STATUS_SUCCESS) {
3696 rrd_set_error("Could not save png to '%s'", im->graphfile);
3702 if (strlen(im->graphfile)) {
3703 cairo_show_page(im->cr);
3705 cairo_surface_finish(im->surface);
3714 /*****************************************************
3716 *****************************************************/
3723 if ((im->gdes = (graph_desc_t *)
3724 rrd_realloc(im->gdes, (im->gdes_c)
3725 * sizeof(graph_desc_t))) == NULL) {
3726 rrd_set_error("realloc graph_descs");
3731 im->gdes[im->gdes_c - 1].step = im->step;
3732 im->gdes[im->gdes_c - 1].step_orig = im->step;
3733 im->gdes[im->gdes_c - 1].stack = 0;
3734 im->gdes[im->gdes_c - 1].linewidth = 0;
3735 im->gdes[im->gdes_c - 1].debug = 0;
3736 im->gdes[im->gdes_c - 1].start = im->start;
3737 im->gdes[im->gdes_c - 1].start_orig = im->start;
3738 im->gdes[im->gdes_c - 1].end = im->end;
3739 im->gdes[im->gdes_c - 1].end_orig = im->end;
3740 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3741 im->gdes[im->gdes_c - 1].data = NULL;
3742 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3743 im->gdes[im->gdes_c - 1].data_first = 0;
3744 im->gdes[im->gdes_c - 1].p_data = NULL;
3745 im->gdes[im->gdes_c - 1].rpnp = NULL;
3746 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3747 im->gdes[im->gdes_c - 1].shift = 0.0;
3748 im->gdes[im->gdes_c - 1].dash = 0;
3749 im->gdes[im->gdes_c - 1].ndash = 0;
3750 im->gdes[im->gdes_c - 1].offset = 0;
3751 im->gdes[im->gdes_c - 1].col.red = 0.0;
3752 im->gdes[im->gdes_c - 1].col.green = 0.0;
3753 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3754 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3755 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3756 im->gdes[im->gdes_c - 1].format[0] = '\0';
3757 im->gdes[im->gdes_c - 1].strftm = 0;
3758 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3759 im->gdes[im->gdes_c - 1].ds = -1;
3760 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3761 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3762 im->gdes[im->gdes_c - 1].yrule = DNAN;
3763 im->gdes[im->gdes_c - 1].xrule = 0;
3767 /* copies input untill the first unescaped colon is found
3768 or until input ends. backslashes have to be escaped as well */
3770 const char *const input,
3776 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3777 if (input[inp] == '\\'
3778 && input[inp + 1] != '\0'
3779 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3780 output[outp++] = input[++inp];
3782 output[outp++] = input[inp];
3785 output[outp] = '\0';
3789 /* Now just a wrapper around rrd_graph_v */
3801 rrd_info_t *grinfo = NULL;
3804 grinfo = rrd_graph_v(argc, argv);
3810 if (strcmp(walker->key, "image_info") == 0) {
3813 (char**)rrd_realloc((*prdata),
3814 (prlines + 1) * sizeof(char *))) == NULL) {
3815 rrd_set_error("realloc prdata");
3818 /* imginfo goes to position 0 in the prdata array */
3819 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3820 + 2) * sizeof(char));
3821 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3822 (*prdata)[prlines] = NULL;
3824 /* skip anything else */
3825 walker = walker->next;
3833 if (strcmp(walker->key, "image_width") == 0) {
3834 *xsize = walker->value.u_cnt;
3835 } else if (strcmp(walker->key, "image_height") == 0) {
3836 *ysize = walker->value.u_cnt;
3837 } else if (strcmp(walker->key, "value_min") == 0) {
3838 *ymin = walker->value.u_val;
3839 } else if (strcmp(walker->key, "value_max") == 0) {
3840 *ymax = walker->value.u_val;
3841 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3844 (char**)rrd_realloc((*prdata),
3845 (prlines + 1) * sizeof(char *))) == NULL) {
3846 rrd_set_error("realloc prdata");
3849 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3850 + 2) * sizeof(char));
3851 (*prdata)[prlines] = NULL;
3852 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3853 } else if (strcmp(walker->key, "image") == 0) {
3854 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3855 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3856 rrd_set_error("writing image");
3860 /* skip anything else */
3861 walker = walker->next;
3863 rrd_info_free(grinfo);
3868 /* Some surgery done on this function, it became ridiculously big.
3870 ** - initializing now in rrd_graph_init()
3871 ** - options parsing now in rrd_graph_options()
3872 ** - script parsing now in rrd_graph_script()
3874 rrd_info_t *rrd_graph_v(
3880 rrd_graph_init(&im);
3881 /* a dummy surface so that we can measure text sizes for placements */
3883 rrd_graph_options(argc, argv, &im);
3884 if (rrd_test_error()) {
3885 rrd_info_free(im.grinfo);
3890 if (optind >= argc) {
3891 rrd_info_free(im.grinfo);
3893 rrd_set_error("missing filename");
3897 if (strlen(argv[optind]) >= MAXPATH) {
3898 rrd_set_error("filename (including path) too long");
3899 rrd_info_free(im.grinfo);
3904 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3905 im.graphfile[MAXPATH - 1] = '\0';
3907 if (strcmp(im.graphfile, "-") == 0) {
3908 im.graphfile[0] = '\0';
3911 rrd_graph_script(argc, argv, &im, 1);
3912 if (rrd_test_error()) {
3913 rrd_info_free(im.grinfo);
3918 /* Everything is now read and the actual work can start */
3920 if (graph_paint(&im) == -1) {
3921 rrd_info_free(im.grinfo);
3927 /* The image is generated and needs to be output.
3928 ** Also, if needed, print a line with information about the image.
3936 path = strdup(im.graphfile);
3937 filename = basename(path);
3939 sprintf_alloc(im.imginfo,
3942 im.ximg), (long) (im.zoom * im.yimg));
3943 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3947 if (im.rendered_image) {
3950 img.u_blo.size = im.rendered_image_size;
3951 img.u_blo.ptr = im.rendered_image;
3952 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3961 image_desc_t *im,int prop,char *font, double size ){
3963 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3964 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3965 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3968 im->text_prop[prop].size = size;
3970 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3971 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3975 void rrd_graph_init(
3980 char *deffont = getenv("RRD_DEFAULT_FONT");
3981 static PangoFontMap *fontmap = NULL;
3982 PangoContext *context;
3987 #ifdef HAVE_SETLOCALE
3988 setlocale(LC_TIME, "");
3989 #ifdef HAVE_MBSTOWCS
3990 setlocale(LC_CTYPE, "");
3994 im->daemon_addr = NULL;
3995 im->draw_x_grid = 1;
3996 im->draw_y_grid = 1;
3997 im->draw_3d_border = 2;
3998 im->extra_flags = 0;
3999 im->font_options = cairo_font_options_create();
4000 im->forceleftspace = 0;
4003 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4004 im->grid_dash_off = 1;
4005 im->grid_dash_on = 1;
4007 im->grinfo = (rrd_info_t *) NULL;
4008 im->grinfo_current = (rrd_info_t *) NULL;
4009 im->imgformat = IF_PNG;
4012 im->legenddirection = TOP_DOWN;
4013 im->legendheight = 0;
4014 im->legendposition = SOUTH;
4015 im->legendwidth = 0;
4016 im->logarithmic = 0;
4022 im->rendered_image_size = 0;
4023 im->rendered_image = NULL;
4027 im->tabwidth = 40.0;
4028 im->title[0] = '\0';
4029 im->unitsexponent = 9999;
4030 im->unitslength = 6;
4031 im->viewfactor = 1.0;
4032 im->watermark[0] = '\0';
4033 im->with_markup = 0;
4035 im->xlab_user.minsec = -1;
4037 im->xOriginLegend = 0;
4038 im->xOriginLegendY = 0;
4039 im->xOriginLegendY2 = 0;
4040 im->xOriginTitle = 0;
4042 im->ygridstep = DNAN;
4044 im->ylegend[0] = '\0';
4045 im->second_axis_scale = 0; /* 0 disables it */
4046 im->second_axis_shift = 0; /* no shift by default */
4047 im->second_axis_legend[0] = '\0';
4048 im->second_axis_format[0] = '\0';
4050 im->yOriginLegend = 0;
4051 im->yOriginLegendY = 0;
4052 im->yOriginLegendY2 = 0;
4053 im->yOriginTitle = 0;
4057 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4058 im->cr = cairo_create(im->surface);
4060 for (i = 0; i < DIM(text_prop); i++) {
4061 im->text_prop[i].size = -1;
4062 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4065 if (fontmap == NULL){
4066 fontmap = pango_cairo_font_map_get_default();
4069 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4071 pango_cairo_context_set_resolution(context, 100);
4073 pango_cairo_update_context(im->cr,context);
4075 im->layout = pango_layout_new(context);
4077 // im->layout = pango_cairo_create_layout(im->cr);
4080 cairo_font_options_set_hint_style
4081 (im->font_options, CAIRO_HINT_STYLE_FULL);
4082 cairo_font_options_set_hint_metrics
4083 (im->font_options, CAIRO_HINT_METRICS_ON);
4084 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4088 for (i = 0; i < DIM(graph_col); i++)
4089 im->graph_col[i] = graph_col[i];
4095 void rrd_graph_options(
4102 char *parsetime_error = NULL;
4103 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4104 time_t start_tmp = 0, end_tmp = 0;
4106 rrd_time_value_t start_tv, end_tv;
4107 long unsigned int color;
4108 char *old_locale = "";
4110 /* defines for long options without a short equivalent. should be bytes,
4111 and may not collide with (the ASCII value of) short options */
4112 #define LONGOPT_UNITS_SI 255
4115 struct option long_options[] = {
4116 { "start", required_argument, 0, 's'},
4117 { "end", required_argument, 0, 'e'},
4118 { "x-grid", required_argument, 0, 'x'},
4119 { "y-grid", required_argument, 0, 'y'},
4120 { "vertical-label", required_argument, 0, 'v'},
4121 { "width", required_argument, 0, 'w'},
4122 { "height", required_argument, 0, 'h'},
4123 { "full-size-mode", no_argument, 0, 'D'},
4124 { "interlaced", no_argument, 0, 'i'},
4125 { "upper-limit", required_argument, 0, 'u'},
4126 { "lower-limit", required_argument, 0, 'l'},
4127 { "rigid", no_argument, 0, 'r'},
4128 { "base", required_argument, 0, 'b'},
4129 { "logarithmic", no_argument, 0, 'o'},
4130 { "color", required_argument, 0, 'c'},
4131 { "border", required_argument, 0, 1007},
4132 { "font", required_argument, 0, 'n'},
4133 { "title", required_argument, 0, 't'},
4134 { "imginfo", required_argument, 0, 'f'},
4135 { "imgformat", required_argument, 0, 'a'},
4136 { "lazy", no_argument, 0, 'z'},
4137 { "zoom", required_argument, 0, 'm'},
4138 { "no-legend", no_argument, 0, 'g'},
4139 { "legend-position", required_argument, 0, 1005},
4140 { "legend-direction", required_argument, 0, 1006},
4141 { "force-rules-legend", no_argument, 0, 'F'},
4142 { "only-graph", no_argument, 0, 'j'},
4143 { "alt-y-grid", no_argument, 0, 'Y'},
4144 {"disable-rrdtool-tag", no_argument, 0, 1001},
4145 {"right-axis", required_argument, 0, 1002},
4146 {"right-axis-label", required_argument, 0, 1003},
4147 {"right-axis-format", required_argument, 0, 1004},
4148 { "no-minor", no_argument, 0, 'I'},
4149 { "slope-mode", no_argument, 0, 'E'},
4150 { "alt-autoscale", no_argument, 0, 'A'},
4151 { "alt-autoscale-min", no_argument, 0, 'J'},
4152 { "alt-autoscale-max", no_argument, 0, 'M'},
4153 { "no-gridfit", no_argument, 0, 'N'},
4154 { "units-exponent", required_argument, 0, 'X'},
4155 { "units-length", required_argument, 0, 'L'},
4156 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4157 { "step", required_argument, 0, 'S'},
4158 { "tabwidth", required_argument, 0, 'T'},
4159 { "font-render-mode", required_argument, 0, 'R'},
4160 { "graph-render-mode", required_argument, 0, 'G'},
4161 { "font-smoothing-threshold", required_argument, 0, 'B'},
4162 { "watermark", required_argument, 0, 'W'},
4163 { "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 */
4164 { "pango-markup", no_argument, 0, 'P'},
4165 { "daemon", required_argument, 0, 'd'},
4171 opterr = 0; /* initialize getopt */
4172 rrd_parsetime("end-24h", &start_tv);
4173 rrd_parsetime("now", &end_tv);
4175 int option_index = 0;
4177 int col_start, col_end;
4179 opt = getopt_long(argc, argv,
4180 "s:e:x:y:v:w:h:D:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:kPd:",
4181 long_options, &option_index);
4186 im->extra_flags |= NOMINOR;
4189 im->extra_flags |= ALTYGRID;
4192 im->extra_flags |= ALTAUTOSCALE;
4195 im->extra_flags |= ALTAUTOSCALE_MIN;
4198 im->extra_flags |= ALTAUTOSCALE_MAX;
4201 im->extra_flags |= ONLY_GRAPH;
4204 im->extra_flags |= NOLEGEND;
4207 if (strcmp(optarg, "north") == 0) {
4208 im->legendposition = NORTH;
4209 } else if (strcmp(optarg, "west") == 0) {
4210 im->legendposition = WEST;
4211 } else if (strcmp(optarg, "south") == 0) {
4212 im->legendposition = SOUTH;
4213 } else if (strcmp(optarg, "east") == 0) {
4214 im->legendposition = EAST;
4216 rrd_set_error("unknown legend-position '%s'", optarg);
4221 if (strcmp(optarg, "topdown") == 0) {
4222 im->legenddirection = TOP_DOWN;
4223 } else if (strcmp(optarg, "bottomup") == 0) {
4224 im->legenddirection = BOTTOM_UP;
4226 rrd_set_error("unknown legend-position '%s'", optarg);
4231 im->extra_flags |= FORCE_RULES_LEGEND;
4234 im->extra_flags |= NO_RRDTOOL_TAG;
4236 case LONGOPT_UNITS_SI:
4237 if (im->extra_flags & FORCE_UNITS) {
4238 rrd_set_error("--units can only be used once!");
4239 setlocale(LC_NUMERIC, old_locale);
4242 if (strcmp(optarg, "si") == 0)
4243 im->extra_flags |= FORCE_UNITS_SI;
4245 rrd_set_error("invalid argument for --units: %s", optarg);
4250 im->unitsexponent = atoi(optarg);
4253 im->unitslength = atoi(optarg);
4254 im->forceleftspace = 1;
4257 old_locale = setlocale(LC_NUMERIC, "C");
4258 im->tabwidth = atof(optarg);
4259 setlocale(LC_NUMERIC, old_locale);
4262 old_locale = setlocale(LC_NUMERIC, "C");
4263 im->step = atoi(optarg);
4264 setlocale(LC_NUMERIC, old_locale);
4270 im->with_markup = 1;
4273 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4274 rrd_set_error("start time: %s", parsetime_error);
4279 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4280 rrd_set_error("end time: %s", parsetime_error);
4285 if (strcmp(optarg, "none") == 0) {
4286 im->draw_x_grid = 0;
4290 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4292 &im->xlab_user.gridst,
4294 &im->xlab_user.mgridst,
4296 &im->xlab_user.labst,
4297 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4298 strncpy(im->xlab_form, optarg + stroff,
4299 sizeof(im->xlab_form) - 1);
4300 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4302 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4303 rrd_set_error("unknown keyword %s", scan_gtm);
4306 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4308 rrd_set_error("unknown keyword %s", scan_mtm);
4311 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4312 rrd_set_error("unknown keyword %s", scan_ltm);
4315 im->xlab_user.minsec = 1;
4316 im->xlab_user.stst = im->xlab_form;
4318 rrd_set_error("invalid x-grid format");
4324 if (strcmp(optarg, "none") == 0) {
4325 im->draw_y_grid = 0;
4328 old_locale = setlocale(LC_NUMERIC, "C");
4329 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4330 setlocale(LC_NUMERIC, old_locale);
4331 if (im->ygridstep <= 0) {
4332 rrd_set_error("grid step must be > 0");
4334 } else if (im->ylabfact < 1) {
4335 rrd_set_error("label factor must be > 0");
4339 setlocale(LC_NUMERIC, old_locale);
4340 rrd_set_error("invalid y-grid format");
4345 im->draw_3d_border = atoi(optarg);
4347 case 1002: /* right y axis */
4351 &im->second_axis_scale,
4352 &im->second_axis_shift) == 2) {
4353 if(im->second_axis_scale==0){
4354 rrd_set_error("the second_axis_scale must not be 0");
4358 rrd_set_error("invalid right-axis format expected scale:shift");
4363 strncpy(im->second_axis_legend,optarg,150);
4364 im->second_axis_legend[150]='\0';
4367 if (bad_format(optarg)){
4368 rrd_set_error("use either %le or %lf formats");
4371 strncpy(im->second_axis_format,optarg,150);
4372 im->second_axis_format[150]='\0';
4375 strncpy(im->ylegend, optarg, 150);
4376 im->ylegend[150] = '\0';
4379 old_locale = setlocale(LC_NUMERIC, "C");
4380 im->maxval = atof(optarg);
4381 setlocale(LC_NUMERIC, old_locale);
4384 old_locale = setlocale(LC_NUMERIC, "C");
4385 im->minval = atof(optarg);
4386 setlocale(LC_NUMERIC, old_locale);
4389 im->base = atol(optarg);
4390 if (im->base != 1024 && im->base != 1000) {
4392 ("the only sensible value for base apart from 1000 is 1024");
4397 long_tmp = atol(optarg);
4398 if (long_tmp < 10) {
4399 rrd_set_error("width below 10 pixels");
4402 im->xsize = long_tmp;
4405 long_tmp = atol(optarg);
4406 if (long_tmp < 10) {
4407 rrd_set_error("height below 10 pixels");
4410 im->ysize = long_tmp;
4413 im->extra_flags |= FULL_SIZE_MODE;
4416 /* interlaced png not supported at the moment */
4422 im->imginfo = optarg;
4426 (im->imgformat = if_conv(optarg)) == -1) {
4427 rrd_set_error("unsupported graphics format '%s'", optarg);
4438 im->logarithmic = 1;
4442 "%10[A-Z]#%n%8lx%n",
4443 col_nam, &col_start, &color, &col_end) == 2) {
4445 int col_len = col_end - col_start;
4450 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4458 (((color & 0xF000) *
4459 0x11000) | ((color & 0x0F00) *
4460 0x01100) | ((color &
4463 ((color & 0x000F) * 0x00011)
4467 color = (color << 8) + 0xff /* shift left by 8 */ ;
4472 rrd_set_error("the color format is #RRGGBB[AA]");
4475 if ((ci = grc_conv(col_nam)) != -1) {
4476 im->graph_col[ci] = gfx_hex_to_col(color);
4478 rrd_set_error("invalid color name '%s'", col_nam);
4482 rrd_set_error("invalid color def format");
4491 old_locale = setlocale(LC_NUMERIC, "C");
4492 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4493 int sindex, propidx;
4495 setlocale(LC_NUMERIC, old_locale);
4496 if ((sindex = text_prop_conv(prop)) != -1) {
4497 for (propidx = sindex;
4498 propidx < TEXT_PROP_LAST; propidx++) {
4500 rrd_set_font_desc(im,propidx,NULL,size);
4502 if ((int) strlen(optarg) > end+2) {
4503 if (optarg[end] == ':') {
4504 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4507 ("expected : after font size in '%s'",
4512 /* only run the for loop for DEFAULT (0) for
4513 all others, we break here. woodo programming */
4514 if (propidx == sindex && sindex != 0)
4518 rrd_set_error("invalid fonttag '%s'", prop);
4522 setlocale(LC_NUMERIC, old_locale);
4523 rrd_set_error("invalid text property format");
4529 old_locale = setlocale(LC_NUMERIC, "C");
4530 im->zoom = atof(optarg);
4531 setlocale(LC_NUMERIC, old_locale);
4532 if (im->zoom <= 0.0) {
4533 rrd_set_error("zoom factor must be > 0");
4538 strncpy(im->title, optarg, 150);
4539 im->title[150] = '\0';
4542 if (strcmp(optarg, "normal") == 0) {
4543 cairo_font_options_set_antialias
4544 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4545 cairo_font_options_set_hint_style
4546 (im->font_options, CAIRO_HINT_STYLE_FULL);
4547 } else if (strcmp(optarg, "light") == 0) {
4548 cairo_font_options_set_antialias
4549 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4550 cairo_font_options_set_hint_style
4551 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4552 } else if (strcmp(optarg, "mono") == 0) {
4553 cairo_font_options_set_antialias
4554 (im->font_options, CAIRO_ANTIALIAS_NONE);
4555 cairo_font_options_set_hint_style
4556 (im->font_options, CAIRO_HINT_STYLE_FULL);
4558 rrd_set_error("unknown font-render-mode '%s'", optarg);
4563 if (strcmp(optarg, "normal") == 0)
4564 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4565 else if (strcmp(optarg, "mono") == 0)
4566 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4568 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4573 /* not supported curently */
4576 strncpy(im->watermark, optarg, 100);
4577 im->watermark[99] = '\0';
4581 if (im->daemon_addr != NULL)
4583 rrd_set_error ("You cannot specify --daemon "
4588 im->daemon_addr = strdup(optarg);
4589 if (im->daemon_addr == NULL)
4591 rrd_set_error("strdup failed");
4599 rrd_set_error("unknown option '%c'", optopt);
4601 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4606 { /* try to connect to rrdcached */
4607 int status = rrdc_connect(im->daemon_addr);
4608 if (status != 0) return;
4611 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4612 pango_layout_context_changed(im->layout);
4616 if (im->logarithmic && im->minval <= 0) {
4618 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4622 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4623 /* error string is set in rrd_parsetime.c */
4627 if (start_tmp < 3600 * 24 * 365 * 10) {
4629 ("the first entry to fetch should be after 1980 (%ld)",
4634 if (end_tmp < start_tmp) {
4636 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4640 im->start = start_tmp;
4642 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4645 int rrd_graph_color(
4653 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4655 color = strstr(var, "#");
4656 if (color == NULL) {
4657 if (optional == 0) {
4658 rrd_set_error("Found no color in %s", err);
4665 long unsigned int col;
4667 rest = strstr(color, ":");
4674 sscanf(color, "#%6lx%n", &col, &n);
4675 col = (col << 8) + 0xff /* shift left by 8 */ ;
4677 rrd_set_error("Color problem in %s", err);
4680 sscanf(color, "#%8lx%n", &col, &n);
4684 rrd_set_error("Color problem in %s", err);
4686 if (rrd_test_error())
4688 gdp->col = gfx_hex_to_col(col);
4701 while (*ptr != '\0')
4702 if (*ptr++ == '%') {
4704 /* line cannot end with percent char */
4707 /* '%s', '%S' and '%%' are allowed */
4708 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4710 /* %c is allowed (but use only with vdef!) */
4711 else if (*ptr == 'c') {
4716 /* or else '% 6.2lf' and such are allowed */
4718 /* optional padding character */
4719 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4721 /* This should take care of 'm.n' with all three optional */
4722 while (*ptr >= '0' && *ptr <= '9')
4726 while (*ptr >= '0' && *ptr <= '9')
4728 /* Either 'le', 'lf' or 'lg' must follow here */
4731 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4746 const char *const str)
4748 /* A VDEF currently is either "func" or "param,func"
4749 * so the parsing is rather simple. Change if needed.
4757 old_locale = setlocale(LC_NUMERIC, "C");
4758 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4759 setlocale(LC_NUMERIC, old_locale);
4760 if (n == (int) strlen(str)) { /* matched */
4764 sscanf(str, "%29[A-Z]%n", func, &n);
4765 if (n == (int) strlen(str)) { /* matched */
4769 ("Unknown function string '%s' in VDEF '%s'",
4774 if (!strcmp("PERCENT", func))
4775 gdes->vf.op = VDEF_PERCENT;
4776 else if (!strcmp("PERCENTNAN", func))
4777 gdes->vf.op = VDEF_PERCENTNAN;
4778 else if (!strcmp("MAXIMUM", func))
4779 gdes->vf.op = VDEF_MAXIMUM;
4780 else if (!strcmp("AVERAGE", func))
4781 gdes->vf.op = VDEF_AVERAGE;
4782 else if (!strcmp("STDEV", func))
4783 gdes->vf.op = VDEF_STDEV;
4784 else if (!strcmp("MINIMUM", func))
4785 gdes->vf.op = VDEF_MINIMUM;
4786 else if (!strcmp("TOTAL", func))
4787 gdes->vf.op = VDEF_TOTAL;
4788 else if (!strcmp("FIRST", func))
4789 gdes->vf.op = VDEF_FIRST;
4790 else if (!strcmp("LAST", func))
4791 gdes->vf.op = VDEF_LAST;
4792 else if (!strcmp("LSLSLOPE", func))
4793 gdes->vf.op = VDEF_LSLSLOPE;
4794 else if (!strcmp("LSLINT", func))
4795 gdes->vf.op = VDEF_LSLINT;
4796 else if (!strcmp("LSLCORREL", func))
4797 gdes->vf.op = VDEF_LSLCORREL;
4800 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4803 switch (gdes->vf.op) {
4805 case VDEF_PERCENTNAN:
4806 if (isnan(param)) { /* no parameter given */
4808 ("Function '%s' needs parameter in VDEF '%s'\n",
4812 if (param >= 0.0 && param <= 100.0) {
4813 gdes->vf.param = param;
4814 gdes->vf.val = DNAN; /* undefined */
4815 gdes->vf.when = 0; /* undefined */
4818 ("Parameter '%f' out of range in VDEF '%s'\n",
4819 param, gdes->vname);
4832 case VDEF_LSLCORREL:
4834 gdes->vf.param = DNAN;
4835 gdes->vf.val = DNAN;
4839 ("Function '%s' needs no parameter in VDEF '%s'\n",
4853 graph_desc_t *src, *dst;
4857 dst = &im->gdes[gdi];
4858 src = &im->gdes[dst->vidx];
4859 data = src->data + src->ds;
4861 steps = (src->end - src->start) / src->step;
4864 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4865 src->start, src->end, steps);
4867 switch (dst->vf.op) {
4871 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4872 rrd_set_error("malloc VDEV_PERCENT");
4875 for (step = 0; step < steps; step++) {
4876 array[step] = data[step * src->ds_cnt];
4878 qsort(array, step, sizeof(double), vdef_percent_compar);
4879 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4880 dst->vf.val = array[field];
4881 dst->vf.when = 0; /* no time component */
4884 for (step = 0; step < steps; step++)
4885 printf("DEBUG: %3li:%10.2f %c\n",
4886 step, array[step], step == field ? '*' : ' ');
4890 case VDEF_PERCENTNAN:{
4893 /* count number of "valid" values */
4895 for (step = 0; step < steps; step++) {
4896 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4898 /* and allocate it */
4899 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4900 rrd_set_error("malloc VDEV_PERCENT");
4903 /* and fill it in */
4905 for (step = 0; step < steps; step++) {
4906 if (!isnan(data[step * src->ds_cnt])) {
4907 array[field] = data[step * src->ds_cnt];
4911 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4912 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4913 dst->vf.val = array[field];
4914 dst->vf.when = 0; /* no time component */
4920 while (step != steps && isnan(data[step * src->ds_cnt]))
4922 if (step == steps) {
4926 dst->vf.val = data[step * src->ds_cnt];
4927 dst->vf.when = src->start + (step + 1) * src->step;
4929 while (step != steps) {
4930 if (finite(data[step * src->ds_cnt])) {
4931 if (data[step * src->ds_cnt] > dst->vf.val) {
4932 dst->vf.val = data[step * src->ds_cnt];
4933 dst->vf.when = src->start + (step + 1) * src->step;
4944 double average = 0.0;
4946 for (step = 0; step < steps; step++) {
4947 if (finite(data[step * src->ds_cnt])) {
4948 sum += data[step * src->ds_cnt];
4953 if (dst->vf.op == VDEF_TOTAL) {
4954 dst->vf.val = sum * src->step;
4955 dst->vf.when = 0; /* no time component */
4956 } else if (dst->vf.op == VDEF_AVERAGE) {
4957 dst->vf.val = sum / cnt;
4958 dst->vf.when = 0; /* no time component */
4960 average = sum / cnt;
4962 for (step = 0; step < steps; step++) {
4963 if (finite(data[step * src->ds_cnt])) {
4964 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4967 dst->vf.val = pow(sum / cnt, 0.5);
4968 dst->vf.when = 0; /* no time component */
4978 while (step != steps && isnan(data[step * src->ds_cnt]))
4980 if (step == steps) {
4984 dst->vf.val = data[step * src->ds_cnt];
4985 dst->vf.when = src->start + (step + 1) * src->step;
4987 while (step != steps) {
4988 if (finite(data[step * src->ds_cnt])) {
4989 if (data[step * src->ds_cnt] < dst->vf.val) {
4990 dst->vf.val = data[step * src->ds_cnt];
4991 dst->vf.when = src->start + (step + 1) * src->step;
4998 /* The time value returned here is one step before the
4999 * actual time value. This is the start of the first
5003 while (step != steps && isnan(data[step * src->ds_cnt]))
5005 if (step == steps) { /* all entries were NaN */
5009 dst->vf.val = data[step * src->ds_cnt];
5010 dst->vf.when = src->start + step * src->step;
5014 /* The time value returned here is the
5015 * actual time value. This is the end of the last
5019 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5021 if (step < 0) { /* all entries were NaN */
5025 dst->vf.val = data[step * src->ds_cnt];
5026 dst->vf.when = src->start + (step + 1) * src->step;
5031 case VDEF_LSLCORREL:{
5032 /* Bestfit line by linear least squares method */
5035 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5042 for (step = 0; step < steps; step++) {
5043 if (finite(data[step * src->ds_cnt])) {
5046 SUMxx += step * step;
5047 SUMxy += step * data[step * src->ds_cnt];
5048 SUMy += data[step * src->ds_cnt];
5049 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5053 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5054 y_intercept = (SUMy - slope * SUMx) / cnt;
5057 (SUMx * SUMy) / cnt) /
5059 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5061 if (dst->vf.op == VDEF_LSLSLOPE) {
5062 dst->vf.val = slope;
5064 } else if (dst->vf.op == VDEF_LSLINT) {
5065 dst->vf.val = y_intercept;
5067 } else if (dst->vf.op == VDEF_LSLCORREL) {
5068 dst->vf.val = correl;
5081 /* NaN < -INF < finite_values < INF */
5082 int vdef_percent_compar(
5088 /* Equality is not returned; this doesn't hurt except
5089 * (maybe) for a little performance.
5092 /* First catch NaN values. They are smallest */
5093 if (isnan(*(double *) a))
5095 if (isnan(*(double *) b))
5097 /* NaN doesn't reach this part so INF and -INF are extremes.
5098 * The sign from isinf() is compatible with the sign we return
5100 if (isinf(*(double *) a))
5101 return isinf(*(double *) a);
5102 if (isinf(*(double *) b))
5103 return isinf(*(double *) b);
5104 /* If we reach this, both values must be finite */
5105 if (*(double *) a < *(double *) b)
5114 rrd_info_type_t type,
5115 rrd_infoval_t value)
5117 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5118 if (im->grinfo == NULL) {
5119 im->grinfo = im->grinfo_current;