1 /****************************************************************************
2 * RRDtool 1.2.23 Copyright by Tobi Oetiker, 1997-2007
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
39 text_prop_t text_prop[] = {
40 {8.0, RRD_DEFAULT_FONT}
42 {9.0, RRD_DEFAULT_FONT}
44 {7.0, RRD_DEFAULT_FONT}
46 {8.0, RRD_DEFAULT_FONT}
48 {8.0, RRD_DEFAULT_FONT} /* legend */
52 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
54 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
56 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
58 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
60 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
62 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
64 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 4, 0, "%a %H:%M"}
66 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
68 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
70 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
71 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
73 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
75 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
77 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
79 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
81 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
84 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
87 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
90 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
92 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
93 365 * 24 * 3600, "%y"}
95 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
98 /* sensible y label intervals ...*/
122 {20.0, {1, 5, 10, 20}
128 {100.0, {1, 2, 5, 10}
131 {200.0, {1, 5, 10, 20}
134 {500.0, {1, 2, 4, 10}
142 gfx_color_t graph_col[] = /* default colors */
144 {1.00, 1.00, 1.00, 1.00}, /* canvas */
145 {0.95, 0.95, 0.95, 1.00}, /* background */
146 {0.81, 0.81, 0.81, 1.00}, /* shade A */
147 {0.62, 0.62, 0.62, 1.00}, /* shade B */
148 {0.56, 0.56, 0.56, 0.75}, /* grid */
149 {0.87, 0.31, 0.31, 0.60}, /* major grid */
150 {0.00, 0.00, 0.00, 1.00}, /* font */
151 {0.50, 0.12, 0.12, 1.00}, /* arrow */
152 {0.12, 0.12, 0.12, 1.00}, /* axis */
153 {0.00, 0.00, 0.00, 1.00} /* frame */
160 # define DPRINT(x) (void)(printf x, printf("\n"))
166 /* initialize with xtr(im,0); */
174 pixie = (double) im->xsize / (double) (im->end - im->start);
177 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
180 /* translate data values into y coordinates */
189 if (!im->logarithmic)
190 pixie = (double) im->ysize / (im->maxval - im->minval);
193 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
195 } else if (!im->logarithmic) {
196 yval = im->yorigin - pixie * (value - im->minval);
198 if (value < im->minval) {
201 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
204 /* make sure we don't return anything too unreasonable. GD lib can
205 get terribly slow when drawing lines outside its scope. This is
206 especially problematic in connection with the rigid option */
208 /* keep yval as-is */
209 } else if (yval > im->yorigin) {
210 yval = im->yorigin + 0.00001;
211 } else if (yval < im->yorigin - im->ysize) {
212 yval = im->yorigin - im->ysize - 0.00001;
219 /* conversion function for symbolic entry names */
222 #define conv_if(VV,VVV) \
223 if (strcmp(#VV, string) == 0) return VVV ;
229 conv_if(PRINT, GF_PRINT);
230 conv_if(GPRINT, GF_GPRINT);
231 conv_if(COMMENT, GF_COMMENT);
232 conv_if(HRULE, GF_HRULE);
233 conv_if(VRULE, GF_VRULE);
234 conv_if(LINE, GF_LINE);
235 conv_if(AREA, GF_AREA);
236 conv_if(STACK, GF_STACK);
237 conv_if(TICK, GF_TICK);
238 conv_if(DEF, GF_DEF);
239 conv_if(CDEF, GF_CDEF);
240 conv_if(VDEF, GF_VDEF);
241 conv_if(XPORT, GF_XPORT);
242 conv_if(SHIFT, GF_SHIFT);
247 enum gfx_if_en if_conv(
251 conv_if(PNG, IF_PNG);
252 conv_if(SVG, IF_SVG);
253 conv_if(EPS, IF_EPS);
254 conv_if(PDF, IF_PDF);
259 enum tmt_en tmt_conv(
263 conv_if(SECOND, TMT_SECOND);
264 conv_if(MINUTE, TMT_MINUTE);
265 conv_if(HOUR, TMT_HOUR);
266 conv_if(DAY, TMT_DAY);
267 conv_if(WEEK, TMT_WEEK);
268 conv_if(MONTH, TMT_MONTH);
269 conv_if(YEAR, TMT_YEAR);
273 enum grc_en grc_conv(
277 conv_if(BACK, GRC_BACK);
278 conv_if(CANVAS, GRC_CANVAS);
279 conv_if(SHADEA, GRC_SHADEA);
280 conv_if(SHADEB, GRC_SHADEB);
281 conv_if(GRID, GRC_GRID);
282 conv_if(MGRID, GRC_MGRID);
283 conv_if(FONT, GRC_FONT);
284 conv_if(ARROW, GRC_ARROW);
285 conv_if(AXIS, GRC_AXIS);
286 conv_if(FRAME, GRC_FRAME);
291 enum text_prop_en text_prop_conv(
295 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
296 conv_if(TITLE, TEXT_PROP_TITLE);
297 conv_if(AXIS, TEXT_PROP_AXIS);
298 conv_if(UNIT, TEXT_PROP_UNIT);
299 conv_if(LEGEND, TEXT_PROP_LEGEND);
310 cairo_status_t status;
314 for (i = 0; i < (unsigned) im->gdes_c; i++) {
315 if (im->gdes[i].data_first) {
316 /* careful here, because a single pointer can occur several times */
317 free(im->gdes[i].data);
318 if (im->gdes[i].ds_namv) {
319 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
320 free(im->gdes[i].ds_namv[ii]);
321 free(im->gdes[i].ds_namv);
324 free(im->gdes[i].p_data);
325 free(im->gdes[i].rpnp);
328 if (im->font_options)
329 cairo_font_options_destroy(im->font_options);
331 status = cairo_status (im->cr);
334 cairo_destroy(im->cr);
336 cairo_surface_destroy(im->surface);
338 fprintf(stderr,"OOPS: Cairo has issuesm it can't even die: %s\n",
339 cairo_status_to_string (status));
344 /* find SI magnitude symbol for the given number*/
346 image_desc_t *im, /* image description */
352 char *symbol[] = { "a", /* 10e-18 Atto */
353 "f", /* 10e-15 Femto */
354 "p", /* 10e-12 Pico */
355 "n", /* 10e-9 Nano */
356 "u", /* 10e-6 Micro */
357 "m", /* 10e-3 Milli */
362 "T", /* 10e12 Tera */
363 "P", /* 10e15 Peta */
370 if (*value == 0.0 || isnan(*value)) {
374 sindex = floor(log(fabs(*value)) / log((double) im->base));
375 *magfact = pow((double) im->base, (double) sindex);
376 (*value) /= (*magfact);
378 if (sindex <= symbcenter && sindex >= -symbcenter) {
379 (*symb_ptr) = symbol[sindex + symbcenter];
386 static char si_symbol[] = {
387 'a', /* 10e-18 Atto */
388 'f', /* 10e-15 Femto */
389 'p', /* 10e-12 Pico */
390 'n', /* 10e-9 Nano */
391 'u', /* 10e-6 Micro */
392 'm', /* 10e-3 Milli */
397 'T', /* 10e12 Tera */
398 'P', /* 10e15 Peta */
401 static const int si_symbcenter = 6;
403 /* find SI magnitude symbol for the numbers on the y-axis*/
405 image_desc_t *im /* image description */
409 double digits, viewdigits = 0;
412 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
413 log((double) im->base));
415 if (im->unitsexponent != 9999) {
416 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
417 viewdigits = floor(im->unitsexponent / 3);
422 im->magfact = pow((double) im->base, digits);
425 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
428 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
430 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
431 ((viewdigits + si_symbcenter) >= 0))
432 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
437 /* move min and max values around to become sensible */
442 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
443 600.0, 500.0, 400.0, 300.0, 250.0,
444 200.0, 125.0, 100.0, 90.0, 80.0,
445 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
446 25.0, 20.0, 10.0, 9.0, 8.0,
447 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
448 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
449 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
452 double scaled_min, scaled_max;
459 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
460 im->minval, im->maxval, im->magfact);
463 if (isnan(im->ygridstep)) {
464 if (im->extra_flags & ALTAUTOSCALE) {
465 /* measure the amplitude of the function. Make sure that
466 graph boundaries are slightly higher then max/min vals
467 so we can see amplitude on the graph */
470 delt = im->maxval - im->minval;
472 fact = 2.0 * pow(10.0,
474 (max(fabs(im->minval), fabs(im->maxval)) /
477 adj = (fact - delt) * 0.55;
480 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
481 im->minval, im->maxval, delt, fact, adj);
486 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
487 /* measure the amplitude of the function. Make sure that
488 graph boundaries are slightly lower than min vals
489 so we can see amplitude on the graph */
490 adj = (im->maxval - im->minval) * 0.1;
492 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
493 /* measure the amplitude of the function. Make sure that
494 graph boundaries are slightly higher than max vals
495 so we can see amplitude on the graph */
496 adj = (im->maxval - im->minval) * 0.1;
499 scaled_min = im->minval / im->magfact;
500 scaled_max = im->maxval / im->magfact;
502 for (i = 1; sensiblevalues[i] > 0; i++) {
503 if (sensiblevalues[i - 1] >= scaled_min &&
504 sensiblevalues[i] <= scaled_min)
505 im->minval = sensiblevalues[i] * (im->magfact);
507 if (-sensiblevalues[i - 1] <= scaled_min &&
508 -sensiblevalues[i] >= scaled_min)
509 im->minval = -sensiblevalues[i - 1] * (im->magfact);
511 if (sensiblevalues[i - 1] >= scaled_max &&
512 sensiblevalues[i] <= scaled_max)
513 im->maxval = sensiblevalues[i - 1] * (im->magfact);
515 if (-sensiblevalues[i - 1] <= scaled_max &&
516 -sensiblevalues[i] >= scaled_max)
517 im->maxval = -sensiblevalues[i] * (im->magfact);
521 /* adjust min and max to the grid definition if there is one */
522 im->minval = (double) im->ylabfact * im->ygridstep *
523 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
524 im->maxval = (double) im->ylabfact * im->ygridstep *
525 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
529 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
530 im->minval, im->maxval, im->magfact);
538 if (isnan(im->minval) || isnan(im->maxval))
541 if (im->logarithmic) {
542 double ya, yb, ypix, ypixfrac;
543 double log10_range = log10(im->maxval) - log10(im->minval);
545 ya = pow((double) 10, floor(log10(im->minval)));
546 while (ya < im->minval)
549 return; /* don't have y=10^x gridline */
551 if (yb <= im->maxval) {
552 /* we have at least 2 y=10^x gridlines.
553 Make sure distance between them in pixels
554 are an integer by expanding im->maxval */
555 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
556 double factor = y_pixel_delta / floor(y_pixel_delta);
557 double new_log10_range = factor * log10_range;
558 double new_ymax_log10 = log10(im->minval) + new_log10_range;
560 im->maxval = pow(10, new_ymax_log10);
561 ytr(im, DNAN); /* reset precalc */
562 log10_range = log10(im->maxval) - log10(im->minval);
564 /* make sure first y=10^x gridline is located on
565 integer pixel position by moving scale slightly
566 downwards (sub-pixel movement) */
567 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
568 ypixfrac = ypix - floor(ypix);
569 if (ypixfrac > 0 && ypixfrac < 1) {
570 double yfrac = ypixfrac / im->ysize;
572 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
573 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
574 ytr(im, DNAN); /* reset precalc */
577 /* Make sure we have an integer pixel distance between
578 each minor gridline */
579 double ypos1 = ytr(im, im->minval);
580 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
581 double y_pixel_delta = ypos1 - ypos2;
582 double factor = y_pixel_delta / floor(y_pixel_delta);
583 double new_range = factor * (im->maxval - im->minval);
584 double gridstep = im->ygrid_scale.gridstep;
585 double minor_y, minor_y_px, minor_y_px_frac;
587 if (im->maxval > 0.0)
588 im->maxval = im->minval + new_range;
590 im->minval = im->maxval - new_range;
591 ytr(im, DNAN); /* reset precalc */
592 /* make sure first minor gridline is on integer pixel y coord */
593 minor_y = gridstep * floor(im->minval / gridstep);
594 while (minor_y < im->minval)
596 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
597 minor_y_px_frac = minor_y_px - floor(minor_y_px);
598 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
599 double yfrac = minor_y_px_frac / im->ysize;
600 double range = im->maxval - im->minval;
602 im->minval = im->minval - yfrac * range;
603 im->maxval = im->maxval - yfrac * range;
604 ytr(im, DNAN); /* reset precalc */
606 calc_horizontal_grid(im); /* recalc with changed im->maxval */
610 /* reduce data reimplementation by Alex */
613 enum cf_en cf, /* which consolidation function ? */
614 unsigned long cur_step, /* step the data currently is in */
615 time_t *start, /* start, end and step as requested ... */
616 time_t *end, /* ... by the application will be ... */
617 unsigned long *step, /* ... adjusted to represent reality */
618 unsigned long *ds_cnt, /* number of data sources in file */
620 { /* two dimensional array containing the data */
621 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
622 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
624 rrd_value_t *srcptr, *dstptr;
626 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
629 row_cnt = ((*end) - (*start)) / cur_step;
635 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
636 row_cnt, reduce_factor, *start, *end, cur_step);
637 for (col = 0; col < row_cnt; col++) {
638 printf("time %10lu: ", *start + (col + 1) * cur_step);
639 for (i = 0; i < *ds_cnt; i++)
640 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
645 /* We have to combine [reduce_factor] rows of the source
646 ** into one row for the destination. Doing this we also
647 ** need to take care to combine the correct rows. First
648 ** alter the start and end time so that they are multiples
649 ** of the new step time. We cannot reduce the amount of
650 ** time so we have to move the end towards the future and
651 ** the start towards the past.
653 end_offset = (*end) % (*step);
654 start_offset = (*start) % (*step);
656 /* If there is a start offset (which cannot be more than
657 ** one destination row), skip the appropriate number of
658 ** source rows and one destination row. The appropriate
659 ** number is what we do know (start_offset/cur_step) of
660 ** the new interval (*step/cur_step aka reduce_factor).
663 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
664 printf("row_cnt before: %lu\n", row_cnt);
667 (*start) = (*start) - start_offset;
668 skiprows = reduce_factor - start_offset / cur_step;
669 srcptr += skiprows * *ds_cnt;
670 for (col = 0; col < (*ds_cnt); col++)
675 printf("row_cnt between: %lu\n", row_cnt);
678 /* At the end we have some rows that are not going to be
679 ** used, the amount is end_offset/cur_step
682 (*end) = (*end) - end_offset + (*step);
683 skiprows = end_offset / cur_step;
687 printf("row_cnt after: %lu\n", row_cnt);
690 /* Sanity check: row_cnt should be multiple of reduce_factor */
691 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
693 if (row_cnt % reduce_factor) {
694 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
695 row_cnt, reduce_factor);
696 printf("BUG in reduce_data()\n");
700 /* Now combine reduce_factor intervals at a time
701 ** into one interval for the destination.
704 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
705 for (col = 0; col < (*ds_cnt); col++) {
706 rrd_value_t newval = DNAN;
707 unsigned long validval = 0;
709 for (i = 0; i < reduce_factor; i++) {
710 if (isnan(srcptr[i * (*ds_cnt) + col])) {
715 newval = srcptr[i * (*ds_cnt) + col];
723 newval += srcptr[i * (*ds_cnt) + col];
726 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
729 /* an interval contains a failure if any subintervals contained a failure */
731 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
734 newval = srcptr[i * (*ds_cnt) + col];
759 srcptr += (*ds_cnt) * reduce_factor;
760 row_cnt -= reduce_factor;
762 /* If we had to alter the endtime, we didn't have enough
763 ** source rows to fill the last row. Fill it with NaN.
766 for (col = 0; col < (*ds_cnt); col++)
769 row_cnt = ((*end) - (*start)) / *step;
771 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
772 row_cnt, *start, *end, *step);
773 for (col = 0; col < row_cnt; col++) {
774 printf("time %10lu: ", *start + (col + 1) * (*step));
775 for (i = 0; i < *ds_cnt; i++)
776 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
783 /* get the data required for the graphs from the
792 /* pull the data from the rrd files ... */
793 for (i = 0; i < (int) im->gdes_c; i++) {
794 /* only GF_DEF elements fetch data */
795 if (im->gdes[i].gf != GF_DEF)
799 /* do we have it already ? */
800 for (ii = 0; ii < i; ii++) {
801 if (im->gdes[ii].gf != GF_DEF)
803 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
804 && (im->gdes[i].cf == im->gdes[ii].cf)
805 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
806 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
807 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
808 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
809 /* OK, the data is already there.
810 ** Just copy the header portion
812 im->gdes[i].start = im->gdes[ii].start;
813 im->gdes[i].end = im->gdes[ii].end;
814 im->gdes[i].step = im->gdes[ii].step;
815 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
816 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
817 im->gdes[i].data = im->gdes[ii].data;
818 im->gdes[i].data_first = 0;
825 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
827 if ((rrd_fetch_fn(im->gdes[i].rrd,
833 &im->gdes[i].ds_namv,
834 &im->gdes[i].data)) == -1) {
837 im->gdes[i].data_first = 1;
839 if (ft_step < im->gdes[i].step) {
840 reduce_data(im->gdes[i].cf_reduce,
845 &im->gdes[i].ds_cnt, &im->gdes[i].data);
847 im->gdes[i].step = ft_step;
851 /* lets see if the required data source is really there */
852 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
853 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
857 if (im->gdes[i].ds == -1) {
858 rrd_set_error("No DS called '%s' in '%s'",
859 im->gdes[i].ds_nam, im->gdes[i].rrd);
867 /* evaluate the expressions in the CDEF functions */
869 /*************************************************************
871 *************************************************************/
873 long find_var_wrapper(
877 return find_var((image_desc_t *) arg1, key);
880 /* find gdes containing var*/
887 for (ii = 0; ii < im->gdes_c - 1; ii++) {
888 if ((im->gdes[ii].gf == GF_DEF
889 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
890 && (strcmp(im->gdes[ii].vname, key) == 0)) {
897 /* find the largest common denominator for all the numbers
898 in the 0 terminated num array */
905 for (i = 0; num[i + 1] != 0; i++) {
907 rest = num[i] % num[i + 1];
913 /* return i==0?num[i]:num[i-1]; */
917 /* run the rpn calculator on all the VDEF and CDEF arguments */
924 long *steparray, rpi;
929 rpnstack_init(&rpnstack);
931 for (gdi = 0; gdi < im->gdes_c; gdi++) {
932 /* Look for GF_VDEF and GF_CDEF in the same loop,
933 * so CDEFs can use VDEFs and vice versa
935 switch (im->gdes[gdi].gf) {
939 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
941 /* remove current shift */
942 vdp->start -= vdp->shift;
943 vdp->end -= vdp->shift;
946 if (im->gdes[gdi].shidx >= 0)
947 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
950 vdp->shift = im->gdes[gdi].shval;
952 /* normalize shift to multiple of consolidated step */
953 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
956 vdp->start += vdp->shift;
957 vdp->end += vdp->shift;
961 /* A VDEF has no DS. This also signals other parts
962 * of rrdtool that this is a VDEF value, not a CDEF.
964 im->gdes[gdi].ds_cnt = 0;
965 if (vdef_calc(im, gdi)) {
966 rrd_set_error("Error processing VDEF '%s'",
967 im->gdes[gdi].vname);
968 rpnstack_free(&rpnstack);
973 im->gdes[gdi].ds_cnt = 1;
974 im->gdes[gdi].ds = 0;
975 im->gdes[gdi].data_first = 1;
976 im->gdes[gdi].start = 0;
977 im->gdes[gdi].end = 0;
982 /* Find the variables in the expression.
983 * - VDEF variables are substituted by their values
984 * and the opcode is changed into OP_NUMBER.
985 * - CDEF variables are analized for their step size,
986 * the lowest common denominator of all the step
987 * sizes of the data sources involved is calculated
988 * and the resulting number is the step size for the
989 * resulting data source.
991 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
992 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
993 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
994 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
996 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
999 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1000 im->gdes[gdi].vname, im->gdes[ptr].vname);
1001 printf("DEBUG: value from vdef is %f\n",
1002 im->gdes[ptr].vf.val);
1004 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1005 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1006 } else { /* normal variables and PREF(variables) */
1008 /* add one entry to the array that keeps track of the step sizes of the
1009 * data sources going into the CDEF. */
1011 rrd_realloc(steparray,
1013 1) * sizeof(*steparray))) == NULL) {
1014 rrd_set_error("realloc steparray");
1015 rpnstack_free(&rpnstack);
1019 steparray[stepcnt - 1] = im->gdes[ptr].step;
1021 /* adjust start and end of cdef (gdi) so
1022 * that it runs from the latest start point
1023 * to the earliest endpoint of any of the
1024 * rras involved (ptr)
1027 if (im->gdes[gdi].start < im->gdes[ptr].start)
1028 im->gdes[gdi].start = im->gdes[ptr].start;
1030 if (im->gdes[gdi].end == 0 ||
1031 im->gdes[gdi].end > im->gdes[ptr].end)
1032 im->gdes[gdi].end = im->gdes[ptr].end;
1034 /* store pointer to the first element of
1035 * the rra providing data for variable,
1036 * further save step size and data source
1039 im->gdes[gdi].rpnp[rpi].data =
1040 im->gdes[ptr].data + im->gdes[ptr].ds;
1041 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1042 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1044 /* backoff the *.data ptr; this is done so
1045 * rpncalc() function doesn't have to treat
1046 * the first case differently
1048 } /* if ds_cnt != 0 */
1049 } /* if OP_VARIABLE */
1050 } /* loop through all rpi */
1052 /* move the data pointers to the correct period */
1053 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1054 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1055 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1056 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1058 im->gdes[gdi].start - im->gdes[ptr].start;
1061 im->gdes[gdi].rpnp[rpi].data +=
1062 (diff / im->gdes[ptr].step) *
1063 im->gdes[ptr].ds_cnt;
1067 if (steparray == NULL) {
1068 rrd_set_error("rpn expressions without DEF"
1069 " or CDEF variables are not supported");
1070 rpnstack_free(&rpnstack);
1073 steparray[stepcnt] = 0;
1074 /* Now find the resulting step. All steps in all
1075 * used RRAs have to be visited
1077 im->gdes[gdi].step = lcd(steparray);
1079 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1080 im->gdes[gdi].start)
1081 / im->gdes[gdi].step)
1082 * sizeof(double))) == NULL) {
1083 rrd_set_error("malloc im->gdes[gdi].data");
1084 rpnstack_free(&rpnstack);
1088 /* Step through the new cdef results array and
1089 * calculate the values
1091 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1092 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1093 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1095 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1096 * in this case we are advancing by timesteps;
1097 * we use the fact that time_t is a synonym for long
1099 if (rpn_calc(rpnp, &rpnstack, (long) now,
1100 im->gdes[gdi].data, ++dataidx) == -1) {
1101 /* rpn_calc sets the error string */
1102 rpnstack_free(&rpnstack);
1105 } /* enumerate over time steps within a CDEF */
1110 } /* enumerate over CDEFs */
1111 rpnstack_free(&rpnstack);
1115 /* massage data so, that we get one value for each x coordinate in the graph */
1120 double pixstep = (double) (im->end - im->start)
1121 / (double) im->xsize; /* how much time
1122 passes in one pixel */
1124 double minval = DNAN, maxval = DNAN;
1126 unsigned long gr_time;
1128 /* memory for the processed data */
1129 for (i = 0; i < im->gdes_c; i++) {
1130 if ((im->gdes[i].gf == GF_LINE) ||
1131 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1132 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1133 * sizeof(rrd_value_t))) == NULL) {
1134 rrd_set_error("malloc data_proc");
1140 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1143 gr_time = im->start + pixstep * i; /* time of the current step */
1146 for (ii = 0; ii < im->gdes_c; ii++) {
1149 switch (im->gdes[ii].gf) {
1153 if (!im->gdes[ii].stack)
1155 value = im->gdes[ii].yrule;
1156 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1157 /* The time of the data doesn't necessarily match
1158 ** the time of the graph. Beware.
1160 vidx = im->gdes[ii].vidx;
1161 if (im->gdes[vidx].gf == GF_VDEF) {
1162 value = im->gdes[vidx].vf.val;
1164 if (((long int) gr_time >=
1165 (long int) im->gdes[vidx].start)
1166 && ((long int) gr_time <=
1167 (long int) im->gdes[vidx].end)) {
1168 value = im->gdes[vidx].data[(unsigned long)
1174 im->gdes[vidx].step)
1175 * im->gdes[vidx].ds_cnt +
1182 if (!isnan(value)) {
1184 im->gdes[ii].p_data[i] = paintval;
1185 /* GF_TICK: the data values are not
1186 ** relevant for min and max
1188 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1189 if ((isnan(minval) || paintval < minval) &&
1190 !(im->logarithmic && paintval <= 0.0))
1192 if (isnan(maxval) || paintval > maxval)
1196 im->gdes[ii].p_data[i] = DNAN;
1201 ("STACK should already be turned into LINE or AREA here");
1210 /* if min or max have not been asigned a value this is because
1211 there was no data in the graph ... this is not good ...
1212 lets set these to dummy values then ... */
1214 if (im->logarithmic) {
1226 /* adjust min and max values */
1227 if (isnan(im->minval)
1228 /* don't adjust low-end with log scale *//* why not? */
1229 || ((!im->rigid) && im->minval > minval)
1231 if (im->logarithmic)
1232 im->minval = minval * 0.5;
1234 im->minval = minval;
1236 if (isnan(im->maxval)
1237 || (!im->rigid && im->maxval < maxval)
1239 if (im->logarithmic)
1240 im->maxval = maxval * 2.0;
1242 im->maxval = maxval;
1244 /* make sure min is smaller than max */
1245 if (im->minval > im->maxval) {
1246 im->minval = 0.99 * im->maxval;
1249 /* make sure min and max are not equal */
1250 if (im->minval == im->maxval) {
1252 if (!im->logarithmic) {
1255 /* make sure min and max are not both zero */
1256 if (im->maxval == 0.0) {
1265 /* identify the point where the first gridline, label ... gets placed */
1267 time_t find_first_time(
1268 time_t start, /* what is the initial time */
1269 enum tmt_en baseint, /* what is the basic interval */
1270 long basestep /* how many if these do we jump a time */
1275 localtime_r(&start, &tm);
1279 tm. tm_sec -= tm.tm_sec % basestep;
1284 tm. tm_min -= tm.tm_min % basestep;
1290 tm. tm_hour -= tm.tm_hour % basestep;
1294 /* we do NOT look at the basestep for this ... */
1301 /* we do NOT look at the basestep for this ... */
1305 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1307 if (tm.tm_wday == 0)
1308 tm. tm_mday -= 7; /* we want the *previous* monday */
1316 tm. tm_mon -= tm.tm_mon % basestep;
1327 tm.tm_year + 1900) %basestep;
1333 /* identify the point where the next gridline, label ... gets placed */
1334 time_t find_next_time(
1335 time_t current, /* what is the initial time */
1336 enum tmt_en baseint, /* what is the basic interval */
1337 long basestep /* how many if these do we jump a time */
1343 localtime_r(¤t, &tm);
1348 tm. tm_sec += basestep;
1352 tm. tm_min += basestep;
1356 tm. tm_hour += basestep;
1360 tm. tm_mday += basestep;
1364 tm. tm_mday += 7 * basestep;
1368 tm. tm_mon += basestep;
1372 tm. tm_year += basestep;
1374 madetime = mktime(&tm);
1375 } while (madetime == -1); /* this is necessary to skip impssible times
1376 like the daylight saving time skips */
1382 /* calculate values required for PRINT and GPRINT functions */
1388 long i, ii, validsteps;
1391 int graphelement = 0;
1394 double magfact = -1;
1399 /* wow initializing tmvdef is quite a task :-) */
1400 time_t now = time(NULL);
1402 localtime_r(&now, &tmvdef);
1405 for (i = 0; i < im->gdes_c; i++) {
1406 vidx = im->gdes[i].vidx;
1407 switch (im->gdes[i].gf) {
1411 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1412 rrd_set_error("realloc prdata");
1416 /* PRINT and GPRINT can now print VDEF generated values.
1417 * There's no need to do any calculations on them as these
1418 * calculations were already made.
1420 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1421 printval = im->gdes[vidx].vf.val;
1422 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1423 } else { /* need to calculate max,min,avg etcetera */
1424 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1425 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1428 for (ii = im->gdes[vidx].ds;
1429 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1430 if (!finite(im->gdes[vidx].data[ii]))
1432 if (isnan(printval)) {
1433 printval = im->gdes[vidx].data[ii];
1438 switch (im->gdes[i].cf) {
1441 case CF_DEVSEASONAL:
1445 printval += im->gdes[vidx].data[ii];
1448 printval = min(printval, im->gdes[vidx].data[ii]);
1452 printval = max(printval, im->gdes[vidx].data[ii]);
1455 printval = im->gdes[vidx].data[ii];
1458 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1459 if (validsteps > 1) {
1460 printval = (printval / validsteps);
1463 } /* prepare printval */
1465 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1466 /* Magfact is set to -1 upon entry to print_calc. If it
1467 * is still less than 0, then we need to run auto_scale.
1468 * Otherwise, put the value into the correct units. If
1469 * the value is 0, then do not set the symbol or magnification
1470 * so next the calculation will be performed again. */
1471 if (magfact < 0.0) {
1472 auto_scale(im, &printval, &si_symb, &magfact);
1473 if (printval == 0.0)
1476 printval /= magfact;
1478 *(++percent_s) = 's';
1479 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1480 auto_scale(im, &printval, &si_symb, &magfact);
1483 if (im->gdes[i].gf == GF_PRINT) {
1484 (*prdata)[prlines - 2] =
1485 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1486 (*prdata)[prlines - 1] = NULL;
1487 if (im->gdes[i].strftm) {
1488 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1489 im->gdes[i].format, &tmvdef);
1491 if (bad_format(im->gdes[i].format)) {
1492 rrd_set_error("bad format for PRINT in '%s'",
1493 im->gdes[i].format);
1496 #ifdef HAVE_SNPRINTF
1497 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1498 im->gdes[i].format, printval, si_symb);
1500 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1507 if (im->gdes[i].strftm) {
1508 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1509 im->gdes[i].format, &tmvdef);
1511 if (bad_format(im->gdes[i].format)) {
1512 rrd_set_error("bad format for GPRINT in '%s'",
1513 im->gdes[i].format);
1516 #ifdef HAVE_SNPRINTF
1517 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1518 im->gdes[i].format, printval, si_symb);
1520 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1533 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1534 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1539 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1540 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1548 #ifdef WITH_PIECHART
1556 ("STACK should already be turned into LINE or AREA here");
1561 return graphelement;
1565 /* place legends with color spots */
1571 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1572 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1573 int fill = 0, fill_last;
1575 int leg_x = border, leg_y = im->yimg;
1576 int leg_y_prev = im->yimg;
1579 int i, ii, mark = 0;
1580 char prt_fctn; /*special printfunctions */
1583 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1584 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1585 rrd_set_error("malloc for legspace");
1589 if (im->extra_flags & FULL_SIZE_MODE)
1590 leg_y = leg_y_prev =
1591 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1593 for (i = 0; i < im->gdes_c; i++) {
1596 /* hide legends for rules which are not displayed */
1598 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1599 if (im->gdes[i].gf == GF_HRULE &&
1600 (im->gdes[i].yrule < im->minval
1601 || im->gdes[i].yrule > im->maxval))
1602 im->gdes[i].legend[0] = '\0';
1604 if (im->gdes[i].gf == GF_VRULE &&
1605 (im->gdes[i].xrule < im->start
1606 || im->gdes[i].xrule > im->end))
1607 im->gdes[i].legend[0] = '\0';
1610 leg_cc = strlen(im->gdes[i].legend);
1612 /* is there a controle code ant the end of the legend string ? */
1613 /* and it is not a tab \\t */
1614 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1615 && im->gdes[i].legend[leg_cc - 1] != 't') {
1616 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1618 im->gdes[i].legend[leg_cc] = '\0';
1622 /* only valid control codes */
1623 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1628 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1630 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1631 im->gdes[i].legend, prt_fctn);
1636 /* remove exess space */
1637 if (prt_fctn == 'n') {
1641 while (prt_fctn == 'g' &&
1642 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1644 im->gdes[i].legend[leg_cc] = '\0';
1647 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1650 /* no interleg space if string ends in \g */
1651 fill += legspace[i];
1653 fill += gfx_get_text_width(im, fill + border,
1654 im->text_prop[TEXT_PROP_LEGEND].
1656 im->text_prop[TEXT_PROP_LEGEND].
1658 im->gdes[i].legend);
1663 /* who said there was a special tag ... ? */
1664 if (prt_fctn == 'g') {
1667 if (prt_fctn == '\0') {
1668 if (i == im->gdes_c - 1)
1671 /* is it time to place the legends ? */
1672 if (fill > im->ximg - 2 * border) {
1687 if (prt_fctn != '\0') {
1689 if (leg_c >= 2 && prt_fctn == 'j') {
1690 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1694 if (prt_fctn == 'c')
1695 leg_x = (im->ximg - fill) / 2.0;
1696 if (prt_fctn == 'r')
1697 leg_x = im->ximg - fill - border;
1699 for (ii = mark; ii <= i; ii++) {
1700 if (im->gdes[ii].legend[0] == '\0')
1701 continue; /* skip empty legends */
1702 im->gdes[ii].leg_x = leg_x;
1703 im->gdes[ii].leg_y = leg_y;
1705 gfx_get_text_width(im, leg_x,
1706 im->text_prop[TEXT_PROP_LEGEND].
1708 im->text_prop[TEXT_PROP_LEGEND].
1710 im->gdes[ii].legend)
1715 if (im->extra_flags & FULL_SIZE_MODE) {
1716 /* only add y space if there was text on the line */
1717 if (leg_x > border || prt_fctn == 's')
1718 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1719 if (prt_fctn == 's')
1720 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1722 if (leg_x > border || prt_fctn == 's')
1723 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1724 if (prt_fctn == 's')
1725 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1733 if (im->extra_flags & FULL_SIZE_MODE) {
1734 if (leg_y != leg_y_prev) {
1735 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1737 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1740 im->yimg = leg_y_prev;
1741 /* if we did place some legends we have to add vertical space */
1742 if (leg_y != im->yimg)
1743 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1750 /* create a grid on the graph. it determines what to do
1751 from the values of xsize, start and end */
1753 /* the xaxis labels are determined from the number of seconds per pixel
1754 in the requested graph */
1758 int calc_horizontal_grid(
1765 int decimals, fractionals;
1767 im->ygrid_scale.labfact = 2;
1768 range = im->maxval - im->minval;
1769 scaledrange = range / im->magfact;
1771 /* does the scale of this graph make it impossible to put lines
1772 on it? If so, give up. */
1773 if (isnan(scaledrange)) {
1777 /* find grid spaceing */
1779 if (isnan(im->ygridstep)) {
1780 if (im->extra_flags & ALTYGRID) {
1781 /* find the value with max number of digits. Get number of digits */
1784 (max(fabs(im->maxval), fabs(im->minval)) *
1785 im->viewfactor / im->magfact));
1786 if (decimals <= 0) /* everything is small. make place for zero */
1789 im->ygrid_scale.gridstep =
1791 floor(log10(range * im->viewfactor / im->magfact))) /
1792 im->viewfactor * im->magfact;
1794 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1795 im->ygrid_scale.gridstep = 0.1;
1796 /* should have at least 5 lines but no more then 15 */
1797 if (range / im->ygrid_scale.gridstep < 5)
1798 im->ygrid_scale.gridstep /= 10;
1799 if (range / im->ygrid_scale.gridstep > 15)
1800 im->ygrid_scale.gridstep *= 10;
1801 if (range / im->ygrid_scale.gridstep > 5) {
1802 im->ygrid_scale.labfact = 1;
1803 if (range / im->ygrid_scale.gridstep > 8)
1804 im->ygrid_scale.labfact = 2;
1806 im->ygrid_scale.gridstep /= 5;
1807 im->ygrid_scale.labfact = 5;
1811 (im->ygrid_scale.gridstep *
1812 (double) im->ygrid_scale.labfact * im->viewfactor /
1814 if (fractionals < 0) { /* small amplitude. */
1815 int len = decimals - fractionals + 1;
1817 if (im->unitslength < len + 2)
1818 im->unitslength = len + 2;
1819 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1820 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1822 int len = decimals + 1;
1824 if (im->unitslength < len + 2)
1825 im->unitslength = len + 2;
1826 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1827 (im->symbol != ' ' ? " %c" : ""));
1830 for (i = 0; ylab[i].grid > 0; i++) {
1831 pixel = im->ysize / (scaledrange / ylab[i].grid);
1837 for (i = 0; i < 4; i++) {
1838 if (pixel * ylab[gridind].lfac[i] >=
1839 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1840 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1845 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1848 im->ygrid_scale.gridstep = im->ygridstep;
1849 im->ygrid_scale.labfact = im->ylabfact;
1854 int draw_horizontal_grid(
1859 char graph_label[100];
1861 double X0 = im->xorigin;
1862 double X1 = im->xorigin + im->xsize;
1864 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1865 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1869 im->ygrid_scale.gridstep / (double) im->magfact *
1870 (double) im->viewfactor;
1871 MaxY = scaledstep * (double) egrid;
1872 for (i = sgrid; i <= egrid; i++) {
1873 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1874 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1876 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1877 && floor(Y0 + 0.5) <= im->yorigin) {
1878 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1879 with the chosen settings. Add a label if required by settings, or if
1880 there is only one label so far and the next grid line is out of bounds. */
1881 if (i % im->ygrid_scale.labfact == 0
1883 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1884 if (im->symbol == ' ') {
1885 if (im->extra_flags & ALTYGRID) {
1886 sprintf(graph_label, im->ygrid_scale.labfmt,
1887 scaledstep * (double) i);
1890 sprintf(graph_label, "%4.1f",
1891 scaledstep * (double) i);
1893 sprintf(graph_label, "%4.0f",
1894 scaledstep * (double) i);
1898 char sisym = (i == 0 ? ' ' : im->symbol);
1900 if (im->extra_flags & ALTYGRID) {
1901 sprintf(graph_label, im->ygrid_scale.labfmt,
1902 scaledstep * (double) i, sisym);
1905 sprintf(graph_label, "%4.1f %c",
1906 scaledstep * (double) i, sisym);
1908 sprintf(graph_label, "%4.0f %c",
1909 scaledstep * (double) i, sisym);
1916 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1917 im->graph_col[GRC_FONT],
1918 im->text_prop[TEXT_PROP_AXIS].font,
1919 im->text_prop[TEXT_PROP_AXIS].size,
1920 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1924 X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1927 X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1931 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1932 im->grid_dash_on, im->grid_dash_off);
1934 } else if (!(im->extra_flags & NOMINOR)) {
1937 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1940 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1944 GRIDWIDTH, im->graph_col[GRC_GRID],
1945 im->grid_dash_on, im->grid_dash_off);
1953 /* this is frexp for base 10 */
1964 iexp = floor(log(fabs(x)) / log(10));
1965 mnt = x / pow(10.0, iexp);
1968 mnt = x / pow(10.0, iexp);
1974 static int AlmostEqual2sComplement(
1980 int aInt = *(int *) &A;
1981 int bInt = *(int *) &B;
1984 /* Make sure maxUlps is non-negative and small enough that the
1985 default NAN won't compare as equal to anything. */
1987 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1989 /* Make aInt lexicographically ordered as a twos-complement int */
1992 aInt = 0x80000000l - aInt;
1994 /* Make bInt lexicographically ordered as a twos-complement int */
1997 bInt = 0x80000000l - bInt;
1999 intDiff = abs(aInt - bInt);
2001 if (intDiff <= maxUlps)
2007 /* logaritmic horizontal grid */
2008 int horizontal_log_grid(
2011 double yloglab[][10] = {
2012 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2013 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2014 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2015 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2016 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2017 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2020 int i, j, val_exp, min_exp;
2021 double nex; /* number of decades in data */
2022 double logscale; /* scale in logarithmic space */
2023 int exfrac = 1; /* decade spacing */
2024 int mid = -1; /* row in yloglab for major grid */
2025 double mspac; /* smallest major grid spacing (pixels) */
2026 int flab; /* first value in yloglab to use */
2027 double value, tmp, pre_value;
2029 char graph_label[100];
2031 nex = log10(im->maxval / im->minval);
2032 logscale = im->ysize / nex;
2034 /* major spacing for data with high dynamic range */
2035 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2042 /* major spacing for less dynamic data */
2044 /* search best row in yloglab */
2046 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2047 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2048 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2049 && yloglab[mid][0] > 0);
2053 /* find first value in yloglab */
2055 yloglab[mid][flab] < 10
2056 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2057 if (yloglab[mid][flab] == 10.0) {
2062 if (val_exp % exfrac)
2063 val_exp += abs(-val_exp % exfrac);
2066 X1 = im->xorigin + im->xsize;
2072 value = yloglab[mid][flab] * pow(10.0, val_exp);
2073 if (AlmostEqual2sComplement(value, pre_value, 4))
2074 break; /* it seems we are not converging */
2078 Y0 = ytr(im, value);
2079 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2082 /* major grid line */
2085 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2087 X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2093 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2094 im->grid_dash_on, im->grid_dash_off);
2097 if (im->extra_flags & FORCE_UNITS_SI) {
2102 scale = floor(val_exp / 3.0);
2104 pvalue = pow(10.0, val_exp % 3);
2106 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2107 pvalue *= yloglab[mid][flab];
2109 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2110 ((scale + si_symbcenter) >= 0))
2111 symbol = si_symbol[scale + si_symbcenter];
2115 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2117 sprintf(graph_label, "%3.0e", value);
2119 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2120 im->graph_col[GRC_FONT],
2121 im->text_prop[TEXT_PROP_AXIS].font,
2122 im->text_prop[TEXT_PROP_AXIS].size,
2123 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2126 if (mid < 4 && exfrac == 1) {
2127 /* find first and last minor line behind current major line
2128 * i is the first line and j tha last */
2130 min_exp = val_exp - 1;
2131 for (i = 1; yloglab[mid][i] < 10.0; i++);
2132 i = yloglab[mid][i - 1] + 1;
2136 i = yloglab[mid][flab - 1] + 1;
2137 j = yloglab[mid][flab];
2140 /* draw minor lines below current major line */
2141 for (; i < j; i++) {
2143 value = i * pow(10.0, min_exp);
2144 if (value < im->minval)
2147 Y0 = ytr(im, value);
2148 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2154 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2157 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2161 GRIDWIDTH, im->graph_col[GRC_GRID],
2162 im->grid_dash_on, im->grid_dash_off);
2164 } else if (exfrac > 1) {
2165 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2166 value = pow(10.0, i);
2167 if (value < im->minval)
2170 Y0 = ytr(im, value);
2171 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2177 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2180 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2184 GRIDWIDTH, im->graph_col[GRC_GRID],
2185 im->grid_dash_on, im->grid_dash_off);
2190 if (yloglab[mid][++flab] == 10.0) {
2196 /* draw minor lines after highest major line */
2197 if (mid < 4 && exfrac == 1) {
2198 /* find first and last minor line below current major line
2199 * i is the first line and j tha last */
2201 min_exp = val_exp - 1;
2202 for (i = 1; yloglab[mid][i] < 10.0; i++);
2203 i = yloglab[mid][i - 1] + 1;
2207 i = yloglab[mid][flab - 1] + 1;
2208 j = yloglab[mid][flab];
2211 /* draw minor lines below current major line */
2212 for (; i < j; i++) {
2214 value = i * pow(10.0, min_exp);
2215 if (value < im->minval)
2218 Y0 = ytr(im, value);
2219 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2224 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2226 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2230 GRIDWIDTH, im->graph_col[GRC_GRID],
2231 im->grid_dash_on, im->grid_dash_off);
2234 /* fancy minor gridlines */
2235 else if (exfrac > 1) {
2236 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2237 value = pow(10.0, i);
2238 if (value < im->minval)
2241 Y0 = ytr(im, value);
2242 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2247 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2249 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2253 GRIDWIDTH, im->graph_col[GRC_GRID],
2254 im->grid_dash_on, im->grid_dash_off);
2265 int xlab_sel; /* which sort of label and grid ? */
2266 time_t ti, tilab, timajor;
2268 char graph_label[100];
2269 double X0, Y0, Y1; /* points for filled graph and more */
2272 /* the type of time grid is determined by finding
2273 the number of seconds per pixel in the graph */
2276 if (im->xlab_user.minsec == -1) {
2277 factor = (im->end - im->start) / im->xsize;
2279 while (xlab[xlab_sel + 1].minsec != -1
2280 && xlab[xlab_sel + 1].minsec <= factor) {
2282 } /* pick the last one */
2283 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2284 && xlab[xlab_sel].length > (im->end - im->start)) {
2286 } /* go back to the smallest size */
2287 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2288 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2289 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2290 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2291 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2292 im->xlab_user.labst = xlab[xlab_sel].labst;
2293 im->xlab_user.precis = xlab[xlab_sel].precis;
2294 im->xlab_user.stst = xlab[xlab_sel].stst;
2297 /* y coords are the same for every line ... */
2299 Y1 = im->yorigin - im->ysize;
2302 /* paint the minor grid */
2303 if (!(im->extra_flags & NOMINOR)) {
2304 for (ti = find_first_time(im->start,
2305 im->xlab_user.gridtm,
2306 im->xlab_user.gridst),
2307 timajor = find_first_time(im->start,
2308 im->xlab_user.mgridtm,
2309 im->xlab_user.mgridst);
2312 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2314 /* are we inside the graph ? */
2315 if (ti < im->start || ti > im->end)
2317 while (timajor < ti) {
2318 timajor = find_next_time(timajor,
2319 im->xlab_user.mgridtm,
2320 im->xlab_user.mgridst);
2323 continue; /* skip as falls on major grid line */
2325 gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2326 im->graph_col[GRC_GRID]);
2327 gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2328 im->graph_col[GRC_GRID]);
2329 gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2330 im->graph_col[GRC_GRID],
2331 im->grid_dash_on, im->grid_dash_off);
2336 /* paint the major grid */
2337 for (ti = find_first_time(im->start,
2338 im->xlab_user.mgridtm,
2339 im->xlab_user.mgridst);
2341 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2343 /* are we inside the graph ? */
2344 if (ti < im->start || ti > im->end)
2347 gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2348 im->graph_col[GRC_MGRID]);
2349 gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2350 im->graph_col[GRC_MGRID]);
2351 gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2352 im->graph_col[GRC_MGRID],
2353 im->grid_dash_on, im->grid_dash_off);
2356 /* paint the labels below the graph */
2357 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2358 im->xlab_user.labtm,
2359 im->xlab_user.labst);
2360 ti <= im->end - im->xlab_user.precis / 2;
2361 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2363 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2364 /* are we inside the graph ? */
2365 if (tilab < im->start || tilab > im->end)
2369 localtime_r(&tilab, &tm);
2370 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2372 # error "your libc has no strftime I guess we'll abort the exercise here."
2377 im->graph_col[GRC_FONT],
2378 im->text_prop[TEXT_PROP_AXIS].font,
2379 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2380 GFX_H_CENTER, GFX_V_TOP, graph_label);
2390 /* draw x and y axis */
2391 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2392 im->xorigin+im->xsize,im->yorigin-im->ysize,
2393 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2395 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2396 im->xorigin+im->xsize,im->yorigin-im->ysize,
2397 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2399 gfx_line(im, im->xorigin - 4, im->yorigin,
2400 im->xorigin + im->xsize + 4, im->yorigin,
2401 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2403 gfx_line(im, im->xorigin, im->yorigin + 4,
2404 im->xorigin, im->yorigin - im->ysize - 4,
2405 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2408 /* arrow for X and Y axis direction */
2409 gfx_new_area(im, im->xorigin + im->xsize + 2, im->yorigin - 2, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin + 0.5, /* LINEOFFSET */
2410 im->graph_col[GRC_ARROW]);
2413 gfx_new_area(im, im->xorigin - 2, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin + 0.5, im->yorigin - im->ysize - 7, /* LINEOFFSET */
2414 im->graph_col[GRC_ARROW]);
2425 double X0, Y0; /* points for filled graph and more */
2426 struct gfx_color_t water_color;
2428 /* draw 3d border */
2429 gfx_new_area(im, 0, im->yimg,
2430 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2431 gfx_add_point(im, im->ximg - 2, 2);
2432 gfx_add_point(im, im->ximg, 0);
2433 gfx_add_point(im, 0, 0);
2436 gfx_new_area(im, 2, im->yimg - 2,
2437 im->ximg - 2, im->yimg - 2,
2438 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2439 gfx_add_point(im, im->ximg, 0);
2440 gfx_add_point(im, im->ximg, im->yimg);
2441 gfx_add_point(im, 0, im->yimg);
2445 if (im->draw_x_grid == 1)
2448 if (im->draw_y_grid == 1) {
2449 if (im->logarithmic) {
2450 res = horizontal_log_grid(im);
2452 res = draw_horizontal_grid(im);
2455 /* dont draw horizontal grid if there is no min and max val */
2457 char *nodata = "No Data found";
2459 gfx_text(im, im->ximg / 2,
2460 (2 * im->yorigin - im->ysize) / 2,
2461 im->graph_col[GRC_FONT],
2462 im->text_prop[TEXT_PROP_AXIS].font,
2463 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2464 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2468 /* yaxis unit description */
2470 10, (im->yorigin - im->ysize / 2),
2471 im->graph_col[GRC_FONT],
2472 im->text_prop[TEXT_PROP_UNIT].font,
2473 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2474 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2479 im->graph_col[GRC_FONT],
2480 im->text_prop[TEXT_PROP_TITLE].font,
2481 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2482 GFX_H_CENTER, GFX_V_TOP, im->title);
2483 /* rrdtool 'logo' */
2484 water_color = im->graph_col[GRC_FONT];
2485 water_color.alpha = 0.3;
2489 im->text_prop[TEXT_PROP_AXIS].font,
2490 5.5, im->tabwidth, -90,
2491 GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2493 /* graph watermark */
2494 if (im->watermark[0] != '\0') {
2496 im->ximg / 2, im->yimg - 6,
2498 im->text_prop[TEXT_PROP_AXIS].font,
2499 5.5, im->tabwidth, 0,
2500 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2504 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2505 for (i = 0; i < im->gdes_c; i++) {
2506 if (im->gdes[i].legend[0] == '\0')
2509 /* im->gdes[i].leg_y is the bottom of the legend */
2510 X0 = im->gdes[i].leg_x;
2511 Y0 = im->gdes[i].leg_y;
2512 gfx_text(im, X0, Y0,
2513 im->graph_col[GRC_FONT],
2514 im->text_prop[TEXT_PROP_LEGEND].font,
2515 im->text_prop[TEXT_PROP_LEGEND].size,
2516 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2517 im->gdes[i].legend);
2518 /* The legend for GRAPH items starts with "M " to have
2519 enough space for the box */
2520 if (im->gdes[i].gf != GF_PRINT &&
2521 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2526 boxH = gfx_get_text_width(im, 0,
2527 im->text_prop[TEXT_PROP_LEGEND].
2529 im->text_prop[TEXT_PROP_LEGEND].
2530 size, im->tabwidth, "o") * 1.2;
2533 /* shift the box up a bit */
2536 /* make sure transparent colors show up the same way as in the graph */
2540 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2541 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2546 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2547 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2551 cairo_new_path(im->cr);
2552 cairo_set_line_width(im->cr, 1.0);
2555 gfx_line_fit(im, &X0, &Y0);
2556 gfx_line_fit(im, &X1, &Y1);
2557 cairo_move_to(im->cr, X0, Y0);
2558 cairo_line_to(im->cr, X1, Y0);
2559 cairo_line_to(im->cr, X1, Y1);
2560 cairo_line_to(im->cr, X0, Y1);
2561 cairo_close_path(im->cr);
2562 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2563 im->graph_col[GRC_FRAME].green,
2564 im->graph_col[GRC_FRAME].blue,
2565 im->graph_col[GRC_FRAME].alpha);
2566 cairo_stroke(im->cr);
2567 cairo_restore(im->cr);
2574 /*****************************************************
2575 * lazy check make sure we rely need to create this graph
2576 *****************************************************/
2583 struct stat imgstat;
2586 return 0; /* no lazy option */
2587 if (stat(im->graphfile, &imgstat) != 0)
2588 return 0; /* can't stat */
2589 /* one pixel in the existing graph is more then what we would
2591 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2593 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2594 return 0; /* the file does not exist */
2595 switch (im->imgformat) {
2597 size = PngSize(fd, &(im->ximg), &(im->yimg));
2607 int graph_size_location(
2611 /* The actual size of the image to draw is determined from
2612 ** several sources. The size given on the command line is
2613 ** the graph area but we need more as we have to draw labels
2614 ** and other things outside the graph area
2617 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2618 Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2620 if (im->extra_flags & ONLY_GRAPH) {
2622 im->ximg = im->xsize;
2623 im->yimg = im->ysize;
2624 im->yorigin = im->ysize;
2629 /** +---+--------------------------------------------+
2630 ** | y |...............graph title..................|
2631 ** | +---+-------------------------------+--------+
2634 ** | i | a | | pie |
2635 ** | s | x | main graph area | chart |
2640 ** | l | b +-------------------------------+--------+
2641 ** | e | l | x axis labels | |
2642 ** +---+---+-------------------------------+--------+
2643 ** |....................legends.....................|
2644 ** +------------------------------------------------+
2646 ** +------------------------------------------------+
2649 if (im->ylegend[0] != '\0') {
2650 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2653 if (im->title[0] != '\0') {
2654 /* The title is placed "inbetween" two text lines so it
2655 ** automatically has some vertical spacing. The horizontal
2656 ** spacing is added here, on each side.
2658 /* if necessary, reduce the font size of the title until it fits the image width */
2659 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2663 if (im->draw_x_grid) {
2664 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2666 if (im->draw_y_grid || im->forceleftspace) {
2667 Xylabel = gfx_get_text_width(im, 0,
2668 im->text_prop[TEXT_PROP_AXIS].font,
2669 im->text_prop[TEXT_PROP_AXIS].size,
2670 im->tabwidth, "0") * im->unitslength;
2674 if (im->extra_flags & FULL_SIZE_MODE) {
2675 /* The actual size of the image to draw has been determined by the user.
2676 ** The graph area is the space remaining after accounting for the legend,
2677 ** the watermark, the pie chart, the axis labels, and the title.
2680 im->ximg = im->xsize;
2681 im->yimg = im->ysize;
2682 im->yorigin = im->ysize;
2686 im->yorigin += Ytitle;
2688 /* Now calculate the total size. Insert some spacing where
2689 desired. im->xorigin and im->yorigin need to correspond
2690 with the lower left corner of the main graph area or, if
2691 this one is not set, the imaginary box surrounding the
2694 /* Initial size calculation for the main graph area */
2695 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2697 Xmain -= Xspacing; /* put space between main graph area and right edge */
2699 im->xorigin = Xspacing + Xylabel;
2701 /* the length of the title should not influence with width of the graph
2702 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2704 if (Xvertical) { /* unit description */
2706 im->xorigin += Xvertical;
2711 /* The vertical size of the image is known in advance. The main graph area
2712 ** (Ymain) and im->yorigin must be set according to the space requirements
2713 ** of the legend and the axis labels.
2716 if (im->extra_flags & NOLEGEND) {
2717 /* set dimensions correctly if using full size mode with no legend */
2719 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2721 Ymain = im->yorigin;
2723 /* Determine where to place the legends onto the image.
2724 ** Set Ymain and adjust im->yorigin to match the space requirements.
2726 if (leg_place(im, &Ymain) == -1)
2731 /* remove title space *or* some padding above the graph from the main graph area */
2735 Ymain -= 1.5 * Yspacing;
2738 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2739 if (im->watermark[0] != '\0') {
2740 Ymain -= Ywatermark;
2745 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2747 /* The actual size of the image to draw is determined from
2748 ** several sources. The size given on the command line is
2749 ** the graph area but we need more as we have to draw labels
2750 ** and other things outside the graph area.
2753 if (im->ylegend[0] != '\0') {
2754 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2758 if (im->title[0] != '\0') {
2759 /* The title is placed "inbetween" two text lines so it
2760 ** automatically has some vertical spacing. The horizontal
2761 ** spacing is added here, on each side.
2763 /* don't care for the with of the title
2764 Xtitle = gfx_get_text_width(im->canvas, 0,
2765 im->text_prop[TEXT_PROP_TITLE].font,
2766 im->text_prop[TEXT_PROP_TITLE].size,
2768 im->title, 0) + 2*Xspacing; */
2769 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2776 /* Now calculate the total size. Insert some spacing where
2777 desired. im->xorigin and im->yorigin need to correspond
2778 with the lower left corner of the main graph area or, if
2779 this one is not set, the imaginary box surrounding the
2782 /* The legend width cannot yet be determined, as a result we
2783 ** have problems adjusting the image to it. For now, we just
2784 ** forget about it at all; the legend will have to fit in the
2785 ** size already allocated.
2787 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2790 im->ximg += Xspacing;
2792 im->xorigin = Xspacing + Xylabel;
2794 /* the length of the title should not influence with width of the graph
2795 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2797 if (Xvertical) { /* unit description */
2798 im->ximg += Xvertical;
2799 im->xorigin += Xvertical;
2803 /* The vertical size is interesting... we need to compare
2804 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2805 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2806 ** in order to start even thinking about Ylegend or Ywatermark.
2808 ** Do it in three portions: First calculate the inner part,
2809 ** then do the legend, then adjust the total height of the img,
2810 ** adding space for a watermark if one exists;
2813 /* reserve space for main and/or pie */
2815 im->yimg = Ymain + Yxlabel;
2818 im->yorigin = im->yimg - Yxlabel;
2820 /* reserve space for the title *or* some padding above the graph */
2823 im->yorigin += Ytitle;
2825 im->yimg += 1.5 * Yspacing;
2826 im->yorigin += 1.5 * Yspacing;
2828 /* reserve space for padding below the graph */
2829 im->yimg += Yspacing;
2831 /* Determine where to place the legends onto the image.
2832 ** Adjust im->yimg to match the space requirements.
2834 if (leg_place(im, 0) == -1)
2837 if (im->watermark[0] != '\0') {
2838 im->yimg += Ywatermark;
2846 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2847 /* yes we are loosing precision by doing tos with floats instead of doubles
2848 but it seems more stable this way. */
2851 /* draw that picture thing ... */
2857 int lazy = lazy_check(im);
2859 double areazero = 0.0;
2860 graph_desc_t *lastgdes = NULL;
2862 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2865 /* if we are lazy and there is nothing to PRINT ... quit now */
2866 if (lazy && im->prt_c == 0)
2869 /* pull the data from the rrd files ... */
2871 if (data_fetch(im) == -1)
2874 /* evaluate VDEF and CDEF operations ... */
2875 if (data_calc(im) == -1)
2879 /* calculate and PRINT and GPRINT definitions. We have to do it at
2880 * this point because it will affect the length of the legends
2881 * if there are no graph elements we stop here ...
2882 * if we are lazy, try to quit ...
2884 i = print_calc(im, calcpr);
2887 if ((i == 0) || lazy)
2890 /**************************************************************
2891 *** Calculating sizes and locations became a bit confusing ***
2892 *** so I moved this into a separate function. ***
2893 **************************************************************/
2894 if (graph_size_location(im, i) == -1)
2897 /* get actual drawing data and find min and max values */
2898 if (data_proc(im) == -1)
2901 if (!im->logarithmic) {
2904 /* identify si magnitude Kilo, Mega Giga ? */
2905 if (!im->rigid && !im->logarithmic)
2906 expand_range(im); /* make sure the upper and lower limit are
2909 if (!calc_horizontal_grid(im))
2916 apply_gridfit(im); */
2919 /* the actual graph is created by going through the individual
2920 graph elements and then drawing them */
2921 cairo_surface_destroy(im->surface);
2923 switch (im->imgformat) {
2926 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2927 im->ximg * im->zoom,
2928 im->yimg * im->zoom);
2933 cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2934 im->yimg * im->zoom);
2939 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
2940 im->yimg * im->zoom);
2945 cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
2946 im->yimg * im->zoom);
2947 cairo_svg_surface_restrict_to_version(im->surface,
2948 CAIRO_SVG_VERSION_1_1);
2951 im->cr = cairo_create(im->surface);
2952 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
2953 cairo_set_antialias(im->cr, im->graph_antialias);
2954 cairo_scale(im->cr, im->zoom, im->zoom);
2958 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2960 gfx_add_point(im, im->ximg, 0);
2964 im->xorigin, im->yorigin,
2965 im->xorigin + im->xsize, im->yorigin,
2966 im->xorigin + im->xsize, im->yorigin - im->ysize,
2967 im->graph_col[GRC_CANVAS]);
2969 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
2972 if (im->minval > 0.0)
2973 areazero = im->minval;
2974 if (im->maxval < 0.0)
2975 areazero = im->maxval;
2977 for (i = 0; i < im->gdes_c; i++) {
2978 switch (im->gdes[i].gf) {
2991 for (ii = 0; ii < im->xsize; ii++) {
2992 if (!isnan(im->gdes[i].p_data[ii]) &&
2993 im->gdes[i].p_data[ii] != 0.0) {
2994 if (im->gdes[i].yrule > 0) {
2996 im->xorigin + ii, im->yorigin,
2999 im->gdes[i].yrule * im->ysize, 1.0,
3001 } else if (im->gdes[i].yrule < 0) {
3004 im->yorigin - im->ysize,
3007 im->gdes[i].yrule) *
3008 im->ysize, 1.0, im->gdes[i].col);
3016 /* fix data points at oo and -oo */
3017 for (ii = 0; ii < im->xsize; ii++) {
3018 if (isinf(im->gdes[i].p_data[ii])) {
3019 if (im->gdes[i].p_data[ii] > 0) {
3020 im->gdes[i].p_data[ii] = im->maxval;
3022 im->gdes[i].p_data[ii] = im->minval;
3028 /* *******************************************************
3033 -------|--t-1--t--------------------------------
3035 if we know the value at time t was a then
3036 we draw a square from t-1 to t with the value a.
3038 ********************************************************* */
3039 if (im->gdes[i].col.alpha != 0.0) {
3040 /* GF_LINE and friend */
3041 if (im->gdes[i].gf == GF_LINE) {
3042 double last_y = 0.0;
3046 cairo_new_path(im->cr);
3048 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3049 for (ii = 1; ii < im->xsize; ii++) {
3050 if (isnan(im->gdes[i].p_data[ii])
3051 || (im->slopemode == 1
3052 && isnan(im->gdes[i].p_data[ii - 1]))) {
3057 last_y = ytr(im, im->gdes[i].p_data[ii]);
3058 if (im->slopemode == 0) {
3059 double x = ii - 1 + im->xorigin;
3062 gfx_line_fit(im, &x, &y);
3063 cairo_move_to(im->cr, x, y);
3064 x = ii + im->xorigin;
3066 gfx_line_fit(im, &x, &y);
3067 cairo_line_to(im->cr, x, y);
3069 double x = ii - 1 + im->xorigin;
3071 im->gdes[i].p_data[ii - 1]);
3073 gfx_line_fit(im, &x, &y);
3074 cairo_move_to(im->cr, x, y);
3075 x = ii + im->xorigin;
3077 gfx_line_fit(im, &x, &y);
3078 cairo_line_to(im->cr, x, y);
3082 double x1 = ii + im->xorigin;
3083 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3085 if (im->slopemode == 0
3086 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3087 double x = ii - 1 + im->xorigin;
3090 gfx_line_fit(im, &x, &y);
3091 cairo_line_to(im->cr, x, y);
3094 gfx_line_fit(im, &x1, &y1);
3095 cairo_line_to(im->cr, x1, y1);
3099 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3100 im->gdes[i].col.green,
3101 im->gdes[i].col.blue,
3102 im->gdes[i].col.alpha);
3103 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3104 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3105 cairo_stroke(im->cr);
3106 cairo_restore(im->cr);
3109 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3110 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3111 double *backY = malloc(sizeof(double) * im->xsize * 2);
3112 double *backX = malloc(sizeof(double) * im->xsize * 2);
3115 for (ii = 0; ii <= im->xsize; ii++) {
3118 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3123 && AlmostEqual2sComplement(foreY[lastI],
3125 && AlmostEqual2sComplement(foreY[lastI],
3133 foreX[cntI], foreY[cntI],
3135 while (cntI < idxI) {
3140 AlmostEqual2sComplement(foreY[lastI],
3143 AlmostEqual2sComplement(foreY[lastI],
3148 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3150 gfx_add_point(im, backX[idxI], backY[idxI]);
3156 AlmostEqual2sComplement(backY[lastI],
3159 AlmostEqual2sComplement(backY[lastI],
3164 gfx_add_point(im, backX[idxI], backY[idxI]);
3174 if (ii == im->xsize)
3177 if (im->slopemode == 0 && ii == 0) {
3180 if (isnan(im->gdes[i].p_data[ii])) {
3184 ytop = ytr(im, im->gdes[i].p_data[ii]);
3185 if (lastgdes && im->gdes[i].stack) {
3186 ybase = ytr(im, lastgdes->p_data[ii]);
3188 ybase = ytr(im, areazero);
3190 if (ybase == ytop) {
3196 double extra = ytop;
3201 if (im->slopemode == 0) {
3202 backY[++idxI] = ybase - 0.2;
3203 backX[idxI] = ii + im->xorigin - 1;
3204 foreY[idxI] = ytop + 0.2;
3205 foreX[idxI] = ii + im->xorigin - 1;
3207 backY[++idxI] = ybase - 0.2;
3208 backX[idxI] = ii + im->xorigin;
3209 foreY[idxI] = ytop + 0.2;
3210 foreX[idxI] = ii + im->xorigin;
3212 /* close up any remaining area */
3217 } /* else GF_LINE */
3219 /* if color != 0x0 */
3220 /* make sure we do not run into trouble when stacking on NaN */
3221 for (ii = 0; ii < im->xsize; ii++) {
3222 if (isnan(im->gdes[i].p_data[ii])) {
3223 if (lastgdes && (im->gdes[i].stack)) {
3224 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3226 im->gdes[i].p_data[ii] = areazero;
3230 lastgdes = &(im->gdes[i]);
3234 ("STACK should already be turned into LINE or AREA here");
3241 /* grid_paint also does the text */
3242 if (!(im->extra_flags & ONLY_GRAPH))
3246 if (!(im->extra_flags & ONLY_GRAPH))
3249 /* the RULES are the last thing to paint ... */
3250 for (i = 0; i < im->gdes_c; i++) {
3252 switch (im->gdes[i].gf) {
3254 if (im->gdes[i].yrule >= im->minval
3255 && im->gdes[i].yrule <= im->maxval)
3257 im->xorigin, ytr(im, im->gdes[i].yrule),
3258 im->xorigin + im->xsize, ytr(im,
3260 1.0, im->gdes[i].col);
3263 if (im->gdes[i].xrule >= im->start
3264 && im->gdes[i].xrule <= im->end)
3266 xtr(im, im->gdes[i].xrule), im->yorigin,
3267 xtr(im, im->gdes[i].xrule),
3268 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3276 switch (im->imgformat) {
3278 if (cairo_surface_write_to_png(im->surface, im->graphfile) !=
3279 CAIRO_STATUS_SUCCESS) {
3280 rrd_set_error("Could not save png to '%s'", im->graphfile);
3285 cairo_show_page(im->cr);
3292 /*****************************************************
3294 *****************************************************/
3301 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3302 * sizeof(graph_desc_t))) ==
3304 rrd_set_error("realloc graph_descs");
3309 im->gdes[im->gdes_c - 1].step = im->step;
3310 im->gdes[im->gdes_c - 1].step_orig = im->step;
3311 im->gdes[im->gdes_c - 1].stack = 0;
3312 im->gdes[im->gdes_c - 1].linewidth = 0;
3313 im->gdes[im->gdes_c - 1].debug = 0;
3314 im->gdes[im->gdes_c - 1].start = im->start;
3315 im->gdes[im->gdes_c - 1].start_orig = im->start;
3316 im->gdes[im->gdes_c - 1].end = im->end;
3317 im->gdes[im->gdes_c - 1].end_orig = im->end;
3318 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3319 im->gdes[im->gdes_c - 1].data = NULL;
3320 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3321 im->gdes[im->gdes_c - 1].data_first = 0;
3322 im->gdes[im->gdes_c - 1].p_data = NULL;
3323 im->gdes[im->gdes_c - 1].rpnp = NULL;
3324 im->gdes[im->gdes_c - 1].shift = 0.0;
3325 im->gdes[im->gdes_c - 1].col.red = 0.0;
3326 im->gdes[im->gdes_c - 1].col.green = 0.0;
3327 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3328 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3329 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3330 im->gdes[im->gdes_c - 1].format[0] = '\0';
3331 im->gdes[im->gdes_c - 1].strftm = 0;
3332 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3333 im->gdes[im->gdes_c - 1].ds = -1;
3334 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3335 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3336 im->gdes[im->gdes_c - 1].p_data = NULL;
3337 im->gdes[im->gdes_c - 1].yrule = DNAN;
3338 im->gdes[im->gdes_c - 1].xrule = 0;
3342 /* copies input untill the first unescaped colon is found
3343 or until input ends. backslashes have to be escaped as well */
3345 const char *const input,
3351 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3352 if (input[inp] == '\\' &&
3353 input[inp + 1] != '\0' &&
3354 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3355 output[outp++] = input[++inp];
3357 output[outp++] = input[inp];
3360 output[outp] = '\0';
3364 /* Some surgery done on this function, it became ridiculously big.
3366 ** - initializing now in rrd_graph_init()
3367 ** - options parsing now in rrd_graph_options()
3368 ** - script parsing now in rrd_graph_script()
3382 rrd_graph_init(&im);
3384 /* a dummy surface so that we can measure text sizes for placements */
3385 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3386 im.cr = cairo_create(im.surface);
3389 /* not currently using this ... */
3390 im.graphhandle = stream;
3392 rrd_graph_options(argc, argv, &im);
3393 if (rrd_test_error()) {
3398 if (strlen(argv[optind]) >= MAXPATH) {
3399 rrd_set_error("filename (including path) too long");
3403 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3404 im.graphfile[MAXPATH - 1] = '\0';
3406 rrd_graph_script(argc, argv, &im, 1);
3407 if (rrd_test_error()) {
3412 /* Everything is now read and the actual work can start */
3415 if (graph_paint(&im, prdata) == -1) {
3420 /* The image is generated and needs to be output.
3421 ** Also, if needed, print a line with information about the image.
3432 /* maybe prdata is not allocated yet ... lets do it now */
3433 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3434 rrd_set_error("malloc imginfo");
3439 malloc((strlen(im.imginfo) + 200 +
3440 strlen(im.graphfile)) * sizeof(char)))
3442 rrd_set_error("malloc imginfo");
3445 filename = im.graphfile + strlen(im.graphfile);
3446 while (filename > im.graphfile) {
3447 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3452 sprintf((*prdata)[0], im.imginfo, filename,
3453 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3459 void rrd_graph_init(
3467 #ifdef HAVE_SETLOCALE
3468 setlocale(LC_TIME, "");
3469 #ifdef HAVE_MBSTOWCS
3470 setlocale(LC_CTYPE, "");
3476 im->xlab_user.minsec = -1;
3482 im->ylegend[0] = '\0';
3483 im->title[0] = '\0';
3484 im->watermark[0] = '\0';
3487 im->unitsexponent = 9999;
3488 im->unitslength = 6;
3489 im->forceleftspace = 0;
3491 im->viewfactor = 1.0;
3492 im->imgformat = IF_PNG;
3495 im->extra_flags = 0;
3501 im->logarithmic = 0;
3502 im->ygridstep = DNAN;
3503 im->draw_x_grid = 1;
3504 im->draw_y_grid = 1;
3509 im->grid_dash_on = 1;
3510 im->grid_dash_off = 1;
3511 im->tabwidth = 40.0;
3513 im->font_options = cairo_font_options_create();
3514 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3516 cairo_font_options_set_hint_style(im->font_options,
3517 CAIRO_HINT_STYLE_FULL);
3518 cairo_font_options_set_hint_metrics(im->font_options,
3519 CAIRO_HINT_METRICS_ON);
3520 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3523 for (i = 0; i < DIM(graph_col); i++)
3524 im->graph_col[i] = graph_col[i];
3526 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3529 char rrd_win_default_font[1000];
3531 windir = getenv("windir");
3532 /* %windir% is something like D:\windows or C:\winnt */
3533 if (windir != NULL) {
3534 strncpy(rrd_win_default_font, windir, 500);
3535 rrd_win_default_font[500] = '\0';
3536 strcat(rrd_win_default_font, "\\fonts\\");
3537 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3538 for (i = 0; i < DIM(text_prop); i++) {
3539 strncpy(text_prop[i].font, rrd_win_default_font,
3540 sizeof(text_prop[i].font) - 1);
3541 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3549 deffont = getenv("RRD_DEFAULT_FONT");
3550 if (deffont != NULL) {
3551 for (i = 0; i < DIM(text_prop); i++) {
3552 strncpy(text_prop[i].font, deffont,
3553 sizeof(text_prop[i].font) - 1);
3554 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3558 for (i = 0; i < DIM(text_prop); i++) {
3559 im->text_prop[i].size = text_prop[i].size;
3560 strcpy(im->text_prop[i].font, text_prop[i].font);
3564 void rrd_graph_options(
3570 char *parsetime_error = NULL;
3571 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3572 time_t start_tmp = 0, end_tmp = 0;
3574 struct rrd_time_value start_tv, end_tv;
3575 long unsigned int color;
3578 opterr = 0; /* initialize getopt */
3580 parsetime("end-24h", &start_tv);
3581 parsetime("now", &end_tv);
3583 /* defines for long options without a short equivalent. should be bytes,
3584 and may not collide with (the ASCII value of) short options */
3585 #define LONGOPT_UNITS_SI 255
3588 static struct option long_options[] = {
3589 {"start", required_argument, 0, 's'},
3590 {"end", required_argument, 0, 'e'},
3591 {"x-grid", required_argument, 0, 'x'},
3592 {"y-grid", required_argument, 0, 'y'},
3593 {"vertical-label", required_argument, 0, 'v'},
3594 {"width", required_argument, 0, 'w'},
3595 {"height", required_argument, 0, 'h'},
3596 {"full-size-mode", no_argument, 0, 'D'},
3597 {"interlaced", no_argument, 0, 'i'},
3598 {"upper-limit", required_argument, 0, 'u'},
3599 {"lower-limit", required_argument, 0, 'l'},
3600 {"rigid", no_argument, 0, 'r'},
3601 {"base", required_argument, 0, 'b'},
3602 {"logarithmic", no_argument, 0, 'o'},
3603 {"color", required_argument, 0, 'c'},
3604 {"font", required_argument, 0, 'n'},
3605 {"title", required_argument, 0, 't'},
3606 {"imginfo", required_argument, 0, 'f'},
3607 {"imgformat", required_argument, 0, 'a'},
3608 {"lazy", no_argument, 0, 'z'},
3609 {"zoom", required_argument, 0, 'm'},
3610 {"no-legend", no_argument, 0, 'g'},
3611 {"force-rules-legend", no_argument, 0, 'F'},
3612 {"only-graph", no_argument, 0, 'j'},
3613 {"alt-y-grid", no_argument, 0, 'Y'},
3614 {"no-minor", no_argument, 0, 'I'},
3615 {"slope-mode", no_argument, 0, 'E'},
3616 {"alt-autoscale", no_argument, 0, 'A'},
3617 {"alt-autoscale-min", no_argument, 0, 'J'},
3618 {"alt-autoscale-max", no_argument, 0, 'M'},
3619 {"no-gridfit", no_argument, 0, 'N'},
3620 {"units-exponent", required_argument, 0, 'X'},
3621 {"units-length", required_argument, 0, 'L'},
3622 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3623 {"step", required_argument, 0, 'S'},
3624 {"tabwidth", required_argument, 0, 'T'},
3625 {"font-render-mode", required_argument, 0, 'R'},
3626 {"graph-render-mode", required_argument, 0, 'G'},
3627 {"font-smoothing-threshold", required_argument, 0, 'B'},
3628 {"watermark", required_argument, 0, 'W'},
3629 {"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 */
3632 int option_index = 0;
3634 int col_start, col_end;
3636 opt = getopt_long(argc, argv,
3637 "s:e:x:y:v:w:h:D:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3638 long_options, &option_index);
3645 im->extra_flags |= NOMINOR;
3648 im->extra_flags |= ALTYGRID;
3651 im->extra_flags |= ALTAUTOSCALE;
3654 im->extra_flags |= ALTAUTOSCALE_MIN;
3657 im->extra_flags |= ALTAUTOSCALE_MAX;
3660 im->extra_flags |= ONLY_GRAPH;
3663 im->extra_flags |= NOLEGEND;
3666 im->extra_flags |= FORCE_RULES_LEGEND;
3668 case LONGOPT_UNITS_SI:
3669 if (im->extra_flags & FORCE_UNITS) {
3670 rrd_set_error("--units can only be used once!");
3673 if (strcmp(optarg, "si") == 0)
3674 im->extra_flags |= FORCE_UNITS_SI;
3676 rrd_set_error("invalid argument for --units: %s", optarg);
3681 im->unitsexponent = atoi(optarg);
3684 im->unitslength = atoi(optarg);
3685 im->forceleftspace = 1;
3688 im->tabwidth = atof(optarg);
3691 im->step = atoi(optarg);
3697 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3698 rrd_set_error("start time: %s", parsetime_error);
3703 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3704 rrd_set_error("end time: %s", parsetime_error);
3709 if (strcmp(optarg, "none") == 0) {
3710 im->draw_x_grid = 0;
3715 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3717 &im->xlab_user.gridst,
3719 &im->xlab_user.mgridst,
3721 &im->xlab_user.labst,
3722 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3723 strncpy(im->xlab_form, optarg + stroff,
3724 sizeof(im->xlab_form) - 1);
3725 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3726 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3727 rrd_set_error("unknown keyword %s", scan_gtm);
3729 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3731 rrd_set_error("unknown keyword %s", scan_mtm);
3733 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3735 rrd_set_error("unknown keyword %s", scan_ltm);
3738 im->xlab_user.minsec = 1;
3739 im->xlab_user.stst = im->xlab_form;
3741 rrd_set_error("invalid x-grid format");
3747 if (strcmp(optarg, "none") == 0) {
3748 im->draw_y_grid = 0;
3752 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3753 if (im->ygridstep <= 0) {
3754 rrd_set_error("grid step must be > 0");
3756 } else if (im->ylabfact < 1) {
3757 rrd_set_error("label factor must be > 0");
3761 rrd_set_error("invalid y-grid format");
3766 strncpy(im->ylegend, optarg, 150);
3767 im->ylegend[150] = '\0';
3770 im->maxval = atof(optarg);
3773 im->minval = atof(optarg);
3776 im->base = atol(optarg);
3777 if (im->base != 1024 && im->base != 1000) {
3779 ("the only sensible value for base apart from 1000 is 1024");
3784 long_tmp = atol(optarg);
3785 if (long_tmp < 10) {
3786 rrd_set_error("width below 10 pixels");
3789 im->xsize = long_tmp;
3792 long_tmp = atol(optarg);
3793 if (long_tmp < 10) {
3794 rrd_set_error("height below 10 pixels");
3797 im->ysize = long_tmp;
3800 im->extra_flags |= FULL_SIZE_MODE;
3803 /* interlaced png not supported at the moment */
3809 im->imginfo = optarg;
3812 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3813 rrd_set_error("unsupported graphics format '%s'", optarg);
3825 im->logarithmic = 1;
3829 "%10[A-Z]#%n%8lx%n",
3830 col_nam, &col_start, &color, &col_end) == 2) {
3832 int col_len = col_end - col_start;
3836 color = (((color & 0xF00) * 0x110000) |
3837 ((color & 0x0F0) * 0x011000) |
3838 ((color & 0x00F) * 0x001100) | 0x000000FF);
3841 color = (((color & 0xF000) * 0x11000) |
3842 ((color & 0x0F00) * 0x01100) |
3843 ((color & 0x00F0) * 0x00110) |
3844 ((color & 0x000F) * 0x00011)
3848 color = (color << 8) + 0xff /* shift left by 8 */ ;
3853 rrd_set_error("the color format is #RRGGBB[AA]");
3856 if ((ci = grc_conv(col_nam)) != -1) {
3857 im->graph_col[ci] = gfx_hex_to_col(color);
3859 rrd_set_error("invalid color name '%s'", col_nam);
3863 rrd_set_error("invalid color def format");
3870 char font[1024] = "";
3872 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3873 int sindex, propidx;
3875 if ((sindex = text_prop_conv(prop)) != -1) {
3876 for (propidx = sindex; propidx < TEXT_PROP_LAST;
3879 im->text_prop[propidx].size = size;
3881 if (strlen(font) > 0) {
3882 strcpy(im->text_prop[propidx].font, font);
3884 if (propidx == sindex && sindex != 0)
3888 rrd_set_error("invalid fonttag '%s'", prop);
3892 rrd_set_error("invalid text property format");
3898 im->zoom = atof(optarg);
3899 if (im->zoom <= 0.0) {
3900 rrd_set_error("zoom factor must be > 0");
3905 strncpy(im->title, optarg, 150);
3906 im->title[150] = '\0';
3910 if (strcmp(optarg, "normal") == 0) {
3911 cairo_font_options_set_antialias(im->font_options,
3912 CAIRO_ANTIALIAS_GRAY);
3913 cairo_font_options_set_hint_style(im->font_options,
3914 CAIRO_HINT_STYLE_FULL);
3915 } else if (strcmp(optarg, "light") == 0) {
3916 cairo_font_options_set_antialias(im->font_options,
3917 CAIRO_ANTIALIAS_GRAY);
3918 cairo_font_options_set_hint_style(im->font_options,
3919 CAIRO_HINT_STYLE_SLIGHT);
3920 } else if (strcmp(optarg, "mono") == 0) {
3921 cairo_font_options_set_antialias(im->font_options,
3922 CAIRO_ANTIALIAS_NONE);
3923 cairo_font_options_set_hint_style(im->font_options,
3924 CAIRO_HINT_STYLE_FULL);
3926 rrd_set_error("unknown font-render-mode '%s'", optarg);
3931 if (strcmp(optarg, "normal") == 0)
3932 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3933 else if (strcmp(optarg, "mono") == 0)
3934 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
3936 rrd_set_error("unknown graph-render-mode '%s'", optarg);
3941 /* not supported curently */
3945 strncpy(im->watermark, optarg, 100);
3946 im->watermark[99] = '\0';
3951 rrd_set_error("unknown option '%c'", optopt);
3953 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3958 if (optind >= argc) {
3959 rrd_set_error("missing filename");
3963 if (im->logarithmic == 1 && im->minval <= 0) {
3965 ("for a logarithmic yaxis you must specify a lower-limit > 0");
3969 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3970 /* error string is set in parsetime.c */
3974 if (start_tmp < 3600 * 24 * 365 * 10) {
3975 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3980 if (end_tmp < start_tmp) {
3981 rrd_set_error("start (%ld) should be less than end (%ld)",
3982 start_tmp, end_tmp);
3986 im->start = start_tmp;
3988 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3991 int rrd_graph_color(
3998 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4000 color = strstr(var, "#");
4001 if (color == NULL) {
4002 if (optional == 0) {
4003 rrd_set_error("Found no color in %s", err);
4010 long unsigned int col;
4012 rest = strstr(color, ":");
4020 sscanf(color, "#%6lx%n", &col, &n);
4021 col = (col << 8) + 0xff /* shift left by 8 */ ;
4023 rrd_set_error("Color problem in %s", err);
4026 sscanf(color, "#%8lx%n", &col, &n);
4030 rrd_set_error("Color problem in %s", err);
4032 if (rrd_test_error())
4034 gdp->col = gfx_hex_to_col(col);
4047 while (*ptr != '\0')
4048 if (*ptr++ == '%') {
4050 /* line cannot end with percent char */
4054 /* '%s', '%S' and '%%' are allowed */
4055 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4058 /* %c is allowed (but use only with vdef!) */
4059 else if (*ptr == 'c') {
4064 /* or else '% 6.2lf' and such are allowed */
4066 /* optional padding character */
4067 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4070 /* This should take care of 'm.n' with all three optional */
4071 while (*ptr >= '0' && *ptr <= '9')
4075 while (*ptr >= '0' && *ptr <= '9')
4078 /* Either 'le', 'lf' or 'lg' must follow here */
4081 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4096 struct graph_desc_t *gdes;
4097 const char *const str;
4099 /* A VDEF currently is either "func" or "param,func"
4100 * so the parsing is rather simple. Change if needed.
4107 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4108 if (n == (int) strlen(str)) { /* matched */
4112 sscanf(str, "%29[A-Z]%n", func, &n);
4113 if (n == (int) strlen(str)) { /* matched */
4116 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4121 if (!strcmp("PERCENT", func))
4122 gdes->vf.op = VDEF_PERCENT;
4123 else if (!strcmp("MAXIMUM", func))
4124 gdes->vf.op = VDEF_MAXIMUM;
4125 else if (!strcmp("AVERAGE", func))
4126 gdes->vf.op = VDEF_AVERAGE;
4127 else if (!strcmp("MINIMUM", func))
4128 gdes->vf.op = VDEF_MINIMUM;
4129 else if (!strcmp("TOTAL", func))
4130 gdes->vf.op = VDEF_TOTAL;
4131 else if (!strcmp("FIRST", func))
4132 gdes->vf.op = VDEF_FIRST;
4133 else if (!strcmp("LAST", func))
4134 gdes->vf.op = VDEF_LAST;
4135 else if (!strcmp("LSLSLOPE", func))
4136 gdes->vf.op = VDEF_LSLSLOPE;
4137 else if (!strcmp("LSLINT", func))
4138 gdes->vf.op = VDEF_LSLINT;
4139 else if (!strcmp("LSLCORREL", func))
4140 gdes->vf.op = VDEF_LSLCORREL;
4142 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4147 switch (gdes->vf.op) {
4149 if (isnan(param)) { /* no parameter given */
4150 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4154 if (param >= 0.0 && param <= 100.0) {
4155 gdes->vf.param = param;
4156 gdes->vf.val = DNAN; /* undefined */
4157 gdes->vf.when = 0; /* undefined */
4159 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4172 case VDEF_LSLCORREL:
4174 gdes->vf.param = DNAN;
4175 gdes->vf.val = DNAN;
4178 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4194 graph_desc_t *src, *dst;
4198 dst = &im->gdes[gdi];
4199 src = &im->gdes[dst->vidx];
4200 data = src->data + src->ds;
4201 steps = (src->end - src->start) / src->step;
4204 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4208 switch (dst->vf.op) {
4214 if ((array = malloc(steps * sizeof(double))) == NULL) {
4215 rrd_set_error("malloc VDEV_PERCENT");
4218 for (step = 0; step < steps; step++) {
4219 array[step] = data[step * src->ds_cnt];
4221 qsort(array, step, sizeof(double), vdef_percent_compar);
4223 field = (steps - 1) * dst->vf.param / 100;
4224 dst->vf.val = array[field];
4225 dst->vf.when = 0; /* no time component */
4228 for (step = 0; step < steps; step++)
4229 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4230 step == field ? '*' : ' ');
4236 while (step != steps && isnan(data[step * src->ds_cnt]))
4238 if (step == steps) {
4242 dst->vf.val = data[step * src->ds_cnt];
4243 dst->vf.when = src->start + (step + 1) * src->step;
4245 while (step != steps) {
4246 if (finite(data[step * src->ds_cnt])) {
4247 if (data[step * src->ds_cnt] > dst->vf.val) {
4248 dst->vf.val = data[step * src->ds_cnt];
4249 dst->vf.when = src->start + (step + 1) * src->step;
4260 for (step = 0; step < steps; step++) {
4261 if (finite(data[step * src->ds_cnt])) {
4262 sum += data[step * src->ds_cnt];
4267 if (dst->vf.op == VDEF_TOTAL) {
4268 dst->vf.val = sum * src->step;
4269 dst->vf.when = 0; /* no time component */
4271 dst->vf.val = sum / cnt;
4272 dst->vf.when = 0; /* no time component */
4282 while (step != steps && isnan(data[step * src->ds_cnt]))
4284 if (step == steps) {
4288 dst->vf.val = data[step * src->ds_cnt];
4289 dst->vf.when = src->start + (step + 1) * src->step;
4291 while (step != steps) {
4292 if (finite(data[step * src->ds_cnt])) {
4293 if (data[step * src->ds_cnt] < dst->vf.val) {
4294 dst->vf.val = data[step * src->ds_cnt];
4295 dst->vf.when = src->start + (step + 1) * src->step;
4302 /* The time value returned here is one step before the
4303 * actual time value. This is the start of the first
4307 while (step != steps && isnan(data[step * src->ds_cnt]))
4309 if (step == steps) { /* all entries were NaN */
4313 dst->vf.val = data[step * src->ds_cnt];
4314 dst->vf.when = src->start + step * src->step;
4318 /* The time value returned here is the
4319 * actual time value. This is the end of the last
4323 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4325 if (step < 0) { /* all entries were NaN */
4329 dst->vf.val = data[step * src->ds_cnt];
4330 dst->vf.when = src->start + (step + 1) * src->step;
4335 case VDEF_LSLCORREL:{
4336 /* Bestfit line by linear least squares method */
4339 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4347 for (step = 0; step < steps; step++) {
4348 if (finite(data[step * src->ds_cnt])) {
4351 SUMxx += step * step;
4352 SUMxy += step * data[step * src->ds_cnt];
4353 SUMy += data[step * src->ds_cnt];
4354 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4358 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4359 y_intercept = (SUMy - slope * SUMx) / cnt;
4362 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4363 (SUMx * SUMx) / cnt) * (SUMyy -
4369 if (dst->vf.op == VDEF_LSLSLOPE) {
4370 dst->vf.val = slope;
4372 } else if (dst->vf.op == VDEF_LSLINT) {
4373 dst->vf.val = y_intercept;
4375 } else if (dst->vf.op == VDEF_LSLCORREL) {
4376 dst->vf.val = correl;
4390 /* NaN < -INF < finite_values < INF */
4391 int vdef_percent_compar(
4396 /* Equality is not returned; this doesn't hurt except
4397 * (maybe) for a little performance.
4400 /* First catch NaN values. They are smallest */
4401 if (isnan(*(double *) a))
4403 if (isnan(*(double *) b))
4406 /* NaN doesn't reach this part so INF and -INF are extremes.
4407 * The sign from isinf() is compatible with the sign we return
4409 if (isinf(*(double *) a))
4410 return isinf(*(double *) a);
4411 if (isinf(*(double *) b))
4412 return isinf(*(double *) b);
4414 /* If we reach this, both values must be finite */
4415 if (*(double *) a < *(double *) b)