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(TEXTALIGN, GF_TEXTALIGN);
239 conv_if(DEF, GF_DEF);
240 conv_if(CDEF, GF_CDEF);
241 conv_if(VDEF, GF_VDEF);
242 conv_if(XPORT, GF_XPORT);
243 conv_if(SHIFT, GF_SHIFT);
248 enum gfx_if_en if_conv(
252 conv_if(PNG, IF_PNG);
253 conv_if(SVG, IF_SVG);
254 conv_if(EPS, IF_EPS);
255 conv_if(PDF, IF_PDF);
260 enum tmt_en tmt_conv(
264 conv_if(SECOND, TMT_SECOND);
265 conv_if(MINUTE, TMT_MINUTE);
266 conv_if(HOUR, TMT_HOUR);
267 conv_if(DAY, TMT_DAY);
268 conv_if(WEEK, TMT_WEEK);
269 conv_if(MONTH, TMT_MONTH);
270 conv_if(YEAR, TMT_YEAR);
274 enum grc_en grc_conv(
278 conv_if(BACK, GRC_BACK);
279 conv_if(CANVAS, GRC_CANVAS);
280 conv_if(SHADEA, GRC_SHADEA);
281 conv_if(SHADEB, GRC_SHADEB);
282 conv_if(GRID, GRC_GRID);
283 conv_if(MGRID, GRC_MGRID);
284 conv_if(FONT, GRC_FONT);
285 conv_if(ARROW, GRC_ARROW);
286 conv_if(AXIS, GRC_AXIS);
287 conv_if(FRAME, GRC_FRAME);
292 enum text_prop_en text_prop_conv(
296 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
297 conv_if(TITLE, TEXT_PROP_TITLE);
298 conv_if(AXIS, TEXT_PROP_AXIS);
299 conv_if(UNIT, TEXT_PROP_UNIT);
300 conv_if(LEGEND, TEXT_PROP_LEGEND);
311 cairo_status_t status = 0;
315 for (i = 0; i < (unsigned) im->gdes_c; i++) {
316 if (im->gdes[i].data_first) {
317 /* careful here, because a single pointer can occur several times */
318 free(im->gdes[i].data);
319 if (im->gdes[i].ds_namv) {
320 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
321 free(im->gdes[i].ds_namv[ii]);
322 free(im->gdes[i].ds_namv);
325 free(im->gdes[i].p_data);
326 free(im->gdes[i].rpnp);
329 if (im->font_options)
330 cairo_font_options_destroy(im->font_options);
333 status = cairo_status(im->cr);
334 cairo_destroy(im->cr);
337 cairo_surface_destroy(im->surface);
339 fprintf(stderr, "OOPS: Cairo has issuesm it can't even die: %s\n",
340 cairo_status_to_string(status));
345 /* find SI magnitude symbol for the given number*/
347 image_desc_t *im, /* image description */
353 char *symbol[] = { "a", /* 10e-18 Atto */
354 "f", /* 10e-15 Femto */
355 "p", /* 10e-12 Pico */
356 "n", /* 10e-9 Nano */
357 "u", /* 10e-6 Micro */
358 "m", /* 10e-3 Milli */
363 "T", /* 10e12 Tera */
364 "P", /* 10e15 Peta */
371 if (*value == 0.0 || isnan(*value)) {
375 sindex = floor(log(fabs(*value)) / log((double) im->base));
376 *magfact = pow((double) im->base, (double) sindex);
377 (*value) /= (*magfact);
379 if (sindex <= symbcenter && sindex >= -symbcenter) {
380 (*symb_ptr) = symbol[sindex + symbcenter];
387 static char si_symbol[] = {
388 'a', /* 10e-18 Atto */
389 'f', /* 10e-15 Femto */
390 'p', /* 10e-12 Pico */
391 'n', /* 10e-9 Nano */
392 'u', /* 10e-6 Micro */
393 'm', /* 10e-3 Milli */
398 'T', /* 10e12 Tera */
399 'P', /* 10e15 Peta */
402 static const int si_symbcenter = 6;
404 /* find SI magnitude symbol for the numbers on the y-axis*/
406 image_desc_t *im /* image description */
410 double digits, viewdigits = 0;
413 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
414 log((double) im->base));
416 if (im->unitsexponent != 9999) {
417 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
418 viewdigits = floor(im->unitsexponent / 3);
423 im->magfact = pow((double) im->base, digits);
426 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
429 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
431 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
432 ((viewdigits + si_symbcenter) >= 0))
433 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
438 /* move min and max values around to become sensible */
443 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
444 600.0, 500.0, 400.0, 300.0, 250.0,
445 200.0, 125.0, 100.0, 90.0, 80.0,
446 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
447 25.0, 20.0, 10.0, 9.0, 8.0,
448 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
449 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
450 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
453 double scaled_min, scaled_max;
460 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
461 im->minval, im->maxval, im->magfact);
464 if (isnan(im->ygridstep)) {
465 if (im->extra_flags & ALTAUTOSCALE) {
466 /* measure the amplitude of the function. Make sure that
467 graph boundaries are slightly higher then max/min vals
468 so we can see amplitude on the graph */
471 delt = im->maxval - im->minval;
473 fact = 2.0 * pow(10.0,
475 (max(fabs(im->minval), fabs(im->maxval)) /
478 adj = (fact - delt) * 0.55;
481 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
482 im->minval, im->maxval, delt, fact, adj);
487 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
488 /* measure the amplitude of the function. Make sure that
489 graph boundaries are slightly lower than min vals
490 so we can see amplitude on the graph */
491 adj = (im->maxval - im->minval) * 0.1;
493 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
494 /* measure the amplitude of the function. Make sure that
495 graph boundaries are slightly higher than max vals
496 so we can see amplitude on the graph */
497 adj = (im->maxval - im->minval) * 0.1;
500 scaled_min = im->minval / im->magfact;
501 scaled_max = im->maxval / im->magfact;
503 for (i = 1; sensiblevalues[i] > 0; i++) {
504 if (sensiblevalues[i - 1] >= scaled_min &&
505 sensiblevalues[i] <= scaled_min)
506 im->minval = sensiblevalues[i] * (im->magfact);
508 if (-sensiblevalues[i - 1] <= scaled_min &&
509 -sensiblevalues[i] >= scaled_min)
510 im->minval = -sensiblevalues[i - 1] * (im->magfact);
512 if (sensiblevalues[i - 1] >= scaled_max &&
513 sensiblevalues[i] <= scaled_max)
514 im->maxval = sensiblevalues[i - 1] * (im->magfact);
516 if (-sensiblevalues[i - 1] <= scaled_max &&
517 -sensiblevalues[i] >= scaled_max)
518 im->maxval = -sensiblevalues[i] * (im->magfact);
522 /* adjust min and max to the grid definition if there is one */
523 im->minval = (double) im->ylabfact * im->ygridstep *
524 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
525 im->maxval = (double) im->ylabfact * im->ygridstep *
526 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
530 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
531 im->minval, im->maxval, im->magfact);
539 if (isnan(im->minval) || isnan(im->maxval))
542 if (im->logarithmic) {
543 double ya, yb, ypix, ypixfrac;
544 double log10_range = log10(im->maxval) - log10(im->minval);
546 ya = pow((double) 10, floor(log10(im->minval)));
547 while (ya < im->minval)
550 return; /* don't have y=10^x gridline */
552 if (yb <= im->maxval) {
553 /* we have at least 2 y=10^x gridlines.
554 Make sure distance between them in pixels
555 are an integer by expanding im->maxval */
556 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
557 double factor = y_pixel_delta / floor(y_pixel_delta);
558 double new_log10_range = factor * log10_range;
559 double new_ymax_log10 = log10(im->minval) + new_log10_range;
561 im->maxval = pow(10, new_ymax_log10);
562 ytr(im, DNAN); /* reset precalc */
563 log10_range = log10(im->maxval) - log10(im->minval);
565 /* make sure first y=10^x gridline is located on
566 integer pixel position by moving scale slightly
567 downwards (sub-pixel movement) */
568 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
569 ypixfrac = ypix - floor(ypix);
570 if (ypixfrac > 0 && ypixfrac < 1) {
571 double yfrac = ypixfrac / im->ysize;
573 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
574 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
575 ytr(im, DNAN); /* reset precalc */
578 /* Make sure we have an integer pixel distance between
579 each minor gridline */
580 double ypos1 = ytr(im, im->minval);
581 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
582 double y_pixel_delta = ypos1 - ypos2;
583 double factor = y_pixel_delta / floor(y_pixel_delta);
584 double new_range = factor * (im->maxval - im->minval);
585 double gridstep = im->ygrid_scale.gridstep;
586 double minor_y, minor_y_px, minor_y_px_frac;
588 if (im->maxval > 0.0)
589 im->maxval = im->minval + new_range;
591 im->minval = im->maxval - new_range;
592 ytr(im, DNAN); /* reset precalc */
593 /* make sure first minor gridline is on integer pixel y coord */
594 minor_y = gridstep * floor(im->minval / gridstep);
595 while (minor_y < im->minval)
597 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
598 minor_y_px_frac = minor_y_px - floor(minor_y_px);
599 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
600 double yfrac = minor_y_px_frac / im->ysize;
601 double range = im->maxval - im->minval;
603 im->minval = im->minval - yfrac * range;
604 im->maxval = im->maxval - yfrac * range;
605 ytr(im, DNAN); /* reset precalc */
607 calc_horizontal_grid(im); /* recalc with changed im->maxval */
611 /* reduce data reimplementation by Alex */
614 enum cf_en cf, /* which consolidation function ? */
615 unsigned long cur_step, /* step the data currently is in */
616 time_t *start, /* start, end and step as requested ... */
617 time_t *end, /* ... by the application will be ... */
618 unsigned long *step, /* ... adjusted to represent reality */
619 unsigned long *ds_cnt, /* number of data sources in file */
621 { /* two dimensional array containing the data */
622 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
623 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
625 rrd_value_t *srcptr, *dstptr;
627 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
630 row_cnt = ((*end) - (*start)) / cur_step;
636 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
637 row_cnt, reduce_factor, *start, *end, cur_step);
638 for (col = 0; col < row_cnt; col++) {
639 printf("time %10lu: ", *start + (col + 1) * cur_step);
640 for (i = 0; i < *ds_cnt; i++)
641 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
646 /* We have to combine [reduce_factor] rows of the source
647 ** into one row for the destination. Doing this we also
648 ** need to take care to combine the correct rows. First
649 ** alter the start and end time so that they are multiples
650 ** of the new step time. We cannot reduce the amount of
651 ** time so we have to move the end towards the future and
652 ** the start towards the past.
654 end_offset = (*end) % (*step);
655 start_offset = (*start) % (*step);
657 /* If there is a start offset (which cannot be more than
658 ** one destination row), skip the appropriate number of
659 ** source rows and one destination row. The appropriate
660 ** number is what we do know (start_offset/cur_step) of
661 ** the new interval (*step/cur_step aka reduce_factor).
664 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
665 printf("row_cnt before: %lu\n", row_cnt);
668 (*start) = (*start) - start_offset;
669 skiprows = reduce_factor - start_offset / cur_step;
670 srcptr += skiprows * *ds_cnt;
671 for (col = 0; col < (*ds_cnt); col++)
676 printf("row_cnt between: %lu\n", row_cnt);
679 /* At the end we have some rows that are not going to be
680 ** used, the amount is end_offset/cur_step
683 (*end) = (*end) - end_offset + (*step);
684 skiprows = end_offset / cur_step;
688 printf("row_cnt after: %lu\n", row_cnt);
691 /* Sanity check: row_cnt should be multiple of reduce_factor */
692 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
694 if (row_cnt % reduce_factor) {
695 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
696 row_cnt, reduce_factor);
697 printf("BUG in reduce_data()\n");
701 /* Now combine reduce_factor intervals at a time
702 ** into one interval for the destination.
705 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
706 for (col = 0; col < (*ds_cnt); col++) {
707 rrd_value_t newval = DNAN;
708 unsigned long validval = 0;
710 for (i = 0; i < reduce_factor; i++) {
711 if (isnan(srcptr[i * (*ds_cnt) + col])) {
716 newval = srcptr[i * (*ds_cnt) + col];
725 newval += srcptr[i * (*ds_cnt) + col];
728 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
731 /* an interval contains a failure if any subintervals contained a failure */
733 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
736 newval = srcptr[i * (*ds_cnt) + col];
762 srcptr += (*ds_cnt) * reduce_factor;
763 row_cnt -= reduce_factor;
765 /* If we had to alter the endtime, we didn't have enough
766 ** source rows to fill the last row. Fill it with NaN.
769 for (col = 0; col < (*ds_cnt); col++)
772 row_cnt = ((*end) - (*start)) / *step;
774 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
775 row_cnt, *start, *end, *step);
776 for (col = 0; col < row_cnt; col++) {
777 printf("time %10lu: ", *start + (col + 1) * (*step));
778 for (i = 0; i < *ds_cnt; i++)
779 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
786 /* get the data required for the graphs from the
795 /* pull the data from the rrd files ... */
796 for (i = 0; i < (int) im->gdes_c; i++) {
797 /* only GF_DEF elements fetch data */
798 if (im->gdes[i].gf != GF_DEF)
802 /* do we have it already ? */
803 for (ii = 0; ii < i; ii++) {
804 if (im->gdes[ii].gf != GF_DEF)
806 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
807 && (im->gdes[i].cf == im->gdes[ii].cf)
808 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
809 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
810 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
811 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
812 /* OK, the data is already there.
813 ** Just copy the header portion
815 im->gdes[i].start = im->gdes[ii].start;
816 im->gdes[i].end = im->gdes[ii].end;
817 im->gdes[i].step = im->gdes[ii].step;
818 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
819 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
820 im->gdes[i].data = im->gdes[ii].data;
821 im->gdes[i].data_first = 0;
828 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
830 if ((rrd_fetch_fn(im->gdes[i].rrd,
836 &im->gdes[i].ds_namv,
837 &im->gdes[i].data)) == -1) {
840 im->gdes[i].data_first = 1;
842 if (ft_step < im->gdes[i].step) {
843 reduce_data(im->gdes[i].cf_reduce,
848 &im->gdes[i].ds_cnt, &im->gdes[i].data);
850 im->gdes[i].step = ft_step;
854 /* lets see if the required data source is really there */
855 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
856 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
860 if (im->gdes[i].ds == -1) {
861 rrd_set_error("No DS called '%s' in '%s'",
862 im->gdes[i].ds_nam, im->gdes[i].rrd);
870 /* evaluate the expressions in the CDEF functions */
872 /*************************************************************
874 *************************************************************/
876 long find_var_wrapper(
880 return find_var((image_desc_t *) arg1, key);
883 /* find gdes containing var*/
890 for (ii = 0; ii < im->gdes_c - 1; ii++) {
891 if ((im->gdes[ii].gf == GF_DEF
892 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
893 && (strcmp(im->gdes[ii].vname, key) == 0)) {
900 /* find the largest common denominator for all the numbers
901 in the 0 terminated num array */
908 for (i = 0; num[i + 1] != 0; i++) {
910 rest = num[i] % num[i + 1];
916 /* return i==0?num[i]:num[i-1]; */
920 /* run the rpn calculator on all the VDEF and CDEF arguments */
927 long *steparray, rpi;
932 rpnstack_init(&rpnstack);
934 for (gdi = 0; gdi < im->gdes_c; gdi++) {
935 /* Look for GF_VDEF and GF_CDEF in the same loop,
936 * so CDEFs can use VDEFs and vice versa
938 switch (im->gdes[gdi].gf) {
942 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
944 /* remove current shift */
945 vdp->start -= vdp->shift;
946 vdp->end -= vdp->shift;
949 if (im->gdes[gdi].shidx >= 0)
950 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
953 vdp->shift = im->gdes[gdi].shval;
955 /* normalize shift to multiple of consolidated step */
956 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
959 vdp->start += vdp->shift;
960 vdp->end += vdp->shift;
964 /* A VDEF has no DS. This also signals other parts
965 * of rrdtool that this is a VDEF value, not a CDEF.
967 im->gdes[gdi].ds_cnt = 0;
968 if (vdef_calc(im, gdi)) {
969 rrd_set_error("Error processing VDEF '%s'",
970 im->gdes[gdi].vname);
971 rpnstack_free(&rpnstack);
976 im->gdes[gdi].ds_cnt = 1;
977 im->gdes[gdi].ds = 0;
978 im->gdes[gdi].data_first = 1;
979 im->gdes[gdi].start = 0;
980 im->gdes[gdi].end = 0;
985 /* Find the variables in the expression.
986 * - VDEF variables are substituted by their values
987 * and the opcode is changed into OP_NUMBER.
988 * - CDEF variables are analized for their step size,
989 * the lowest common denominator of all the step
990 * sizes of the data sources involved is calculated
991 * and the resulting number is the step size for the
992 * resulting data source.
994 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
995 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
996 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
997 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
999 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1002 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1003 im->gdes[gdi].vname, im->gdes[ptr].vname);
1004 printf("DEBUG: value from vdef is %f\n",
1005 im->gdes[ptr].vf.val);
1007 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1008 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1009 } else { /* normal variables and PREF(variables) */
1011 /* add one entry to the array that keeps track of the step sizes of the
1012 * data sources going into the CDEF. */
1014 rrd_realloc(steparray,
1016 1) * sizeof(*steparray))) == NULL) {
1017 rrd_set_error("realloc steparray");
1018 rpnstack_free(&rpnstack);
1022 steparray[stepcnt - 1] = im->gdes[ptr].step;
1024 /* adjust start and end of cdef (gdi) so
1025 * that it runs from the latest start point
1026 * to the earliest endpoint of any of the
1027 * rras involved (ptr)
1030 if (im->gdes[gdi].start < im->gdes[ptr].start)
1031 im->gdes[gdi].start = im->gdes[ptr].start;
1033 if (im->gdes[gdi].end == 0 ||
1034 im->gdes[gdi].end > im->gdes[ptr].end)
1035 im->gdes[gdi].end = im->gdes[ptr].end;
1037 /* store pointer to the first element of
1038 * the rra providing data for variable,
1039 * further save step size and data source
1042 im->gdes[gdi].rpnp[rpi].data =
1043 im->gdes[ptr].data + im->gdes[ptr].ds;
1044 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1045 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1047 /* backoff the *.data ptr; this is done so
1048 * rpncalc() function doesn't have to treat
1049 * the first case differently
1051 } /* if ds_cnt != 0 */
1052 } /* if OP_VARIABLE */
1053 } /* loop through all rpi */
1055 /* move the data pointers to the correct period */
1056 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1057 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1058 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1059 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1061 im->gdes[gdi].start - im->gdes[ptr].start;
1064 im->gdes[gdi].rpnp[rpi].data +=
1065 (diff / im->gdes[ptr].step) *
1066 im->gdes[ptr].ds_cnt;
1070 if (steparray == NULL) {
1071 rrd_set_error("rpn expressions without DEF"
1072 " or CDEF variables are not supported");
1073 rpnstack_free(&rpnstack);
1076 steparray[stepcnt] = 0;
1077 /* Now find the resulting step. All steps in all
1078 * used RRAs have to be visited
1080 im->gdes[gdi].step = lcd(steparray);
1082 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1083 im->gdes[gdi].start)
1084 / im->gdes[gdi].step)
1085 * sizeof(double))) == NULL) {
1086 rrd_set_error("malloc im->gdes[gdi].data");
1087 rpnstack_free(&rpnstack);
1091 /* Step through the new cdef results array and
1092 * calculate the values
1094 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1095 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1096 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1098 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1099 * in this case we are advancing by timesteps;
1100 * we use the fact that time_t is a synonym for long
1102 if (rpn_calc(rpnp, &rpnstack, (long) now,
1103 im->gdes[gdi].data, ++dataidx) == -1) {
1104 /* rpn_calc sets the error string */
1105 rpnstack_free(&rpnstack);
1108 } /* enumerate over time steps within a CDEF */
1113 } /* enumerate over CDEFs */
1114 rpnstack_free(&rpnstack);
1118 /* massage data so, that we get one value for each x coordinate in the graph */
1123 double pixstep = (double) (im->end - im->start)
1124 / (double) im->xsize; /* how much time
1125 passes in one pixel */
1127 double minval = DNAN, maxval = DNAN;
1129 unsigned long gr_time;
1131 /* memory for the processed data */
1132 for (i = 0; i < im->gdes_c; i++) {
1133 if ((im->gdes[i].gf == GF_LINE) ||
1134 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1135 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1136 * sizeof(rrd_value_t))) == NULL) {
1137 rrd_set_error("malloc data_proc");
1143 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1146 gr_time = im->start + pixstep * i; /* time of the current step */
1149 for (ii = 0; ii < im->gdes_c; ii++) {
1152 switch (im->gdes[ii].gf) {
1156 if (!im->gdes[ii].stack)
1158 value = im->gdes[ii].yrule;
1159 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1160 /* The time of the data doesn't necessarily match
1161 ** the time of the graph. Beware.
1163 vidx = im->gdes[ii].vidx;
1164 if (im->gdes[vidx].gf == GF_VDEF) {
1165 value = im->gdes[vidx].vf.val;
1167 if (((long int) gr_time >=
1168 (long int) im->gdes[vidx].start)
1169 && ((long int) gr_time <=
1170 (long int) im->gdes[vidx].end)) {
1171 value = im->gdes[vidx].data[(unsigned long)
1177 im->gdes[vidx].step)
1178 * im->gdes[vidx].ds_cnt +
1185 if (!isnan(value)) {
1187 im->gdes[ii].p_data[i] = paintval;
1188 /* GF_TICK: the data values are not
1189 ** relevant for min and max
1191 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1192 if ((isnan(minval) || paintval < minval) &&
1193 !(im->logarithmic && paintval <= 0.0))
1195 if (isnan(maxval) || paintval > maxval)
1199 im->gdes[ii].p_data[i] = DNAN;
1204 ("STACK should already be turned into LINE or AREA here");
1213 /* if min or max have not been asigned a value this is because
1214 there was no data in the graph ... this is not good ...
1215 lets set these to dummy values then ... */
1217 if (im->logarithmic) {
1229 /* adjust min and max values */
1230 if (isnan(im->minval)
1231 /* don't adjust low-end with log scale *//* why not? */
1232 || ((!im->rigid) && im->minval > minval)
1234 if (im->logarithmic)
1235 im->minval = minval * 0.5;
1237 im->minval = minval;
1239 if (isnan(im->maxval)
1240 || (!im->rigid && im->maxval < maxval)
1242 if (im->logarithmic)
1243 im->maxval = maxval * 2.0;
1245 im->maxval = maxval;
1247 /* make sure min is smaller than max */
1248 if (im->minval > im->maxval) {
1249 im->minval = 0.99 * im->maxval;
1252 /* make sure min and max are not equal */
1253 if (im->minval == im->maxval) {
1255 if (!im->logarithmic) {
1258 /* make sure min and max are not both zero */
1259 if (im->maxval == 0.0) {
1268 /* identify the point where the first gridline, label ... gets placed */
1270 time_t find_first_time(
1271 time_t start, /* what is the initial time */
1272 enum tmt_en baseint, /* what is the basic interval */
1273 long basestep /* how many if these do we jump a time */
1278 localtime_r(&start, &tm);
1282 tm. tm_sec -= tm.tm_sec % basestep;
1287 tm. tm_min -= tm.tm_min % basestep;
1293 tm. tm_hour -= tm.tm_hour % basestep;
1297 /* we do NOT look at the basestep for this ... */
1304 /* we do NOT look at the basestep for this ... */
1308 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1310 if (tm.tm_wday == 0)
1311 tm. tm_mday -= 7; /* we want the *previous* monday */
1319 tm. tm_mon -= tm.tm_mon % basestep;
1330 tm.tm_year + 1900) %basestep;
1336 /* identify the point where the next gridline, label ... gets placed */
1337 time_t find_next_time(
1338 time_t current, /* what is the initial time */
1339 enum tmt_en baseint, /* what is the basic interval */
1340 long basestep /* how many if these do we jump a time */
1346 localtime_r(¤t, &tm);
1351 tm. tm_sec += basestep;
1355 tm. tm_min += basestep;
1359 tm. tm_hour += basestep;
1363 tm. tm_mday += basestep;
1367 tm. tm_mday += 7 * basestep;
1371 tm. tm_mon += basestep;
1375 tm. tm_year += basestep;
1377 madetime = mktime(&tm);
1378 } while (madetime == -1); /* this is necessary to skip impssible times
1379 like the daylight saving time skips */
1385 /* calculate values required for PRINT and GPRINT functions */
1391 long i, ii, validsteps;
1394 int graphelement = 0;
1397 double magfact = -1;
1402 /* wow initializing tmvdef is quite a task :-) */
1403 time_t now = time(NULL);
1405 localtime_r(&now, &tmvdef);
1408 for (i = 0; i < im->gdes_c; i++) {
1409 vidx = im->gdes[i].vidx;
1410 switch (im->gdes[i].gf) {
1414 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1415 rrd_set_error("realloc prdata");
1419 /* PRINT and GPRINT can now print VDEF generated values.
1420 * There's no need to do any calculations on them as these
1421 * calculations were already made.
1423 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1424 printval = im->gdes[vidx].vf.val;
1425 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1426 } else { /* need to calculate max,min,avg etcetera */
1427 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1428 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1431 for (ii = im->gdes[vidx].ds;
1432 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1433 if (!finite(im->gdes[vidx].data[ii]))
1435 if (isnan(printval)) {
1436 printval = im->gdes[vidx].data[ii];
1441 switch (im->gdes[i].cf) {
1445 case CF_DEVSEASONAL:
1449 printval += im->gdes[vidx].data[ii];
1452 printval = min(printval, im->gdes[vidx].data[ii]);
1456 printval = max(printval, im->gdes[vidx].data[ii]);
1459 printval = im->gdes[vidx].data[ii];
1462 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1463 if (validsteps > 1) {
1464 printval = (printval / validsteps);
1467 } /* prepare printval */
1469 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1470 /* Magfact is set to -1 upon entry to print_calc. If it
1471 * is still less than 0, then we need to run auto_scale.
1472 * Otherwise, put the value into the correct units. If
1473 * the value is 0, then do not set the symbol or magnification
1474 * so next the calculation will be performed again. */
1475 if (magfact < 0.0) {
1476 auto_scale(im, &printval, &si_symb, &magfact);
1477 if (printval == 0.0)
1480 printval /= magfact;
1482 *(++percent_s) = 's';
1483 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1484 auto_scale(im, &printval, &si_symb, &magfact);
1487 if (im->gdes[i].gf == GF_PRINT) {
1488 (*prdata)[prlines - 2] =
1489 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1490 (*prdata)[prlines - 1] = NULL;
1491 if (im->gdes[i].strftm) {
1492 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1493 im->gdes[i].format, &tmvdef);
1495 if (bad_format(im->gdes[i].format)) {
1496 rrd_set_error("bad format for PRINT in '%s'",
1497 im->gdes[i].format);
1500 #ifdef HAVE_SNPRINTF
1501 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1502 im->gdes[i].format, printval, si_symb);
1504 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1511 if (im->gdes[i].strftm) {
1512 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1513 im->gdes[i].format, &tmvdef);
1515 if (bad_format(im->gdes[i].format)) {
1516 rrd_set_error("bad format for GPRINT in '%s'",
1517 im->gdes[i].format);
1520 #ifdef HAVE_SNPRINTF
1521 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1522 im->gdes[i].format, printval, si_symb);
1524 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1537 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1538 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1543 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1544 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1553 #ifdef WITH_PIECHART
1561 ("STACK should already be turned into LINE or AREA here");
1566 return graphelement;
1570 /* place legends with color spots */
1576 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1577 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1578 int fill = 0, fill_last;
1580 int leg_x = border, leg_y = im->yimg;
1581 int leg_y_prev = im->yimg;
1584 int i, ii, mark = 0;
1585 char prt_fctn; /*special printfunctions */
1586 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1589 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1590 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1591 rrd_set_error("malloc for legspace");
1595 if (im->extra_flags & FULL_SIZE_MODE)
1596 leg_y = leg_y_prev =
1597 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1599 for (i = 0; i < im->gdes_c; i++) {
1602 /* hide legends for rules which are not displayed */
1604 if (im->gdes[i].gf == GF_TEXTALIGN) {
1605 default_txtalign = im->gdes[i].txtalign;
1608 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1609 if (im->gdes[i].gf == GF_HRULE &&
1610 (im->gdes[i].yrule < im->minval
1611 || im->gdes[i].yrule > im->maxval))
1612 im->gdes[i].legend[0] = '\0';
1614 if (im->gdes[i].gf == GF_VRULE &&
1615 (im->gdes[i].xrule < im->start
1616 || im->gdes[i].xrule > im->end))
1617 im->gdes[i].legend[0] = '\0';
1620 leg_cc = strlen(im->gdes[i].legend);
1622 /* is there a controle code ant the end of the legend string ? */
1623 /* and it is not a tab \\t */
1624 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1625 && im->gdes[i].legend[leg_cc - 1] != 't') {
1626 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1628 im->gdes[i].legend[leg_cc] = '\0';
1632 /* only valid control codes */
1633 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1638 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1640 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1641 im->gdes[i].legend, prt_fctn);
1646 if (prt_fctn == 'n') {
1650 /* remove exess space from the end of the legend for \g */
1651 while (prt_fctn == 'g' &&
1652 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1654 im->gdes[i].legend[leg_cc] = '\0';
1659 /* no interleg space if string ends in \g */
1660 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1663 fill += legspace[i];
1665 fill += gfx_get_text_width(im, fill + border,
1666 im->text_prop[TEXT_PROP_LEGEND].
1668 im->text_prop[TEXT_PROP_LEGEND].
1670 im->gdes[i].legend);
1675 /* who said there was a special tag ... ? */
1676 if (prt_fctn == 'g') {
1680 if (prt_fctn == '\0') {
1681 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1682 /* just one legend item is left right or center */
1683 switch (default_txtalign) {
1698 /* is it time to place the legends ? */
1699 if (fill > im->ximg - 2 * border) {
1707 if (leg_c == 1 && prt_fctn == 'j') {
1713 if (prt_fctn != '\0') {
1715 if (leg_c >= 2 && prt_fctn == 'j') {
1716 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1720 if (prt_fctn == 'c')
1721 leg_x = (im->ximg - fill) / 2.0;
1722 if (prt_fctn == 'r')
1723 leg_x = im->ximg - fill - border;
1725 for (ii = mark; ii <= i; ii++) {
1726 if (im->gdes[ii].legend[0] == '\0')
1727 continue; /* skip empty legends */
1728 im->gdes[ii].leg_x = leg_x;
1729 im->gdes[ii].leg_y = leg_y;
1731 gfx_get_text_width(im, leg_x,
1732 im->text_prop[TEXT_PROP_LEGEND].
1734 im->text_prop[TEXT_PROP_LEGEND].
1736 im->gdes[ii].legend)
1741 if (im->extra_flags & FULL_SIZE_MODE) {
1742 /* only add y space if there was text on the line */
1743 if (leg_x > border || prt_fctn == 's')
1744 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1745 if (prt_fctn == 's')
1746 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1748 if (leg_x > border || prt_fctn == 's')
1749 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1750 if (prt_fctn == 's')
1751 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1759 if (im->extra_flags & FULL_SIZE_MODE) {
1760 if (leg_y != leg_y_prev) {
1761 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1763 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1766 im->yimg = leg_y_prev;
1767 /* if we did place some legends we have to add vertical space */
1768 if (leg_y != im->yimg)
1769 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1776 /* create a grid on the graph. it determines what to do
1777 from the values of xsize, start and end */
1779 /* the xaxis labels are determined from the number of seconds per pixel
1780 in the requested graph */
1784 int calc_horizontal_grid(
1791 int decimals, fractionals;
1793 im->ygrid_scale.labfact = 2;
1794 range = im->maxval - im->minval;
1795 scaledrange = range / im->magfact;
1797 /* does the scale of this graph make it impossible to put lines
1798 on it? If so, give up. */
1799 if (isnan(scaledrange)) {
1803 /* find grid spaceing */
1805 if (isnan(im->ygridstep)) {
1806 if (im->extra_flags & ALTYGRID) {
1807 /* find the value with max number of digits. Get number of digits */
1810 (max(fabs(im->maxval), fabs(im->minval)) *
1811 im->viewfactor / im->magfact));
1812 if (decimals <= 0) /* everything is small. make place for zero */
1815 im->ygrid_scale.gridstep =
1817 floor(log10(range * im->viewfactor / im->magfact))) /
1818 im->viewfactor * im->magfact;
1820 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1821 im->ygrid_scale.gridstep = 0.1;
1822 /* should have at least 5 lines but no more then 15 */
1823 if (range / im->ygrid_scale.gridstep < 5)
1824 im->ygrid_scale.gridstep /= 10;
1825 if (range / im->ygrid_scale.gridstep > 15)
1826 im->ygrid_scale.gridstep *= 10;
1827 if (range / im->ygrid_scale.gridstep > 5) {
1828 im->ygrid_scale.labfact = 1;
1829 if (range / im->ygrid_scale.gridstep > 8)
1830 im->ygrid_scale.labfact = 2;
1832 im->ygrid_scale.gridstep /= 5;
1833 im->ygrid_scale.labfact = 5;
1837 (im->ygrid_scale.gridstep *
1838 (double) im->ygrid_scale.labfact * im->viewfactor /
1840 if (fractionals < 0) { /* small amplitude. */
1841 int len = decimals - fractionals + 1;
1843 if (im->unitslength < len + 2)
1844 im->unitslength = len + 2;
1845 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1846 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1848 int len = decimals + 1;
1850 if (im->unitslength < len + 2)
1851 im->unitslength = len + 2;
1852 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1853 (im->symbol != ' ' ? " %c" : ""));
1856 for (i = 0; ylab[i].grid > 0; i++) {
1857 pixel = im->ysize / (scaledrange / ylab[i].grid);
1863 for (i = 0; i < 4; i++) {
1864 if (pixel * ylab[gridind].lfac[i] >=
1865 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1866 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1871 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1874 im->ygrid_scale.gridstep = im->ygridstep;
1875 im->ygrid_scale.labfact = im->ylabfact;
1880 int draw_horizontal_grid(
1885 char graph_label[100];
1887 double X0 = im->xorigin;
1888 double X1 = im->xorigin + im->xsize;
1890 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1891 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1895 im->ygrid_scale.gridstep / (double) im->magfact *
1896 (double) im->viewfactor;
1897 MaxY = scaledstep * (double) egrid;
1898 for (i = sgrid; i <= egrid; i++) {
1899 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1900 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1902 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1903 && floor(Y0 + 0.5) <= im->yorigin) {
1904 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1905 with the chosen settings. Add a label if required by settings, or if
1906 there is only one label so far and the next grid line is out of bounds. */
1907 if (i % im->ygrid_scale.labfact == 0
1909 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1910 if (im->symbol == ' ') {
1911 if (im->extra_flags & ALTYGRID) {
1912 sprintf(graph_label, im->ygrid_scale.labfmt,
1913 scaledstep * (double) i);
1916 sprintf(graph_label, "%4.1f",
1917 scaledstep * (double) i);
1919 sprintf(graph_label, "%4.0f",
1920 scaledstep * (double) i);
1924 char sisym = (i == 0 ? ' ' : im->symbol);
1926 if (im->extra_flags & ALTYGRID) {
1927 sprintf(graph_label, im->ygrid_scale.labfmt,
1928 scaledstep * (double) i, sisym);
1931 sprintf(graph_label, "%4.1f %c",
1932 scaledstep * (double) i, sisym);
1934 sprintf(graph_label, "%4.0f %c",
1935 scaledstep * (double) i, sisym);
1942 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1943 im->graph_col[GRC_FONT],
1944 im->text_prop[TEXT_PROP_AXIS].font,
1945 im->text_prop[TEXT_PROP_AXIS].size,
1946 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1950 X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1953 X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1957 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1958 im->grid_dash_on, im->grid_dash_off);
1960 } else if (!(im->extra_flags & NOMINOR)) {
1963 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1966 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1970 GRIDWIDTH, im->graph_col[GRC_GRID],
1971 im->grid_dash_on, im->grid_dash_off);
1979 /* this is frexp for base 10 */
1990 iexp = floor(log(fabs(x)) / log(10));
1991 mnt = x / pow(10.0, iexp);
1994 mnt = x / pow(10.0, iexp);
2000 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2001 /* yes we are loosing precision by doing tos with floats instead of doubles
2002 but it seems more stable this way. */
2004 static int AlmostEqual2sComplement(
2010 int aInt = *(int *) &A;
2011 int bInt = *(int *) &B;
2014 /* Make sure maxUlps is non-negative and small enough that the
2015 default NAN won't compare as equal to anything. */
2017 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2019 /* Make aInt lexicographically ordered as a twos-complement int */
2022 aInt = 0x80000000l - aInt;
2024 /* Make bInt lexicographically ordered as a twos-complement int */
2027 bInt = 0x80000000l - bInt;
2029 intDiff = abs(aInt - bInt);
2031 if (intDiff <= maxUlps)
2037 /* logaritmic horizontal grid */
2038 int horizontal_log_grid(
2041 double yloglab[][10] = {
2042 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2043 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2044 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2045 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2046 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2047 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2050 int i, j, val_exp, min_exp;
2051 double nex; /* number of decades in data */
2052 double logscale; /* scale in logarithmic space */
2053 int exfrac = 1; /* decade spacing */
2054 int mid = -1; /* row in yloglab for major grid */
2055 double mspac; /* smallest major grid spacing (pixels) */
2056 int flab; /* first value in yloglab to use */
2057 double value, tmp, pre_value;
2059 char graph_label[100];
2061 nex = log10(im->maxval / im->minval);
2062 logscale = im->ysize / nex;
2064 /* major spacing for data with high dynamic range */
2065 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2072 /* major spacing for less dynamic data */
2074 /* search best row in yloglab */
2076 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2077 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2078 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2079 && yloglab[mid][0] > 0);
2083 /* find first value in yloglab */
2085 yloglab[mid][flab] < 10
2086 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2087 if (yloglab[mid][flab] == 10.0) {
2092 if (val_exp % exfrac)
2093 val_exp += abs(-val_exp % exfrac);
2096 X1 = im->xorigin + im->xsize;
2102 value = yloglab[mid][flab] * pow(10.0, val_exp);
2103 if (AlmostEqual2sComplement(value, pre_value, 4))
2104 break; /* it seems we are not converging */
2108 Y0 = ytr(im, value);
2109 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2112 /* major grid line */
2115 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2117 X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2123 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2124 im->grid_dash_on, im->grid_dash_off);
2127 if (im->extra_flags & FORCE_UNITS_SI) {
2132 scale = floor(val_exp / 3.0);
2134 pvalue = pow(10.0, val_exp % 3);
2136 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2137 pvalue *= yloglab[mid][flab];
2139 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2140 ((scale + si_symbcenter) >= 0))
2141 symbol = si_symbol[scale + si_symbcenter];
2145 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2147 sprintf(graph_label, "%3.0e", value);
2149 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2150 im->graph_col[GRC_FONT],
2151 im->text_prop[TEXT_PROP_AXIS].font,
2152 im->text_prop[TEXT_PROP_AXIS].size,
2153 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2156 if (mid < 4 && exfrac == 1) {
2157 /* find first and last minor line behind current major line
2158 * i is the first line and j tha last */
2160 min_exp = val_exp - 1;
2161 for (i = 1; yloglab[mid][i] < 10.0; i++);
2162 i = yloglab[mid][i - 1] + 1;
2166 i = yloglab[mid][flab - 1] + 1;
2167 j = yloglab[mid][flab];
2170 /* draw minor lines below current major line */
2171 for (; i < j; i++) {
2173 value = i * pow(10.0, min_exp);
2174 if (value < im->minval)
2177 Y0 = ytr(im, value);
2178 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2184 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2187 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2191 GRIDWIDTH, im->graph_col[GRC_GRID],
2192 im->grid_dash_on, im->grid_dash_off);
2194 } else if (exfrac > 1) {
2195 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2196 value = pow(10.0, i);
2197 if (value < im->minval)
2200 Y0 = ytr(im, value);
2201 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2207 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2210 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2214 GRIDWIDTH, im->graph_col[GRC_GRID],
2215 im->grid_dash_on, im->grid_dash_off);
2220 if (yloglab[mid][++flab] == 10.0) {
2226 /* draw minor lines after highest major line */
2227 if (mid < 4 && exfrac == 1) {
2228 /* find first and last minor line below current major line
2229 * i is the first line and j tha last */
2231 min_exp = val_exp - 1;
2232 for (i = 1; yloglab[mid][i] < 10.0; i++);
2233 i = yloglab[mid][i - 1] + 1;
2237 i = yloglab[mid][flab - 1] + 1;
2238 j = yloglab[mid][flab];
2241 /* draw minor lines below current major line */
2242 for (; i < j; i++) {
2244 value = i * pow(10.0, min_exp);
2245 if (value < im->minval)
2248 Y0 = ytr(im, value);
2249 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2254 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2256 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2260 GRIDWIDTH, im->graph_col[GRC_GRID],
2261 im->grid_dash_on, im->grid_dash_off);
2264 /* fancy minor gridlines */
2265 else if (exfrac > 1) {
2266 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2267 value = pow(10.0, i);
2268 if (value < im->minval)
2271 Y0 = ytr(im, value);
2272 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2277 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2279 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2283 GRIDWIDTH, im->graph_col[GRC_GRID],
2284 im->grid_dash_on, im->grid_dash_off);
2295 int xlab_sel; /* which sort of label and grid ? */
2296 time_t ti, tilab, timajor;
2298 char graph_label[100];
2299 double X0, Y0, Y1; /* points for filled graph and more */
2302 /* the type of time grid is determined by finding
2303 the number of seconds per pixel in the graph */
2306 if (im->xlab_user.minsec == -1) {
2307 factor = (im->end - im->start) / im->xsize;
2309 while (xlab[xlab_sel + 1].minsec != -1
2310 && xlab[xlab_sel + 1].minsec <= factor) {
2312 } /* pick the last one */
2313 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2314 && xlab[xlab_sel].length > (im->end - im->start)) {
2316 } /* go back to the smallest size */
2317 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2318 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2319 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2320 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2321 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2322 im->xlab_user.labst = xlab[xlab_sel].labst;
2323 im->xlab_user.precis = xlab[xlab_sel].precis;
2324 im->xlab_user.stst = xlab[xlab_sel].stst;
2327 /* y coords are the same for every line ... */
2329 Y1 = im->yorigin - im->ysize;
2332 /* paint the minor grid */
2333 if (!(im->extra_flags & NOMINOR)) {
2334 for (ti = find_first_time(im->start,
2335 im->xlab_user.gridtm,
2336 im->xlab_user.gridst),
2337 timajor = find_first_time(im->start,
2338 im->xlab_user.mgridtm,
2339 im->xlab_user.mgridst);
2342 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2344 /* are we inside the graph ? */
2345 if (ti < im->start || ti > im->end)
2347 while (timajor < ti) {
2348 timajor = find_next_time(timajor,
2349 im->xlab_user.mgridtm,
2350 im->xlab_user.mgridst);
2353 continue; /* skip as falls on major grid line */
2355 gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2356 im->graph_col[GRC_GRID]);
2357 gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2358 im->graph_col[GRC_GRID]);
2359 gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2360 im->graph_col[GRC_GRID],
2361 im->grid_dash_on, im->grid_dash_off);
2366 /* paint the major grid */
2367 for (ti = find_first_time(im->start,
2368 im->xlab_user.mgridtm,
2369 im->xlab_user.mgridst);
2371 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2373 /* are we inside the graph ? */
2374 if (ti < im->start || ti > im->end)
2377 gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2378 im->graph_col[GRC_MGRID]);
2379 gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2380 im->graph_col[GRC_MGRID]);
2381 gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2382 im->graph_col[GRC_MGRID],
2383 im->grid_dash_on, im->grid_dash_off);
2386 /* paint the labels below the graph */
2387 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2388 im->xlab_user.labtm,
2389 im->xlab_user.labst);
2390 ti <= im->end - im->xlab_user.precis / 2;
2391 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2393 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2394 /* are we inside the graph ? */
2395 if (tilab < im->start || tilab > im->end)
2399 localtime_r(&tilab, &tm);
2400 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2402 # error "your libc has no strftime I guess we'll abort the exercise here."
2407 im->graph_col[GRC_FONT],
2408 im->text_prop[TEXT_PROP_AXIS].font,
2409 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2410 GFX_H_CENTER, GFX_V_TOP, graph_label);
2420 /* draw x and y axis */
2421 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2422 im->xorigin+im->xsize,im->yorigin-im->ysize,
2423 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2425 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2426 im->xorigin+im->xsize,im->yorigin-im->ysize,
2427 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2429 gfx_line(im, im->xorigin - 4, im->yorigin,
2430 im->xorigin + im->xsize + 4, im->yorigin,
2431 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2433 gfx_line(im, im->xorigin, im->yorigin + 4,
2434 im->xorigin, im->yorigin - im->ysize - 4,
2435 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2438 /* arrow for X and Y axis direction */
2440 gfx_new_area(im, im->xorigin + im->xsize + 2, im->yorigin - 3, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin, /* horyzontal */
2441 im->graph_col[GRC_ARROW]);
2444 gfx_new_area(im, im->xorigin - 3, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin, im->yorigin - im->ysize - 7, /* vertical */
2445 im->graph_col[GRC_ARROW]);
2456 double X0, Y0; /* points for filled graph and more */
2457 struct gfx_color_t water_color;
2459 /* draw 3d border */
2460 gfx_new_area(im, 0, im->yimg,
2461 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2462 gfx_add_point(im, im->ximg - 2, 2);
2463 gfx_add_point(im, im->ximg, 0);
2464 gfx_add_point(im, 0, 0);
2467 gfx_new_area(im, 2, im->yimg - 2,
2468 im->ximg - 2, im->yimg - 2,
2469 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2470 gfx_add_point(im, im->ximg, 0);
2471 gfx_add_point(im, im->ximg, im->yimg);
2472 gfx_add_point(im, 0, im->yimg);
2476 if (im->draw_x_grid == 1)
2479 if (im->draw_y_grid == 1) {
2480 if (im->logarithmic) {
2481 res = horizontal_log_grid(im);
2483 res = draw_horizontal_grid(im);
2486 /* dont draw horizontal grid if there is no min and max val */
2488 char *nodata = "No Data found";
2490 gfx_text(im, im->ximg / 2,
2491 (2 * im->yorigin - im->ysize) / 2,
2492 im->graph_col[GRC_FONT],
2493 im->text_prop[TEXT_PROP_AXIS].font,
2494 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2495 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2499 /* yaxis unit description */
2501 10, (im->yorigin - im->ysize / 2),
2502 im->graph_col[GRC_FONT],
2503 im->text_prop[TEXT_PROP_UNIT].font,
2504 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2505 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2510 im->graph_col[GRC_FONT],
2511 im->text_prop[TEXT_PROP_TITLE].font,
2512 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2513 GFX_H_CENTER, GFX_V_TOP, im->title);
2514 /* rrdtool 'logo' */
2515 water_color = im->graph_col[GRC_FONT];
2516 water_color.alpha = 0.3;
2520 im->text_prop[TEXT_PROP_AXIS].font,
2521 5.5, im->tabwidth, -90,
2522 GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2524 /* graph watermark */
2525 if (im->watermark[0] != '\0') {
2527 im->ximg / 2, im->yimg - 6,
2529 im->text_prop[TEXT_PROP_AXIS].font,
2530 5.5, im->tabwidth, 0,
2531 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2535 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2536 for (i = 0; i < im->gdes_c; i++) {
2537 if (im->gdes[i].legend[0] == '\0')
2540 /* im->gdes[i].leg_y is the bottom of the legend */
2541 X0 = im->gdes[i].leg_x;
2542 Y0 = im->gdes[i].leg_y;
2543 gfx_text(im, X0, Y0,
2544 im->graph_col[GRC_FONT],
2545 im->text_prop[TEXT_PROP_LEGEND].font,
2546 im->text_prop[TEXT_PROP_LEGEND].size,
2547 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2548 im->gdes[i].legend);
2549 /* The legend for GRAPH items starts with "M " to have
2550 enough space for the box */
2551 if (im->gdes[i].gf != GF_PRINT &&
2552 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2557 boxH = gfx_get_text_width(im, 0,
2558 im->text_prop[TEXT_PROP_LEGEND].
2560 im->text_prop[TEXT_PROP_LEGEND].
2561 size, im->tabwidth, "o") * 1.2;
2564 /* shift the box up a bit */
2567 /* make sure transparent colors show up the same way as in the graph */
2571 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2572 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2577 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2578 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2582 cairo_new_path(im->cr);
2583 cairo_set_line_width(im->cr, 1.0);
2586 gfx_line_fit(im, &X0, &Y0);
2587 gfx_line_fit(im, &X1, &Y1);
2588 cairo_move_to(im->cr, X0, Y0);
2589 cairo_line_to(im->cr, X1, Y0);
2590 cairo_line_to(im->cr, X1, Y1);
2591 cairo_line_to(im->cr, X0, Y1);
2592 cairo_close_path(im->cr);
2593 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2594 im->graph_col[GRC_FRAME].green,
2595 im->graph_col[GRC_FRAME].blue,
2596 im->graph_col[GRC_FRAME].alpha);
2597 cairo_stroke(im->cr);
2598 cairo_restore(im->cr);
2605 /*****************************************************
2606 * lazy check make sure we rely need to create this graph
2607 *****************************************************/
2614 struct stat imgstat;
2617 return 0; /* no lazy option */
2618 if (stat(im->graphfile, &imgstat) != 0)
2619 return 0; /* can't stat */
2620 /* one pixel in the existing graph is more then what we would
2622 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2624 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2625 return 0; /* the file does not exist */
2626 switch (im->imgformat) {
2628 size = PngSize(fd, &(im->ximg), &(im->yimg));
2638 int graph_size_location(
2642 /* The actual size of the image to draw is determined from
2643 ** several sources. The size given on the command line is
2644 ** the graph area but we need more as we have to draw labels
2645 ** and other things outside the graph area
2648 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2649 Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2651 if (im->extra_flags & ONLY_GRAPH) {
2653 im->ximg = im->xsize;
2654 im->yimg = im->ysize;
2655 im->yorigin = im->ysize;
2660 /** +---+--------------------------------------------+
2661 ** | y |...............graph title..................|
2662 ** | +---+-------------------------------+--------+
2665 ** | i | a | | pie |
2666 ** | s | x | main graph area | chart |
2671 ** | l | b +-------------------------------+--------+
2672 ** | e | l | x axis labels | |
2673 ** +---+---+-------------------------------+--------+
2674 ** |....................legends.....................|
2675 ** +------------------------------------------------+
2677 ** +------------------------------------------------+
2680 if (im->ylegend[0] != '\0') {
2681 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2684 if (im->title[0] != '\0') {
2685 /* The title is placed "inbetween" two text lines so it
2686 ** automatically has some vertical spacing. The horizontal
2687 ** spacing is added here, on each side.
2689 /* if necessary, reduce the font size of the title until it fits the image width */
2690 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2694 if (im->draw_x_grid) {
2695 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2697 if (im->draw_y_grid || im->forceleftspace) {
2698 Xylabel = gfx_get_text_width(im, 0,
2699 im->text_prop[TEXT_PROP_AXIS].font,
2700 im->text_prop[TEXT_PROP_AXIS].size,
2701 im->tabwidth, "0") * im->unitslength;
2705 if (im->extra_flags & FULL_SIZE_MODE) {
2706 /* The actual size of the image to draw has been determined by the user.
2707 ** The graph area is the space remaining after accounting for the legend,
2708 ** the watermark, the pie chart, the axis labels, and the title.
2711 im->ximg = im->xsize;
2712 im->yimg = im->ysize;
2713 im->yorigin = im->ysize;
2717 im->yorigin += Ytitle;
2719 /* Now calculate the total size. Insert some spacing where
2720 desired. im->xorigin and im->yorigin need to correspond
2721 with the lower left corner of the main graph area or, if
2722 this one is not set, the imaginary box surrounding the
2725 /* Initial size calculation for the main graph area */
2726 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2728 Xmain -= Xspacing; /* put space between main graph area and right edge */
2730 im->xorigin = Xspacing + Xylabel;
2732 /* the length of the title should not influence with width of the graph
2733 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2735 if (Xvertical) { /* unit description */
2737 im->xorigin += Xvertical;
2742 /* The vertical size of the image is known in advance. The main graph area
2743 ** (Ymain) and im->yorigin must be set according to the space requirements
2744 ** of the legend and the axis labels.
2747 if (im->extra_flags & NOLEGEND) {
2748 /* set dimensions correctly if using full size mode with no legend */
2750 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2752 Ymain = im->yorigin;
2754 /* Determine where to place the legends onto the image.
2755 ** Set Ymain and adjust im->yorigin to match the space requirements.
2757 if (leg_place(im, &Ymain) == -1)
2762 /* remove title space *or* some padding above the graph from the main graph area */
2766 Ymain -= 1.5 * Yspacing;
2769 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2770 if (im->watermark[0] != '\0') {
2771 Ymain -= Ywatermark;
2776 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2778 /* The actual size of the image to draw is determined from
2779 ** several sources. The size given on the command line is
2780 ** the graph area but we need more as we have to draw labels
2781 ** and other things outside the graph area.
2784 if (im->ylegend[0] != '\0') {
2785 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2789 if (im->title[0] != '\0') {
2790 /* The title is placed "inbetween" two text lines so it
2791 ** automatically has some vertical spacing. The horizontal
2792 ** spacing is added here, on each side.
2794 /* don't care for the with of the title
2795 Xtitle = gfx_get_text_width(im->canvas, 0,
2796 im->text_prop[TEXT_PROP_TITLE].font,
2797 im->text_prop[TEXT_PROP_TITLE].size,
2799 im->title, 0) + 2*Xspacing; */
2800 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2807 /* Now calculate the total size. Insert some spacing where
2808 desired. im->xorigin and im->yorigin need to correspond
2809 with the lower left corner of the main graph area or, if
2810 this one is not set, the imaginary box surrounding the
2813 /* The legend width cannot yet be determined, as a result we
2814 ** have problems adjusting the image to it. For now, we just
2815 ** forget about it at all; the legend will have to fit in the
2816 ** size already allocated.
2818 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2821 im->ximg += Xspacing;
2823 im->xorigin = Xspacing + Xylabel;
2825 /* the length of the title should not influence with width of the graph
2826 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2828 if (Xvertical) { /* unit description */
2829 im->ximg += Xvertical;
2830 im->xorigin += Xvertical;
2834 /* The vertical size is interesting... we need to compare
2835 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2836 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2837 ** in order to start even thinking about Ylegend or Ywatermark.
2839 ** Do it in three portions: First calculate the inner part,
2840 ** then do the legend, then adjust the total height of the img,
2841 ** adding space for a watermark if one exists;
2844 /* reserve space for main and/or pie */
2846 im->yimg = Ymain + Yxlabel;
2849 im->yorigin = im->yimg - Yxlabel;
2851 /* reserve space for the title *or* some padding above the graph */
2854 im->yorigin += Ytitle;
2856 im->yimg += 1.5 * Yspacing;
2857 im->yorigin += 1.5 * Yspacing;
2859 /* reserve space for padding below the graph */
2860 im->yimg += Yspacing;
2862 /* Determine where to place the legends onto the image.
2863 ** Adjust im->yimg to match the space requirements.
2865 if (leg_place(im, 0) == -1)
2868 if (im->watermark[0] != '\0') {
2869 im->yimg += Ywatermark;
2879 static cairo_status_t cairo_write_func_filehandle(
2881 const unsigned char *data,
2882 unsigned int length)
2884 if (fwrite(data, length, 1, closure) != 1)
2885 return CAIRO_STATUS_WRITE_ERROR;
2886 return CAIRO_STATUS_SUCCESS;
2889 static cairo_status_t cairo_copy_to_buffer(
2891 const unsigned char *data,
2892 unsigned int length)
2894 image_desc_t *im = closure;
2896 im->rendered_image =
2897 realloc(im->rendered_image, im->rendered_image_size + length);
2898 if (im->rendered_image == NULL) {
2899 return CAIRO_STATUS_WRITE_ERROR;
2902 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2904 im->rendered_image_size += length;
2906 return CAIRO_STATUS_SUCCESS;
2909 /* draw that picture thing ... */
2915 int lazy = lazy_check(im);
2917 double areazero = 0.0;
2918 graph_desc_t *lastgdes = NULL;
2920 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2923 /* if we are lazy and there is nothing to PRINT ... quit now */
2924 if (lazy && im->prt_c == 0)
2927 /* pull the data from the rrd files ... */
2929 if (data_fetch(im) == -1)
2932 /* evaluate VDEF and CDEF operations ... */
2933 if (data_calc(im) == -1)
2937 /* calculate and PRINT and GPRINT definitions. We have to do it at
2938 * this point because it will affect the length of the legends
2939 * if there are no graph elements we stop here ...
2940 * if we are lazy, try to quit ...
2942 i = print_calc(im, calcpr);
2945 if ((i == 0) || lazy)
2948 /**************************************************************
2949 *** Calculating sizes and locations became a bit confusing ***
2950 *** so I moved this into a separate function. ***
2951 **************************************************************/
2952 if (graph_size_location(im, i) == -1)
2955 /* get actual drawing data and find min and max values */
2956 if (data_proc(im) == -1)
2959 if (!im->logarithmic) {
2962 /* identify si magnitude Kilo, Mega Giga ? */
2963 if (!im->rigid && !im->logarithmic)
2964 expand_range(im); /* make sure the upper and lower limit are
2967 if (!calc_horizontal_grid(im))
2974 apply_gridfit(im); */
2977 /* the actual graph is created by going through the individual
2978 graph elements and then drawing them */
2979 cairo_surface_destroy(im->surface);
2981 switch (im->imgformat) {
2984 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2985 im->ximg * im->zoom,
2986 im->yimg * im->zoom);
2990 im->surface = strlen(im->graphfile)
2991 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2992 im->yimg * im->zoom)
2993 : cairo_pdf_surface_create_for_stream(&cairo_copy_to_buffer, im,
2994 im->ximg * im->zoom,
2995 im->yimg * im->zoom);
2999 im->surface = strlen(im->graphfile)
3000 ? cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3001 im->yimg * im->zoom)
3002 : cairo_ps_surface_create_for_stream(&cairo_copy_to_buffer, im,
3003 im->ximg * im->zoom,
3004 im->yimg * im->zoom);
3008 im->surface = strlen(im->graphfile)
3009 ? cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
3010 im->yimg * im->zoom)
3011 : cairo_svg_surface_create_for_stream(&cairo_copy_to_buffer, im,
3012 im->ximg * im->zoom,
3013 im->yimg * im->zoom);
3014 cairo_svg_surface_restrict_to_version(im->surface,
3015 CAIRO_SVG_VERSION_1_1);
3018 im->cr = cairo_create(im->surface);
3019 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3020 cairo_set_antialias(im->cr, im->graph_antialias);
3021 cairo_scale(im->cr, im->zoom, im->zoom);
3025 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3027 gfx_add_point(im, im->ximg, 0);
3031 im->xorigin, im->yorigin,
3032 im->xorigin + im->xsize, im->yorigin,
3033 im->xorigin + im->xsize, im->yorigin - im->ysize,
3034 im->graph_col[GRC_CANVAS]);
3036 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3039 if (im->minval > 0.0)
3040 areazero = im->minval;
3041 if (im->maxval < 0.0)
3042 areazero = im->maxval;
3044 for (i = 0; i < im->gdes_c; i++) {
3045 switch (im->gdes[i].gf) {
3059 for (ii = 0; ii < im->xsize; ii++) {
3060 if (!isnan(im->gdes[i].p_data[ii]) &&
3061 im->gdes[i].p_data[ii] != 0.0) {
3062 if (im->gdes[i].yrule > 0) {
3064 im->xorigin + ii, im->yorigin,
3067 im->gdes[i].yrule * im->ysize, 1.0,
3069 } else if (im->gdes[i].yrule < 0) {
3072 im->yorigin - im->ysize,
3075 im->gdes[i].yrule) *
3076 im->ysize, 1.0, im->gdes[i].col);
3084 /* fix data points at oo and -oo */
3085 for (ii = 0; ii < im->xsize; ii++) {
3086 if (isinf(im->gdes[i].p_data[ii])) {
3087 if (im->gdes[i].p_data[ii] > 0) {
3088 im->gdes[i].p_data[ii] = im->maxval;
3090 im->gdes[i].p_data[ii] = im->minval;
3096 /* *******************************************************
3101 -------|--t-1--t--------------------------------
3103 if we know the value at time t was a then
3104 we draw a square from t-1 to t with the value a.
3106 ********************************************************* */
3107 if (im->gdes[i].col.alpha != 0.0) {
3108 /* GF_LINE and friend */
3109 if (im->gdes[i].gf == GF_LINE) {
3110 double last_y = 0.0;
3114 cairo_new_path(im->cr);
3116 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3117 for (ii = 1; ii < im->xsize; ii++) {
3118 if (isnan(im->gdes[i].p_data[ii])
3119 || (im->slopemode == 1
3120 && isnan(im->gdes[i].p_data[ii - 1]))) {
3125 last_y = ytr(im, im->gdes[i].p_data[ii]);
3126 if (im->slopemode == 0) {
3127 double x = ii - 1 + im->xorigin;
3130 gfx_line_fit(im, &x, &y);
3131 cairo_move_to(im->cr, x, y);
3132 x = ii + im->xorigin;
3134 gfx_line_fit(im, &x, &y);
3135 cairo_line_to(im->cr, x, y);
3137 double x = ii - 1 + im->xorigin;
3139 im->gdes[i].p_data[ii - 1]);
3141 gfx_line_fit(im, &x, &y);
3142 cairo_move_to(im->cr, x, y);
3143 x = ii + im->xorigin;
3145 gfx_line_fit(im, &x, &y);
3146 cairo_line_to(im->cr, x, y);
3150 double x1 = ii + im->xorigin;
3151 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3153 if (im->slopemode == 0
3154 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3155 double x = ii - 1 + im->xorigin;
3158 gfx_line_fit(im, &x, &y);
3159 cairo_line_to(im->cr, x, y);
3162 gfx_line_fit(im, &x1, &y1);
3163 cairo_line_to(im->cr, x1, y1);
3167 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3168 im->gdes[i].col.green,
3169 im->gdes[i].col.blue,
3170 im->gdes[i].col.alpha);
3171 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3172 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3173 cairo_stroke(im->cr);
3174 cairo_restore(im->cr);
3177 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3178 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3179 double *backY = malloc(sizeof(double) * im->xsize * 2);
3180 double *backX = malloc(sizeof(double) * im->xsize * 2);
3183 for (ii = 0; ii <= im->xsize; ii++) {
3186 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3191 && AlmostEqual2sComplement(foreY[lastI],
3193 && AlmostEqual2sComplement(foreY[lastI],
3201 foreX[cntI], foreY[cntI],
3203 while (cntI < idxI) {
3208 AlmostEqual2sComplement(foreY[lastI],
3211 AlmostEqual2sComplement(foreY[lastI],
3216 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3218 gfx_add_point(im, backX[idxI], backY[idxI]);
3224 AlmostEqual2sComplement(backY[lastI],
3227 AlmostEqual2sComplement(backY[lastI],
3232 gfx_add_point(im, backX[idxI], backY[idxI]);
3242 if (ii == im->xsize)
3245 if (im->slopemode == 0 && ii == 0) {
3248 if (isnan(im->gdes[i].p_data[ii])) {
3252 ytop = ytr(im, im->gdes[i].p_data[ii]);
3253 if (lastgdes && im->gdes[i].stack) {
3254 ybase = ytr(im, lastgdes->p_data[ii]);
3256 ybase = ytr(im, areazero);
3258 if (ybase == ytop) {
3264 double extra = ytop;
3269 if (im->slopemode == 0) {
3270 backY[++idxI] = ybase - 0.2;
3271 backX[idxI] = ii + im->xorigin - 1;
3272 foreY[idxI] = ytop + 0.2;
3273 foreX[idxI] = ii + im->xorigin - 1;
3275 backY[++idxI] = ybase - 0.2;
3276 backX[idxI] = ii + im->xorigin;
3277 foreY[idxI] = ytop + 0.2;
3278 foreX[idxI] = ii + im->xorigin;
3280 /* close up any remaining area */
3285 } /* else GF_LINE */
3287 /* if color != 0x0 */
3288 /* make sure we do not run into trouble when stacking on NaN */
3289 for (ii = 0; ii < im->xsize; ii++) {
3290 if (isnan(im->gdes[i].p_data[ii])) {
3291 if (lastgdes && (im->gdes[i].stack)) {
3292 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3294 im->gdes[i].p_data[ii] = areazero;
3298 lastgdes = &(im->gdes[i]);
3302 ("STACK should already be turned into LINE or AREA here");
3309 /* grid_paint also does the text */
3310 if (!(im->extra_flags & ONLY_GRAPH))
3314 if (!(im->extra_flags & ONLY_GRAPH))
3317 /* the RULES are the last thing to paint ... */
3318 for (i = 0; i < im->gdes_c; i++) {
3320 switch (im->gdes[i].gf) {
3322 if (im->gdes[i].yrule >= im->minval
3323 && im->gdes[i].yrule <= im->maxval)
3325 im->xorigin, ytr(im, im->gdes[i].yrule),
3326 im->xorigin + im->xsize, ytr(im,
3328 1.0, im->gdes[i].col);
3331 if (im->gdes[i].xrule >= im->start
3332 && im->gdes[i].xrule <= im->end)
3334 xtr(im, im->gdes[i].xrule), im->yorigin,
3335 xtr(im, im->gdes[i].xrule),
3336 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3344 switch (im->imgformat) {
3347 cairo_status_t status;
3349 if (strlen(im->graphfile) == 0) {
3351 cairo_surface_write_to_png_stream(im->surface,
3352 &cairo_copy_to_buffer, im);
3353 } else if (strcmp(im->graphfile, "-") == 0) {
3355 cairo_surface_write_to_png_stream(im->surface,
3356 &cairo_write_func_filehandle,
3359 status = cairo_surface_write_to_png(im->surface, im->graphfile);
3362 if (status != CAIRO_STATUS_SUCCESS) {
3363 rrd_set_error("Could not save png to '%s'", im->graphfile);
3369 if (strlen(im->graphfile)) {
3370 cairo_show_page(im->cr);
3372 cairo_surface_finish(im->surface);
3380 /*****************************************************
3382 *****************************************************/
3389 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3390 * sizeof(graph_desc_t))) ==
3392 rrd_set_error("realloc graph_descs");
3397 im->gdes[im->gdes_c - 1].step = im->step;
3398 im->gdes[im->gdes_c - 1].step_orig = im->step;
3399 im->gdes[im->gdes_c - 1].stack = 0;
3400 im->gdes[im->gdes_c - 1].linewidth = 0;
3401 im->gdes[im->gdes_c - 1].debug = 0;
3402 im->gdes[im->gdes_c - 1].start = im->start;
3403 im->gdes[im->gdes_c - 1].start_orig = im->start;
3404 im->gdes[im->gdes_c - 1].end = im->end;
3405 im->gdes[im->gdes_c - 1].end_orig = im->end;
3406 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3407 im->gdes[im->gdes_c - 1].data = NULL;
3408 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3409 im->gdes[im->gdes_c - 1].data_first = 0;
3410 im->gdes[im->gdes_c - 1].p_data = NULL;
3411 im->gdes[im->gdes_c - 1].rpnp = NULL;
3412 im->gdes[im->gdes_c - 1].shift = 0.0;
3413 im->gdes[im->gdes_c - 1].col.red = 0.0;
3414 im->gdes[im->gdes_c - 1].col.green = 0.0;
3415 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3416 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3417 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3418 im->gdes[im->gdes_c - 1].format[0] = '\0';
3419 im->gdes[im->gdes_c - 1].strftm = 0;
3420 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3421 im->gdes[im->gdes_c - 1].ds = -1;
3422 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3423 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3424 im->gdes[im->gdes_c - 1].p_data = NULL;
3425 im->gdes[im->gdes_c - 1].yrule = DNAN;
3426 im->gdes[im->gdes_c - 1].xrule = 0;
3430 /* copies input untill the first unescaped colon is found
3431 or until input ends. backslashes have to be escaped as well */
3433 const char *const input,
3439 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3440 if (input[inp] == '\\' &&
3441 input[inp + 1] != '\0' &&
3442 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3443 output[outp++] = input[++inp];
3445 output[outp++] = input[inp];
3448 output[outp] = '\0';
3452 /* Some surgery done on this function, it became ridiculously big.
3454 ** - initializing now in rrd_graph_init()
3455 ** - options parsing now in rrd_graph_options()
3456 ** - script parsing now in rrd_graph_script()
3470 rrd_graph_init(&im);
3472 /* a dummy surface so that we can measure text sizes for placements */
3473 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3474 im.cr = cairo_create(im.surface);
3475 im.graphhandle = stream;
3477 rrd_graph_options(argc, argv, &im);
3478 if (rrd_test_error()) {
3483 if (optind >= argc) {
3484 rrd_set_error("missing filename");
3488 if (strlen(argv[optind]) >= MAXPATH) {
3489 rrd_set_error("filename (including path) too long");
3494 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3495 im.graphfile[MAXPATH - 1] = '\0';
3497 rrd_graph_script(argc, argv, &im, 1);
3498 if (rrd_test_error()) {
3503 /* Everything is now read and the actual work can start */
3506 if (graph_paint(&im, prdata) == -1) {
3511 /* The image is generated and needs to be output.
3512 ** Also, if needed, print a line with information about the image.
3523 /* maybe prdata is not allocated yet ... lets do it now */
3524 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3525 rrd_set_error("malloc imginfo");
3530 malloc((strlen(im.imginfo) + 200 +
3531 strlen(im.graphfile)) * sizeof(char)))
3533 rrd_set_error("malloc imginfo");
3536 filename = im.graphfile + strlen(im.graphfile);
3537 while (filename > im.graphfile) {
3538 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3543 sprintf((*prdata)[0], im.imginfo, filename,
3544 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3550 /* a simplified version of the above that just creates the graph in memory
3551 and returns a pointer to it. */
3553 unsigned char *rrd_graph_in_memory(
3565 rrd_graph_init(&im);
3567 /* a dummy surface so that we can measure text sizes for placements */
3568 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3569 im.cr = cairo_create(im.surface);
3571 rrd_graph_options(argc, argv, &im);
3572 if (rrd_test_error()) {
3577 rrd_graph_script(argc, argv, &im, 1);
3578 if (rrd_test_error()) {
3583 /* Everything is now read and the actual work can start */
3585 /* by not assigning a name to im.graphfile data will be written to
3586 newly allocated memory on im.rendered_image ... */
3589 if (graph_paint(&im, prdata) == -1) {
3598 *img_size = im.rendered_image_size;
3601 return im.rendered_image;
3604 void rrd_graph_init(
3612 #ifdef HAVE_SETLOCALE
3613 setlocale(LC_TIME, "");
3614 #ifdef HAVE_MBSTOWCS
3615 setlocale(LC_CTYPE, "");
3621 im->xlab_user.minsec = -1;
3626 im->rendered_image_size = 0;
3627 im->rendered_image = NULL;
3629 im->ylegend[0] = '\0';
3630 im->title[0] = '\0';
3631 im->watermark[0] = '\0';
3634 im->unitsexponent = 9999;
3635 im->unitslength = 6;
3636 im->forceleftspace = 0;
3638 im->viewfactor = 1.0;
3639 im->imgformat = IF_PNG;
3640 im->graphfile[0] = '\0';
3643 im->extra_flags = 0;
3649 im->logarithmic = 0;
3650 im->ygridstep = DNAN;
3651 im->draw_x_grid = 1;
3652 im->draw_y_grid = 1;
3657 im->grid_dash_on = 1;
3658 im->grid_dash_off = 1;
3659 im->tabwidth = 40.0;
3661 im->font_options = cairo_font_options_create();
3662 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3664 cairo_font_options_set_hint_style(im->font_options,
3665 CAIRO_HINT_STYLE_FULL);
3666 cairo_font_options_set_hint_metrics(im->font_options,
3667 CAIRO_HINT_METRICS_ON);
3668 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3671 for (i = 0; i < DIM(graph_col); i++)
3672 im->graph_col[i] = graph_col[i];
3674 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3677 char rrd_win_default_font[1000];
3679 windir = getenv("windir");
3680 /* %windir% is something like D:\windows or C:\winnt */
3681 if (windir != NULL) {
3682 strncpy(rrd_win_default_font, windir, 500);
3683 rrd_win_default_font[500] = '\0';
3684 strcat(rrd_win_default_font, "\\fonts\\");
3685 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3686 for (i = 0; i < DIM(text_prop); i++) {
3687 strncpy(text_prop[i].font, rrd_win_default_font,
3688 sizeof(text_prop[i].font) - 1);
3689 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3697 deffont = getenv("RRD_DEFAULT_FONT");
3698 if (deffont != NULL) {
3699 for (i = 0; i < DIM(text_prop); i++) {
3700 strncpy(text_prop[i].font, deffont,
3701 sizeof(text_prop[i].font) - 1);
3702 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3706 for (i = 0; i < DIM(text_prop); i++) {
3707 im->text_prop[i].size = text_prop[i].size;
3708 strcpy(im->text_prop[i].font, text_prop[i].font);
3712 void rrd_graph_options(
3718 char *parsetime_error = NULL;
3719 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3720 time_t start_tmp = 0, end_tmp = 0;
3722 struct rrd_time_value start_tv, end_tv;
3723 long unsigned int color;
3724 char *old_locale = "";
3726 /* defines for long options without a short equivalent. should be bytes,
3727 and may not collide with (the ASCII value of) short options */
3728 #define LONGOPT_UNITS_SI 255
3729 struct option long_options[] = {
3730 {"start", required_argument, 0, 's'},
3731 {"end", required_argument, 0, 'e'},
3732 {"x-grid", required_argument, 0, 'x'},
3733 {"y-grid", required_argument, 0, 'y'},
3734 {"vertical-label", required_argument, 0, 'v'},
3735 {"width", required_argument, 0, 'w'},
3736 {"height", required_argument, 0, 'h'},
3737 {"full-size-mode", no_argument, 0, 'D'},
3738 {"interlaced", no_argument, 0, 'i'},
3739 {"upper-limit", required_argument, 0, 'u'},
3740 {"lower-limit", required_argument, 0, 'l'},
3741 {"rigid", no_argument, 0, 'r'},
3742 {"base", required_argument, 0, 'b'},
3743 {"logarithmic", no_argument, 0, 'o'},
3744 {"color", required_argument, 0, 'c'},
3745 {"font", required_argument, 0, 'n'},
3746 {"title", required_argument, 0, 't'},
3747 {"imginfo", required_argument, 0, 'f'},
3748 {"imgformat", required_argument, 0, 'a'},
3749 {"lazy", no_argument, 0, 'z'},
3750 {"zoom", required_argument, 0, 'm'},
3751 {"no-legend", no_argument, 0, 'g'},
3752 {"force-rules-legend", no_argument, 0, 'F'},
3753 {"only-graph", no_argument, 0, 'j'},
3754 {"alt-y-grid", no_argument, 0, 'Y'},
3755 {"no-minor", no_argument, 0, 'I'},
3756 {"slope-mode", no_argument, 0, 'E'},
3757 {"alt-autoscale", no_argument, 0, 'A'},
3758 {"alt-autoscale-min", no_argument, 0, 'J'},
3759 {"alt-autoscale-max", no_argument, 0, 'M'},
3760 {"no-gridfit", no_argument, 0, 'N'},
3761 {"units-exponent", required_argument, 0, 'X'},
3762 {"units-length", required_argument, 0, 'L'},
3763 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3764 {"step", required_argument, 0, 'S'},
3765 {"tabwidth", required_argument, 0, 'T'},
3766 {"font-render-mode", required_argument, 0, 'R'},
3767 {"graph-render-mode", required_argument, 0, 'G'},
3768 {"font-smoothing-threshold", required_argument, 0, 'B'},
3769 {"watermark", required_argument, 0, 'W'},
3770 {"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 */
3775 opterr = 0; /* initialize getopt */
3777 parsetime("end-24h", &start_tv);
3778 parsetime("now", &end_tv);
3781 int option_index = 0;
3783 int col_start, col_end;
3785 opt = getopt_long(argc, argv,
3786 "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:",
3787 long_options, &option_index);
3794 im->extra_flags |= NOMINOR;
3797 im->extra_flags |= ALTYGRID;
3800 im->extra_flags |= ALTAUTOSCALE;
3803 im->extra_flags |= ALTAUTOSCALE_MIN;
3806 im->extra_flags |= ALTAUTOSCALE_MAX;
3809 im->extra_flags |= ONLY_GRAPH;
3812 im->extra_flags |= NOLEGEND;
3815 im->extra_flags |= FORCE_RULES_LEGEND;
3817 case LONGOPT_UNITS_SI:
3818 if (im->extra_flags & FORCE_UNITS) {
3819 rrd_set_error("--units can only be used once!");
3820 setlocale(LC_NUMERIC, old_locale);
3823 if (strcmp(optarg, "si") == 0)
3824 im->extra_flags |= FORCE_UNITS_SI;
3826 rrd_set_error("invalid argument for --units: %s", optarg);
3831 im->unitsexponent = atoi(optarg);
3834 im->unitslength = atoi(optarg);
3835 im->forceleftspace = 1;
3838 old_locale = setlocale(LC_NUMERIC, "C");
3839 im->tabwidth = atof(optarg);
3840 setlocale(LC_NUMERIC, old_locale);
3843 old_locale = setlocale(LC_NUMERIC, "C");
3844 im->step = atoi(optarg);
3845 setlocale(LC_NUMERIC, old_locale);
3851 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3852 rrd_set_error("start time: %s", parsetime_error);
3857 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3858 rrd_set_error("end time: %s", parsetime_error);
3863 if (strcmp(optarg, "none") == 0) {
3864 im->draw_x_grid = 0;
3869 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3871 &im->xlab_user.gridst,
3873 &im->xlab_user.mgridst,
3875 &im->xlab_user.labst,
3876 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3877 strncpy(im->xlab_form, optarg + stroff,
3878 sizeof(im->xlab_form) - 1);
3879 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3880 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3881 rrd_set_error("unknown keyword %s", scan_gtm);
3883 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3885 rrd_set_error("unknown keyword %s", scan_mtm);
3887 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3889 rrd_set_error("unknown keyword %s", scan_ltm);
3892 im->xlab_user.minsec = 1;
3893 im->xlab_user.stst = im->xlab_form;
3895 rrd_set_error("invalid x-grid format");
3901 if (strcmp(optarg, "none") == 0) {
3902 im->draw_y_grid = 0;
3905 old_locale = setlocale(LC_NUMERIC, "C");
3906 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3907 setlocale(LC_NUMERIC, old_locale);
3908 if (im->ygridstep <= 0) {
3909 rrd_set_error("grid step must be > 0");
3911 } else if (im->ylabfact < 1) {
3912 rrd_set_error("label factor must be > 0");
3916 setlocale(LC_NUMERIC, old_locale);
3917 rrd_set_error("invalid y-grid format");
3922 strncpy(im->ylegend, optarg, 150);
3923 im->ylegend[150] = '\0';
3926 old_locale = setlocale(LC_NUMERIC, "C");
3927 im->maxval = atof(optarg);
3928 setlocale(LC_NUMERIC, old_locale);
3931 old_locale = setlocale(LC_NUMERIC, "C");
3932 im->minval = atof(optarg);
3933 setlocale(LC_NUMERIC, old_locale);
3936 im->base = atol(optarg);
3937 if (im->base != 1024 && im->base != 1000) {
3939 ("the only sensible value for base apart from 1000 is 1024");
3944 long_tmp = atol(optarg);
3945 if (long_tmp < 10) {
3946 rrd_set_error("width below 10 pixels");
3949 im->xsize = long_tmp;
3952 long_tmp = atol(optarg);
3953 if (long_tmp < 10) {
3954 rrd_set_error("height below 10 pixels");
3957 im->ysize = long_tmp;
3960 im->extra_flags |= FULL_SIZE_MODE;
3963 /* interlaced png not supported at the moment */
3969 im->imginfo = optarg;
3972 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3973 rrd_set_error("unsupported graphics format '%s'", optarg);
3985 im->logarithmic = 1;
3989 "%10[A-Z]#%n%8lx%n",
3990 col_nam, &col_start, &color, &col_end) == 2) {
3992 int col_len = col_end - col_start;
3996 color = (((color & 0xF00) * 0x110000) |
3997 ((color & 0x0F0) * 0x011000) |
3998 ((color & 0x00F) * 0x001100) | 0x000000FF);
4001 color = (((color & 0xF000) * 0x11000) |
4002 ((color & 0x0F00) * 0x01100) |
4003 ((color & 0x00F0) * 0x00110) |
4004 ((color & 0x000F) * 0x00011)
4008 color = (color << 8) + 0xff /* shift left by 8 */ ;
4013 rrd_set_error("the color format is #RRGGBB[AA]");
4016 if ((ci = grc_conv(col_nam)) != -1) {
4017 im->graph_col[ci] = gfx_hex_to_col(color);
4019 rrd_set_error("invalid color name '%s'", col_nam);
4023 rrd_set_error("invalid color def format");
4030 char font[1024] = "";
4032 old_locale = setlocale(LC_NUMERIC, "C");
4033 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
4034 int sindex, propidx;
4036 setlocale(LC_NUMERIC, old_locale);
4037 if ((sindex = text_prop_conv(prop)) != -1) {
4038 for (propidx = sindex; propidx < TEXT_PROP_LAST;
4041 im->text_prop[propidx].size = size;
4043 if (strlen(font) > 0) {
4044 strcpy(im->text_prop[propidx].font, font);
4046 if (propidx == sindex && sindex != 0)
4050 rrd_set_error("invalid fonttag '%s'", prop);
4054 setlocale(LC_NUMERIC, old_locale);
4055 rrd_set_error("invalid text property format");
4061 old_locale = setlocale(LC_NUMERIC, "C");
4062 im->zoom = atof(optarg);
4063 setlocale(LC_NUMERIC, old_locale);
4064 if (im->zoom <= 0.0) {
4065 rrd_set_error("zoom factor must be > 0");
4070 strncpy(im->title, optarg, 150);
4071 im->title[150] = '\0';
4075 if (strcmp(optarg, "normal") == 0) {
4076 cairo_font_options_set_antialias(im->font_options,
4077 CAIRO_ANTIALIAS_GRAY);
4078 cairo_font_options_set_hint_style(im->font_options,
4079 CAIRO_HINT_STYLE_FULL);
4080 } else if (strcmp(optarg, "light") == 0) {
4081 cairo_font_options_set_antialias(im->font_options,
4082 CAIRO_ANTIALIAS_GRAY);
4083 cairo_font_options_set_hint_style(im->font_options,
4084 CAIRO_HINT_STYLE_SLIGHT);
4085 } else if (strcmp(optarg, "mono") == 0) {
4086 cairo_font_options_set_antialias(im->font_options,
4087 CAIRO_ANTIALIAS_NONE);
4088 cairo_font_options_set_hint_style(im->font_options,
4089 CAIRO_HINT_STYLE_FULL);
4091 rrd_set_error("unknown font-render-mode '%s'", optarg);
4096 if (strcmp(optarg, "normal") == 0)
4097 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4098 else if (strcmp(optarg, "mono") == 0)
4099 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4101 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4106 /* not supported curently */
4110 strncpy(im->watermark, optarg, 100);
4111 im->watermark[99] = '\0';
4116 rrd_set_error("unknown option '%c'", optopt);
4118 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4123 if (im->logarithmic == 1 && im->minval <= 0) {
4125 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4129 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4130 /* error string is set in parsetime.c */
4134 if (start_tmp < 3600 * 24 * 365 * 10) {
4135 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4140 if (end_tmp < start_tmp) {
4141 rrd_set_error("start (%ld) should be less than end (%ld)",
4142 start_tmp, end_tmp);
4146 im->start = start_tmp;
4148 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4151 int rrd_graph_color(
4158 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4160 color = strstr(var, "#");
4161 if (color == NULL) {
4162 if (optional == 0) {
4163 rrd_set_error("Found no color in %s", err);
4170 long unsigned int col;
4172 rest = strstr(color, ":");
4180 sscanf(color, "#%6lx%n", &col, &n);
4181 col = (col << 8) + 0xff /* shift left by 8 */ ;
4183 rrd_set_error("Color problem in %s", err);
4186 sscanf(color, "#%8lx%n", &col, &n);
4190 rrd_set_error("Color problem in %s", err);
4192 if (rrd_test_error())
4194 gdp->col = gfx_hex_to_col(col);
4207 while (*ptr != '\0')
4208 if (*ptr++ == '%') {
4210 /* line cannot end with percent char */
4214 /* '%s', '%S' and '%%' are allowed */
4215 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4218 /* %c is allowed (but use only with vdef!) */
4219 else if (*ptr == 'c') {
4224 /* or else '% 6.2lf' and such are allowed */
4226 /* optional padding character */
4227 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4230 /* This should take care of 'm.n' with all three optional */
4231 while (*ptr >= '0' && *ptr <= '9')
4235 while (*ptr >= '0' && *ptr <= '9')
4238 /* Either 'le', 'lf' or 'lg' must follow here */
4241 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4254 struct graph_desc_t *gdes,
4255 const char *const str)
4257 /* A VDEF currently is either "func" or "param,func"
4258 * so the parsing is rather simple. Change if needed.
4266 old_locale = setlocale(LC_NUMERIC, "C");
4267 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4268 setlocale(LC_NUMERIC, old_locale);
4269 if (n == (int) strlen(str)) { /* matched */
4273 sscanf(str, "%29[A-Z]%n", func, &n);
4274 if (n == (int) strlen(str)) { /* matched */
4277 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4282 if (!strcmp("PERCENT", func))
4283 gdes->vf.op = VDEF_PERCENT;
4284 else if (!strcmp("MAXIMUM", func))
4285 gdes->vf.op = VDEF_MAXIMUM;
4286 else if (!strcmp("AVERAGE", func))
4287 gdes->vf.op = VDEF_AVERAGE;
4288 else if (!strcmp("MINIMUM", func))
4289 gdes->vf.op = VDEF_MINIMUM;
4290 else if (!strcmp("TOTAL", func))
4291 gdes->vf.op = VDEF_TOTAL;
4292 else if (!strcmp("FIRST", func))
4293 gdes->vf.op = VDEF_FIRST;
4294 else if (!strcmp("LAST", func))
4295 gdes->vf.op = VDEF_LAST;
4296 else if (!strcmp("LSLSLOPE", func))
4297 gdes->vf.op = VDEF_LSLSLOPE;
4298 else if (!strcmp("LSLINT", func))
4299 gdes->vf.op = VDEF_LSLINT;
4300 else if (!strcmp("LSLCORREL", func))
4301 gdes->vf.op = VDEF_LSLCORREL;
4303 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4308 switch (gdes->vf.op) {
4310 if (isnan(param)) { /* no parameter given */
4311 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4315 if (param >= 0.0 && param <= 100.0) {
4316 gdes->vf.param = param;
4317 gdes->vf.val = DNAN; /* undefined */
4318 gdes->vf.when = 0; /* undefined */
4320 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4333 case VDEF_LSLCORREL:
4335 gdes->vf.param = DNAN;
4336 gdes->vf.val = DNAN;
4339 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4353 graph_desc_t *src, *dst;
4357 dst = &im->gdes[gdi];
4358 src = &im->gdes[dst->vidx];
4359 data = src->data + src->ds;
4360 steps = (src->end - src->start) / src->step;
4363 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4367 switch (dst->vf.op) {
4373 if ((array = malloc(steps * sizeof(double))) == NULL) {
4374 rrd_set_error("malloc VDEV_PERCENT");
4377 for (step = 0; step < steps; step++) {
4378 array[step] = data[step * src->ds_cnt];
4380 qsort(array, step, sizeof(double), vdef_percent_compar);
4382 field = (steps - 1) * dst->vf.param / 100;
4383 dst->vf.val = array[field];
4384 dst->vf.when = 0; /* no time component */
4387 for (step = 0; step < steps; step++)
4388 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4389 step == field ? '*' : ' ');
4395 while (step != steps && isnan(data[step * src->ds_cnt]))
4397 if (step == steps) {
4401 dst->vf.val = data[step * src->ds_cnt];
4402 dst->vf.when = src->start + (step + 1) * src->step;
4404 while (step != steps) {
4405 if (finite(data[step * src->ds_cnt])) {
4406 if (data[step * src->ds_cnt] > dst->vf.val) {
4407 dst->vf.val = data[step * src->ds_cnt];
4408 dst->vf.when = src->start + (step + 1) * src->step;
4419 for (step = 0; step < steps; step++) {
4420 if (finite(data[step * src->ds_cnt])) {
4421 sum += data[step * src->ds_cnt];
4426 if (dst->vf.op == VDEF_TOTAL) {
4427 dst->vf.val = sum * src->step;
4428 dst->vf.when = 0; /* no time component */
4430 dst->vf.val = sum / cnt;
4431 dst->vf.when = 0; /* no time component */
4441 while (step != steps && isnan(data[step * src->ds_cnt]))
4443 if (step == steps) {
4447 dst->vf.val = data[step * src->ds_cnt];
4448 dst->vf.when = src->start + (step + 1) * src->step;
4450 while (step != steps) {
4451 if (finite(data[step * src->ds_cnt])) {
4452 if (data[step * src->ds_cnt] < dst->vf.val) {
4453 dst->vf.val = data[step * src->ds_cnt];
4454 dst->vf.when = src->start + (step + 1) * src->step;
4461 /* The time value returned here is one step before the
4462 * actual time value. This is the start of the first
4466 while (step != steps && isnan(data[step * src->ds_cnt]))
4468 if (step == steps) { /* all entries were NaN */
4472 dst->vf.val = data[step * src->ds_cnt];
4473 dst->vf.when = src->start + step * src->step;
4477 /* The time value returned here is the
4478 * actual time value. This is the end of the last
4482 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4484 if (step < 0) { /* all entries were NaN */
4488 dst->vf.val = data[step * src->ds_cnt];
4489 dst->vf.when = src->start + (step + 1) * src->step;
4494 case VDEF_LSLCORREL:{
4495 /* Bestfit line by linear least squares method */
4498 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4506 for (step = 0; step < steps; step++) {
4507 if (finite(data[step * src->ds_cnt])) {
4510 SUMxx += step * step;
4511 SUMxy += step * data[step * src->ds_cnt];
4512 SUMy += data[step * src->ds_cnt];
4513 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4517 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4518 y_intercept = (SUMy - slope * SUMx) / cnt;
4521 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4522 (SUMx * SUMx) / cnt) * (SUMyy -
4528 if (dst->vf.op == VDEF_LSLSLOPE) {
4529 dst->vf.val = slope;
4531 } else if (dst->vf.op == VDEF_LSLINT) {
4532 dst->vf.val = y_intercept;
4534 } else if (dst->vf.op == VDEF_LSLCORREL) {
4535 dst->vf.val = correl;
4549 /* NaN < -INF < finite_values < INF */
4550 int vdef_percent_compar(
4554 /* Equality is not returned; this doesn't hurt except
4555 * (maybe) for a little performance.
4558 /* First catch NaN values. They are smallest */
4559 if (isnan(*(double *) a))
4561 if (isnan(*(double *) b))
4564 /* NaN doesn't reach this part so INF and -INF are extremes.
4565 * The sign from isinf() is compatible with the sign we return
4567 if (isinf(*(double *) a))
4568 return isinf(*(double *) a);
4569 if (isinf(*(double *) b))
4570 return isinf(*(double *) b);
4572 /* If we reach this, both values must be finite */
4573 if (*(double *) a < *(double *) b)