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 first_weekday = nl_langinfo(_NL_TIME_FIRST_WEEKDAY)[0] - 1;
1372 return first_weekday;
1375 /* identify the point where the first gridline, label ... gets placed */
1377 time_t find_first_time(
1378 time_t start, /* what is the initial time */
1379 enum tmt_en baseint, /* what is the basic interval */
1380 long basestep /* how many if these do we jump a time */
1385 localtime_r(&start, &tm);
1389 tm. tm_sec -= tm.tm_sec % basestep;
1394 tm. tm_min -= tm.tm_min % basestep;
1400 tm. tm_hour -= tm.tm_hour % basestep;
1404 /* we do NOT look at the basestep for this ... */
1411 /* we do NOT look at the basestep for this ... */
1415 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1417 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1418 tm. tm_mday -= 7; /* we want the *previous* week */
1426 tm. tm_mon -= tm.tm_mon % basestep;
1437 tm.tm_year + 1900) %basestep;
1443 /* identify the point where the next gridline, label ... gets placed */
1444 time_t find_next_time(
1445 time_t current, /* what is the initial time */
1446 enum tmt_en baseint, /* what is the basic interval */
1447 long basestep /* how many if these do we jump a time */
1453 localtime_r(¤t, &tm);
1458 tm. tm_sec += basestep;
1462 tm. tm_min += basestep;
1466 tm. tm_hour += basestep;
1470 tm. tm_mday += basestep;
1474 tm. tm_mday += 7 * basestep;
1478 tm. tm_mon += basestep;
1482 tm. tm_year += basestep;
1484 madetime = mktime(&tm);
1485 } while (madetime == -1); /* this is necessary to skip impssible times
1486 like the daylight saving time skips */
1492 /* calculate values required for PRINT and GPRINT functions */
1497 long i, ii, validsteps;
1500 int graphelement = 0;
1503 double magfact = -1;
1508 /* wow initializing tmvdef is quite a task :-) */
1509 time_t now = time(NULL);
1511 localtime_r(&now, &tmvdef);
1512 for (i = 0; i < im->gdes_c; i++) {
1513 vidx = im->gdes[i].vidx;
1514 switch (im->gdes[i].gf) {
1517 /* PRINT and GPRINT can now print VDEF generated values.
1518 * There's no need to do any calculations on them as these
1519 * calculations were already made.
1521 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1522 printval = im->gdes[vidx].vf.val;
1523 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1524 } else { /* need to calculate max,min,avg etcetera */
1525 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1526 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1529 for (ii = im->gdes[vidx].ds;
1530 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1531 if (!finite(im->gdes[vidx].data[ii]))
1533 if (isnan(printval)) {
1534 printval = im->gdes[vidx].data[ii];
1539 switch (im->gdes[i].cf) {
1543 case CF_DEVSEASONAL:
1547 printval += im->gdes[vidx].data[ii];
1550 printval = min(printval, im->gdes[vidx].data[ii]);
1554 printval = max(printval, im->gdes[vidx].data[ii]);
1557 printval = im->gdes[vidx].data[ii];
1560 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1561 if (validsteps > 1) {
1562 printval = (printval / validsteps);
1565 } /* prepare printval */
1567 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1568 /* Magfact is set to -1 upon entry to print_calc. If it
1569 * is still less than 0, then we need to run auto_scale.
1570 * Otherwise, put the value into the correct units. If
1571 * the value is 0, then do not set the symbol or magnification
1572 * so next the calculation will be performed again. */
1573 if (magfact < 0.0) {
1574 auto_scale(im, &printval, &si_symb, &magfact);
1575 if (printval == 0.0)
1578 printval /= magfact;
1580 *(++percent_s) = 's';
1581 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1582 auto_scale(im, &printval, &si_symb, &magfact);
1585 if (im->gdes[i].gf == GF_PRINT) {
1586 rrd_infoval_t prline;
1588 if (im->gdes[i].strftm) {
1589 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1590 strftime(prline.u_str,
1591 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1592 } else if (bad_format(im->gdes[i].format)) {
1594 ("bad format for PRINT in '%s'", im->gdes[i].format);
1598 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1602 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1607 if (im->gdes[i].strftm) {
1608 strftime(im->gdes[i].legend,
1609 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1611 if (bad_format(im->gdes[i].format)) {
1613 ("bad format for GPRINT in '%s'",
1614 im->gdes[i].format);
1617 #ifdef HAVE_SNPRINTF
1618 snprintf(im->gdes[i].legend,
1620 im->gdes[i].format, printval, si_symb);
1622 sprintf(im->gdes[i].legend,
1623 im->gdes[i].format, printval, si_symb);
1635 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1636 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1641 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1642 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1651 #ifdef WITH_PIECHART
1659 ("STACK should already be turned into LINE or AREA here");
1664 return graphelement;
1669 /* place legends with color spots */
1675 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1676 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1677 int fill = 0, fill_last;
1678 double legendwidth; // = im->ximg - 2 * border;
1680 double leg_x = border;
1681 int leg_y = 0; //im->yimg;
1682 int leg_y_prev = 0; // im->yimg;
1685 int i, ii, mark = 0;
1686 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1689 char saved_legend[FMT_LEG_LEN + 5];
1695 legendwidth = im->legendwidth - 2 * border;
1699 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1700 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1701 rrd_set_error("malloc for legspace");
1705 for (i = 0; i < im->gdes_c; i++) {
1706 char prt_fctn; /*special printfunctions */
1708 strcpy(saved_legend, im->gdes[i].legend);
1712 /* hide legends for rules which are not displayed */
1713 if (im->gdes[i].gf == GF_TEXTALIGN) {
1714 default_txtalign = im->gdes[i].txtalign;
1717 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1718 if (im->gdes[i].gf == GF_HRULE
1719 && (im->gdes[i].yrule <
1720 im->minval || im->gdes[i].yrule > im->maxval))
1721 im->gdes[i].legend[0] = '\0';
1722 if (im->gdes[i].gf == GF_VRULE
1723 && (im->gdes[i].xrule <
1724 im->start || im->gdes[i].xrule > im->end))
1725 im->gdes[i].legend[0] = '\0';
1728 /* turn \\t into tab */
1729 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1730 memmove(tab, tab + 1, strlen(tab));
1734 leg_cc = strlen(im->gdes[i].legend);
1735 /* is there a controle code at the end of the legend string ? */
1736 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1737 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1739 im->gdes[i].legend[leg_cc] = '\0';
1743 /* only valid control codes */
1744 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1749 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1752 ("Unknown control code at the end of '%s\\%c'",
1753 im->gdes[i].legend, prt_fctn);
1757 if (prt_fctn == 'n') {
1761 /* remove exess space from the end of the legend for \g */
1762 while (prt_fctn == 'g' &&
1763 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1765 im->gdes[i].legend[leg_cc] = '\0';
1770 /* no interleg space if string ends in \g */
1771 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1773 fill += legspace[i];
1776 gfx_get_text_width(im,
1782 im->tabwidth, im->gdes[i].legend);
1787 /* who said there was a special tag ... ? */
1788 if (prt_fctn == 'g') {
1792 if (prt_fctn == '\0') {
1793 if(calc_width && (fill > legendwidth)){
1796 if (i == im->gdes_c - 1 || fill > legendwidth) {
1797 /* just one legend item is left right or center */
1798 switch (default_txtalign) {
1813 /* is it time to place the legends ? */
1814 if (fill > legendwidth) {
1822 if (leg_c == 1 && prt_fctn == 'j') {
1827 if (prt_fctn != '\0') {
1829 if (leg_c >= 2 && prt_fctn == 'j') {
1830 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1834 if (prt_fctn == 'c')
1835 leg_x = (double)(legendwidth - fill) / 2.0;
1836 if (prt_fctn == 'r')
1837 leg_x = legendwidth - fill + border;
1838 for (ii = mark; ii <= i; ii++) {
1839 if (im->gdes[ii].legend[0] == '\0')
1840 continue; /* skip empty legends */
1841 im->gdes[ii].leg_x = leg_x;
1842 im->gdes[ii].leg_y = leg_y + border;
1844 (double)gfx_get_text_width(im, leg_x,
1849 im->tabwidth, im->gdes[ii].legend)
1850 +(double)legspace[ii]
1854 if (leg_x > border || prt_fctn == 's')
1855 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1856 if (prt_fctn == 's')
1857 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1858 if (prt_fctn == 'u')
1859 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1861 if(calc_width && (fill > legendwidth)){
1870 strcpy(im->gdes[i].legend, saved_legend);
1875 im->legendwidth = legendwidth + 2 * border;
1878 im->legendheight = leg_y + border * 0.6;
1885 /* create a grid on the graph. it determines what to do
1886 from the values of xsize, start and end */
1888 /* the xaxis labels are determined from the number of seconds per pixel
1889 in the requested graph */
1891 int calc_horizontal_grid(
1899 int decimals, fractionals;
1901 im->ygrid_scale.labfact = 2;
1902 range = im->maxval - im->minval;
1903 scaledrange = range / im->magfact;
1904 /* does the scale of this graph make it impossible to put lines
1905 on it? If so, give up. */
1906 if (isnan(scaledrange)) {
1910 /* find grid spaceing */
1912 if (isnan(im->ygridstep)) {
1913 if (im->extra_flags & ALTYGRID) {
1914 /* find the value with max number of digits. Get number of digits */
1917 (max(fabs(im->maxval), fabs(im->minval)) *
1918 im->viewfactor / im->magfact));
1919 if (decimals <= 0) /* everything is small. make place for zero */
1921 im->ygrid_scale.gridstep =
1923 floor(log10(range * im->viewfactor / im->magfact))) /
1924 im->viewfactor * im->magfact;
1925 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1926 im->ygrid_scale.gridstep = 0.1;
1927 /* should have at least 5 lines but no more then 15 */
1928 if (range / im->ygrid_scale.gridstep < 5
1929 && im->ygrid_scale.gridstep >= 30)
1930 im->ygrid_scale.gridstep /= 10;
1931 if (range / im->ygrid_scale.gridstep > 15)
1932 im->ygrid_scale.gridstep *= 10;
1933 if (range / im->ygrid_scale.gridstep > 5) {
1934 im->ygrid_scale.labfact = 1;
1935 if (range / im->ygrid_scale.gridstep > 8
1936 || im->ygrid_scale.gridstep <
1937 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1938 im->ygrid_scale.labfact = 2;
1940 im->ygrid_scale.gridstep /= 5;
1941 im->ygrid_scale.labfact = 5;
1945 (im->ygrid_scale.gridstep *
1946 (double) im->ygrid_scale.labfact * im->viewfactor /
1948 if (fractionals < 0) { /* small amplitude. */
1949 int len = decimals - fractionals + 1;
1951 if (im->unitslength < len + 2)
1952 im->unitslength = len + 2;
1953 sprintf(im->ygrid_scale.labfmt,
1955 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1957 int len = decimals + 1;
1959 if (im->unitslength < len + 2)
1960 im->unitslength = len + 2;
1961 sprintf(im->ygrid_scale.labfmt,
1962 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1964 } else { /* classic rrd grid */
1965 for (i = 0; ylab[i].grid > 0; i++) {
1966 pixel = im->ysize / (scaledrange / ylab[i].grid);
1972 for (i = 0; i < 4; i++) {
1973 if (pixel * ylab[gridind].lfac[i] >=
1974 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1975 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1980 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1983 im->ygrid_scale.gridstep = im->ygridstep;
1984 im->ygrid_scale.labfact = im->ylabfact;
1989 int draw_horizontal_grid(
1995 char graph_label[100];
1997 double X0 = im->xorigin;
1998 double X1 = im->xorigin + im->xsize;
1999 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2000 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2002 double second_axis_magfact = 0;
2003 char *second_axis_symb = "";
2006 im->ygrid_scale.gridstep /
2007 (double) im->magfact * (double) im->viewfactor;
2008 MaxY = scaledstep * (double) egrid;
2009 for (i = sgrid; i <= egrid; i++) {
2011 im->ygrid_scale.gridstep * i);
2013 im->ygrid_scale.gridstep * (i + 1));
2015 if (floor(Y0 + 0.5) >=
2016 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2017 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2018 with the chosen settings. Add a label if required by settings, or if
2019 there is only one label so far and the next grid line is out of bounds. */
2020 if (i % im->ygrid_scale.labfact == 0
2022 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2023 if (im->symbol == ' ') {
2024 if (im->extra_flags & ALTYGRID) {
2025 sprintf(graph_label,
2026 im->ygrid_scale.labfmt,
2027 scaledstep * (double) i);
2030 sprintf(graph_label, "%4.1f",
2031 scaledstep * (double) i);
2033 sprintf(graph_label, "%4.0f",
2034 scaledstep * (double) i);
2038 char sisym = (i == 0 ? ' ' : im->symbol);
2040 if (im->extra_flags & ALTYGRID) {
2041 sprintf(graph_label,
2042 im->ygrid_scale.labfmt,
2043 scaledstep * (double) i, sisym);
2046 sprintf(graph_label, "%4.1f %c",
2047 scaledstep * (double) i, sisym);
2049 sprintf(graph_label, "%4.0f %c",
2050 scaledstep * (double) i, sisym);
2055 if (im->second_axis_scale != 0){
2056 char graph_label_right[100];
2057 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2058 if (im->second_axis_format[0] == '\0'){
2059 if (!second_axis_magfact){
2060 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2061 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2063 sval /= second_axis_magfact;
2066 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2068 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2072 sprintf(graph_label_right,im->second_axis_format,sval);
2076 im->graph_col[GRC_FONT],
2077 im->text_prop[TEXT_PROP_AXIS].font_desc,
2078 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2079 graph_label_right );
2085 text_prop[TEXT_PROP_AXIS].
2087 im->graph_col[GRC_FONT],
2089 text_prop[TEXT_PROP_AXIS].
2092 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2093 gfx_line(im, X0 - 2, Y0, X0, Y0,
2094 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2095 gfx_line(im, X1, Y0, X1 + 2, Y0,
2096 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2097 gfx_dashed_line(im, X0 - 2, Y0,
2103 im->grid_dash_on, im->grid_dash_off);
2104 } else if (!(im->extra_flags & NOMINOR)) {
2107 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2108 gfx_line(im, X1, Y0, X1 + 2, Y0,
2109 GRIDWIDTH, im->graph_col[GRC_GRID]);
2110 gfx_dashed_line(im, X0 - 1, Y0,
2114 graph_col[GRC_GRID],
2115 im->grid_dash_on, im->grid_dash_off);
2122 /* this is frexp for base 10 */
2133 iexp = floor(log((double)fabs(x)) / log((double)10));
2134 mnt = x / pow(10.0, iexp);
2137 mnt = x / pow(10.0, iexp);
2144 /* logaritmic horizontal grid */
2145 int horizontal_log_grid(
2149 double yloglab[][10] = {
2151 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2153 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2155 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2172 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2174 int i, j, val_exp, min_exp;
2175 double nex; /* number of decades in data */
2176 double logscale; /* scale in logarithmic space */
2177 int exfrac = 1; /* decade spacing */
2178 int mid = -1; /* row in yloglab for major grid */
2179 double mspac; /* smallest major grid spacing (pixels) */
2180 int flab; /* first value in yloglab to use */
2181 double value, tmp, pre_value;
2183 char graph_label[100];
2185 nex = log10(im->maxval / im->minval);
2186 logscale = im->ysize / nex;
2187 /* major spacing for data with high dynamic range */
2188 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2195 /* major spacing for less dynamic data */
2197 /* search best row in yloglab */
2199 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2200 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2203 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2206 /* find first value in yloglab */
2208 yloglab[mid][flab] < 10
2209 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2210 if (yloglab[mid][flab] == 10.0) {
2215 if (val_exp % exfrac)
2216 val_exp += abs(-val_exp % exfrac);
2218 X1 = im->xorigin + im->xsize;
2223 value = yloglab[mid][flab] * pow(10.0, val_exp);
2224 if (AlmostEqual2sComplement(value, pre_value, 4))
2225 break; /* it seems we are not converging */
2227 Y0 = ytr(im, value);
2228 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2230 /* major grid line */
2232 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2233 gfx_line(im, X1, Y0, X1 + 2, Y0,
2234 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2235 gfx_dashed_line(im, X0 - 2, Y0,
2240 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2242 if (im->extra_flags & FORCE_UNITS_SI) {
2247 scale = floor(val_exp / 3.0);
2249 pvalue = pow(10.0, val_exp % 3);
2251 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2252 pvalue *= yloglab[mid][flab];
2253 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2254 && ((scale + si_symbcenter) >= 0))
2255 symbol = si_symbol[scale + si_symbcenter];
2258 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2260 sprintf(graph_label, "%3.0e", value);
2262 if (im->second_axis_scale != 0){
2263 char graph_label_right[100];
2264 double sval = value*im->second_axis_scale+im->second_axis_shift;
2265 if (im->second_axis_format[0] == '\0'){
2266 if (im->extra_flags & FORCE_UNITS_SI) {
2269 auto_scale(im,&sval,&symb,&mfac);
2270 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2273 sprintf(graph_label_right,"%3.0e", sval);
2277 sprintf(graph_label_right,im->second_axis_format,sval);
2282 im->graph_col[GRC_FONT],
2283 im->text_prop[TEXT_PROP_AXIS].font_desc,
2284 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2285 graph_label_right );
2291 text_prop[TEXT_PROP_AXIS].
2293 im->graph_col[GRC_FONT],
2295 text_prop[TEXT_PROP_AXIS].
2298 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2300 if (mid < 4 && exfrac == 1) {
2301 /* find first and last minor line behind current major line
2302 * i is the first line and j tha last */
2304 min_exp = val_exp - 1;
2305 for (i = 1; yloglab[mid][i] < 10.0; i++);
2306 i = yloglab[mid][i - 1] + 1;
2310 i = yloglab[mid][flab - 1] + 1;
2311 j = yloglab[mid][flab];
2314 /* draw minor lines below current major line */
2315 for (; i < j; i++) {
2317 value = i * pow(10.0, min_exp);
2318 if (value < im->minval)
2320 Y0 = ytr(im, value);
2321 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2326 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2327 gfx_line(im, X1, Y0, X1 + 2, Y0,
2328 GRIDWIDTH, im->graph_col[GRC_GRID]);
2329 gfx_dashed_line(im, X0 - 1, Y0,
2333 graph_col[GRC_GRID],
2334 im->grid_dash_on, im->grid_dash_off);
2336 } else if (exfrac > 1) {
2337 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2338 value = pow(10.0, i);
2339 if (value < im->minval)
2341 Y0 = ytr(im, value);
2342 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2347 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2348 gfx_line(im, X1, Y0, X1 + 2, Y0,
2349 GRIDWIDTH, im->graph_col[GRC_GRID]);
2350 gfx_dashed_line(im, X0 - 1, Y0,
2354 graph_col[GRC_GRID],
2355 im->grid_dash_on, im->grid_dash_off);
2360 if (yloglab[mid][++flab] == 10.0) {
2366 /* draw minor lines after highest major line */
2367 if (mid < 4 && exfrac == 1) {
2368 /* find first and last minor line below current major line
2369 * i is the first line and j tha last */
2371 min_exp = val_exp - 1;
2372 for (i = 1; yloglab[mid][i] < 10.0; i++);
2373 i = yloglab[mid][i - 1] + 1;
2377 i = yloglab[mid][flab - 1] + 1;
2378 j = yloglab[mid][flab];
2381 /* draw minor lines below current major line */
2382 for (; i < j; i++) {
2384 value = i * pow(10.0, min_exp);
2385 if (value < im->minval)
2387 Y0 = ytr(im, value);
2388 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2392 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2393 gfx_line(im, X1, Y0, X1 + 2, Y0,
2394 GRIDWIDTH, im->graph_col[GRC_GRID]);
2395 gfx_dashed_line(im, X0 - 1, Y0,
2399 graph_col[GRC_GRID],
2400 im->grid_dash_on, im->grid_dash_off);
2403 /* fancy minor gridlines */
2404 else if (exfrac > 1) {
2405 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2406 value = pow(10.0, i);
2407 if (value < im->minval)
2409 Y0 = ytr(im, value);
2410 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2414 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2415 gfx_line(im, X1, Y0, X1 + 2, Y0,
2416 GRIDWIDTH, im->graph_col[GRC_GRID]);
2417 gfx_dashed_line(im, X0 - 1, Y0,
2421 graph_col[GRC_GRID],
2422 im->grid_dash_on, im->grid_dash_off);
2433 int xlab_sel; /* which sort of label and grid ? */
2434 time_t ti, tilab, timajor;
2436 char graph_label[100];
2437 double X0, Y0, Y1; /* points for filled graph and more */
2440 /* the type of time grid is determined by finding
2441 the number of seconds per pixel in the graph */
2442 if (im->xlab_user.minsec == -1) {
2443 factor = (im->end - im->start) / im->xsize;
2445 while (xlab[xlab_sel + 1].minsec !=
2446 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2448 } /* pick the last one */
2449 while (xlab[xlab_sel - 1].minsec ==
2450 xlab[xlab_sel].minsec
2451 && xlab[xlab_sel].length > (im->end - im->start)) {
2453 } /* go back to the smallest size */
2454 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2455 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2456 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2457 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2458 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2459 im->xlab_user.labst = xlab[xlab_sel].labst;
2460 im->xlab_user.precis = xlab[xlab_sel].precis;
2461 im->xlab_user.stst = xlab[xlab_sel].stst;
2464 /* y coords are the same for every line ... */
2466 Y1 = im->yorigin - im->ysize;
2467 /* paint the minor grid */
2468 if (!(im->extra_flags & NOMINOR)) {
2469 for (ti = find_first_time(im->start,
2477 find_first_time(im->start,
2484 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2486 /* are we inside the graph ? */
2487 if (ti < im->start || ti > im->end)
2489 while (timajor < ti) {
2490 timajor = find_next_time(timajor,
2493 mgridtm, im->xlab_user.mgridst);
2496 continue; /* skip as falls on major grid line */
2498 gfx_line(im, X0, Y1 - 2, X0, Y1,
2499 GRIDWIDTH, im->graph_col[GRC_GRID]);
2500 gfx_line(im, X0, Y0, X0, Y0 + 2,
2501 GRIDWIDTH, im->graph_col[GRC_GRID]);
2502 gfx_dashed_line(im, X0, Y0 + 1, X0,
2505 graph_col[GRC_GRID],
2506 im->grid_dash_on, im->grid_dash_off);
2510 /* paint the major grid */
2511 for (ti = find_first_time(im->start,
2519 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2521 /* are we inside the graph ? */
2522 if (ti < im->start || ti > im->end)
2525 gfx_line(im, X0, Y1 - 2, X0, Y1,
2526 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2527 gfx_line(im, X0, Y0, X0, Y0 + 3,
2528 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2529 gfx_dashed_line(im, X0, Y0 + 3, X0,
2533 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2535 /* paint the labels below the graph */
2537 find_first_time(im->start -
2546 im->xlab_user.precis / 2;
2547 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2549 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2550 /* are we inside the graph ? */
2551 if (tilab < im->start || tilab > im->end)
2554 localtime_r(&tilab, &tm);
2555 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2557 # error "your libc has no strftime I guess we'll abort the exercise here."
2562 im->graph_col[GRC_FONT],
2564 text_prop[TEXT_PROP_AXIS].
2567 GFX_H_CENTER, GFX_V_TOP, graph_label);
2576 /* draw x and y axis */
2577 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2578 im->xorigin+im->xsize,im->yorigin-im->ysize,
2579 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2581 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2582 im->xorigin+im->xsize,im->yorigin-im->ysize,
2583 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2585 gfx_line(im, im->xorigin - 4,
2587 im->xorigin + im->xsize +
2588 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2589 gfx_line(im, im->xorigin,
2592 im->yorigin - im->ysize -
2593 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2594 /* arrow for X and Y axis direction */
2595 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 */
2596 im->graph_col[GRC_ARROW]);
2598 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 */
2599 im->graph_col[GRC_ARROW]);
2601 if (im->second_axis_scale != 0){
2602 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2603 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2604 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2606 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2607 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2608 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2609 im->graph_col[GRC_ARROW]);
2620 double X0, Y0; /* points for filled graph and more */
2621 struct gfx_color_t water_color;
2623 if (im->draw_3d_border > 0) {
2624 /* draw 3d border */
2625 i = im->draw_3d_border;
2626 gfx_new_area(im, 0, im->yimg,
2627 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2628 gfx_add_point(im, im->ximg - i, i);
2629 gfx_add_point(im, im->ximg, 0);
2630 gfx_add_point(im, 0, 0);
2632 gfx_new_area(im, i, im->yimg - i,
2634 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2635 gfx_add_point(im, im->ximg, 0);
2636 gfx_add_point(im, im->ximg, im->yimg);
2637 gfx_add_point(im, 0, im->yimg);
2640 if (im->draw_x_grid == 1)
2642 if (im->draw_y_grid == 1) {
2643 if (im->logarithmic) {
2644 res = horizontal_log_grid(im);
2646 res = draw_horizontal_grid(im);
2649 /* dont draw horizontal grid if there is no min and max val */
2651 char *nodata = "No Data found";
2653 gfx_text(im, im->ximg / 2,
2656 im->graph_col[GRC_FONT],
2658 text_prop[TEXT_PROP_AXIS].
2661 GFX_H_CENTER, GFX_V_CENTER, nodata);
2665 /* yaxis unit description */
2666 if (im->ylegend[0] != '\0'){
2668 im->xOriginLegendY+10,
2670 im->graph_col[GRC_FONT],
2672 text_prop[TEXT_PROP_UNIT].
2675 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2678 if (im->second_axis_legend[0] != '\0'){
2680 im->xOriginLegendY2+10,
2681 im->yOriginLegendY2,
2682 im->graph_col[GRC_FONT],
2683 im->text_prop[TEXT_PROP_UNIT].font_desc,
2685 RRDGRAPH_YLEGEND_ANGLE,
2686 GFX_H_CENTER, GFX_V_CENTER,
2687 im->second_axis_legend);
2692 im->xOriginTitle, im->yOriginTitle+6,
2693 im->graph_col[GRC_FONT],
2695 text_prop[TEXT_PROP_TITLE].
2697 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2698 /* rrdtool 'logo' */
2699 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2700 water_color = im->graph_col[GRC_FONT];
2701 water_color.alpha = 0.3;
2702 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2703 gfx_text(im, xpos, 5,
2706 text_prop[TEXT_PROP_WATERMARK].
2707 font_desc, im->tabwidth,
2708 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2710 /* graph watermark */
2711 if (im->watermark[0] != '\0') {
2712 water_color = im->graph_col[GRC_FONT];
2713 water_color.alpha = 0.3;
2715 im->ximg / 2, im->yimg - 6,
2718 text_prop[TEXT_PROP_WATERMARK].
2719 font_desc, im->tabwidth, 0,
2720 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2724 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2725 for (i = 0; i < im->gdes_c; i++) {
2726 if (im->gdes[i].legend[0] == '\0')
2728 /* im->gdes[i].leg_y is the bottom of the legend */
2729 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2730 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2731 gfx_text(im, X0, Y0,
2732 im->graph_col[GRC_FONT],
2735 [TEXT_PROP_LEGEND].font_desc,
2737 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2738 /* The legend for GRAPH items starts with "M " to have
2739 enough space for the box */
2740 if (im->gdes[i].gf != GF_PRINT &&
2741 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2745 boxH = gfx_get_text_width(im, 0,
2750 im->tabwidth, "o") * 1.2;
2752 /* shift the box up a bit */
2755 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2757 cairo_new_path(im->cr);
2758 cairo_set_line_width(im->cr, 1.0);
2761 X0 + boxH, Y0 - boxV / 2,
2762 1.0, im->gdes[i].col);
2764 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2766 cairo_new_path(im->cr);
2767 cairo_set_line_width(im->cr, 1.0);
2770 X0 + boxH / 2, Y0 - boxV,
2771 1.0, im->gdes[i].col);
2773 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2775 cairo_new_path(im->cr);
2776 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2779 X0 + boxH, Y0 - boxV,
2780 im->gdes[i].linewidth, im->gdes[i].col);
2783 /* make sure transparent colors show up the same way as in the graph */
2786 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2787 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2789 gfx_new_area(im, X0, Y0 - boxV, X0,
2790 Y0, X0 + boxH, Y0, im->gdes[i].col);
2791 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2794 cairo_new_path(im->cr);
2795 cairo_set_line_width(im->cr, 1.0);
2798 gfx_line_fit(im, &X0, &Y0);
2799 gfx_line_fit(im, &X1, &Y1);
2800 cairo_move_to(im->cr, X0, Y0);
2801 cairo_line_to(im->cr, X1, Y0);
2802 cairo_line_to(im->cr, X1, Y1);
2803 cairo_line_to(im->cr, X0, Y1);
2804 cairo_close_path(im->cr);
2805 cairo_set_source_rgba(im->cr,
2806 im->graph_col[GRC_FRAME].red,
2807 im->graph_col[GRC_FRAME].green,
2808 im->graph_col[GRC_FRAME].blue,
2809 im->graph_col[GRC_FRAME].alpha);
2811 if (im->gdes[i].dash) {
2812 /* make box borders in legend dashed if the graph is dashed */
2816 cairo_set_dash(im->cr, dashes, 1, 0.0);
2818 cairo_stroke(im->cr);
2819 cairo_restore(im->cr);
2826 /*****************************************************
2827 * lazy check make sure we rely need to create this graph
2828 *****************************************************/
2835 struct stat imgstat;
2838 return 0; /* no lazy option */
2839 if (strlen(im->graphfile) == 0)
2840 return 0; /* inmemory option */
2841 if (stat(im->graphfile, &imgstat) != 0)
2842 return 0; /* can't stat */
2843 /* one pixel in the existing graph is more then what we would
2845 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2847 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2848 return 0; /* the file does not exist */
2849 switch (im->imgformat) {
2851 size = PngSize(fd, &(im->ximg), &(im->yimg));
2861 int graph_size_location(
2866 /* The actual size of the image to draw is determined from
2867 ** several sources. The size given on the command line is
2868 ** the graph area but we need more as we have to draw labels
2869 ** and other things outside the graph area. If the option
2870 ** --full-size-mode is selected the size defines the total
2871 ** image size and the size available for the graph is
2875 /** +---+-----------------------------------+
2876 ** | y |...............graph title.........|
2877 ** | +---+-------------------------------+
2881 ** | s | x | main graph area |
2886 ** | l | b +-------------------------------+
2887 ** | e | l | x axis labels |
2888 ** +---+---+-------------------------------+
2889 ** |....................legends............|
2890 ** +---------------------------------------+
2892 ** +---------------------------------------+
2895 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2896 0, Xylabel = 0, Xmain = 0, Ymain =
2897 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2899 // no legends and no the shall be plotted it's easy
2900 if (im->extra_flags & ONLY_GRAPH) {
2902 im->ximg = im->xsize;
2903 im->yimg = im->ysize;
2904 im->yorigin = im->ysize;
2909 if(im->watermark[0] != '\0') {
2910 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2913 // calculate the width of the left vertical legend
2914 if (im->ylegend[0] != '\0') {
2915 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2918 // calculate the width of the right vertical legend
2919 if (im->second_axis_legend[0] != '\0') {
2920 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2923 Xvertical2 = Xspacing;
2926 if (im->title[0] != '\0') {
2927 /* The title is placed "inbetween" two text lines so it
2928 ** automatically has some vertical spacing. The horizontal
2929 ** spacing is added here, on each side.
2931 /* if necessary, reduce the font size of the title until it fits the image width */
2932 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2935 // we have no title; get a little clearing from the top
2936 Ytitle = 1.5 * Yspacing;
2940 if (im->draw_x_grid) {
2941 // calculate the height of the horizontal labelling
2942 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2944 if (im->draw_y_grid || im->forceleftspace) {
2945 // calculate the width of the vertical labelling
2947 gfx_get_text_width(im, 0,
2948 im->text_prop[TEXT_PROP_AXIS].font_desc,
2949 im->tabwidth, "0") * im->unitslength;
2953 // add some space to the labelling
2954 Xylabel += Xspacing;
2956 /* If the legend is printed besides the graph the width has to be
2957 ** calculated first. Placing the legend north or south of the
2958 ** graph requires the width calculation first, so the legend is
2959 ** skipped for the moment.
2961 im->legendheight = 0;
2962 im->legendwidth = 0;
2963 if (!(im->extra_flags & NOLEGEND)) {
2964 if(im->legendposition == WEST || im->legendposition == EAST){
2965 if (leg_place(im, 1) == -1){
2971 if (im->extra_flags & FULL_SIZE_MODE) {
2973 /* The actual size of the image to draw has been determined by the user.
2974 ** The graph area is the space remaining after accounting for the legend,
2975 ** the watermark, the axis labels, and the title.
2977 im->ximg = im->xsize;
2978 im->yimg = im->ysize;
2982 /* Now calculate the total size. Insert some spacing where
2983 desired. im->xorigin and im->yorigin need to correspond
2984 with the lower left corner of the main graph area or, if
2985 this one is not set, the imaginary box surrounding the
2987 /* Initial size calculation for the main graph area */
2989 Xmain -= Xylabel;// + Xspacing;
2990 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2991 Xmain -= im->legendwidth;// + Xspacing;
2993 if (im->second_axis_scale != 0){
2996 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3000 Xmain -= Xvertical + Xvertical2;
3002 /* limit the remaining space to 0 */
3008 /* Putting the legend north or south, the height can now be calculated */
3009 if (!(im->extra_flags & NOLEGEND)) {
3010 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3011 im->legendwidth = im->ximg;
3012 if (leg_place(im, 0) == -1){
3018 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3019 Ymain -= Yxlabel + im->legendheight;
3025 /* reserve space for the title *or* some padding above the graph */
3028 /* reserve space for padding below the graph */
3029 if (im->extra_flags & NOLEGEND) {
3033 if (im->watermark[0] != '\0') {
3034 Ymain -= Ywatermark;
3036 /* limit the remaining height to 0 */
3041 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3043 /* The actual size of the image to draw is determined from
3044 ** several sources. The size given on the command line is
3045 ** the graph area but we need more as we have to draw labels
3046 ** and other things outside the graph area.
3050 Xmain = im->xsize; // + Xspacing;
3054 im->ximg = Xmain + Xylabel;
3055 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3056 im->ximg += Xspacing;
3059 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3060 im->ximg += im->legendwidth;// + Xspacing;
3062 if (im->second_axis_scale != 0){
3063 im->ximg += Xylabel;
3066 im->ximg += Xvertical + Xvertical2;
3068 if (!(im->extra_flags & NOLEGEND)) {
3069 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3070 im->legendwidth = im->ximg;
3071 if (leg_place(im, 0) == -1){
3077 im->yimg = Ymain + Yxlabel;
3078 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3079 im->yimg += im->legendheight;
3082 /* reserve space for the title *or* some padding above the graph */
3086 im->yimg += 1.5 * Yspacing;
3088 /* reserve space for padding below the graph */
3089 if (im->extra_flags & NOLEGEND) {
3090 im->yimg += Yspacing;
3093 if (im->watermark[0] != '\0') {
3094 im->yimg += Ywatermark;
3099 /* In case of putting the legend in west or east position the first
3100 ** legend calculation might lead to wrong positions if some items
3101 ** are not aligned on the left hand side (e.g. centered) as the
3102 ** legendwidth wight have been increased after the item was placed.
3103 ** In this case the positions have to be recalculated.
3105 if (!(im->extra_flags & NOLEGEND)) {
3106 if(im->legendposition == WEST || im->legendposition == EAST){
3107 if (leg_place(im, 0) == -1){
3113 /* After calculating all dimensions
3114 ** it is now possible to calculate
3117 switch(im->legendposition){
3119 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3120 im->yOriginTitle = 0;
3122 im->xOriginLegend = 0;
3123 im->yOriginLegend = Ytitle;
3125 im->xOriginLegendY = 0;
3126 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3128 im->xorigin = Xvertical + Xylabel;
3129 im->yorigin = Ytitle + im->legendheight + Ymain;
3131 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3132 if (im->second_axis_scale != 0){
3133 im->xOriginLegendY2 += Xylabel;
3135 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3140 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3141 im->yOriginTitle = 0;
3143 im->xOriginLegend = 0;
3144 im->yOriginLegend = Ytitle;
3146 im->xOriginLegendY = im->legendwidth;
3147 im->yOriginLegendY = Ytitle + (Ymain / 2);
3149 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3150 im->yorigin = Ytitle + Ymain;
3152 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3153 if (im->second_axis_scale != 0){
3154 im->xOriginLegendY2 += Xylabel;
3156 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3161 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3162 im->yOriginTitle = 0;
3164 im->xOriginLegend = 0;
3165 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3167 im->xOriginLegendY = 0;
3168 im->yOriginLegendY = Ytitle + (Ymain / 2);
3170 im->xorigin = Xvertical + Xylabel;
3171 im->yorigin = Ytitle + Ymain;
3173 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3174 if (im->second_axis_scale != 0){
3175 im->xOriginLegendY2 += Xylabel;
3177 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3182 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3183 im->yOriginTitle = 0;
3185 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3186 if (im->second_axis_scale != 0){
3187 im->xOriginLegend += Xylabel;
3189 im->yOriginLegend = Ytitle;
3191 im->xOriginLegendY = 0;
3192 im->yOriginLegendY = Ytitle + (Ymain / 2);
3194 im->xorigin = Xvertical + Xylabel;
3195 im->yorigin = Ytitle + Ymain;
3197 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3198 if (im->second_axis_scale != 0){
3199 im->xOriginLegendY2 += Xylabel;
3201 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3203 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3204 im->xOriginTitle += Xspacing;
3205 im->xOriginLegend += Xspacing;
3206 im->xOriginLegendY += Xspacing;
3207 im->xorigin += Xspacing;
3208 im->xOriginLegendY2 += Xspacing;
3218 static cairo_status_t cairo_output(
3222 unsigned int length)
3224 image_desc_t *im = (image_desc_t*)closure;
3226 im->rendered_image =
3227 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3228 if (im->rendered_image == NULL)
3229 return CAIRO_STATUS_WRITE_ERROR;
3230 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3231 im->rendered_image_size += length;
3232 return CAIRO_STATUS_SUCCESS;
3235 /* draw that picture thing ... */
3240 int lazy = lazy_check(im);
3241 double areazero = 0.0;
3242 graph_desc_t *lastgdes = NULL;
3245 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3247 /* pull the data from the rrd files ... */
3248 if (data_fetch(im) == -1)
3250 /* evaluate VDEF and CDEF operations ... */
3251 if (data_calc(im) == -1)
3253 /* calculate and PRINT and GPRINT definitions. We have to do it at
3254 * this point because it will affect the length of the legends
3255 * if there are no graph elements (i==0) we stop here ...
3256 * if we are lazy, try to quit ...
3262 /* if we want and can be lazy ... quit now */
3266 /**************************************************************
3267 *** Calculating sizes and locations became a bit confusing ***
3268 *** so I moved this into a separate function. ***
3269 **************************************************************/
3270 if (graph_size_location(im, i) == -1)
3273 info.u_cnt = im->xorigin;
3274 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3275 info.u_cnt = im->yorigin - im->ysize;
3276 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3277 info.u_cnt = im->xsize;
3278 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3279 info.u_cnt = im->ysize;
3280 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3281 info.u_cnt = im->ximg;
3282 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3283 info.u_cnt = im->yimg;
3284 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3285 info.u_cnt = im->start;
3286 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3287 info.u_cnt = im->end;
3288 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3290 /* if we want and can be lazy ... quit now */
3294 /* get actual drawing data and find min and max values */
3295 if (data_proc(im) == -1)
3297 if (!im->logarithmic) {
3301 /* identify si magnitude Kilo, Mega Giga ? */
3302 if (!im->rigid && !im->logarithmic)
3303 expand_range(im); /* make sure the upper and lower limit are
3306 info.u_val = im->minval;
3307 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3308 info.u_val = im->maxval;
3309 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3312 if (!calc_horizontal_grid(im))
3317 apply_gridfit(im); */
3318 /* the actual graph is created by going through the individual
3319 graph elements and then drawing them */
3320 cairo_surface_destroy(im->surface);
3321 switch (im->imgformat) {
3324 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3325 im->ximg * im->zoom,
3326 im->yimg * im->zoom);
3330 im->surface = strlen(im->graphfile)
3331 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3332 im->yimg * im->zoom)
3333 : cairo_pdf_surface_create_for_stream
3334 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3338 im->surface = strlen(im->graphfile)
3340 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3341 im->yimg * im->zoom)
3342 : cairo_ps_surface_create_for_stream
3343 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3347 im->surface = strlen(im->graphfile)
3349 cairo_svg_surface_create(im->
3351 im->ximg * im->zoom, im->yimg * im->zoom)
3352 : cairo_svg_surface_create_for_stream
3353 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3354 cairo_svg_surface_restrict_to_version
3355 (im->surface, CAIRO_SVG_VERSION_1_1);
3358 cairo_destroy(im->cr);
3359 im->cr = cairo_create(im->surface);
3360 cairo_set_antialias(im->cr, im->graph_antialias);
3361 cairo_scale(im->cr, im->zoom, im->zoom);
3362 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3363 gfx_new_area(im, 0, 0, 0, im->yimg,
3364 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3365 gfx_add_point(im, im->ximg, 0);
3367 gfx_new_area(im, im->xorigin,
3370 im->xsize, im->yorigin,
3373 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3374 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3376 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3377 im->xsize, im->ysize + 2.0);
3379 if (im->minval > 0.0)
3380 areazero = im->minval;
3381 if (im->maxval < 0.0)
3382 areazero = im->maxval;
3383 for (i = 0; i < im->gdes_c; i++) {
3384 switch (im->gdes[i].gf) {
3398 for (ii = 0; ii < im->xsize; ii++) {
3399 if (!isnan(im->gdes[i].p_data[ii])
3400 && im->gdes[i].p_data[ii] != 0.0) {
3401 if (im->gdes[i].yrule > 0) {
3408 im->ysize, 1.0, im->gdes[i].col);
3409 } else if (im->gdes[i].yrule < 0) {
3412 im->yorigin - im->ysize - 1.0,
3414 im->yorigin - im->ysize -
3417 im->ysize, 1.0, im->gdes[i].col);
3424 /* fix data points at oo and -oo */
3425 for (ii = 0; ii < im->xsize; ii++) {
3426 if (isinf(im->gdes[i].p_data[ii])) {
3427 if (im->gdes[i].p_data[ii] > 0) {
3428 im->gdes[i].p_data[ii] = im->maxval;
3430 im->gdes[i].p_data[ii] = im->minval;
3436 /* *******************************************************
3441 -------|--t-1--t--------------------------------
3443 if we know the value at time t was a then
3444 we draw a square from t-1 to t with the value a.
3446 ********************************************************* */
3447 if (im->gdes[i].col.alpha != 0.0) {
3448 /* GF_LINE and friend */
3449 if (im->gdes[i].gf == GF_LINE) {
3450 double last_y = 0.0;
3454 cairo_new_path(im->cr);
3455 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3456 if (im->gdes[i].dash) {
3457 cairo_set_dash(im->cr,
3458 im->gdes[i].p_dashes,
3459 im->gdes[i].ndash, im->gdes[i].offset);
3462 for (ii = 1; ii < im->xsize; ii++) {
3463 if (isnan(im->gdes[i].p_data[ii])
3464 || (im->slopemode == 1
3465 && isnan(im->gdes[i].p_data[ii - 1]))) {
3470 last_y = ytr(im, im->gdes[i].p_data[ii]);
3471 if (im->slopemode == 0) {
3472 double x = ii - 1 + im->xorigin;
3475 gfx_line_fit(im, &x, &y);
3476 cairo_move_to(im->cr, x, y);
3477 x = ii + im->xorigin;
3479 gfx_line_fit(im, &x, &y);
3480 cairo_line_to(im->cr, x, y);
3482 double x = ii - 1 + im->xorigin;
3484 ytr(im, im->gdes[i].p_data[ii - 1]);
3485 gfx_line_fit(im, &x, &y);
3486 cairo_move_to(im->cr, x, y);
3487 x = ii + im->xorigin;
3489 gfx_line_fit(im, &x, &y);
3490 cairo_line_to(im->cr, x, y);
3494 double x1 = ii + im->xorigin;
3495 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3497 if (im->slopemode == 0
3498 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3499 double x = ii - 1 + im->xorigin;
3502 gfx_line_fit(im, &x, &y);
3503 cairo_line_to(im->cr, x, y);
3506 gfx_line_fit(im, &x1, &y1);
3507 cairo_line_to(im->cr, x1, y1);
3510 cairo_set_source_rgba(im->cr,
3516 col.blue, im->gdes[i].col.alpha);
3517 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3518 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3519 cairo_stroke(im->cr);
3520 cairo_restore(im->cr);
3524 (double *) malloc(sizeof(double) * im->xsize * 2);
3526 (double *) malloc(sizeof(double) * im->xsize * 2);
3528 (double *) malloc(sizeof(double) * im->xsize * 2);
3530 (double *) malloc(sizeof(double) * im->xsize * 2);
3533 for (ii = 0; ii <= im->xsize; ii++) {
3536 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3542 AlmostEqual2sComplement(foreY
3546 AlmostEqual2sComplement(foreY
3556 foreY[cntI], im->gdes[i].col);
3557 while (cntI < idxI) {
3562 AlmostEqual2sComplement(foreY
3566 AlmostEqual2sComplement(foreY
3573 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3575 gfx_add_point(im, backX[idxI], backY[idxI]);
3581 AlmostEqual2sComplement(backY
3585 AlmostEqual2sComplement(backY
3592 gfx_add_point(im, backX[idxI], backY[idxI]);
3602 if (ii == im->xsize)
3604 if (im->slopemode == 0 && ii == 0) {
3607 if (isnan(im->gdes[i].p_data[ii])) {
3611 ytop = ytr(im, im->gdes[i].p_data[ii]);
3612 if (lastgdes && im->gdes[i].stack) {
3613 ybase = ytr(im, lastgdes->p_data[ii]);
3615 ybase = ytr(im, areazero);
3617 if (ybase == ytop) {
3623 double extra = ytop;
3628 if (im->slopemode == 0) {
3629 backY[++idxI] = ybase - 0.2;
3630 backX[idxI] = ii + im->xorigin - 1;
3631 foreY[idxI] = ytop + 0.2;
3632 foreX[idxI] = ii + im->xorigin - 1;
3634 backY[++idxI] = ybase - 0.2;
3635 backX[idxI] = ii + im->xorigin;
3636 foreY[idxI] = ytop + 0.2;
3637 foreX[idxI] = ii + im->xorigin;
3639 /* close up any remaining area */
3644 } /* else GF_LINE */
3646 /* if color != 0x0 */
3647 /* make sure we do not run into trouble when stacking on NaN */
3648 for (ii = 0; ii < im->xsize; ii++) {
3649 if (isnan(im->gdes[i].p_data[ii])) {
3650 if (lastgdes && (im->gdes[i].stack)) {
3651 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3653 im->gdes[i].p_data[ii] = areazero;
3657 lastgdes = &(im->gdes[i]);
3661 ("STACK should already be turned into LINE or AREA here");
3666 cairo_reset_clip(im->cr);
3668 /* grid_paint also does the text */
3669 if (!(im->extra_flags & ONLY_GRAPH))
3671 if (!(im->extra_flags & ONLY_GRAPH))
3673 /* the RULES are the last thing to paint ... */
3674 for (i = 0; i < im->gdes_c; i++) {
3676 switch (im->gdes[i].gf) {
3678 if (im->gdes[i].yrule >= im->minval
3679 && im->gdes[i].yrule <= im->maxval) {
3681 if (im->gdes[i].dash) {
3682 cairo_set_dash(im->cr,
3683 im->gdes[i].p_dashes,
3684 im->gdes[i].ndash, im->gdes[i].offset);
3686 gfx_line(im, im->xorigin,
3687 ytr(im, im->gdes[i].yrule),
3688 im->xorigin + im->xsize,
3689 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3690 cairo_stroke(im->cr);
3691 cairo_restore(im->cr);
3695 if (im->gdes[i].xrule >= im->start
3696 && im->gdes[i].xrule <= im->end) {
3698 if (im->gdes[i].dash) {
3699 cairo_set_dash(im->cr,
3700 im->gdes[i].p_dashes,
3701 im->gdes[i].ndash, im->gdes[i].offset);
3704 xtr(im, im->gdes[i].xrule),
3705 im->yorigin, xtr(im,
3709 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3710 cairo_stroke(im->cr);
3711 cairo_restore(im->cr);
3720 switch (im->imgformat) {
3723 cairo_status_t status;
3725 status = strlen(im->graphfile) ?
3726 cairo_surface_write_to_png(im->surface, im->graphfile)
3727 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3730 if (status != CAIRO_STATUS_SUCCESS) {
3731 rrd_set_error("Could not save png to '%s'", im->graphfile);
3737 if (strlen(im->graphfile)) {
3738 cairo_show_page(im->cr);
3740 cairo_surface_finish(im->surface);
3749 /*****************************************************
3751 *****************************************************/
3758 if ((im->gdes = (graph_desc_t *)
3759 rrd_realloc(im->gdes, (im->gdes_c)
3760 * sizeof(graph_desc_t))) == NULL) {
3761 rrd_set_error("realloc graph_descs");
3766 im->gdes[im->gdes_c - 1].step = im->step;
3767 im->gdes[im->gdes_c - 1].step_orig = im->step;
3768 im->gdes[im->gdes_c - 1].stack = 0;
3769 im->gdes[im->gdes_c - 1].linewidth = 0;
3770 im->gdes[im->gdes_c - 1].debug = 0;
3771 im->gdes[im->gdes_c - 1].start = im->start;
3772 im->gdes[im->gdes_c - 1].start_orig = im->start;
3773 im->gdes[im->gdes_c - 1].end = im->end;
3774 im->gdes[im->gdes_c - 1].end_orig = im->end;
3775 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3776 im->gdes[im->gdes_c - 1].data = NULL;
3777 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3778 im->gdes[im->gdes_c - 1].data_first = 0;
3779 im->gdes[im->gdes_c - 1].p_data = NULL;
3780 im->gdes[im->gdes_c - 1].rpnp = NULL;
3781 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3782 im->gdes[im->gdes_c - 1].shift = 0.0;
3783 im->gdes[im->gdes_c - 1].dash = 0;
3784 im->gdes[im->gdes_c - 1].ndash = 0;
3785 im->gdes[im->gdes_c - 1].offset = 0;
3786 im->gdes[im->gdes_c - 1].col.red = 0.0;
3787 im->gdes[im->gdes_c - 1].col.green = 0.0;
3788 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3789 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3790 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3791 im->gdes[im->gdes_c - 1].format[0] = '\0';
3792 im->gdes[im->gdes_c - 1].strftm = 0;
3793 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3794 im->gdes[im->gdes_c - 1].ds = -1;
3795 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3796 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3797 im->gdes[im->gdes_c - 1].yrule = DNAN;
3798 im->gdes[im->gdes_c - 1].xrule = 0;
3802 /* copies input untill the first unescaped colon is found
3803 or until input ends. backslashes have to be escaped as well */
3805 const char *const input,
3811 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3812 if (input[inp] == '\\'
3813 && input[inp + 1] != '\0'
3814 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3815 output[outp++] = input[++inp];
3817 output[outp++] = input[inp];
3820 output[outp] = '\0';
3824 /* Now just a wrapper around rrd_graph_v */
3836 rrd_info_t *grinfo = NULL;
3839 grinfo = rrd_graph_v(argc, argv);
3845 if (strcmp(walker->key, "image_info") == 0) {
3848 (char**)rrd_realloc((*prdata),
3849 (prlines + 1) * sizeof(char *))) == NULL) {
3850 rrd_set_error("realloc prdata");
3853 /* imginfo goes to position 0 in the prdata array */
3854 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3855 + 2) * sizeof(char));
3856 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3857 (*prdata)[prlines] = NULL;
3859 /* skip anything else */
3860 walker = walker->next;
3868 if (strcmp(walker->key, "image_width") == 0) {
3869 *xsize = walker->value.u_cnt;
3870 } else if (strcmp(walker->key, "image_height") == 0) {
3871 *ysize = walker->value.u_cnt;
3872 } else if (strcmp(walker->key, "value_min") == 0) {
3873 *ymin = walker->value.u_val;
3874 } else if (strcmp(walker->key, "value_max") == 0) {
3875 *ymax = walker->value.u_val;
3876 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3879 (char**)rrd_realloc((*prdata),
3880 (prlines + 1) * sizeof(char *))) == NULL) {
3881 rrd_set_error("realloc prdata");
3884 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3885 + 2) * sizeof(char));
3886 (*prdata)[prlines] = NULL;
3887 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3888 } else if (strcmp(walker->key, "image") == 0) {
3889 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3890 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3891 rrd_set_error("writing image");
3895 /* skip anything else */
3896 walker = walker->next;
3898 rrd_info_free(grinfo);
3903 /* Some surgery done on this function, it became ridiculously big.
3905 ** - initializing now in rrd_graph_init()
3906 ** - options parsing now in rrd_graph_options()
3907 ** - script parsing now in rrd_graph_script()
3909 rrd_info_t *rrd_graph_v(
3916 rrd_graph_init(&im);
3917 /* a dummy surface so that we can measure text sizes for placements */
3918 old_locale = setlocale(LC_NUMERIC, "C");
3919 rrd_graph_options(argc, argv, &im);
3920 if (rrd_test_error()) {
3921 rrd_info_free(im.grinfo);
3926 if (optind >= argc) {
3927 rrd_info_free(im.grinfo);
3929 rrd_set_error("missing filename");
3933 if (strlen(argv[optind]) >= MAXPATH) {
3934 rrd_set_error("filename (including path) too long");
3935 rrd_info_free(im.grinfo);
3940 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3941 im.graphfile[MAXPATH - 1] = '\0';
3943 if (strcmp(im.graphfile, "-") == 0) {
3944 im.graphfile[0] = '\0';
3947 rrd_graph_script(argc, argv, &im, 1);
3948 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3950 if (rrd_test_error()) {
3951 rrd_info_free(im.grinfo);
3956 /* Everything is now read and the actual work can start */
3958 if (graph_paint(&im) == -1) {
3959 rrd_info_free(im.grinfo);
3965 /* The image is generated and needs to be output.
3966 ** Also, if needed, print a line with information about the image.
3974 path = strdup(im.graphfile);
3975 filename = basename(path);
3977 sprintf_alloc(im.imginfo,
3980 im.ximg), (long) (im.zoom * im.yimg));
3981 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3985 if (im.rendered_image) {
3988 img.u_blo.size = im.rendered_image_size;
3989 img.u_blo.ptr = im.rendered_image;
3990 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3999 image_desc_t *im,int prop,char *font, double size ){
4001 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4002 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4003 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4006 im->text_prop[prop].size = size;
4008 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4009 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4013 void rrd_graph_init(
4018 char *deffont = getenv("RRD_DEFAULT_FONT");
4019 static PangoFontMap *fontmap = NULL;
4020 PangoContext *context;
4027 im->daemon_addr = NULL;
4028 im->draw_x_grid = 1;
4029 im->draw_y_grid = 1;
4030 im->draw_3d_border = 2;
4031 im->dynamic_labels = 0;
4032 im->extra_flags = 0;
4033 im->font_options = cairo_font_options_create();
4034 im->forceleftspace = 0;
4037 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4038 im->grid_dash_off = 1;
4039 im->grid_dash_on = 1;
4041 im->grinfo = (rrd_info_t *) NULL;
4042 im->grinfo_current = (rrd_info_t *) NULL;
4043 im->imgformat = IF_PNG;
4046 im->legenddirection = TOP_DOWN;
4047 im->legendheight = 0;
4048 im->legendposition = SOUTH;
4049 im->legendwidth = 0;
4050 im->logarithmic = 0;
4056 im->rendered_image_size = 0;
4057 im->rendered_image = NULL;
4061 im->tabwidth = 40.0;
4062 im->title[0] = '\0';
4063 im->unitsexponent = 9999;
4064 im->unitslength = 6;
4065 im->viewfactor = 1.0;
4066 im->watermark[0] = '\0';
4067 im->with_markup = 0;
4069 im->xlab_user.minsec = -1;
4071 im->xOriginLegend = 0;
4072 im->xOriginLegendY = 0;
4073 im->xOriginLegendY2 = 0;
4074 im->xOriginTitle = 0;
4076 im->ygridstep = DNAN;
4078 im->ylegend[0] = '\0';
4079 im->second_axis_scale = 0; /* 0 disables it */
4080 im->second_axis_shift = 0; /* no shift by default */
4081 im->second_axis_legend[0] = '\0';
4082 im->second_axis_format[0] = '\0';
4084 im->yOriginLegend = 0;
4085 im->yOriginLegendY = 0;
4086 im->yOriginLegendY2 = 0;
4087 im->yOriginTitle = 0;
4091 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4092 im->cr = cairo_create(im->surface);
4094 for (i = 0; i < DIM(text_prop); i++) {
4095 im->text_prop[i].size = -1;
4096 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4099 if (fontmap == NULL){
4100 fontmap = pango_cairo_font_map_get_default();
4103 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4105 pango_cairo_context_set_resolution(context, 100);
4107 pango_cairo_update_context(im->cr,context);
4109 im->layout = pango_layout_new(context);
4111 // im->layout = pango_cairo_create_layout(im->cr);
4114 cairo_font_options_set_hint_style
4115 (im->font_options, CAIRO_HINT_STYLE_FULL);
4116 cairo_font_options_set_hint_metrics
4117 (im->font_options, CAIRO_HINT_METRICS_ON);
4118 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4122 for (i = 0; i < DIM(graph_col); i++)
4123 im->graph_col[i] = graph_col[i];
4129 void rrd_graph_options(
4136 char *parsetime_error = NULL;
4137 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4138 time_t start_tmp = 0, end_tmp = 0;
4140 rrd_time_value_t start_tv, end_tv;
4141 long unsigned int color;
4143 /* defines for long options without a short equivalent. should be bytes,
4144 and may not collide with (the ASCII value of) short options */
4145 #define LONGOPT_UNITS_SI 255
4148 struct option long_options[] = {
4149 { "alt-autoscale", no_argument, 0, 'A'},
4150 { "imgformat", required_argument, 0, 'a'},
4151 { "font-smoothing-threshold", required_argument, 0, 'B'},
4152 { "base", required_argument, 0, 'b'},
4153 { "color", required_argument, 0, 'c'},
4154 { "full-size-mode", no_argument, 0, 'D'},
4155 { "daemon", required_argument, 0, 'd'},
4156 { "slope-mode", no_argument, 0, 'E'},
4157 { "end", required_argument, 0, 'e'},
4158 { "force-rules-legend", no_argument, 0, 'F'},
4159 { "imginfo", required_argument, 0, 'f'},
4160 { "graph-render-mode", required_argument, 0, 'G'},
4161 { "no-legend", no_argument, 0, 'g'},
4162 { "height", required_argument, 0, 'h'},
4163 { "no-minor", no_argument, 0, 'I'},
4164 { "interlaced", no_argument, 0, 'i'},
4165 { "alt-autoscale-min", no_argument, 0, 'J'},
4166 { "only-graph", no_argument, 0, 'j'},
4167 { "units-length", required_argument, 0, 'L'},
4168 { "lower-limit", required_argument, 0, 'l'},
4169 { "alt-autoscale-max", no_argument, 0, 'M'},
4170 { "zoom", required_argument, 0, 'm'},
4171 { "no-gridfit", no_argument, 0, 'N'},
4172 { "font", required_argument, 0, 'n'},
4173 { "logarithmic", no_argument, 0, 'o'},
4174 { "pango-markup", no_argument, 0, 'P'},
4175 { "font-render-mode", required_argument, 0, 'R'},
4176 { "rigid", no_argument, 0, 'r'},
4177 { "step", required_argument, 0, 'S'},
4178 { "start", required_argument, 0, 's'},
4179 { "tabwidth", required_argument, 0, 'T'},
4180 { "title", required_argument, 0, 't'},
4181 { "upper-limit", required_argument, 0, 'u'},
4182 { "vertical-label", required_argument, 0, 'v'},
4183 { "watermark", required_argument, 0, 'W'},
4184 { "width", required_argument, 0, 'w'},
4185 { "units-exponent", required_argument, 0, 'X'},
4186 { "x-grid", required_argument, 0, 'x'},
4187 { "alt-y-grid", no_argument, 0, 'Y'},
4188 { "y-grid", required_argument, 0, 'y'},
4189 { "lazy", no_argument, 0, 'z'},
4190 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4191 { "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 */
4192 { "disable-rrdtool-tag",no_argument, 0, 1001},
4193 { "right-axis", required_argument, 0, 1002},
4194 { "right-axis-label", required_argument, 0, 1003},
4195 { "right-axis-format", required_argument, 0, 1004},
4196 { "legend-position", required_argument, 0, 1005},
4197 { "legend-direction", required_argument, 0, 1006},
4198 { "border", required_argument, 0, 1007},
4199 { "grid-dash", required_argument, 0, 1008},
4200 { "dynamic-labels", no_argument, 0, 1009},
4206 opterr = 0; /* initialize getopt */
4207 rrd_parsetime("end-24h", &start_tv);
4208 rrd_parsetime("now", &end_tv);
4210 int option_index = 0;
4212 int col_start, col_end;
4214 opt = getopt_long(argc, argv,
4215 "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",
4216 long_options, &option_index);
4221 im->extra_flags |= NOMINOR;
4224 im->extra_flags |= ALTYGRID;
4227 im->extra_flags |= ALTAUTOSCALE;
4230 im->extra_flags |= ALTAUTOSCALE_MIN;
4233 im->extra_flags |= ALTAUTOSCALE_MAX;
4236 im->extra_flags |= ONLY_GRAPH;
4239 im->extra_flags |= NOLEGEND;
4242 if (strcmp(optarg, "north") == 0) {
4243 im->legendposition = NORTH;
4244 } else if (strcmp(optarg, "west") == 0) {
4245 im->legendposition = WEST;
4246 } else if (strcmp(optarg, "south") == 0) {
4247 im->legendposition = SOUTH;
4248 } else if (strcmp(optarg, "east") == 0) {
4249 im->legendposition = EAST;
4251 rrd_set_error("unknown legend-position '%s'", optarg);
4256 if (strcmp(optarg, "topdown") == 0) {
4257 im->legenddirection = TOP_DOWN;
4258 } else if (strcmp(optarg, "bottomup") == 0) {
4259 im->legenddirection = BOTTOM_UP;
4261 rrd_set_error("unknown legend-position '%s'", optarg);
4266 im->extra_flags |= FORCE_RULES_LEGEND;
4269 im->extra_flags |= NO_RRDTOOL_TAG;
4271 case LONGOPT_UNITS_SI:
4272 if (im->extra_flags & FORCE_UNITS) {
4273 rrd_set_error("--units can only be used once!");
4276 if (strcmp(optarg, "si") == 0)
4277 im->extra_flags |= FORCE_UNITS_SI;
4279 rrd_set_error("invalid argument for --units: %s", optarg);
4284 im->unitsexponent = atoi(optarg);
4287 im->unitslength = atoi(optarg);
4288 im->forceleftspace = 1;
4291 im->tabwidth = atof(optarg);
4294 im->step = atoi(optarg);
4300 im->with_markup = 1;
4303 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4304 rrd_set_error("start time: %s", parsetime_error);
4309 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4310 rrd_set_error("end time: %s", parsetime_error);
4315 if (strcmp(optarg, "none") == 0) {
4316 im->draw_x_grid = 0;
4320 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4322 &im->xlab_user.gridst,
4324 &im->xlab_user.mgridst,
4326 &im->xlab_user.labst,
4327 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4328 strncpy(im->xlab_form, optarg + stroff,
4329 sizeof(im->xlab_form) - 1);
4330 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4332 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4333 rrd_set_error("unknown keyword %s", scan_gtm);
4336 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4338 rrd_set_error("unknown keyword %s", scan_mtm);
4341 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4342 rrd_set_error("unknown keyword %s", scan_ltm);
4345 im->xlab_user.minsec = 1;
4346 im->xlab_user.stst = im->xlab_form;
4348 rrd_set_error("invalid x-grid format");
4354 if (strcmp(optarg, "none") == 0) {
4355 im->draw_y_grid = 0;
4358 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4359 if (im->ygridstep <= 0) {
4360 rrd_set_error("grid step must be > 0");
4362 } else if (im->ylabfact < 1) {
4363 rrd_set_error("label factor must be > 0");
4367 rrd_set_error("invalid y-grid format");
4372 im->draw_3d_border = atoi(optarg);
4374 case 1008: /* grid-dash */
4378 &im->grid_dash_off) != 2) {
4379 rrd_set_error("expected grid-dash format float:float");
4383 case 1009: /* enable dynamic labels */
4384 im->dynamic_labels = 1;
4386 case 1002: /* right y axis */
4390 &im->second_axis_scale,
4391 &im->second_axis_shift) == 2) {
4392 if(im->second_axis_scale==0){
4393 rrd_set_error("the second_axis_scale must not be 0");
4397 rrd_set_error("invalid right-axis format expected scale:shift");
4402 strncpy(im->second_axis_legend,optarg,150);
4403 im->second_axis_legend[150]='\0';
4406 if (bad_format(optarg)){
4407 rrd_set_error("use either %le or %lf formats");
4410 strncpy(im->second_axis_format,optarg,150);
4411 im->second_axis_format[150]='\0';
4414 strncpy(im->ylegend, optarg, 150);
4415 im->ylegend[150] = '\0';
4418 im->maxval = atof(optarg);
4421 im->minval = atof(optarg);
4424 im->base = atol(optarg);
4425 if (im->base != 1024 && im->base != 1000) {
4427 ("the only sensible value for base apart from 1000 is 1024");
4432 long_tmp = atol(optarg);
4433 if (long_tmp < 10) {
4434 rrd_set_error("width below 10 pixels");
4437 im->xsize = long_tmp;
4440 long_tmp = atol(optarg);
4441 if (long_tmp < 10) {
4442 rrd_set_error("height below 10 pixels");
4445 im->ysize = long_tmp;
4448 im->extra_flags |= FULL_SIZE_MODE;
4451 /* interlaced png not supported at the moment */
4457 im->imginfo = optarg;
4461 (im->imgformat = if_conv(optarg)) == -1) {
4462 rrd_set_error("unsupported graphics format '%s'", optarg);
4473 im->logarithmic = 1;
4477 "%10[A-Z]#%n%8lx%n",
4478 col_nam, &col_start, &color, &col_end) == 2) {
4480 int col_len = col_end - col_start;
4485 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4493 (((color & 0xF000) *
4494 0x11000) | ((color & 0x0F00) *
4495 0x01100) | ((color &
4498 ((color & 0x000F) * 0x00011)
4502 color = (color << 8) + 0xff /* shift left by 8 */ ;
4507 rrd_set_error("the color format is #RRGGBB[AA]");
4510 if ((ci = grc_conv(col_nam)) != -1) {
4511 im->graph_col[ci] = gfx_hex_to_col(color);
4513 rrd_set_error("invalid color name '%s'", col_nam);
4517 rrd_set_error("invalid color def format");
4526 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4527 int sindex, propidx;
4529 if ((sindex = text_prop_conv(prop)) != -1) {
4530 for (propidx = sindex;
4531 propidx < TEXT_PROP_LAST; propidx++) {
4533 rrd_set_font_desc(im,propidx,NULL,size);
4535 if ((int) strlen(optarg) > end+2) {
4536 if (optarg[end] == ':') {
4537 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4540 ("expected : after font size in '%s'",
4545 /* only run the for loop for DEFAULT (0) for
4546 all others, we break here. woodo programming */
4547 if (propidx == sindex && sindex != 0)
4551 rrd_set_error("invalid fonttag '%s'", prop);
4555 rrd_set_error("invalid text property format");
4561 im->zoom = atof(optarg);
4562 if (im->zoom <= 0.0) {
4563 rrd_set_error("zoom factor must be > 0");
4568 strncpy(im->title, optarg, 150);
4569 im->title[150] = '\0';
4572 if (strcmp(optarg, "normal") == 0) {
4573 cairo_font_options_set_antialias
4574 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4575 cairo_font_options_set_hint_style
4576 (im->font_options, CAIRO_HINT_STYLE_FULL);
4577 } else if (strcmp(optarg, "light") == 0) {
4578 cairo_font_options_set_antialias
4579 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4580 cairo_font_options_set_hint_style
4581 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4582 } else if (strcmp(optarg, "mono") == 0) {
4583 cairo_font_options_set_antialias
4584 (im->font_options, CAIRO_ANTIALIAS_NONE);
4585 cairo_font_options_set_hint_style
4586 (im->font_options, CAIRO_HINT_STYLE_FULL);
4588 rrd_set_error("unknown font-render-mode '%s'", optarg);
4593 if (strcmp(optarg, "normal") == 0)
4594 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4595 else if (strcmp(optarg, "mono") == 0)
4596 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4598 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4603 /* not supported curently */
4606 strncpy(im->watermark, optarg, 100);
4607 im->watermark[99] = '\0';
4611 if (im->daemon_addr != NULL)
4613 rrd_set_error ("You cannot specify --daemon "
4618 im->daemon_addr = strdup(optarg);
4619 if (im->daemon_addr == NULL)
4621 rrd_set_error("strdup failed");
4629 rrd_set_error("unknown option '%c'", optopt);
4631 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4636 { /* try to connect to rrdcached */
4637 int status = rrdc_connect(im->daemon_addr);
4638 if (status != 0) return;
4641 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4642 pango_layout_context_changed(im->layout);
4646 if (im->logarithmic && im->minval <= 0) {
4648 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4652 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4653 /* error string is set in rrd_parsetime.c */
4657 if (start_tmp < 3600 * 24 * 365 * 10) {
4659 ("the first entry to fetch should be after 1980 (%ld)",
4664 if (end_tmp < start_tmp) {
4666 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4670 im->start = start_tmp;
4672 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4675 int rrd_graph_color(
4683 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4685 color = strstr(var, "#");
4686 if (color == NULL) {
4687 if (optional == 0) {
4688 rrd_set_error("Found no color in %s", err);
4695 long unsigned int col;
4697 rest = strstr(color, ":");
4704 sscanf(color, "#%6lx%n", &col, &n);
4705 col = (col << 8) + 0xff /* shift left by 8 */ ;
4707 rrd_set_error("Color problem in %s", err);
4710 sscanf(color, "#%8lx%n", &col, &n);
4714 rrd_set_error("Color problem in %s", err);
4716 if (rrd_test_error())
4718 gdp->col = gfx_hex_to_col(col);
4731 while (*ptr != '\0')
4732 if (*ptr++ == '%') {
4734 /* line cannot end with percent char */
4737 /* '%s', '%S' and '%%' are allowed */
4738 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4740 /* %c is allowed (but use only with vdef!) */
4741 else if (*ptr == 'c') {
4746 /* or else '% 6.2lf' and such are allowed */
4748 /* optional padding character */
4749 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4751 /* This should take care of 'm.n' with all three optional */
4752 while (*ptr >= '0' && *ptr <= '9')
4756 while (*ptr >= '0' && *ptr <= '9')
4758 /* Either 'le', 'lf' or 'lg' must follow here */
4761 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4776 const char *const str)
4778 /* A VDEF currently is either "func" or "param,func"
4779 * so the parsing is rather simple. Change if needed.
4786 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4787 if (n == (int) strlen(str)) { /* matched */
4791 sscanf(str, "%29[A-Z]%n", func, &n);
4792 if (n == (int) strlen(str)) { /* matched */
4796 ("Unknown function string '%s' in VDEF '%s'",
4801 if (!strcmp("PERCENT", func))
4802 gdes->vf.op = VDEF_PERCENT;
4803 else if (!strcmp("PERCENTNAN", func))
4804 gdes->vf.op = VDEF_PERCENTNAN;
4805 else if (!strcmp("MAXIMUM", func))
4806 gdes->vf.op = VDEF_MAXIMUM;
4807 else if (!strcmp("AVERAGE", func))
4808 gdes->vf.op = VDEF_AVERAGE;
4809 else if (!strcmp("STDEV", func))
4810 gdes->vf.op = VDEF_STDEV;
4811 else if (!strcmp("MINIMUM", func))
4812 gdes->vf.op = VDEF_MINIMUM;
4813 else if (!strcmp("TOTAL", func))
4814 gdes->vf.op = VDEF_TOTAL;
4815 else if (!strcmp("FIRST", func))
4816 gdes->vf.op = VDEF_FIRST;
4817 else if (!strcmp("LAST", func))
4818 gdes->vf.op = VDEF_LAST;
4819 else if (!strcmp("LSLSLOPE", func))
4820 gdes->vf.op = VDEF_LSLSLOPE;
4821 else if (!strcmp("LSLINT", func))
4822 gdes->vf.op = VDEF_LSLINT;
4823 else if (!strcmp("LSLCORREL", func))
4824 gdes->vf.op = VDEF_LSLCORREL;
4827 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4830 switch (gdes->vf.op) {
4832 case VDEF_PERCENTNAN:
4833 if (isnan(param)) { /* no parameter given */
4835 ("Function '%s' needs parameter in VDEF '%s'\n",
4839 if (param >= 0.0 && param <= 100.0) {
4840 gdes->vf.param = param;
4841 gdes->vf.val = DNAN; /* undefined */
4842 gdes->vf.when = 0; /* undefined */
4845 ("Parameter '%f' out of range in VDEF '%s'\n",
4846 param, gdes->vname);
4859 case VDEF_LSLCORREL:
4861 gdes->vf.param = DNAN;
4862 gdes->vf.val = DNAN;
4866 ("Function '%s' needs no parameter in VDEF '%s'\n",
4880 graph_desc_t *src, *dst;
4884 dst = &im->gdes[gdi];
4885 src = &im->gdes[dst->vidx];
4886 data = src->data + src->ds;
4888 steps = (src->end - src->start) / src->step;
4891 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4892 src->start, src->end, steps);
4894 switch (dst->vf.op) {
4898 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4899 rrd_set_error("malloc VDEV_PERCENT");
4902 for (step = 0; step < steps; step++) {
4903 array[step] = data[step * src->ds_cnt];
4905 qsort(array, step, sizeof(double), vdef_percent_compar);
4906 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4907 dst->vf.val = array[field];
4908 dst->vf.when = 0; /* no time component */
4911 for (step = 0; step < steps; step++)
4912 printf("DEBUG: %3li:%10.2f %c\n",
4913 step, array[step], step == field ? '*' : ' ');
4917 case VDEF_PERCENTNAN:{
4920 /* count number of "valid" values */
4922 for (step = 0; step < steps; step++) {
4923 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4925 /* and allocate it */
4926 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4927 rrd_set_error("malloc VDEV_PERCENT");
4930 /* and fill it in */
4932 for (step = 0; step < steps; step++) {
4933 if (!isnan(data[step * src->ds_cnt])) {
4934 array[field] = data[step * src->ds_cnt];
4938 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4939 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4940 dst->vf.val = array[field];
4941 dst->vf.when = 0; /* no time component */
4947 while (step != steps && isnan(data[step * src->ds_cnt]))
4949 if (step == steps) {
4953 dst->vf.val = data[step * src->ds_cnt];
4954 dst->vf.when = src->start + (step + 1) * src->step;
4956 while (step != steps) {
4957 if (finite(data[step * src->ds_cnt])) {
4958 if (data[step * src->ds_cnt] > dst->vf.val) {
4959 dst->vf.val = data[step * src->ds_cnt];
4960 dst->vf.when = src->start + (step + 1) * src->step;
4971 double average = 0.0;
4973 for (step = 0; step < steps; step++) {
4974 if (finite(data[step * src->ds_cnt])) {
4975 sum += data[step * src->ds_cnt];
4980 if (dst->vf.op == VDEF_TOTAL) {
4981 dst->vf.val = sum * src->step;
4982 dst->vf.when = 0; /* no time component */
4983 } else if (dst->vf.op == VDEF_AVERAGE) {
4984 dst->vf.val = sum / cnt;
4985 dst->vf.when = 0; /* no time component */
4987 average = sum / cnt;
4989 for (step = 0; step < steps; step++) {
4990 if (finite(data[step * src->ds_cnt])) {
4991 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4994 dst->vf.val = pow(sum / cnt, 0.5);
4995 dst->vf.when = 0; /* no time component */
5005 while (step != steps && isnan(data[step * src->ds_cnt]))
5007 if (step == steps) {
5011 dst->vf.val = data[step * src->ds_cnt];
5012 dst->vf.when = src->start + (step + 1) * src->step;
5014 while (step != steps) {
5015 if (finite(data[step * src->ds_cnt])) {
5016 if (data[step * src->ds_cnt] < dst->vf.val) {
5017 dst->vf.val = data[step * src->ds_cnt];
5018 dst->vf.when = src->start + (step + 1) * src->step;
5025 /* The time value returned here is one step before the
5026 * actual time value. This is the start of the first
5030 while (step != steps && isnan(data[step * src->ds_cnt]))
5032 if (step == steps) { /* all entries were NaN */
5036 dst->vf.val = data[step * src->ds_cnt];
5037 dst->vf.when = src->start + step * src->step;
5041 /* The time value returned here is the
5042 * actual time value. This is the end of the last
5046 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5048 if (step < 0) { /* all entries were NaN */
5052 dst->vf.val = data[step * src->ds_cnt];
5053 dst->vf.when = src->start + (step + 1) * src->step;
5058 case VDEF_LSLCORREL:{
5059 /* Bestfit line by linear least squares method */
5062 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5069 for (step = 0; step < steps; step++) {
5070 if (finite(data[step * src->ds_cnt])) {
5073 SUMxx += step * step;
5074 SUMxy += step * data[step * src->ds_cnt];
5075 SUMy += data[step * src->ds_cnt];
5076 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5080 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5081 y_intercept = (SUMy - slope * SUMx) / cnt;
5084 (SUMx * SUMy) / cnt) /
5086 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5088 if (dst->vf.op == VDEF_LSLSLOPE) {
5089 dst->vf.val = slope;
5091 } else if (dst->vf.op == VDEF_LSLINT) {
5092 dst->vf.val = y_intercept;
5094 } else if (dst->vf.op == VDEF_LSLCORREL) {
5095 dst->vf.val = correl;
5108 /* NaN < -INF < finite_values < INF */
5109 int vdef_percent_compar(
5115 /* Equality is not returned; this doesn't hurt except
5116 * (maybe) for a little performance.
5119 /* First catch NaN values. They are smallest */
5120 if (isnan(*(double *) a))
5122 if (isnan(*(double *) b))
5124 /* NaN doesn't reach this part so INF and -INF are extremes.
5125 * The sign from isinf() is compatible with the sign we return
5127 if (isinf(*(double *) a))
5128 return isinf(*(double *) a);
5129 if (isinf(*(double *) b))
5130 return isinf(*(double *) b);
5131 /* If we reach this, both values must be finite */
5132 if (*(double *) a < *(double *) b)
5141 rrd_info_type_t type,
5142 rrd_infoval_t value)
5144 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5145 if (im->grinfo == NULL) {
5146 im->grinfo = im->grinfo_current;