1 /****************************************************************************
2 * RRDtool 1.4.2 Copyright by Tobi Oetiker, 1997-2009
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
20 #include "plbasename.h"
23 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
32 #ifdef HAVE_LANGINFO_H
36 #include "rrd_graph.h"
37 #include "rrd_client.h"
39 /* some constant definitions */
43 #ifndef RRD_DEFAULT_FONT
44 /* there is special code later to pick Cour.ttf when running on windows */
45 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
48 text_prop_t text_prop[] = {
49 {8.0, RRD_DEFAULT_FONT,NULL}
51 {9.0, RRD_DEFAULT_FONT,NULL}
53 {7.0, RRD_DEFAULT_FONT,NULL}
55 {8.0, RRD_DEFAULT_FONT,NULL}
57 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
59 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
63 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
65 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
67 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
69 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
71 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
73 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
75 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
77 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
79 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
81 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
82 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
84 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
86 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
88 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
90 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
92 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
95 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
98 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
101 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
103 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
104 365 * 24 * 3600, "%y"}
106 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
109 /* sensible y label intervals ...*/
133 {20.0, {1, 5, 10, 20}
139 {100.0, {1, 2, 5, 10}
142 {200.0, {1, 5, 10, 20}
145 {500.0, {1, 2, 4, 10}
153 gfx_color_t graph_col[] = /* default colors */
155 {1.00, 1.00, 1.00, 1.00}, /* canvas */
156 {0.95, 0.95, 0.95, 1.00}, /* background */
157 {0.81, 0.81, 0.81, 1.00}, /* shade A */
158 {0.62, 0.62, 0.62, 1.00}, /* shade B */
159 {0.56, 0.56, 0.56, 0.75}, /* grid */
160 {0.87, 0.31, 0.31, 0.60}, /* major grid */
161 {0.00, 0.00, 0.00, 1.00}, /* font */
162 {0.50, 0.12, 0.12, 1.00}, /* arrow */
163 {0.12, 0.12, 0.12, 1.00}, /* axis */
164 {0.00, 0.00, 0.00, 1.00} /* frame */
171 # define DPRINT(x) (void)(printf x, printf("\n"))
177 /* initialize with xtr(im,0); */
185 pixie = (double) im->xsize / (double) (im->end - im->start);
188 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
191 /* translate data values into y coordinates */
200 if (!im->logarithmic)
201 pixie = (double) im->ysize / (im->maxval - im->minval);
204 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
206 } else if (!im->logarithmic) {
207 yval = im->yorigin - pixie * (value - im->minval);
209 if (value < im->minval) {
212 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
220 /* conversion function for symbolic entry names */
223 #define conv_if(VV,VVV) \
224 if (strcmp(#VV, string) == 0) return VVV ;
230 conv_if(PRINT, GF_PRINT);
231 conv_if(GPRINT, GF_GPRINT);
232 conv_if(COMMENT, GF_COMMENT);
233 conv_if(HRULE, GF_HRULE);
234 conv_if(VRULE, GF_VRULE);
235 conv_if(LINE, GF_LINE);
236 conv_if(AREA, GF_AREA);
237 conv_if(STACK, GF_STACK);
238 conv_if(TICK, GF_TICK);
239 conv_if(TEXTALIGN, GF_TEXTALIGN);
240 conv_if(DEF, GF_DEF);
241 conv_if(CDEF, GF_CDEF);
242 conv_if(VDEF, GF_VDEF);
243 conv_if(XPORT, GF_XPORT);
244 conv_if(SHIFT, GF_SHIFT);
246 return (enum gf_en)(-1);
249 enum gfx_if_en if_conv(
253 conv_if(PNG, IF_PNG);
254 conv_if(SVG, IF_SVG);
255 conv_if(EPS, IF_EPS);
256 conv_if(PDF, IF_PDF);
258 return (enum gfx_if_en)(-1);
261 enum tmt_en tmt_conv(
265 conv_if(SECOND, TMT_SECOND);
266 conv_if(MINUTE, TMT_MINUTE);
267 conv_if(HOUR, TMT_HOUR);
268 conv_if(DAY, TMT_DAY);
269 conv_if(WEEK, TMT_WEEK);
270 conv_if(MONTH, TMT_MONTH);
271 conv_if(YEAR, TMT_YEAR);
272 return (enum tmt_en)(-1);
275 enum grc_en grc_conv(
279 conv_if(BACK, GRC_BACK);
280 conv_if(CANVAS, GRC_CANVAS);
281 conv_if(SHADEA, GRC_SHADEA);
282 conv_if(SHADEB, GRC_SHADEB);
283 conv_if(GRID, GRC_GRID);
284 conv_if(MGRID, GRC_MGRID);
285 conv_if(FONT, GRC_FONT);
286 conv_if(ARROW, GRC_ARROW);
287 conv_if(AXIS, GRC_AXIS);
288 conv_if(FRAME, GRC_FRAME);
290 return (enum grc_en)(-1);
293 enum text_prop_en text_prop_conv(
297 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
298 conv_if(TITLE, TEXT_PROP_TITLE);
299 conv_if(AXIS, TEXT_PROP_AXIS);
300 conv_if(UNIT, TEXT_PROP_UNIT);
301 conv_if(LEGEND, TEXT_PROP_LEGEND);
302 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
303 return (enum text_prop_en)(-1);
313 cairo_status_t status = (cairo_status_t) 0;
318 if (im->daemon_addr != NULL)
319 free(im->daemon_addr);
321 for (i = 0; i < (unsigned) im->gdes_c; i++) {
322 if (im->gdes[i].data_first) {
323 /* careful here, because a single pointer can occur several times */
324 free(im->gdes[i].data);
325 if (im->gdes[i].ds_namv) {
326 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
327 free(im->gdes[i].ds_namv[ii]);
328 free(im->gdes[i].ds_namv);
331 /* free allocated memory used for dashed lines */
332 if (im->gdes[i].p_dashes != NULL)
333 free(im->gdes[i].p_dashes);
335 free(im->gdes[i].p_data);
336 free(im->gdes[i].rpnp);
339 if (im->font_options)
340 cairo_font_options_destroy(im->font_options);
343 status = cairo_status(im->cr);
344 cairo_destroy(im->cr);
346 if (im->rendered_image) {
347 free(im->rendered_image);
351 g_object_unref (im->layout);
355 cairo_surface_destroy(im->surface);
358 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
359 cairo_status_to_string(status));
364 /* find SI magnitude symbol for the given number*/
366 image_desc_t *im, /* image description */
372 char *symbol[] = { "a", /* 10e-18 Atto */
373 "f", /* 10e-15 Femto */
374 "p", /* 10e-12 Pico */
375 "n", /* 10e-9 Nano */
376 "u", /* 10e-6 Micro */
377 "m", /* 10e-3 Milli */
382 "T", /* 10e12 Tera */
383 "P", /* 10e15 Peta */
390 if (*value == 0.0 || isnan(*value)) {
394 sindex = floor(log(fabs(*value)) / log((double) im->base));
395 *magfact = pow((double) im->base, (double) sindex);
396 (*value) /= (*magfact);
398 if (sindex <= symbcenter && sindex >= -symbcenter) {
399 (*symb_ptr) = symbol[sindex + symbcenter];
406 static char si_symbol[] = {
407 'a', /* 10e-18 Atto */
408 'f', /* 10e-15 Femto */
409 'p', /* 10e-12 Pico */
410 'n', /* 10e-9 Nano */
411 'u', /* 10e-6 Micro */
412 'm', /* 10e-3 Milli */
417 'T', /* 10e12 Tera */
418 'P', /* 10e15 Peta */
421 static const int si_symbcenter = 6;
423 /* find SI magnitude symbol for the numbers on the y-axis*/
425 image_desc_t *im /* image description */
429 double digits, viewdigits = 0;
432 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
433 log((double) im->base));
435 if (im->unitsexponent != 9999) {
436 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
437 viewdigits = floor((double)(im->unitsexponent / 3));
442 im->magfact = pow((double) im->base, digits);
445 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
448 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
450 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
451 ((viewdigits + si_symbcenter) >= 0))
452 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
457 /* move min and max values around to become sensible */
462 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
463 600.0, 500.0, 400.0, 300.0, 250.0,
464 200.0, 125.0, 100.0, 90.0, 80.0,
465 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
466 25.0, 20.0, 10.0, 9.0, 8.0,
467 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
468 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
469 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
472 double scaled_min, scaled_max;
479 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
480 im->minval, im->maxval, im->magfact);
483 if (isnan(im->ygridstep)) {
484 if (im->extra_flags & ALTAUTOSCALE) {
485 /* measure the amplitude of the function. Make sure that
486 graph boundaries are slightly higher then max/min vals
487 so we can see amplitude on the graph */
490 delt = im->maxval - im->minval;
492 fact = 2.0 * pow(10.0,
494 (max(fabs(im->minval), fabs(im->maxval)) /
497 adj = (fact - delt) * 0.55;
500 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
501 im->minval, im->maxval, delt, fact, adj);
506 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
507 /* measure the amplitude of the function. Make sure that
508 graph boundaries are slightly lower than min vals
509 so we can see amplitude on the graph */
510 adj = (im->maxval - im->minval) * 0.1;
512 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
513 /* measure the amplitude of the function. Make sure that
514 graph boundaries are slightly higher than max vals
515 so we can see amplitude on the graph */
516 adj = (im->maxval - im->minval) * 0.1;
519 scaled_min = im->minval / im->magfact;
520 scaled_max = im->maxval / im->magfact;
522 for (i = 1; sensiblevalues[i] > 0; i++) {
523 if (sensiblevalues[i - 1] >= scaled_min &&
524 sensiblevalues[i] <= scaled_min)
525 im->minval = sensiblevalues[i] * (im->magfact);
527 if (-sensiblevalues[i - 1] <= scaled_min &&
528 -sensiblevalues[i] >= scaled_min)
529 im->minval = -sensiblevalues[i - 1] * (im->magfact);
531 if (sensiblevalues[i - 1] >= scaled_max &&
532 sensiblevalues[i] <= scaled_max)
533 im->maxval = sensiblevalues[i - 1] * (im->magfact);
535 if (-sensiblevalues[i - 1] <= scaled_max &&
536 -sensiblevalues[i] >= scaled_max)
537 im->maxval = -sensiblevalues[i] * (im->magfact);
541 /* adjust min and max to the grid definition if there is one */
542 im->minval = (double) im->ylabfact * im->ygridstep *
543 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
544 im->maxval = (double) im->ylabfact * im->ygridstep *
545 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
549 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
550 im->minval, im->maxval, im->magfact);
558 if (isnan(im->minval) || isnan(im->maxval))
561 if (im->logarithmic) {
562 double ya, yb, ypix, ypixfrac;
563 double log10_range = log10(im->maxval) - log10(im->minval);
565 ya = pow((double) 10, floor(log10(im->minval)));
566 while (ya < im->minval)
569 return; /* don't have y=10^x gridline */
571 if (yb <= im->maxval) {
572 /* we have at least 2 y=10^x gridlines.
573 Make sure distance between them in pixels
574 are an integer by expanding im->maxval */
575 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
576 double factor = y_pixel_delta / floor(y_pixel_delta);
577 double new_log10_range = factor * log10_range;
578 double new_ymax_log10 = log10(im->minval) + new_log10_range;
580 im->maxval = pow(10, new_ymax_log10);
581 ytr(im, DNAN); /* reset precalc */
582 log10_range = log10(im->maxval) - log10(im->minval);
584 /* make sure first y=10^x gridline is located on
585 integer pixel position by moving scale slightly
586 downwards (sub-pixel movement) */
587 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
588 ypixfrac = ypix - floor(ypix);
589 if (ypixfrac > 0 && ypixfrac < 1) {
590 double yfrac = ypixfrac / im->ysize;
592 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
593 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
594 ytr(im, DNAN); /* reset precalc */
597 /* Make sure we have an integer pixel distance between
598 each minor gridline */
599 double ypos1 = ytr(im, im->minval);
600 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
601 double y_pixel_delta = ypos1 - ypos2;
602 double factor = y_pixel_delta / floor(y_pixel_delta);
603 double new_range = factor * (im->maxval - im->minval);
604 double gridstep = im->ygrid_scale.gridstep;
605 double minor_y, minor_y_px, minor_y_px_frac;
607 if (im->maxval > 0.0)
608 im->maxval = im->minval + new_range;
610 im->minval = im->maxval - new_range;
611 ytr(im, DNAN); /* reset precalc */
612 /* make sure first minor gridline is on integer pixel y coord */
613 minor_y = gridstep * floor(im->minval / gridstep);
614 while (minor_y < im->minval)
616 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
617 minor_y_px_frac = minor_y_px - floor(minor_y_px);
618 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
619 double yfrac = minor_y_px_frac / im->ysize;
620 double range = im->maxval - im->minval;
622 im->minval = im->minval - yfrac * range;
623 im->maxval = im->maxval - yfrac * range;
624 ytr(im, DNAN); /* reset precalc */
626 calc_horizontal_grid(im); /* recalc with changed im->maxval */
630 /* reduce data reimplementation by Alex */
633 enum cf_en cf, /* which consolidation function ? */
634 unsigned long cur_step, /* step the data currently is in */
635 time_t *start, /* start, end and step as requested ... */
636 time_t *end, /* ... by the application will be ... */
637 unsigned long *step, /* ... adjusted to represent reality */
638 unsigned long *ds_cnt, /* number of data sources in file */
640 { /* two dimensional array containing the data */
641 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
642 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
644 rrd_value_t *srcptr, *dstptr;
646 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
649 row_cnt = ((*end) - (*start)) / cur_step;
655 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
656 row_cnt, reduce_factor, *start, *end, cur_step);
657 for (col = 0; col < row_cnt; col++) {
658 printf("time %10lu: ", *start + (col + 1) * cur_step);
659 for (i = 0; i < *ds_cnt; i++)
660 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
665 /* We have to combine [reduce_factor] rows of the source
666 ** into one row for the destination. Doing this we also
667 ** need to take care to combine the correct rows. First
668 ** alter the start and end time so that they are multiples
669 ** of the new step time. We cannot reduce the amount of
670 ** time so we have to move the end towards the future and
671 ** the start towards the past.
673 end_offset = (*end) % (*step);
674 start_offset = (*start) % (*step);
676 /* If there is a start offset (which cannot be more than
677 ** one destination row), skip the appropriate number of
678 ** source rows and one destination row. The appropriate
679 ** number is what we do know (start_offset/cur_step) of
680 ** the new interval (*step/cur_step aka reduce_factor).
683 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
684 printf("row_cnt before: %lu\n", row_cnt);
687 (*start) = (*start) - start_offset;
688 skiprows = reduce_factor - start_offset / cur_step;
689 srcptr += skiprows * *ds_cnt;
690 for (col = 0; col < (*ds_cnt); col++)
695 printf("row_cnt between: %lu\n", row_cnt);
698 /* At the end we have some rows that are not going to be
699 ** used, the amount is end_offset/cur_step
702 (*end) = (*end) - end_offset + (*step);
703 skiprows = end_offset / cur_step;
707 printf("row_cnt after: %lu\n", row_cnt);
710 /* Sanity check: row_cnt should be multiple of reduce_factor */
711 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
713 if (row_cnt % reduce_factor) {
714 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
715 row_cnt, reduce_factor);
716 printf("BUG in reduce_data()\n");
720 /* Now combine reduce_factor intervals at a time
721 ** into one interval for the destination.
724 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
725 for (col = 0; col < (*ds_cnt); col++) {
726 rrd_value_t newval = DNAN;
727 unsigned long validval = 0;
729 for (i = 0; i < reduce_factor; i++) {
730 if (isnan(srcptr[i * (*ds_cnt) + col])) {
735 newval = srcptr[i * (*ds_cnt) + col];
744 newval += srcptr[i * (*ds_cnt) + col];
747 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
750 /* an interval contains a failure if any subintervals contained a failure */
752 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
755 newval = srcptr[i * (*ds_cnt) + col];
781 srcptr += (*ds_cnt) * reduce_factor;
782 row_cnt -= reduce_factor;
784 /* If we had to alter the endtime, we didn't have enough
785 ** source rows to fill the last row. Fill it with NaN.
788 for (col = 0; col < (*ds_cnt); col++)
791 row_cnt = ((*end) - (*start)) / *step;
793 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
794 row_cnt, *start, *end, *step);
795 for (col = 0; col < row_cnt; col++) {
796 printf("time %10lu: ", *start + (col + 1) * (*step));
797 for (i = 0; i < *ds_cnt; i++)
798 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
805 /* get the data required for the graphs from the
814 /* pull the data from the rrd files ... */
815 for (i = 0; i < (int) im->gdes_c; i++) {
816 /* only GF_DEF elements fetch data */
817 if (im->gdes[i].gf != GF_DEF)
821 /* do we have it already ? */
822 for (ii = 0; ii < i; ii++) {
823 if (im->gdes[ii].gf != GF_DEF)
825 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
826 && (im->gdes[i].cf == im->gdes[ii].cf)
827 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
828 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
829 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
830 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
831 /* OK, the data is already there.
832 ** Just copy the header portion
834 im->gdes[i].start = im->gdes[ii].start;
835 im->gdes[i].end = im->gdes[ii].end;
836 im->gdes[i].step = im->gdes[ii].step;
837 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
838 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
839 im->gdes[i].data = im->gdes[ii].data;
840 im->gdes[i].data_first = 0;
847 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
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)) {
1363 static int find_first_weekday(void){
1364 static int first_weekday = -1;
1365 if (first_weekday == -1){
1366 #if defined(HAVE_NL_LANGINFO)
1367 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1368 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1369 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1370 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1371 else first_weekday = 1; /* we go for a monday default */
1376 return first_weekday;
1379 /* identify the point where the first gridline, label ... gets placed */
1381 time_t find_first_time(
1382 time_t start, /* what is the initial time */
1383 enum tmt_en baseint, /* what is the basic interval */
1384 long basestep /* how many if these do we jump a time */
1389 localtime_r(&start, &tm);
1393 tm. tm_sec -= tm.tm_sec % basestep;
1398 tm. tm_min -= tm.tm_min % basestep;
1404 tm. tm_hour -= tm.tm_hour % basestep;
1408 /* we do NOT look at the basestep for this ... */
1415 /* we do NOT look at the basestep for this ... */
1419 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1421 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1422 tm. tm_mday -= 7; /* we want the *previous* week */
1430 tm. tm_mon -= tm.tm_mon % basestep;
1441 tm.tm_year + 1900) %basestep;
1447 /* identify the point where the next gridline, label ... gets placed */
1448 time_t find_next_time(
1449 time_t current, /* what is the initial time */
1450 enum tmt_en baseint, /* what is the basic interval */
1451 long basestep /* how many if these do we jump a time */
1457 localtime_r(¤t, &tm);
1462 tm. tm_sec += basestep;
1466 tm. tm_min += basestep;
1470 tm. tm_hour += basestep;
1474 tm. tm_mday += basestep;
1478 tm. tm_mday += 7 * basestep;
1482 tm. tm_mon += basestep;
1486 tm. tm_year += basestep;
1488 madetime = mktime(&tm);
1489 } while (madetime == -1); /* this is necessary to skip impssible times
1490 like the daylight saving time skips */
1496 /* calculate values required for PRINT and GPRINT functions */
1501 long i, ii, validsteps;
1504 int graphelement = 0;
1507 double magfact = -1;
1512 /* wow initializing tmvdef is quite a task :-) */
1513 time_t now = time(NULL);
1515 localtime_r(&now, &tmvdef);
1516 for (i = 0; i < im->gdes_c; i++) {
1517 vidx = im->gdes[i].vidx;
1518 switch (im->gdes[i].gf) {
1521 /* PRINT and GPRINT can now print VDEF generated values.
1522 * There's no need to do any calculations on them as these
1523 * calculations were already made.
1525 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1526 printval = im->gdes[vidx].vf.val;
1527 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1528 } else { /* need to calculate max,min,avg etcetera */
1529 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1530 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1533 for (ii = im->gdes[vidx].ds;
1534 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1535 if (!finite(im->gdes[vidx].data[ii]))
1537 if (isnan(printval)) {
1538 printval = im->gdes[vidx].data[ii];
1543 switch (im->gdes[i].cf) {
1547 case CF_DEVSEASONAL:
1551 printval += im->gdes[vidx].data[ii];
1554 printval = min(printval, im->gdes[vidx].data[ii]);
1558 printval = max(printval, im->gdes[vidx].data[ii]);
1561 printval = im->gdes[vidx].data[ii];
1564 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1565 if (validsteps > 1) {
1566 printval = (printval / validsteps);
1569 } /* prepare printval */
1571 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1572 /* Magfact is set to -1 upon entry to print_calc. If it
1573 * is still less than 0, then we need to run auto_scale.
1574 * Otherwise, put the value into the correct units. If
1575 * the value is 0, then do not set the symbol or magnification
1576 * so next the calculation will be performed again. */
1577 if (magfact < 0.0) {
1578 auto_scale(im, &printval, &si_symb, &magfact);
1579 if (printval == 0.0)
1582 printval /= magfact;
1584 *(++percent_s) = 's';
1585 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1586 auto_scale(im, &printval, &si_symb, &magfact);
1589 if (im->gdes[i].gf == GF_PRINT) {
1590 rrd_infoval_t prline;
1592 if (im->gdes[i].strftm) {
1593 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1594 strftime(prline.u_str,
1595 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1596 } else if (bad_format(im->gdes[i].format)) {
1598 ("bad format for PRINT in '%s'", im->gdes[i].format);
1602 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1606 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1611 if (im->gdes[i].strftm) {
1612 strftime(im->gdes[i].legend,
1613 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1615 if (bad_format(im->gdes[i].format)) {
1617 ("bad format for GPRINT in '%s'",
1618 im->gdes[i].format);
1621 #ifdef HAVE_SNPRINTF
1622 snprintf(im->gdes[i].legend,
1624 im->gdes[i].format, printval, si_symb);
1626 sprintf(im->gdes[i].legend,
1627 im->gdes[i].format, printval, si_symb);
1639 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1640 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1645 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1646 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1655 #ifdef WITH_PIECHART
1663 ("STACK should already be turned into LINE or AREA here");
1668 return graphelement;
1673 /* place legends with color spots */
1679 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1680 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1681 int fill = 0, fill_last;
1682 double legendwidth; // = im->ximg - 2 * border;
1684 double leg_x = border;
1685 int leg_y = 0; //im->yimg;
1686 int leg_y_prev = 0; // im->yimg;
1689 int i, ii, mark = 0;
1690 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1693 char saved_legend[FMT_LEG_LEN + 5];
1699 legendwidth = im->legendwidth - 2 * border;
1703 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1704 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1705 rrd_set_error("malloc for legspace");
1709 for (i = 0; i < im->gdes_c; i++) {
1710 char prt_fctn; /*special printfunctions */
1712 strcpy(saved_legend, im->gdes[i].legend);
1716 /* hide legends for rules which are not displayed */
1717 if (im->gdes[i].gf == GF_TEXTALIGN) {
1718 default_txtalign = im->gdes[i].txtalign;
1721 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1722 if (im->gdes[i].gf == GF_HRULE
1723 && (im->gdes[i].yrule <
1724 im->minval || im->gdes[i].yrule > im->maxval))
1725 im->gdes[i].legend[0] = '\0';
1726 if (im->gdes[i].gf == GF_VRULE
1727 && (im->gdes[i].xrule <
1728 im->start || im->gdes[i].xrule > im->end))
1729 im->gdes[i].legend[0] = '\0';
1732 /* turn \\t into tab */
1733 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1734 memmove(tab, tab + 1, strlen(tab));
1738 leg_cc = strlen(im->gdes[i].legend);
1739 /* is there a controle code at the end of the legend string ? */
1740 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1741 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1743 im->gdes[i].legend[leg_cc] = '\0';
1747 /* only valid control codes */
1748 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1753 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1756 ("Unknown control code at the end of '%s\\%c'",
1757 im->gdes[i].legend, prt_fctn);
1761 if (prt_fctn == 'n') {
1765 /* remove exess space from the end of the legend for \g */
1766 while (prt_fctn == 'g' &&
1767 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1769 im->gdes[i].legend[leg_cc] = '\0';
1774 /* no interleg space if string ends in \g */
1775 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1777 fill += legspace[i];
1780 gfx_get_text_width(im,
1786 im->tabwidth, im->gdes[i].legend);
1791 /* who said there was a special tag ... ? */
1792 if (prt_fctn == 'g') {
1796 if (prt_fctn == '\0') {
1797 if(calc_width && (fill > legendwidth)){
1800 if (i == im->gdes_c - 1 || fill > legendwidth) {
1801 /* just one legend item is left right or center */
1802 switch (default_txtalign) {
1817 /* is it time to place the legends ? */
1818 if (fill > legendwidth) {
1826 if (leg_c == 1 && prt_fctn == 'j') {
1831 if (prt_fctn != '\0') {
1833 if (leg_c >= 2 && prt_fctn == 'j') {
1834 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1838 if (prt_fctn == 'c')
1839 leg_x = (double)(legendwidth - fill) / 2.0;
1840 if (prt_fctn == 'r')
1841 leg_x = legendwidth - fill + border;
1842 for (ii = mark; ii <= i; ii++) {
1843 if (im->gdes[ii].legend[0] == '\0')
1844 continue; /* skip empty legends */
1845 im->gdes[ii].leg_x = leg_x;
1846 im->gdes[ii].leg_y = leg_y + border;
1848 (double)gfx_get_text_width(im, leg_x,
1853 im->tabwidth, im->gdes[ii].legend)
1854 +(double)legspace[ii]
1858 if (leg_x > border || prt_fctn == 's')
1859 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1860 if (prt_fctn == 's')
1861 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1862 if (prt_fctn == 'u')
1863 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1865 if(calc_width && (fill > legendwidth)){
1874 strcpy(im->gdes[i].legend, saved_legend);
1879 im->legendwidth = legendwidth + 2 * border;
1882 im->legendheight = leg_y + border * 0.6;
1889 /* create a grid on the graph. it determines what to do
1890 from the values of xsize, start and end */
1892 /* the xaxis labels are determined from the number of seconds per pixel
1893 in the requested graph */
1895 int calc_horizontal_grid(
1903 int decimals, fractionals;
1905 im->ygrid_scale.labfact = 2;
1906 range = im->maxval - im->minval;
1907 scaledrange = range / im->magfact;
1908 /* does the scale of this graph make it impossible to put lines
1909 on it? If so, give up. */
1910 if (isnan(scaledrange)) {
1914 /* find grid spaceing */
1916 if (isnan(im->ygridstep)) {
1917 if (im->extra_flags & ALTYGRID) {
1918 /* find the value with max number of digits. Get number of digits */
1921 (max(fabs(im->maxval), fabs(im->minval)) *
1922 im->viewfactor / im->magfact));
1923 if (decimals <= 0) /* everything is small. make place for zero */
1925 im->ygrid_scale.gridstep =
1927 floor(log10(range * im->viewfactor / im->magfact))) /
1928 im->viewfactor * im->magfact;
1929 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1930 im->ygrid_scale.gridstep = 0.1;
1931 /* should have at least 5 lines but no more then 15 */
1932 if (range / im->ygrid_scale.gridstep < 5
1933 && im->ygrid_scale.gridstep >= 30)
1934 im->ygrid_scale.gridstep /= 10;
1935 if (range / im->ygrid_scale.gridstep > 15)
1936 im->ygrid_scale.gridstep *= 10;
1937 if (range / im->ygrid_scale.gridstep > 5) {
1938 im->ygrid_scale.labfact = 1;
1939 if (range / im->ygrid_scale.gridstep > 8
1940 || im->ygrid_scale.gridstep <
1941 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1942 im->ygrid_scale.labfact = 2;
1944 im->ygrid_scale.gridstep /= 5;
1945 im->ygrid_scale.labfact = 5;
1949 (im->ygrid_scale.gridstep *
1950 (double) im->ygrid_scale.labfact * im->viewfactor /
1952 if (fractionals < 0) { /* small amplitude. */
1953 int len = decimals - fractionals + 1;
1955 if (im->unitslength < len + 2)
1956 im->unitslength = len + 2;
1957 sprintf(im->ygrid_scale.labfmt,
1959 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1961 int len = decimals + 1;
1963 if (im->unitslength < len + 2)
1964 im->unitslength = len + 2;
1965 sprintf(im->ygrid_scale.labfmt,
1966 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1968 } else { /* classic rrd grid */
1969 for (i = 0; ylab[i].grid > 0; i++) {
1970 pixel = im->ysize / (scaledrange / ylab[i].grid);
1976 for (i = 0; i < 4; i++) {
1977 if (pixel * ylab[gridind].lfac[i] >=
1978 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1979 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1984 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1987 im->ygrid_scale.gridstep = im->ygridstep;
1988 im->ygrid_scale.labfact = im->ylabfact;
1993 int draw_horizontal_grid(
1999 char graph_label[100];
2001 double X0 = im->xorigin;
2002 double X1 = im->xorigin + im->xsize;
2003 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2004 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2006 double second_axis_magfact = 0;
2007 char *second_axis_symb = "";
2010 im->ygrid_scale.gridstep /
2011 (double) im->magfact * (double) im->viewfactor;
2012 MaxY = scaledstep * (double) egrid;
2013 for (i = sgrid; i <= egrid; i++) {
2015 im->ygrid_scale.gridstep * i);
2017 im->ygrid_scale.gridstep * (i + 1));
2019 if (floor(Y0 + 0.5) >=
2020 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2021 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2022 with the chosen settings. Add a label if required by settings, or if
2023 there is only one label so far and the next grid line is out of bounds. */
2024 if (i % im->ygrid_scale.labfact == 0
2026 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2027 if (im->symbol == ' ') {
2028 if (im->extra_flags & ALTYGRID) {
2029 sprintf(graph_label,
2030 im->ygrid_scale.labfmt,
2031 scaledstep * (double) i);
2034 sprintf(graph_label, "%4.1f",
2035 scaledstep * (double) i);
2037 sprintf(graph_label, "%4.0f",
2038 scaledstep * (double) i);
2042 char sisym = (i == 0 ? ' ' : im->symbol);
2044 if (im->extra_flags & ALTYGRID) {
2045 sprintf(graph_label,
2046 im->ygrid_scale.labfmt,
2047 scaledstep * (double) i, sisym);
2050 sprintf(graph_label, "%4.1f %c",
2051 scaledstep * (double) i, sisym);
2053 sprintf(graph_label, "%4.0f %c",
2054 scaledstep * (double) i, sisym);
2059 if (im->second_axis_scale != 0){
2060 char graph_label_right[100];
2061 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2062 if (im->second_axis_format[0] == '\0'){
2063 if (!second_axis_magfact){
2064 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2065 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2067 sval /= second_axis_magfact;
2070 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2072 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2076 sprintf(graph_label_right,im->second_axis_format,sval);
2080 im->graph_col[GRC_FONT],
2081 im->text_prop[TEXT_PROP_AXIS].font_desc,
2082 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2083 graph_label_right );
2089 text_prop[TEXT_PROP_AXIS].
2091 im->graph_col[GRC_FONT],
2093 text_prop[TEXT_PROP_AXIS].
2096 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2097 gfx_line(im, X0 - 2, Y0, X0, Y0,
2098 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2099 gfx_line(im, X1, Y0, X1 + 2, Y0,
2100 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2101 gfx_dashed_line(im, X0 - 2, Y0,
2107 im->grid_dash_on, im->grid_dash_off);
2108 } else if (!(im->extra_flags & NOMINOR)) {
2111 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2112 gfx_line(im, X1, Y0, X1 + 2, Y0,
2113 GRIDWIDTH, im->graph_col[GRC_GRID]);
2114 gfx_dashed_line(im, X0 - 1, Y0,
2118 graph_col[GRC_GRID],
2119 im->grid_dash_on, im->grid_dash_off);
2126 /* this is frexp for base 10 */
2137 iexp = floor(log((double)fabs(x)) / log((double)10));
2138 mnt = x / pow(10.0, iexp);
2141 mnt = x / pow(10.0, iexp);
2148 /* logaritmic horizontal grid */
2149 int horizontal_log_grid(
2153 double yloglab[][10] = {
2155 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2157 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2159 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2176 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2178 int i, j, val_exp, min_exp;
2179 double nex; /* number of decades in data */
2180 double logscale; /* scale in logarithmic space */
2181 int exfrac = 1; /* decade spacing */
2182 int mid = -1; /* row in yloglab for major grid */
2183 double mspac; /* smallest major grid spacing (pixels) */
2184 int flab; /* first value in yloglab to use */
2185 double value, tmp, pre_value;
2187 char graph_label[100];
2189 nex = log10(im->maxval / im->minval);
2190 logscale = im->ysize / nex;
2191 /* major spacing for data with high dynamic range */
2192 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2199 /* major spacing for less dynamic data */
2201 /* search best row in yloglab */
2203 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2204 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2207 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2210 /* find first value in yloglab */
2212 yloglab[mid][flab] < 10
2213 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2214 if (yloglab[mid][flab] == 10.0) {
2219 if (val_exp % exfrac)
2220 val_exp += abs(-val_exp % exfrac);
2222 X1 = im->xorigin + im->xsize;
2227 value = yloglab[mid][flab] * pow(10.0, val_exp);
2228 if (AlmostEqual2sComplement(value, pre_value, 4))
2229 break; /* it seems we are not converging */
2231 Y0 = ytr(im, value);
2232 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2234 /* major grid line */
2236 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2237 gfx_line(im, X1, Y0, X1 + 2, Y0,
2238 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2239 gfx_dashed_line(im, X0 - 2, Y0,
2244 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2246 if (im->extra_flags & FORCE_UNITS_SI) {
2251 scale = floor(val_exp / 3.0);
2253 pvalue = pow(10.0, val_exp % 3);
2255 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2256 pvalue *= yloglab[mid][flab];
2257 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2258 && ((scale + si_symbcenter) >= 0))
2259 symbol = si_symbol[scale + si_symbcenter];
2262 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2264 sprintf(graph_label, "%3.0e", value);
2266 if (im->second_axis_scale != 0){
2267 char graph_label_right[100];
2268 double sval = value*im->second_axis_scale+im->second_axis_shift;
2269 if (im->second_axis_format[0] == '\0'){
2270 if (im->extra_flags & FORCE_UNITS_SI) {
2273 auto_scale(im,&sval,&symb,&mfac);
2274 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2277 sprintf(graph_label_right,"%3.0e", sval);
2281 sprintf(graph_label_right,im->second_axis_format,sval);
2286 im->graph_col[GRC_FONT],
2287 im->text_prop[TEXT_PROP_AXIS].font_desc,
2288 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2289 graph_label_right );
2295 text_prop[TEXT_PROP_AXIS].
2297 im->graph_col[GRC_FONT],
2299 text_prop[TEXT_PROP_AXIS].
2302 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2304 if (mid < 4 && exfrac == 1) {
2305 /* find first and last minor line behind current major line
2306 * i is the first line and j tha last */
2308 min_exp = val_exp - 1;
2309 for (i = 1; yloglab[mid][i] < 10.0; i++);
2310 i = yloglab[mid][i - 1] + 1;
2314 i = yloglab[mid][flab - 1] + 1;
2315 j = yloglab[mid][flab];
2318 /* draw minor lines below current major line */
2319 for (; i < j; i++) {
2321 value = i * pow(10.0, min_exp);
2322 if (value < im->minval)
2324 Y0 = ytr(im, value);
2325 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2330 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2331 gfx_line(im, X1, Y0, X1 + 2, Y0,
2332 GRIDWIDTH, im->graph_col[GRC_GRID]);
2333 gfx_dashed_line(im, X0 - 1, Y0,
2337 graph_col[GRC_GRID],
2338 im->grid_dash_on, im->grid_dash_off);
2340 } else if (exfrac > 1) {
2341 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2342 value = pow(10.0, i);
2343 if (value < im->minval)
2345 Y0 = ytr(im, value);
2346 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2351 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2352 gfx_line(im, X1, Y0, X1 + 2, Y0,
2353 GRIDWIDTH, im->graph_col[GRC_GRID]);
2354 gfx_dashed_line(im, X0 - 1, Y0,
2358 graph_col[GRC_GRID],
2359 im->grid_dash_on, im->grid_dash_off);
2364 if (yloglab[mid][++flab] == 10.0) {
2370 /* draw minor lines after highest major line */
2371 if (mid < 4 && exfrac == 1) {
2372 /* find first and last minor line below current major line
2373 * i is the first line and j tha last */
2375 min_exp = val_exp - 1;
2376 for (i = 1; yloglab[mid][i] < 10.0; i++);
2377 i = yloglab[mid][i - 1] + 1;
2381 i = yloglab[mid][flab - 1] + 1;
2382 j = yloglab[mid][flab];
2385 /* draw minor lines below current major line */
2386 for (; i < j; i++) {
2388 value = i * pow(10.0, min_exp);
2389 if (value < im->minval)
2391 Y0 = ytr(im, value);
2392 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2396 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2397 gfx_line(im, X1, Y0, X1 + 2, Y0,
2398 GRIDWIDTH, im->graph_col[GRC_GRID]);
2399 gfx_dashed_line(im, X0 - 1, Y0,
2403 graph_col[GRC_GRID],
2404 im->grid_dash_on, im->grid_dash_off);
2407 /* fancy minor gridlines */
2408 else if (exfrac > 1) {
2409 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2410 value = pow(10.0, i);
2411 if (value < im->minval)
2413 Y0 = ytr(im, value);
2414 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2418 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2419 gfx_line(im, X1, Y0, X1 + 2, Y0,
2420 GRIDWIDTH, im->graph_col[GRC_GRID]);
2421 gfx_dashed_line(im, X0 - 1, Y0,
2425 graph_col[GRC_GRID],
2426 im->grid_dash_on, im->grid_dash_off);
2437 int xlab_sel; /* which sort of label and grid ? */
2438 time_t ti, tilab, timajor;
2440 char graph_label[100];
2441 double X0, Y0, Y1; /* points for filled graph and more */
2444 /* the type of time grid is determined by finding
2445 the number of seconds per pixel in the graph */
2446 if (im->xlab_user.minsec == -1) {
2447 factor = (im->end - im->start) / im->xsize;
2449 while (xlab[xlab_sel + 1].minsec !=
2450 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2452 } /* pick the last one */
2453 while (xlab[xlab_sel - 1].minsec ==
2454 xlab[xlab_sel].minsec
2455 && xlab[xlab_sel].length > (im->end - im->start)) {
2457 } /* go back to the smallest size */
2458 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2459 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2460 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2461 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2462 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2463 im->xlab_user.labst = xlab[xlab_sel].labst;
2464 im->xlab_user.precis = xlab[xlab_sel].precis;
2465 im->xlab_user.stst = xlab[xlab_sel].stst;
2468 /* y coords are the same for every line ... */
2470 Y1 = im->yorigin - im->ysize;
2471 /* paint the minor grid */
2472 if (!(im->extra_flags & NOMINOR)) {
2473 for (ti = find_first_time(im->start,
2481 find_first_time(im->start,
2488 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2490 /* are we inside the graph ? */
2491 if (ti < im->start || ti > im->end)
2493 while (timajor < ti) {
2494 timajor = find_next_time(timajor,
2497 mgridtm, im->xlab_user.mgridst);
2500 continue; /* skip as falls on major grid line */
2502 gfx_line(im, X0, Y1 - 2, X0, Y1,
2503 GRIDWIDTH, im->graph_col[GRC_GRID]);
2504 gfx_line(im, X0, Y0, X0, Y0 + 2,
2505 GRIDWIDTH, im->graph_col[GRC_GRID]);
2506 gfx_dashed_line(im, X0, Y0 + 1, X0,
2509 graph_col[GRC_GRID],
2510 im->grid_dash_on, im->grid_dash_off);
2514 /* paint the major grid */
2515 for (ti = find_first_time(im->start,
2523 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2525 /* are we inside the graph ? */
2526 if (ti < im->start || ti > im->end)
2529 gfx_line(im, X0, Y1 - 2, X0, Y1,
2530 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2531 gfx_line(im, X0, Y0, X0, Y0 + 3,
2532 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2533 gfx_dashed_line(im, X0, Y0 + 3, X0,
2537 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2539 /* paint the labels below the graph */
2541 find_first_time(im->start -
2550 im->xlab_user.precis / 2;
2551 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2553 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2554 /* are we inside the graph ? */
2555 if (tilab < im->start || tilab > im->end)
2558 localtime_r(&tilab, &tm);
2559 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2561 # error "your libc has no strftime I guess we'll abort the exercise here."
2566 im->graph_col[GRC_FONT],
2568 text_prop[TEXT_PROP_AXIS].
2571 GFX_H_CENTER, GFX_V_TOP, graph_label);
2580 /* draw x and y axis */
2581 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2582 im->xorigin+im->xsize,im->yorigin-im->ysize,
2583 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2585 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2586 im->xorigin+im->xsize,im->yorigin-im->ysize,
2587 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2589 gfx_line(im, im->xorigin - 4,
2591 im->xorigin + im->xsize +
2592 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2593 gfx_line(im, im->xorigin,
2596 im->yorigin - im->ysize -
2597 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2598 /* arrow for X and Y axis direction */
2599 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 */
2600 im->graph_col[GRC_ARROW]);
2602 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 */
2603 im->graph_col[GRC_ARROW]);
2605 if (im->second_axis_scale != 0){
2606 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2607 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2608 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2610 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2611 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2612 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2613 im->graph_col[GRC_ARROW]);
2624 double X0, Y0; /* points for filled graph and more */
2625 struct gfx_color_t water_color;
2627 if (im->draw_3d_border > 0) {
2628 /* draw 3d border */
2629 i = im->draw_3d_border;
2630 gfx_new_area(im, 0, im->yimg,
2631 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2632 gfx_add_point(im, im->ximg - i, i);
2633 gfx_add_point(im, im->ximg, 0);
2634 gfx_add_point(im, 0, 0);
2636 gfx_new_area(im, i, im->yimg - i,
2638 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2639 gfx_add_point(im, im->ximg, 0);
2640 gfx_add_point(im, im->ximg, im->yimg);
2641 gfx_add_point(im, 0, im->yimg);
2644 if (im->draw_x_grid == 1)
2646 if (im->draw_y_grid == 1) {
2647 if (im->logarithmic) {
2648 res = horizontal_log_grid(im);
2650 res = draw_horizontal_grid(im);
2653 /* dont draw horizontal grid if there is no min and max val */
2655 char *nodata = "No Data found";
2657 gfx_text(im, im->ximg / 2,
2660 im->graph_col[GRC_FONT],
2662 text_prop[TEXT_PROP_AXIS].
2665 GFX_H_CENTER, GFX_V_CENTER, nodata);
2669 /* yaxis unit description */
2670 if (im->ylegend[0] != '\0'){
2672 im->xOriginLegendY+10,
2674 im->graph_col[GRC_FONT],
2676 text_prop[TEXT_PROP_UNIT].
2679 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2682 if (im->second_axis_legend[0] != '\0'){
2684 im->xOriginLegendY2+10,
2685 im->yOriginLegendY2,
2686 im->graph_col[GRC_FONT],
2687 im->text_prop[TEXT_PROP_UNIT].font_desc,
2689 RRDGRAPH_YLEGEND_ANGLE,
2690 GFX_H_CENTER, GFX_V_CENTER,
2691 im->second_axis_legend);
2696 im->xOriginTitle, im->yOriginTitle+6,
2697 im->graph_col[GRC_FONT],
2699 text_prop[TEXT_PROP_TITLE].
2701 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2702 /* rrdtool 'logo' */
2703 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2704 water_color = im->graph_col[GRC_FONT];
2705 water_color.alpha = 0.3;
2706 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2707 gfx_text(im, xpos, 5,
2710 text_prop[TEXT_PROP_WATERMARK].
2711 font_desc, im->tabwidth,
2712 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2714 /* graph watermark */
2715 if (im->watermark[0] != '\0') {
2716 water_color = im->graph_col[GRC_FONT];
2717 water_color.alpha = 0.3;
2719 im->ximg / 2, im->yimg - 6,
2722 text_prop[TEXT_PROP_WATERMARK].
2723 font_desc, im->tabwidth, 0,
2724 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2728 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2729 for (i = 0; i < im->gdes_c; i++) {
2730 if (im->gdes[i].legend[0] == '\0')
2732 /* im->gdes[i].leg_y is the bottom of the legend */
2733 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2734 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2735 gfx_text(im, X0, Y0,
2736 im->graph_col[GRC_FONT],
2739 [TEXT_PROP_LEGEND].font_desc,
2741 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2742 /* The legend for GRAPH items starts with "M " to have
2743 enough space for the box */
2744 if (im->gdes[i].gf != GF_PRINT &&
2745 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2749 boxH = gfx_get_text_width(im, 0,
2754 im->tabwidth, "o") * 1.2;
2756 /* shift the box up a bit */
2759 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2761 cairo_new_path(im->cr);
2762 cairo_set_line_width(im->cr, 1.0);
2765 X0 + boxH, Y0 - boxV / 2,
2766 1.0, im->gdes[i].col);
2768 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2770 cairo_new_path(im->cr);
2771 cairo_set_line_width(im->cr, 1.0);
2774 X0 + boxH / 2, Y0 - boxV,
2775 1.0, im->gdes[i].col);
2777 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2779 cairo_new_path(im->cr);
2780 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2783 X0 + boxH, Y0 - boxV,
2784 im->gdes[i].linewidth, im->gdes[i].col);
2787 /* make sure transparent colors show up the same way as in the graph */
2790 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2791 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2793 gfx_new_area(im, X0, Y0 - boxV, X0,
2794 Y0, X0 + boxH, Y0, im->gdes[i].col);
2795 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2798 cairo_new_path(im->cr);
2799 cairo_set_line_width(im->cr, 1.0);
2802 gfx_line_fit(im, &X0, &Y0);
2803 gfx_line_fit(im, &X1, &Y1);
2804 cairo_move_to(im->cr, X0, Y0);
2805 cairo_line_to(im->cr, X1, Y0);
2806 cairo_line_to(im->cr, X1, Y1);
2807 cairo_line_to(im->cr, X0, Y1);
2808 cairo_close_path(im->cr);
2809 cairo_set_source_rgba(im->cr,
2810 im->graph_col[GRC_FRAME].red,
2811 im->graph_col[GRC_FRAME].green,
2812 im->graph_col[GRC_FRAME].blue,
2813 im->graph_col[GRC_FRAME].alpha);
2815 if (im->gdes[i].dash) {
2816 /* make box borders in legend dashed if the graph is dashed */
2820 cairo_set_dash(im->cr, dashes, 1, 0.0);
2822 cairo_stroke(im->cr);
2823 cairo_restore(im->cr);
2830 /*****************************************************
2831 * lazy check make sure we rely need to create this graph
2832 *****************************************************/
2839 struct stat imgstat;
2842 return 0; /* no lazy option */
2843 if (strlen(im->graphfile) == 0)
2844 return 0; /* inmemory option */
2845 if (stat(im->graphfile, &imgstat) != 0)
2846 return 0; /* can't stat */
2847 /* one pixel in the existing graph is more then what we would
2849 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2851 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2852 return 0; /* the file does not exist */
2853 switch (im->imgformat) {
2855 size = PngSize(fd, &(im->ximg), &(im->yimg));
2865 int graph_size_location(
2870 /* The actual size of the image to draw is determined from
2871 ** several sources. The size given on the command line is
2872 ** the graph area but we need more as we have to draw labels
2873 ** and other things outside the graph area. If the option
2874 ** --full-size-mode is selected the size defines the total
2875 ** image size and the size available for the graph is
2879 /** +---+-----------------------------------+
2880 ** | y |...............graph title.........|
2881 ** | +---+-------------------------------+
2885 ** | s | x | main graph area |
2890 ** | l | b +-------------------------------+
2891 ** | e | l | x axis labels |
2892 ** +---+---+-------------------------------+
2893 ** |....................legends............|
2894 ** +---------------------------------------+
2896 ** +---------------------------------------+
2899 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2900 0, Xylabel = 0, Xmain = 0, Ymain =
2901 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2903 // no legends and no the shall be plotted it's easy
2904 if (im->extra_flags & ONLY_GRAPH) {
2906 im->ximg = im->xsize;
2907 im->yimg = im->ysize;
2908 im->yorigin = im->ysize;
2913 if(im->watermark[0] != '\0') {
2914 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2917 // calculate the width of the left vertical legend
2918 if (im->ylegend[0] != '\0') {
2919 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2922 // calculate the width of the right vertical legend
2923 if (im->second_axis_legend[0] != '\0') {
2924 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2927 Xvertical2 = Xspacing;
2930 if (im->title[0] != '\0') {
2931 /* The title is placed "inbetween" two text lines so it
2932 ** automatically has some vertical spacing. The horizontal
2933 ** spacing is added here, on each side.
2935 /* if necessary, reduce the font size of the title until it fits the image width */
2936 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2939 // we have no title; get a little clearing from the top
2940 Ytitle = 1.5 * Yspacing;
2944 if (im->draw_x_grid) {
2945 // calculate the height of the horizontal labelling
2946 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2948 if (im->draw_y_grid || im->forceleftspace) {
2949 // calculate the width of the vertical labelling
2951 gfx_get_text_width(im, 0,
2952 im->text_prop[TEXT_PROP_AXIS].font_desc,
2953 im->tabwidth, "0") * im->unitslength;
2957 // add some space to the labelling
2958 Xylabel += Xspacing;
2960 /* If the legend is printed besides the graph the width has to be
2961 ** calculated first. Placing the legend north or south of the
2962 ** graph requires the width calculation first, so the legend is
2963 ** skipped for the moment.
2965 im->legendheight = 0;
2966 im->legendwidth = 0;
2967 if (!(im->extra_flags & NOLEGEND)) {
2968 if(im->legendposition == WEST || im->legendposition == EAST){
2969 if (leg_place(im, 1) == -1){
2975 if (im->extra_flags & FULL_SIZE_MODE) {
2977 /* The actual size of the image to draw has been determined by the user.
2978 ** The graph area is the space remaining after accounting for the legend,
2979 ** the watermark, the axis labels, and the title.
2981 im->ximg = im->xsize;
2982 im->yimg = im->ysize;
2986 /* Now calculate the total size. Insert some spacing where
2987 desired. im->xorigin and im->yorigin need to correspond
2988 with the lower left corner of the main graph area or, if
2989 this one is not set, the imaginary box surrounding the
2991 /* Initial size calculation for the main graph area */
2993 Xmain -= Xylabel;// + Xspacing;
2994 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2995 Xmain -= im->legendwidth;// + Xspacing;
2997 if (im->second_axis_scale != 0){
3000 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3004 Xmain -= Xvertical + Xvertical2;
3006 /* limit the remaining space to 0 */
3012 /* Putting the legend north or south, the height can now be calculated */
3013 if (!(im->extra_flags & NOLEGEND)) {
3014 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3015 im->legendwidth = im->ximg;
3016 if (leg_place(im, 0) == -1){
3022 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3023 Ymain -= Yxlabel + im->legendheight;
3029 /* reserve space for the title *or* some padding above the graph */
3032 /* reserve space for padding below the graph */
3033 if (im->extra_flags & NOLEGEND) {
3037 if (im->watermark[0] != '\0') {
3038 Ymain -= Ywatermark;
3040 /* limit the remaining height to 0 */
3045 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3047 /* The actual size of the image to draw is determined from
3048 ** several sources. The size given on the command line is
3049 ** the graph area but we need more as we have to draw labels
3050 ** and other things outside the graph area.
3054 Xmain = im->xsize; // + Xspacing;
3058 im->ximg = Xmain + Xylabel;
3059 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3060 im->ximg += Xspacing;
3063 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3064 im->ximg += im->legendwidth;// + Xspacing;
3066 if (im->second_axis_scale != 0){
3067 im->ximg += Xylabel;
3070 im->ximg += Xvertical + Xvertical2;
3072 if (!(im->extra_flags & NOLEGEND)) {
3073 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3074 im->legendwidth = im->ximg;
3075 if (leg_place(im, 0) == -1){
3081 im->yimg = Ymain + Yxlabel;
3082 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3083 im->yimg += im->legendheight;
3086 /* reserve space for the title *or* some padding above the graph */
3090 im->yimg += 1.5 * Yspacing;
3092 /* reserve space for padding below the graph */
3093 if (im->extra_flags & NOLEGEND) {
3094 im->yimg += Yspacing;
3097 if (im->watermark[0] != '\0') {
3098 im->yimg += Ywatermark;
3103 /* In case of putting the legend in west or east position the first
3104 ** legend calculation might lead to wrong positions if some items
3105 ** are not aligned on the left hand side (e.g. centered) as the
3106 ** legendwidth wight have been increased after the item was placed.
3107 ** In this case the positions have to be recalculated.
3109 if (!(im->extra_flags & NOLEGEND)) {
3110 if(im->legendposition == WEST || im->legendposition == EAST){
3111 if (leg_place(im, 0) == -1){
3117 /* After calculating all dimensions
3118 ** it is now possible to calculate
3121 switch(im->legendposition){
3123 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3124 im->yOriginTitle = 0;
3126 im->xOriginLegend = 0;
3127 im->yOriginLegend = Ytitle;
3129 im->xOriginLegendY = 0;
3130 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3132 im->xorigin = Xvertical + Xylabel;
3133 im->yorigin = Ytitle + im->legendheight + Ymain;
3135 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3136 if (im->second_axis_scale != 0){
3137 im->xOriginLegendY2 += Xylabel;
3139 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3144 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3145 im->yOriginTitle = 0;
3147 im->xOriginLegend = 0;
3148 im->yOriginLegend = Ytitle;
3150 im->xOriginLegendY = im->legendwidth;
3151 im->yOriginLegendY = Ytitle + (Ymain / 2);
3153 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3154 im->yorigin = Ytitle + Ymain;
3156 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3157 if (im->second_axis_scale != 0){
3158 im->xOriginLegendY2 += Xylabel;
3160 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3165 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3166 im->yOriginTitle = 0;
3168 im->xOriginLegend = 0;
3169 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3171 im->xOriginLegendY = 0;
3172 im->yOriginLegendY = Ytitle + (Ymain / 2);
3174 im->xorigin = Xvertical + Xylabel;
3175 im->yorigin = Ytitle + Ymain;
3177 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3178 if (im->second_axis_scale != 0){
3179 im->xOriginLegendY2 += Xylabel;
3181 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3186 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3187 im->yOriginTitle = 0;
3189 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3190 if (im->second_axis_scale != 0){
3191 im->xOriginLegend += Xylabel;
3193 im->yOriginLegend = Ytitle;
3195 im->xOriginLegendY = 0;
3196 im->yOriginLegendY = Ytitle + (Ymain / 2);
3198 im->xorigin = Xvertical + Xylabel;
3199 im->yorigin = Ytitle + Ymain;
3201 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3202 if (im->second_axis_scale != 0){
3203 im->xOriginLegendY2 += Xylabel;
3205 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3207 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3208 im->xOriginTitle += Xspacing;
3209 im->xOriginLegend += Xspacing;
3210 im->xOriginLegendY += Xspacing;
3211 im->xorigin += Xspacing;
3212 im->xOriginLegendY2 += Xspacing;
3222 static cairo_status_t cairo_output(
3226 unsigned int length)
3228 image_desc_t *im = (image_desc_t*)closure;
3230 im->rendered_image =
3231 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3232 if (im->rendered_image == NULL)
3233 return CAIRO_STATUS_WRITE_ERROR;
3234 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3235 im->rendered_image_size += length;
3236 return CAIRO_STATUS_SUCCESS;
3239 /* draw that picture thing ... */
3244 int lazy = lazy_check(im);
3245 double areazero = 0.0;
3246 graph_desc_t *lastgdes = NULL;
3249 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3251 /* pull the data from the rrd files ... */
3252 if (data_fetch(im) == -1)
3254 /* evaluate VDEF and CDEF operations ... */
3255 if (data_calc(im) == -1)
3257 /* calculate and PRINT and GPRINT definitions. We have to do it at
3258 * this point because it will affect the length of the legends
3259 * if there are no graph elements (i==0) we stop here ...
3260 * if we are lazy, try to quit ...
3266 /* if we want and can be lazy ... quit now */
3270 /**************************************************************
3271 *** Calculating sizes and locations became a bit confusing ***
3272 *** so I moved this into a separate function. ***
3273 **************************************************************/
3274 if (graph_size_location(im, i) == -1)
3277 info.u_cnt = im->xorigin;
3278 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3279 info.u_cnt = im->yorigin - im->ysize;
3280 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3281 info.u_cnt = im->xsize;
3282 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3283 info.u_cnt = im->ysize;
3284 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3285 info.u_cnt = im->ximg;
3286 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3287 info.u_cnt = im->yimg;
3288 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3289 info.u_cnt = im->start;
3290 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3291 info.u_cnt = im->end;
3292 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3294 /* if we want and can be lazy ... quit now */
3298 /* get actual drawing data and find min and max values */
3299 if (data_proc(im) == -1)
3301 if (!im->logarithmic) {
3305 /* identify si magnitude Kilo, Mega Giga ? */
3306 if (!im->rigid && !im->logarithmic)
3307 expand_range(im); /* make sure the upper and lower limit are
3310 info.u_val = im->minval;
3311 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3312 info.u_val = im->maxval;
3313 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3316 if (!calc_horizontal_grid(im))
3321 apply_gridfit(im); */
3322 /* the actual graph is created by going through the individual
3323 graph elements and then drawing them */
3324 cairo_surface_destroy(im->surface);
3325 switch (im->imgformat) {
3328 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3329 im->ximg * im->zoom,
3330 im->yimg * im->zoom);
3334 im->surface = strlen(im->graphfile)
3335 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3336 im->yimg * im->zoom)
3337 : cairo_pdf_surface_create_for_stream
3338 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3342 im->surface = strlen(im->graphfile)
3344 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3345 im->yimg * im->zoom)
3346 : cairo_ps_surface_create_for_stream
3347 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3351 im->surface = strlen(im->graphfile)
3353 cairo_svg_surface_create(im->
3355 im->ximg * im->zoom, im->yimg * im->zoom)
3356 : cairo_svg_surface_create_for_stream
3357 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3358 cairo_svg_surface_restrict_to_version
3359 (im->surface, CAIRO_SVG_VERSION_1_1);
3362 cairo_destroy(im->cr);
3363 im->cr = cairo_create(im->surface);
3364 cairo_set_antialias(im->cr, im->graph_antialias);
3365 cairo_scale(im->cr, im->zoom, im->zoom);
3366 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3367 gfx_new_area(im, 0, 0, 0, im->yimg,
3368 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3369 gfx_add_point(im, im->ximg, 0);
3371 gfx_new_area(im, im->xorigin,
3374 im->xsize, im->yorigin,
3377 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3378 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3380 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3381 im->xsize, im->ysize + 2.0);
3383 if (im->minval > 0.0)
3384 areazero = im->minval;
3385 if (im->maxval < 0.0)
3386 areazero = im->maxval;
3387 for (i = 0; i < im->gdes_c; i++) {
3388 switch (im->gdes[i].gf) {
3402 for (ii = 0; ii < im->xsize; ii++) {
3403 if (!isnan(im->gdes[i].p_data[ii])
3404 && im->gdes[i].p_data[ii] != 0.0) {
3405 if (im->gdes[i].yrule > 0) {
3412 im->ysize, 1.0, im->gdes[i].col);
3413 } else if (im->gdes[i].yrule < 0) {
3416 im->yorigin - im->ysize - 1.0,
3418 im->yorigin - im->ysize -
3421 im->ysize, 1.0, im->gdes[i].col);
3428 /* fix data points at oo and -oo */
3429 for (ii = 0; ii < im->xsize; ii++) {
3430 if (isinf(im->gdes[i].p_data[ii])) {
3431 if (im->gdes[i].p_data[ii] > 0) {
3432 im->gdes[i].p_data[ii] = im->maxval;
3434 im->gdes[i].p_data[ii] = im->minval;
3440 /* *******************************************************
3445 -------|--t-1--t--------------------------------
3447 if we know the value at time t was a then
3448 we draw a square from t-1 to t with the value a.
3450 ********************************************************* */
3451 if (im->gdes[i].col.alpha != 0.0) {
3452 /* GF_LINE and friend */
3453 if (im->gdes[i].gf == GF_LINE) {
3454 double last_y = 0.0;
3458 cairo_new_path(im->cr);
3459 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3460 if (im->gdes[i].dash) {
3461 cairo_set_dash(im->cr,
3462 im->gdes[i].p_dashes,
3463 im->gdes[i].ndash, im->gdes[i].offset);
3466 for (ii = 1; ii < im->xsize; ii++) {
3467 if (isnan(im->gdes[i].p_data[ii])
3468 || (im->slopemode == 1
3469 && isnan(im->gdes[i].p_data[ii - 1]))) {
3474 last_y = ytr(im, im->gdes[i].p_data[ii]);
3475 if (im->slopemode == 0) {
3476 double x = ii - 1 + im->xorigin;
3479 gfx_line_fit(im, &x, &y);
3480 cairo_move_to(im->cr, x, y);
3481 x = ii + im->xorigin;
3483 gfx_line_fit(im, &x, &y);
3484 cairo_line_to(im->cr, x, y);
3486 double x = ii - 1 + im->xorigin;
3488 ytr(im, im->gdes[i].p_data[ii - 1]);
3489 gfx_line_fit(im, &x, &y);
3490 cairo_move_to(im->cr, x, y);
3491 x = ii + im->xorigin;
3493 gfx_line_fit(im, &x, &y);
3494 cairo_line_to(im->cr, x, y);
3498 double x1 = ii + im->xorigin;
3499 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3501 if (im->slopemode == 0
3502 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3503 double x = ii - 1 + im->xorigin;
3506 gfx_line_fit(im, &x, &y);
3507 cairo_line_to(im->cr, x, y);
3510 gfx_line_fit(im, &x1, &y1);
3511 cairo_line_to(im->cr, x1, y1);
3514 cairo_set_source_rgba(im->cr,
3520 col.blue, im->gdes[i].col.alpha);
3521 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3522 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3523 cairo_stroke(im->cr);
3524 cairo_restore(im->cr);
3528 (double *) malloc(sizeof(double) * im->xsize * 2);
3530 (double *) malloc(sizeof(double) * im->xsize * 2);
3532 (double *) malloc(sizeof(double) * im->xsize * 2);
3534 (double *) malloc(sizeof(double) * im->xsize * 2);
3537 for (ii = 0; ii <= im->xsize; ii++) {
3540 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3546 AlmostEqual2sComplement(foreY
3550 AlmostEqual2sComplement(foreY
3560 foreY[cntI], im->gdes[i].col);
3561 while (cntI < idxI) {
3566 AlmostEqual2sComplement(foreY
3570 AlmostEqual2sComplement(foreY
3577 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3579 gfx_add_point(im, backX[idxI], backY[idxI]);
3585 AlmostEqual2sComplement(backY
3589 AlmostEqual2sComplement(backY
3596 gfx_add_point(im, backX[idxI], backY[idxI]);
3606 if (ii == im->xsize)
3608 if (im->slopemode == 0 && ii == 0) {
3611 if (isnan(im->gdes[i].p_data[ii])) {
3615 ytop = ytr(im, im->gdes[i].p_data[ii]);
3616 if (lastgdes && im->gdes[i].stack) {
3617 ybase = ytr(im, lastgdes->p_data[ii]);
3619 ybase = ytr(im, areazero);
3621 if (ybase == ytop) {
3627 double extra = ytop;
3632 if (im->slopemode == 0) {
3633 backY[++idxI] = ybase - 0.2;
3634 backX[idxI] = ii + im->xorigin - 1;
3635 foreY[idxI] = ytop + 0.2;
3636 foreX[idxI] = ii + im->xorigin - 1;
3638 backY[++idxI] = ybase - 0.2;
3639 backX[idxI] = ii + im->xorigin;
3640 foreY[idxI] = ytop + 0.2;
3641 foreX[idxI] = ii + im->xorigin;
3643 /* close up any remaining area */
3648 } /* else GF_LINE */
3650 /* if color != 0x0 */
3651 /* make sure we do not run into trouble when stacking on NaN */
3652 for (ii = 0; ii < im->xsize; ii++) {
3653 if (isnan(im->gdes[i].p_data[ii])) {
3654 if (lastgdes && (im->gdes[i].stack)) {
3655 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3657 im->gdes[i].p_data[ii] = areazero;
3661 lastgdes = &(im->gdes[i]);
3665 ("STACK should already be turned into LINE or AREA here");
3670 cairo_reset_clip(im->cr);
3672 /* grid_paint also does the text */
3673 if (!(im->extra_flags & ONLY_GRAPH))
3675 if (!(im->extra_flags & ONLY_GRAPH))
3677 /* the RULES are the last thing to paint ... */
3678 for (i = 0; i < im->gdes_c; i++) {
3680 switch (im->gdes[i].gf) {
3682 if (im->gdes[i].yrule >= im->minval
3683 && im->gdes[i].yrule <= im->maxval) {
3685 if (im->gdes[i].dash) {
3686 cairo_set_dash(im->cr,
3687 im->gdes[i].p_dashes,
3688 im->gdes[i].ndash, im->gdes[i].offset);
3690 gfx_line(im, im->xorigin,
3691 ytr(im, im->gdes[i].yrule),
3692 im->xorigin + im->xsize,
3693 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3694 cairo_stroke(im->cr);
3695 cairo_restore(im->cr);
3699 if (im->gdes[i].xrule >= im->start
3700 && im->gdes[i].xrule <= im->end) {
3702 if (im->gdes[i].dash) {
3703 cairo_set_dash(im->cr,
3704 im->gdes[i].p_dashes,
3705 im->gdes[i].ndash, im->gdes[i].offset);
3708 xtr(im, im->gdes[i].xrule),
3709 im->yorigin, xtr(im,
3713 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3714 cairo_stroke(im->cr);
3715 cairo_restore(im->cr);
3724 switch (im->imgformat) {
3727 cairo_status_t status;
3729 status = strlen(im->graphfile) ?
3730 cairo_surface_write_to_png(im->surface, im->graphfile)
3731 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3734 if (status != CAIRO_STATUS_SUCCESS) {
3735 rrd_set_error("Could not save png to '%s'", im->graphfile);
3741 if (strlen(im->graphfile)) {
3742 cairo_show_page(im->cr);
3744 cairo_surface_finish(im->surface);
3753 /*****************************************************
3755 *****************************************************/
3762 if ((im->gdes = (graph_desc_t *)
3763 rrd_realloc(im->gdes, (im->gdes_c)
3764 * sizeof(graph_desc_t))) == NULL) {
3765 rrd_set_error("realloc graph_descs");
3770 im->gdes[im->gdes_c - 1].step = im->step;
3771 im->gdes[im->gdes_c - 1].step_orig = im->step;
3772 im->gdes[im->gdes_c - 1].stack = 0;
3773 im->gdes[im->gdes_c - 1].linewidth = 0;
3774 im->gdes[im->gdes_c - 1].debug = 0;
3775 im->gdes[im->gdes_c - 1].start = im->start;
3776 im->gdes[im->gdes_c - 1].start_orig = im->start;
3777 im->gdes[im->gdes_c - 1].end = im->end;
3778 im->gdes[im->gdes_c - 1].end_orig = im->end;
3779 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3780 im->gdes[im->gdes_c - 1].data = NULL;
3781 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3782 im->gdes[im->gdes_c - 1].data_first = 0;
3783 im->gdes[im->gdes_c - 1].p_data = NULL;
3784 im->gdes[im->gdes_c - 1].rpnp = NULL;
3785 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3786 im->gdes[im->gdes_c - 1].shift = 0.0;
3787 im->gdes[im->gdes_c - 1].dash = 0;
3788 im->gdes[im->gdes_c - 1].ndash = 0;
3789 im->gdes[im->gdes_c - 1].offset = 0;
3790 im->gdes[im->gdes_c - 1].col.red = 0.0;
3791 im->gdes[im->gdes_c - 1].col.green = 0.0;
3792 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3793 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3794 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3795 im->gdes[im->gdes_c - 1].format[0] = '\0';
3796 im->gdes[im->gdes_c - 1].strftm = 0;
3797 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3798 im->gdes[im->gdes_c - 1].ds = -1;
3799 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3800 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3801 im->gdes[im->gdes_c - 1].yrule = DNAN;
3802 im->gdes[im->gdes_c - 1].xrule = 0;
3806 /* copies input untill the first unescaped colon is found
3807 or until input ends. backslashes have to be escaped as well */
3809 const char *const input,
3815 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3816 if (input[inp] == '\\'
3817 && input[inp + 1] != '\0'
3818 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3819 output[outp++] = input[++inp];
3821 output[outp++] = input[inp];
3824 output[outp] = '\0';
3828 /* Now just a wrapper around rrd_graph_v */
3840 rrd_info_t *grinfo = NULL;
3843 grinfo = rrd_graph_v(argc, argv);
3849 if (strcmp(walker->key, "image_info") == 0) {
3852 (char**)rrd_realloc((*prdata),
3853 (prlines + 1) * sizeof(char *))) == NULL) {
3854 rrd_set_error("realloc prdata");
3857 /* imginfo goes to position 0 in the prdata array */
3858 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3859 + 2) * sizeof(char));
3860 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3861 (*prdata)[prlines] = NULL;
3863 /* skip anything else */
3864 walker = walker->next;
3872 if (strcmp(walker->key, "image_width") == 0) {
3873 *xsize = walker->value.u_cnt;
3874 } else if (strcmp(walker->key, "image_height") == 0) {
3875 *ysize = walker->value.u_cnt;
3876 } else if (strcmp(walker->key, "value_min") == 0) {
3877 *ymin = walker->value.u_val;
3878 } else if (strcmp(walker->key, "value_max") == 0) {
3879 *ymax = walker->value.u_val;
3880 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3883 (char**)rrd_realloc((*prdata),
3884 (prlines + 1) * sizeof(char *))) == NULL) {
3885 rrd_set_error("realloc prdata");
3888 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3889 + 2) * sizeof(char));
3890 (*prdata)[prlines] = NULL;
3891 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3892 } else if (strcmp(walker->key, "image") == 0) {
3893 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3894 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3895 rrd_set_error("writing image");
3899 /* skip anything else */
3900 walker = walker->next;
3902 rrd_info_free(grinfo);
3907 /* Some surgery done on this function, it became ridiculously big.
3909 ** - initializing now in rrd_graph_init()
3910 ** - options parsing now in rrd_graph_options()
3911 ** - script parsing now in rrd_graph_script()
3913 rrd_info_t *rrd_graph_v(
3920 rrd_graph_init(&im);
3921 /* a dummy surface so that we can measure text sizes for placements */
3922 old_locale = setlocale(LC_NUMERIC, "C");
3923 rrd_graph_options(argc, argv, &im);
3924 if (rrd_test_error()) {
3925 rrd_info_free(im.grinfo);
3930 if (optind >= argc) {
3931 rrd_info_free(im.grinfo);
3933 rrd_set_error("missing filename");
3937 if (strlen(argv[optind]) >= MAXPATH) {
3938 rrd_set_error("filename (including path) too long");
3939 rrd_info_free(im.grinfo);
3944 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3945 im.graphfile[MAXPATH - 1] = '\0';
3947 if (strcmp(im.graphfile, "-") == 0) {
3948 im.graphfile[0] = '\0';
3951 rrd_graph_script(argc, argv, &im, 1);
3952 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3954 if (rrd_test_error()) {
3955 rrd_info_free(im.grinfo);
3960 /* Everything is now read and the actual work can start */
3962 if (graph_paint(&im) == -1) {
3963 rrd_info_free(im.grinfo);
3969 /* The image is generated and needs to be output.
3970 ** Also, if needed, print a line with information about the image.
3978 path = strdup(im.graphfile);
3979 filename = basename(path);
3981 sprintf_alloc(im.imginfo,
3984 im.ximg), (long) (im.zoom * im.yimg));
3985 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3989 if (im.rendered_image) {
3992 img.u_blo.size = im.rendered_image_size;
3993 img.u_blo.ptr = im.rendered_image;
3994 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4003 image_desc_t *im,int prop,char *font, double size ){
4005 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4006 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4007 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4010 im->text_prop[prop].size = size;
4012 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4013 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4017 void rrd_graph_init(
4022 char *deffont = getenv("RRD_DEFAULT_FONT");
4023 static PangoFontMap *fontmap = NULL;
4024 PangoContext *context;
4031 im->daemon_addr = NULL;
4032 im->draw_x_grid = 1;
4033 im->draw_y_grid = 1;
4034 im->draw_3d_border = 2;
4035 im->dynamic_labels = 0;
4036 im->extra_flags = 0;
4037 im->font_options = cairo_font_options_create();
4038 im->forceleftspace = 0;
4041 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4042 im->grid_dash_off = 1;
4043 im->grid_dash_on = 1;
4045 im->grinfo = (rrd_info_t *) NULL;
4046 im->grinfo_current = (rrd_info_t *) NULL;
4047 im->imgformat = IF_PNG;
4050 im->legenddirection = TOP_DOWN;
4051 im->legendheight = 0;
4052 im->legendposition = SOUTH;
4053 im->legendwidth = 0;
4054 im->logarithmic = 0;
4060 im->rendered_image_size = 0;
4061 im->rendered_image = NULL;
4065 im->tabwidth = 40.0;
4066 im->title[0] = '\0';
4067 im->unitsexponent = 9999;
4068 im->unitslength = 6;
4069 im->viewfactor = 1.0;
4070 im->watermark[0] = '\0';
4071 im->with_markup = 0;
4073 im->xlab_user.minsec = -1;
4075 im->xOriginLegend = 0;
4076 im->xOriginLegendY = 0;
4077 im->xOriginLegendY2 = 0;
4078 im->xOriginTitle = 0;
4080 im->ygridstep = DNAN;
4082 im->ylegend[0] = '\0';
4083 im->second_axis_scale = 0; /* 0 disables it */
4084 im->second_axis_shift = 0; /* no shift by default */
4085 im->second_axis_legend[0] = '\0';
4086 im->second_axis_format[0] = '\0';
4088 im->yOriginLegend = 0;
4089 im->yOriginLegendY = 0;
4090 im->yOriginLegendY2 = 0;
4091 im->yOriginTitle = 0;
4095 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4096 im->cr = cairo_create(im->surface);
4098 for (i = 0; i < DIM(text_prop); i++) {
4099 im->text_prop[i].size = -1;
4100 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4103 if (fontmap == NULL){
4104 fontmap = pango_cairo_font_map_get_default();
4107 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4109 pango_cairo_context_set_resolution(context, 100);
4111 pango_cairo_update_context(im->cr,context);
4113 im->layout = pango_layout_new(context);
4115 // im->layout = pango_cairo_create_layout(im->cr);
4118 cairo_font_options_set_hint_style
4119 (im->font_options, CAIRO_HINT_STYLE_FULL);
4120 cairo_font_options_set_hint_metrics
4121 (im->font_options, CAIRO_HINT_METRICS_ON);
4122 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4126 for (i = 0; i < DIM(graph_col); i++)
4127 im->graph_col[i] = graph_col[i];
4133 void rrd_graph_options(
4140 char *parsetime_error = NULL;
4141 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4142 time_t start_tmp = 0, end_tmp = 0;
4144 rrd_time_value_t start_tv, end_tv;
4145 long unsigned int color;
4147 /* defines for long options without a short equivalent. should be bytes,
4148 and may not collide with (the ASCII value of) short options */
4149 #define LONGOPT_UNITS_SI 255
4152 struct option long_options[] = {
4153 { "alt-autoscale", no_argument, 0, 'A'},
4154 { "imgformat", required_argument, 0, 'a'},
4155 { "font-smoothing-threshold", required_argument, 0, 'B'},
4156 { "base", required_argument, 0, 'b'},
4157 { "color", required_argument, 0, 'c'},
4158 { "full-size-mode", no_argument, 0, 'D'},
4159 { "daemon", required_argument, 0, 'd'},
4160 { "slope-mode", no_argument, 0, 'E'},
4161 { "end", required_argument, 0, 'e'},
4162 { "force-rules-legend", no_argument, 0, 'F'},
4163 { "imginfo", required_argument, 0, 'f'},
4164 { "graph-render-mode", required_argument, 0, 'G'},
4165 { "no-legend", no_argument, 0, 'g'},
4166 { "height", required_argument, 0, 'h'},
4167 { "no-minor", no_argument, 0, 'I'},
4168 { "interlaced", no_argument, 0, 'i'},
4169 { "alt-autoscale-min", no_argument, 0, 'J'},
4170 { "only-graph", no_argument, 0, 'j'},
4171 { "units-length", required_argument, 0, 'L'},
4172 { "lower-limit", required_argument, 0, 'l'},
4173 { "alt-autoscale-max", no_argument, 0, 'M'},
4174 { "zoom", required_argument, 0, 'm'},
4175 { "no-gridfit", no_argument, 0, 'N'},
4176 { "font", required_argument, 0, 'n'},
4177 { "logarithmic", no_argument, 0, 'o'},
4178 { "pango-markup", no_argument, 0, 'P'},
4179 { "font-render-mode", required_argument, 0, 'R'},
4180 { "rigid", no_argument, 0, 'r'},
4181 { "step", required_argument, 0, 'S'},
4182 { "start", required_argument, 0, 's'},
4183 { "tabwidth", required_argument, 0, 'T'},
4184 { "title", required_argument, 0, 't'},
4185 { "upper-limit", required_argument, 0, 'u'},
4186 { "vertical-label", required_argument, 0, 'v'},
4187 { "watermark", required_argument, 0, 'W'},
4188 { "width", required_argument, 0, 'w'},
4189 { "units-exponent", required_argument, 0, 'X'},
4190 { "x-grid", required_argument, 0, 'x'},
4191 { "alt-y-grid", no_argument, 0, 'Y'},
4192 { "y-grid", required_argument, 0, 'y'},
4193 { "lazy", no_argument, 0, 'z'},
4194 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4195 { "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 */
4196 { "disable-rrdtool-tag",no_argument, 0, 1001},
4197 { "right-axis", required_argument, 0, 1002},
4198 { "right-axis-label", required_argument, 0, 1003},
4199 { "right-axis-format", required_argument, 0, 1004},
4200 { "legend-position", required_argument, 0, 1005},
4201 { "legend-direction", required_argument, 0, 1006},
4202 { "border", required_argument, 0, 1007},
4203 { "grid-dash", required_argument, 0, 1008},
4204 { "dynamic-labels", no_argument, 0, 1009},
4210 opterr = 0; /* initialize getopt */
4211 rrd_parsetime("end-24h", &start_tv);
4212 rrd_parsetime("now", &end_tv);
4214 int option_index = 0;
4216 int col_start, col_end;
4218 opt = getopt_long(argc, argv,
4219 "Aa:B:b:c:Dd:Ee:Ff:G:gh:IiJjL:l:Nn:Bb:oPR:rS:s:T:t:u:v:W:w:X:x:Yy:z",
4220 long_options, &option_index);
4225 im->extra_flags |= NOMINOR;
4228 im->extra_flags |= ALTYGRID;
4231 im->extra_flags |= ALTAUTOSCALE;
4234 im->extra_flags |= ALTAUTOSCALE_MIN;
4237 im->extra_flags |= ALTAUTOSCALE_MAX;
4240 im->extra_flags |= ONLY_GRAPH;
4243 im->extra_flags |= NOLEGEND;
4246 if (strcmp(optarg, "north") == 0) {
4247 im->legendposition = NORTH;
4248 } else if (strcmp(optarg, "west") == 0) {
4249 im->legendposition = WEST;
4250 } else if (strcmp(optarg, "south") == 0) {
4251 im->legendposition = SOUTH;
4252 } else if (strcmp(optarg, "east") == 0) {
4253 im->legendposition = EAST;
4255 rrd_set_error("unknown legend-position '%s'", optarg);
4260 if (strcmp(optarg, "topdown") == 0) {
4261 im->legenddirection = TOP_DOWN;
4262 } else if (strcmp(optarg, "bottomup") == 0) {
4263 im->legenddirection = BOTTOM_UP;
4265 rrd_set_error("unknown legend-position '%s'", optarg);
4270 im->extra_flags |= FORCE_RULES_LEGEND;
4273 im->extra_flags |= NO_RRDTOOL_TAG;
4275 case LONGOPT_UNITS_SI:
4276 if (im->extra_flags & FORCE_UNITS) {
4277 rrd_set_error("--units can only be used once!");
4280 if (strcmp(optarg, "si") == 0)
4281 im->extra_flags |= FORCE_UNITS_SI;
4283 rrd_set_error("invalid argument for --units: %s", optarg);
4288 im->unitsexponent = atoi(optarg);
4291 im->unitslength = atoi(optarg);
4292 im->forceleftspace = 1;
4295 im->tabwidth = atof(optarg);
4298 im->step = atoi(optarg);
4304 im->with_markup = 1;
4307 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4308 rrd_set_error("start time: %s", parsetime_error);
4313 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4314 rrd_set_error("end time: %s", parsetime_error);
4319 if (strcmp(optarg, "none") == 0) {
4320 im->draw_x_grid = 0;
4324 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4326 &im->xlab_user.gridst,
4328 &im->xlab_user.mgridst,
4330 &im->xlab_user.labst,
4331 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4332 strncpy(im->xlab_form, optarg + stroff,
4333 sizeof(im->xlab_form) - 1);
4334 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4336 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4337 rrd_set_error("unknown keyword %s", scan_gtm);
4340 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4342 rrd_set_error("unknown keyword %s", scan_mtm);
4345 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4346 rrd_set_error("unknown keyword %s", scan_ltm);
4349 im->xlab_user.minsec = 1;
4350 im->xlab_user.stst = im->xlab_form;
4352 rrd_set_error("invalid x-grid format");
4358 if (strcmp(optarg, "none") == 0) {
4359 im->draw_y_grid = 0;
4362 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4363 if (im->ygridstep <= 0) {
4364 rrd_set_error("grid step must be > 0");
4366 } else if (im->ylabfact < 1) {
4367 rrd_set_error("label factor must be > 0");
4371 rrd_set_error("invalid y-grid format");
4376 im->draw_3d_border = atoi(optarg);
4378 case 1008: /* grid-dash */
4382 &im->grid_dash_off) != 2) {
4383 rrd_set_error("expected grid-dash format float:float");
4387 case 1009: /* enable dynamic labels */
4388 im->dynamic_labels = 1;
4390 case 1002: /* right y axis */
4394 &im->second_axis_scale,
4395 &im->second_axis_shift) == 2) {
4396 if(im->second_axis_scale==0){
4397 rrd_set_error("the second_axis_scale must not be 0");
4401 rrd_set_error("invalid right-axis format expected scale:shift");
4406 strncpy(im->second_axis_legend,optarg,150);
4407 im->second_axis_legend[150]='\0';
4410 if (bad_format(optarg)){
4411 rrd_set_error("use either %le or %lf formats");
4414 strncpy(im->second_axis_format,optarg,150);
4415 im->second_axis_format[150]='\0';
4418 strncpy(im->ylegend, optarg, 150);
4419 im->ylegend[150] = '\0';
4422 im->maxval = atof(optarg);
4425 im->minval = atof(optarg);
4428 im->base = atol(optarg);
4429 if (im->base != 1024 && im->base != 1000) {
4431 ("the only sensible value for base apart from 1000 is 1024");
4436 long_tmp = atol(optarg);
4437 if (long_tmp < 10) {
4438 rrd_set_error("width below 10 pixels");
4441 im->xsize = long_tmp;
4444 long_tmp = atol(optarg);
4445 if (long_tmp < 10) {
4446 rrd_set_error("height below 10 pixels");
4449 im->ysize = long_tmp;
4452 im->extra_flags |= FULL_SIZE_MODE;
4455 /* interlaced png not supported at the moment */
4461 im->imginfo = optarg;
4465 (im->imgformat = if_conv(optarg)) == -1) {
4466 rrd_set_error("unsupported graphics format '%s'", optarg);
4477 im->logarithmic = 1;
4481 "%10[A-Z]#%n%8lx%n",
4482 col_nam, &col_start, &color, &col_end) == 2) {
4484 int col_len = col_end - col_start;
4489 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4497 (((color & 0xF000) *
4498 0x11000) | ((color & 0x0F00) *
4499 0x01100) | ((color &
4502 ((color & 0x000F) * 0x00011)
4506 color = (color << 8) + 0xff /* shift left by 8 */ ;
4511 rrd_set_error("the color format is #RRGGBB[AA]");
4514 if ((ci = grc_conv(col_nam)) != -1) {
4515 im->graph_col[ci] = gfx_hex_to_col(color);
4517 rrd_set_error("invalid color name '%s'", col_nam);
4521 rrd_set_error("invalid color def format");
4530 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4531 int sindex, propidx;
4533 if ((sindex = text_prop_conv(prop)) != -1) {
4534 for (propidx = sindex;
4535 propidx < TEXT_PROP_LAST; propidx++) {
4537 rrd_set_font_desc(im,propidx,NULL,size);
4539 if ((int) strlen(optarg) > end+2) {
4540 if (optarg[end] == ':') {
4541 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4544 ("expected : after font size in '%s'",
4549 /* only run the for loop for DEFAULT (0) for
4550 all others, we break here. woodo programming */
4551 if (propidx == sindex && sindex != 0)
4555 rrd_set_error("invalid fonttag '%s'", prop);
4559 rrd_set_error("invalid text property format");
4565 im->zoom = atof(optarg);
4566 if (im->zoom <= 0.0) {
4567 rrd_set_error("zoom factor must be > 0");
4572 strncpy(im->title, optarg, 150);
4573 im->title[150] = '\0';
4576 if (strcmp(optarg, "normal") == 0) {
4577 cairo_font_options_set_antialias
4578 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4579 cairo_font_options_set_hint_style
4580 (im->font_options, CAIRO_HINT_STYLE_FULL);
4581 } else if (strcmp(optarg, "light") == 0) {
4582 cairo_font_options_set_antialias
4583 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4584 cairo_font_options_set_hint_style
4585 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4586 } else if (strcmp(optarg, "mono") == 0) {
4587 cairo_font_options_set_antialias
4588 (im->font_options, CAIRO_ANTIALIAS_NONE);
4589 cairo_font_options_set_hint_style
4590 (im->font_options, CAIRO_HINT_STYLE_FULL);
4592 rrd_set_error("unknown font-render-mode '%s'", optarg);
4597 if (strcmp(optarg, "normal") == 0)
4598 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4599 else if (strcmp(optarg, "mono") == 0)
4600 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4602 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4607 /* not supported curently */
4610 strncpy(im->watermark, optarg, 100);
4611 im->watermark[99] = '\0';
4615 if (im->daemon_addr != NULL)
4617 rrd_set_error ("You cannot specify --daemon "
4622 im->daemon_addr = strdup(optarg);
4623 if (im->daemon_addr == NULL)
4625 rrd_set_error("strdup failed");
4633 rrd_set_error("unknown option '%c'", optopt);
4635 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4640 { /* try to connect to rrdcached */
4641 int status = rrdc_connect(im->daemon_addr);
4642 if (status != 0) return;
4645 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4646 pango_layout_context_changed(im->layout);
4650 if (im->logarithmic && im->minval <= 0) {
4652 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4656 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4657 /* error string is set in rrd_parsetime.c */
4661 if (start_tmp < 3600 * 24 * 365 * 10) {
4663 ("the first entry to fetch should be after 1980 (%ld)",
4668 if (end_tmp < start_tmp) {
4670 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4674 im->start = start_tmp;
4676 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4679 int rrd_graph_color(
4687 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4689 color = strstr(var, "#");
4690 if (color == NULL) {
4691 if (optional == 0) {
4692 rrd_set_error("Found no color in %s", err);
4699 long unsigned int col;
4701 rest = strstr(color, ":");
4708 sscanf(color, "#%6lx%n", &col, &n);
4709 col = (col << 8) + 0xff /* shift left by 8 */ ;
4711 rrd_set_error("Color problem in %s", err);
4714 sscanf(color, "#%8lx%n", &col, &n);
4718 rrd_set_error("Color problem in %s", err);
4720 if (rrd_test_error())
4722 gdp->col = gfx_hex_to_col(col);
4735 while (*ptr != '\0')
4736 if (*ptr++ == '%') {
4738 /* line cannot end with percent char */
4741 /* '%s', '%S' and '%%' are allowed */
4742 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4744 /* %c is allowed (but use only with vdef!) */
4745 else if (*ptr == 'c') {
4750 /* or else '% 6.2lf' and such are allowed */
4752 /* optional padding character */
4753 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4755 /* This should take care of 'm.n' with all three optional */
4756 while (*ptr >= '0' && *ptr <= '9')
4760 while (*ptr >= '0' && *ptr <= '9')
4762 /* Either 'le', 'lf' or 'lg' must follow here */
4765 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4780 const char *const str)
4782 /* A VDEF currently is either "func" or "param,func"
4783 * so the parsing is rather simple. Change if needed.
4790 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4791 if (n == (int) strlen(str)) { /* matched */
4795 sscanf(str, "%29[A-Z]%n", func, &n);
4796 if (n == (int) strlen(str)) { /* matched */
4800 ("Unknown function string '%s' in VDEF '%s'",
4805 if (!strcmp("PERCENT", func))
4806 gdes->vf.op = VDEF_PERCENT;
4807 else if (!strcmp("PERCENTNAN", func))
4808 gdes->vf.op = VDEF_PERCENTNAN;
4809 else if (!strcmp("MAXIMUM", func))
4810 gdes->vf.op = VDEF_MAXIMUM;
4811 else if (!strcmp("AVERAGE", func))
4812 gdes->vf.op = VDEF_AVERAGE;
4813 else if (!strcmp("STDEV", func))
4814 gdes->vf.op = VDEF_STDEV;
4815 else if (!strcmp("MINIMUM", func))
4816 gdes->vf.op = VDEF_MINIMUM;
4817 else if (!strcmp("TOTAL", func))
4818 gdes->vf.op = VDEF_TOTAL;
4819 else if (!strcmp("FIRST", func))
4820 gdes->vf.op = VDEF_FIRST;
4821 else if (!strcmp("LAST", func))
4822 gdes->vf.op = VDEF_LAST;
4823 else if (!strcmp("LSLSLOPE", func))
4824 gdes->vf.op = VDEF_LSLSLOPE;
4825 else if (!strcmp("LSLINT", func))
4826 gdes->vf.op = VDEF_LSLINT;
4827 else if (!strcmp("LSLCORREL", func))
4828 gdes->vf.op = VDEF_LSLCORREL;
4831 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4834 switch (gdes->vf.op) {
4836 case VDEF_PERCENTNAN:
4837 if (isnan(param)) { /* no parameter given */
4839 ("Function '%s' needs parameter in VDEF '%s'\n",
4843 if (param >= 0.0 && param <= 100.0) {
4844 gdes->vf.param = param;
4845 gdes->vf.val = DNAN; /* undefined */
4846 gdes->vf.when = 0; /* undefined */
4849 ("Parameter '%f' out of range in VDEF '%s'\n",
4850 param, gdes->vname);
4863 case VDEF_LSLCORREL:
4865 gdes->vf.param = DNAN;
4866 gdes->vf.val = DNAN;
4870 ("Function '%s' needs no parameter in VDEF '%s'\n",
4884 graph_desc_t *src, *dst;
4888 dst = &im->gdes[gdi];
4889 src = &im->gdes[dst->vidx];
4890 data = src->data + src->ds;
4892 steps = (src->end - src->start) / src->step;
4895 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4896 src->start, src->end, steps);
4898 switch (dst->vf.op) {
4902 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4903 rrd_set_error("malloc VDEV_PERCENT");
4906 for (step = 0; step < steps; step++) {
4907 array[step] = data[step * src->ds_cnt];
4909 qsort(array, step, sizeof(double), vdef_percent_compar);
4910 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4911 dst->vf.val = array[field];
4912 dst->vf.when = 0; /* no time component */
4915 for (step = 0; step < steps; step++)
4916 printf("DEBUG: %3li:%10.2f %c\n",
4917 step, array[step], step == field ? '*' : ' ');
4921 case VDEF_PERCENTNAN:{
4924 /* count number of "valid" values */
4926 for (step = 0; step < steps; step++) {
4927 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4929 /* and allocate it */
4930 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4931 rrd_set_error("malloc VDEV_PERCENT");
4934 /* and fill it in */
4936 for (step = 0; step < steps; step++) {
4937 if (!isnan(data[step * src->ds_cnt])) {
4938 array[field] = data[step * src->ds_cnt];
4942 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4943 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4944 dst->vf.val = array[field];
4945 dst->vf.when = 0; /* no time component */
4951 while (step != steps && isnan(data[step * src->ds_cnt]))
4953 if (step == steps) {
4957 dst->vf.val = data[step * src->ds_cnt];
4958 dst->vf.when = src->start + (step + 1) * src->step;
4960 while (step != steps) {
4961 if (finite(data[step * src->ds_cnt])) {
4962 if (data[step * src->ds_cnt] > dst->vf.val) {
4963 dst->vf.val = data[step * src->ds_cnt];
4964 dst->vf.when = src->start + (step + 1) * src->step;
4975 double average = 0.0;
4977 for (step = 0; step < steps; step++) {
4978 if (finite(data[step * src->ds_cnt])) {
4979 sum += data[step * src->ds_cnt];
4984 if (dst->vf.op == VDEF_TOTAL) {
4985 dst->vf.val = sum * src->step;
4986 dst->vf.when = 0; /* no time component */
4987 } else if (dst->vf.op == VDEF_AVERAGE) {
4988 dst->vf.val = sum / cnt;
4989 dst->vf.when = 0; /* no time component */
4991 average = sum / cnt;
4993 for (step = 0; step < steps; step++) {
4994 if (finite(data[step * src->ds_cnt])) {
4995 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4998 dst->vf.val = pow(sum / cnt, 0.5);
4999 dst->vf.when = 0; /* no time component */
5009 while (step != steps && isnan(data[step * src->ds_cnt]))
5011 if (step == steps) {
5015 dst->vf.val = data[step * src->ds_cnt];
5016 dst->vf.when = src->start + (step + 1) * src->step;
5018 while (step != steps) {
5019 if (finite(data[step * src->ds_cnt])) {
5020 if (data[step * src->ds_cnt] < dst->vf.val) {
5021 dst->vf.val = data[step * src->ds_cnt];
5022 dst->vf.when = src->start + (step + 1) * src->step;
5029 /* The time value returned here is one step before the
5030 * actual time value. This is the start of the first
5034 while (step != steps && isnan(data[step * src->ds_cnt]))
5036 if (step == steps) { /* all entries were NaN */
5040 dst->vf.val = data[step * src->ds_cnt];
5041 dst->vf.when = src->start + step * src->step;
5045 /* The time value returned here is the
5046 * actual time value. This is the end of the last
5050 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5052 if (step < 0) { /* all entries were NaN */
5056 dst->vf.val = data[step * src->ds_cnt];
5057 dst->vf.when = src->start + (step + 1) * src->step;
5062 case VDEF_LSLCORREL:{
5063 /* Bestfit line by linear least squares method */
5066 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5073 for (step = 0; step < steps; step++) {
5074 if (finite(data[step * src->ds_cnt])) {
5077 SUMxx += step * step;
5078 SUMxy += step * data[step * src->ds_cnt];
5079 SUMy += data[step * src->ds_cnt];
5080 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5084 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5085 y_intercept = (SUMy - slope * SUMx) / cnt;
5088 (SUMx * SUMy) / cnt) /
5090 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5092 if (dst->vf.op == VDEF_LSLSLOPE) {
5093 dst->vf.val = slope;
5095 } else if (dst->vf.op == VDEF_LSLINT) {
5096 dst->vf.val = y_intercept;
5098 } else if (dst->vf.op == VDEF_LSLCORREL) {
5099 dst->vf.val = correl;
5112 /* NaN < -INF < finite_values < INF */
5113 int vdef_percent_compar(
5119 /* Equality is not returned; this doesn't hurt except
5120 * (maybe) for a little performance.
5123 /* First catch NaN values. They are smallest */
5124 if (isnan(*(double *) a))
5126 if (isnan(*(double *) b))
5128 /* NaN doesn't reach this part so INF and -INF are extremes.
5129 * The sign from isinf() is compatible with the sign we return
5131 if (isinf(*(double *) a))
5132 return isinf(*(double *) a);
5133 if (isinf(*(double *) b))
5134 return isinf(*(double *) b);
5135 /* If we reach this, both values must be finite */
5136 if (*(double *) a < *(double *) b)
5145 rrd_info_type_t type,
5146 rrd_infoval_t value)
5148 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5149 if (im->grinfo == NULL) {
5150 im->grinfo = im->grinfo_current;