1 /****************************************************************************
2 * RRDtool 1.3.1 Copyright by Tobi Oetiker, 1997-2008
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
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 "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
37 #endif
39 text_prop_t text_prop[] = {
40 {8.0, RRD_DEFAULT_FONT,NULL}
41 , /* default */
42 {9.0, RRD_DEFAULT_FONT,NULL}
43 , /* title */
44 {7.0, RRD_DEFAULT_FONT,NULL}
45 , /* axis */
46 {8.0, RRD_DEFAULT_FONT,NULL}
47 , /* unit */
48 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
49 ,
50 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
51 };
53 xlab_t xlab[] = {
54 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
55 ,
56 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
57 ,
58 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
59 ,
60 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
61 ,
62 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
63 ,
64 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
65 ,
66 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
67 ,
68 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
69 ,
70 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
71 ,
72 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
73 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
74 ,
75 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
76 ,
77 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
78 ,
79 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
80 ,
81 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
82 ,
83 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
84 "Week %V"}
85 ,
86 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
87 "%b"}
88 ,
89 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
90 "%b"}
91 ,
92 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
93 ,
94 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
95 365 * 24 * 3600, "%y"}
96 ,
97 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
98 };
100 /* sensible y label intervals ...*/
102 ylab_t ylab[] = {
103 {0.1, {1, 2, 5, 10}
104 }
105 ,
106 {0.2, {1, 5, 10, 20}
107 }
108 ,
109 {0.5, {1, 2, 4, 10}
110 }
111 ,
112 {1.0, {1, 2, 5, 10}
113 }
114 ,
115 {2.0, {1, 5, 10, 20}
116 }
117 ,
118 {5.0, {1, 2, 4, 10}
119 }
120 ,
121 {10.0, {1, 2, 5, 10}
122 }
123 ,
124 {20.0, {1, 5, 10, 20}
125 }
126 ,
127 {50.0, {1, 2, 4, 10}
128 }
129 ,
130 {100.0, {1, 2, 5, 10}
131 }
132 ,
133 {200.0, {1, 5, 10, 20}
134 }
135 ,
136 {500.0, {1, 2, 4, 10}
137 }
138 ,
139 {0.0, {0, 0, 0, 0}
140 }
141 };
144 gfx_color_t graph_col[] = /* default colors */
145 {
146 {1.00, 1.00, 1.00, 1.00}, /* canvas */
147 {0.95, 0.95, 0.95, 1.00}, /* background */
148 {0.81, 0.81, 0.81, 1.00}, /* shade A */
149 {0.62, 0.62, 0.62, 1.00}, /* shade B */
150 {0.56, 0.56, 0.56, 0.75}, /* grid */
151 {0.87, 0.31, 0.31, 0.60}, /* major grid */
152 {0.00, 0.00, 0.00, 1.00}, /* font */
153 {0.50, 0.12, 0.12, 1.00}, /* arrow */
154 {0.12, 0.12, 0.12, 1.00}, /* axis */
155 {0.00, 0.00, 0.00, 1.00} /* frame */
156 };
159 /* #define DEBUG */
161 #ifdef DEBUG
162 # define DPRINT(x) (void)(printf x, printf("\n"))
163 #else
164 # define DPRINT(x)
165 #endif
168 /* initialize with xtr(im,0); */
169 int xtr(
170 image_desc_t *im,
171 time_t mytime)
172 {
173 static double pixie;
175 if (mytime == 0) {
176 pixie = (double) im->xsize / (double) (im->end - im->start);
177 return im->xorigin;
178 }
179 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
180 }
182 /* translate data values into y coordinates */
183 double ytr(
184 image_desc_t *im,
185 double value)
186 {
187 static double pixie;
188 double yval;
190 if (isnan(value)) {
191 if (!im->logarithmic)
192 pixie = (double) im->ysize / (im->maxval - im->minval);
193 else
194 pixie =
195 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
196 yval = im->yorigin;
197 } else if (!im->logarithmic) {
198 yval = im->yorigin - pixie * (value - im->minval);
199 } else {
200 if (value < im->minval) {
201 yval = im->yorigin;
202 } else {
203 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
204 }
205 }
206 return yval;
207 }
211 /* conversion function for symbolic entry names */
214 #define conv_if(VV,VVV) \
215 if (strcmp(#VV, string) == 0) return VVV ;
217 enum gf_en gf_conv(
218 char *string)
219 {
221 conv_if(PRINT, GF_PRINT);
222 conv_if(GPRINT, GF_GPRINT);
223 conv_if(COMMENT, GF_COMMENT);
224 conv_if(HRULE, GF_HRULE);
225 conv_if(VRULE, GF_VRULE);
226 conv_if(LINE, GF_LINE);
227 conv_if(AREA, GF_AREA);
228 conv_if(STACK, GF_STACK);
229 conv_if(TICK, GF_TICK);
230 conv_if(TEXTALIGN, GF_TEXTALIGN);
231 conv_if(DEF, GF_DEF);
232 conv_if(CDEF, GF_CDEF);
233 conv_if(VDEF, GF_VDEF);
234 conv_if(XPORT, GF_XPORT);
235 conv_if(SHIFT, GF_SHIFT);
237 return (-1);
238 }
240 enum gfx_if_en if_conv(
241 char *string)
242 {
244 conv_if(PNG, IF_PNG);
245 conv_if(SVG, IF_SVG);
246 conv_if(EPS, IF_EPS);
247 conv_if(PDF, IF_PDF);
249 return (-1);
250 }
252 enum tmt_en tmt_conv(
253 char *string)
254 {
256 conv_if(SECOND, TMT_SECOND);
257 conv_if(MINUTE, TMT_MINUTE);
258 conv_if(HOUR, TMT_HOUR);
259 conv_if(DAY, TMT_DAY);
260 conv_if(WEEK, TMT_WEEK);
261 conv_if(MONTH, TMT_MONTH);
262 conv_if(YEAR, TMT_YEAR);
263 return (-1);
264 }
266 enum grc_en grc_conv(
267 char *string)
268 {
270 conv_if(BACK, GRC_BACK);
271 conv_if(CANVAS, GRC_CANVAS);
272 conv_if(SHADEA, GRC_SHADEA);
273 conv_if(SHADEB, GRC_SHADEB);
274 conv_if(GRID, GRC_GRID);
275 conv_if(MGRID, GRC_MGRID);
276 conv_if(FONT, GRC_FONT);
277 conv_if(ARROW, GRC_ARROW);
278 conv_if(AXIS, GRC_AXIS);
279 conv_if(FRAME, GRC_FRAME);
281 return -1;
282 }
284 enum text_prop_en text_prop_conv(
285 char *string)
286 {
288 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
289 conv_if(TITLE, TEXT_PROP_TITLE);
290 conv_if(AXIS, TEXT_PROP_AXIS);
291 conv_if(UNIT, TEXT_PROP_UNIT);
292 conv_if(LEGEND, TEXT_PROP_LEGEND);
293 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
294 return -1;
295 }
298 #undef conv_if
300 int im_free(
301 image_desc_t *im)
302 {
303 unsigned long i, ii;
304 cairo_status_t status = 0;
306 if (im == NULL)
307 return 0;
308 for (i = 0; i < (unsigned) im->gdes_c; i++) {
309 if (im->gdes[i].data_first) {
310 /* careful here, because a single pointer can occur several times */
311 free(im->gdes[i].data);
312 if (im->gdes[i].ds_namv) {
313 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
314 free(im->gdes[i].ds_namv[ii]);
315 free(im->gdes[i].ds_namv);
316 }
317 }
318 /* free allocated memory used for dashed lines */
319 if (im->gdes[i].p_dashes != NULL)
320 free(im->gdes[i].p_dashes);
322 free(im->gdes[i].p_data);
323 free(im->gdes[i].rpnp);
324 }
325 free(im->gdes);
326 if (im->font_options)
327 cairo_font_options_destroy(im->font_options);
329 if (im->cr) {
330 status = cairo_status(im->cr);
331 cairo_destroy(im->cr);
332 }
333 if (im->rendered_image) {
334 free(im->rendered_image);
335 }
337 if (im->layout) {
338 g_object_unref (im->layout);
339 }
341 if (im->surface)
342 cairo_surface_destroy(im->surface);
344 if (status)
345 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
346 cairo_status_to_string(status));
348 return 0;
349 }
351 /* find SI magnitude symbol for the given number*/
352 void auto_scale(
353 image_desc_t *im, /* image description */
354 double *value,
355 char **symb_ptr,
356 double *magfact)
357 {
359 char *symbol[] = { "a", /* 10e-18 Atto */
360 "f", /* 10e-15 Femto */
361 "p", /* 10e-12 Pico */
362 "n", /* 10e-9 Nano */
363 "u", /* 10e-6 Micro */
364 "m", /* 10e-3 Milli */
365 " ", /* Base */
366 "k", /* 10e3 Kilo */
367 "M", /* 10e6 Mega */
368 "G", /* 10e9 Giga */
369 "T", /* 10e12 Tera */
370 "P", /* 10e15 Peta */
371 "E"
372 }; /* 10e18 Exa */
374 int symbcenter = 6;
375 int sindex;
377 if (*value == 0.0 || isnan(*value)) {
378 sindex = 0;
379 *magfact = 1.0;
380 } else {
381 sindex = floor(log(fabs(*value)) / log((double) im->base));
382 *magfact = pow((double) im->base, (double) sindex);
383 (*value) /= (*magfact);
384 }
385 if (sindex <= symbcenter && sindex >= -symbcenter) {
386 (*symb_ptr) = symbol[sindex + symbcenter];
387 } else {
388 (*symb_ptr) = "?";
389 }
390 }
393 static char si_symbol[] = {
394 'a', /* 10e-18 Atto */
395 'f', /* 10e-15 Femto */
396 'p', /* 10e-12 Pico */
397 'n', /* 10e-9 Nano */
398 'u', /* 10e-6 Micro */
399 'm', /* 10e-3 Milli */
400 ' ', /* Base */
401 'k', /* 10e3 Kilo */
402 'M', /* 10e6 Mega */
403 'G', /* 10e9 Giga */
404 'T', /* 10e12 Tera */
405 'P', /* 10e15 Peta */
406 'E', /* 10e18 Exa */
407 };
408 static const int si_symbcenter = 6;
410 /* find SI magnitude symbol for the numbers on the y-axis*/
411 void si_unit(
412 image_desc_t *im /* image description */
413 )
414 {
416 double digits, viewdigits = 0;
418 digits =
419 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
420 log((double) im->base));
422 if (im->unitsexponent != 9999) {
423 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
424 viewdigits = floor(im->unitsexponent / 3);
425 } else {
426 viewdigits = digits;
427 }
429 im->magfact = pow((double) im->base, digits);
431 #ifdef DEBUG
432 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
433 #endif
435 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
437 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
438 ((viewdigits + si_symbcenter) >= 0))
439 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
440 else
441 im->symbol = '?';
442 }
444 /* move min and max values around to become sensible */
446 void expand_range(
447 image_desc_t *im)
448 {
449 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
450 600.0, 500.0, 400.0, 300.0, 250.0,
451 200.0, 125.0, 100.0, 90.0, 80.0,
452 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
453 25.0, 20.0, 10.0, 9.0, 8.0,
454 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
455 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
456 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
457 };
459 double scaled_min, scaled_max;
460 double adj;
461 int i;
465 #ifdef DEBUG
466 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
467 im->minval, im->maxval, im->magfact);
468 #endif
470 if (isnan(im->ygridstep)) {
471 if (im->extra_flags & ALTAUTOSCALE) {
472 /* measure the amplitude of the function. Make sure that
473 graph boundaries are slightly higher then max/min vals
474 so we can see amplitude on the graph */
475 double delt, fact;
477 delt = im->maxval - im->minval;
478 adj = delt * 0.1;
479 fact = 2.0 * pow(10.0,
480 floor(log10
481 (max(fabs(im->minval), fabs(im->maxval)) /
482 im->magfact)) - 2);
483 if (delt < fact) {
484 adj = (fact - delt) * 0.55;
485 #ifdef DEBUG
486 printf
487 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
488 im->minval, im->maxval, delt, fact, adj);
489 #endif
490 }
491 im->minval -= adj;
492 im->maxval += adj;
493 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
494 /* measure the amplitude of the function. Make sure that
495 graph boundaries are slightly lower than min vals
496 so we can see amplitude on the graph */
497 adj = (im->maxval - im->minval) * 0.1;
498 im->minval -= adj;
499 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
500 /* measure the amplitude of the function. Make sure that
501 graph boundaries are slightly higher than max vals
502 so we can see amplitude on the graph */
503 adj = (im->maxval - im->minval) * 0.1;
504 im->maxval += adj;
505 } else {
506 scaled_min = im->minval / im->magfact;
507 scaled_max = im->maxval / im->magfact;
509 for (i = 1; sensiblevalues[i] > 0; i++) {
510 if (sensiblevalues[i - 1] >= scaled_min &&
511 sensiblevalues[i] <= scaled_min)
512 im->minval = sensiblevalues[i] * (im->magfact);
514 if (-sensiblevalues[i - 1] <= scaled_min &&
515 -sensiblevalues[i] >= scaled_min)
516 im->minval = -sensiblevalues[i - 1] * (im->magfact);
518 if (sensiblevalues[i - 1] >= scaled_max &&
519 sensiblevalues[i] <= scaled_max)
520 im->maxval = sensiblevalues[i - 1] * (im->magfact);
522 if (-sensiblevalues[i - 1] <= scaled_max &&
523 -sensiblevalues[i] >= scaled_max)
524 im->maxval = -sensiblevalues[i] * (im->magfact);
525 }
526 }
527 } else {
528 /* adjust min and max to the grid definition if there is one */
529 im->minval = (double) im->ylabfact * im->ygridstep *
530 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
531 im->maxval = (double) im->ylabfact * im->ygridstep *
532 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
533 }
535 #ifdef DEBUG
536 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
537 im->minval, im->maxval, im->magfact);
538 #endif
539 }
542 void apply_gridfit(
543 image_desc_t *im)
544 {
545 if (isnan(im->minval) || isnan(im->maxval))
546 return;
547 ytr(im, DNAN);
548 if (im->logarithmic) {
549 double ya, yb, ypix, ypixfrac;
550 double log10_range = log10(im->maxval) - log10(im->minval);
552 ya = pow((double) 10, floor(log10(im->minval)));
553 while (ya < im->minval)
554 ya *= 10;
555 if (ya > im->maxval)
556 return; /* don't have y=10^x gridline */
557 yb = ya * 10;
558 if (yb <= im->maxval) {
559 /* we have at least 2 y=10^x gridlines.
560 Make sure distance between them in pixels
561 are an integer by expanding im->maxval */
562 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
563 double factor = y_pixel_delta / floor(y_pixel_delta);
564 double new_log10_range = factor * log10_range;
565 double new_ymax_log10 = log10(im->minval) + new_log10_range;
567 im->maxval = pow(10, new_ymax_log10);
568 ytr(im, DNAN); /* reset precalc */
569 log10_range = log10(im->maxval) - log10(im->minval);
570 }
571 /* make sure first y=10^x gridline is located on
572 integer pixel position by moving scale slightly
573 downwards (sub-pixel movement) */
574 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
575 ypixfrac = ypix - floor(ypix);
576 if (ypixfrac > 0 && ypixfrac < 1) {
577 double yfrac = ypixfrac / im->ysize;
579 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
580 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
581 ytr(im, DNAN); /* reset precalc */
582 }
583 } else {
584 /* Make sure we have an integer pixel distance between
585 each minor gridline */
586 double ypos1 = ytr(im, im->minval);
587 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
588 double y_pixel_delta = ypos1 - ypos2;
589 double factor = y_pixel_delta / floor(y_pixel_delta);
590 double new_range = factor * (im->maxval - im->minval);
591 double gridstep = im->ygrid_scale.gridstep;
592 double minor_y, minor_y_px, minor_y_px_frac;
594 if (im->maxval > 0.0)
595 im->maxval = im->minval + new_range;
596 else
597 im->minval = im->maxval - new_range;
598 ytr(im, DNAN); /* reset precalc */
599 /* make sure first minor gridline is on integer pixel y coord */
600 minor_y = gridstep * floor(im->minval / gridstep);
601 while (minor_y < im->minval)
602 minor_y += gridstep;
603 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
604 minor_y_px_frac = minor_y_px - floor(minor_y_px);
605 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
606 double yfrac = minor_y_px_frac / im->ysize;
607 double range = im->maxval - im->minval;
609 im->minval = im->minval - yfrac * range;
610 im->maxval = im->maxval - yfrac * range;
611 ytr(im, DNAN); /* reset precalc */
612 }
613 calc_horizontal_grid(im); /* recalc with changed im->maxval */
614 }
615 }
617 /* reduce data reimplementation by Alex */
619 void reduce_data(
620 enum cf_en cf, /* which consolidation function ? */
621 unsigned long cur_step, /* step the data currently is in */
622 time_t *start, /* start, end and step as requested ... */
623 time_t *end, /* ... by the application will be ... */
624 unsigned long *step, /* ... adjusted to represent reality */
625 unsigned long *ds_cnt, /* number of data sources in file */
626 rrd_value_t **data)
627 { /* two dimensional array containing the data */
628 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
629 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
630 0;
631 rrd_value_t *srcptr, *dstptr;
633 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
634 dstptr = *data;
635 srcptr = *data;
636 row_cnt = ((*end) - (*start)) / cur_step;
638 #ifdef DEBUG
639 #define DEBUG_REDUCE
640 #endif
641 #ifdef DEBUG_REDUCE
642 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
643 row_cnt, reduce_factor, *start, *end, cur_step);
644 for (col = 0; col < row_cnt; col++) {
645 printf("time %10lu: ", *start + (col + 1) * cur_step);
646 for (i = 0; i < *ds_cnt; i++)
647 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
648 printf("\n");
649 }
650 #endif
652 /* We have to combine [reduce_factor] rows of the source
653 ** into one row for the destination. Doing this we also
654 ** need to take care to combine the correct rows. First
655 ** alter the start and end time so that they are multiples
656 ** of the new step time. We cannot reduce the amount of
657 ** time so we have to move the end towards the future and
658 ** the start towards the past.
659 */
660 end_offset = (*end) % (*step);
661 start_offset = (*start) % (*step);
663 /* If there is a start offset (which cannot be more than
664 ** one destination row), skip the appropriate number of
665 ** source rows and one destination row. The appropriate
666 ** number is what we do know (start_offset/cur_step) of
667 ** the new interval (*step/cur_step aka reduce_factor).
668 */
669 #ifdef DEBUG_REDUCE
670 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
671 printf("row_cnt before: %lu\n", row_cnt);
672 #endif
673 if (start_offset) {
674 (*start) = (*start) - start_offset;
675 skiprows = reduce_factor - start_offset / cur_step;
676 srcptr += skiprows * *ds_cnt;
677 for (col = 0; col < (*ds_cnt); col++)
678 *dstptr++ = DNAN;
679 row_cnt -= skiprows;
680 }
681 #ifdef DEBUG_REDUCE
682 printf("row_cnt between: %lu\n", row_cnt);
683 #endif
685 /* At the end we have some rows that are not going to be
686 ** used, the amount is end_offset/cur_step
687 */
688 if (end_offset) {
689 (*end) = (*end) - end_offset + (*step);
690 skiprows = end_offset / cur_step;
691 row_cnt -= skiprows;
692 }
693 #ifdef DEBUG_REDUCE
694 printf("row_cnt after: %lu\n", row_cnt);
695 #endif
697 /* Sanity check: row_cnt should be multiple of reduce_factor */
698 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
700 if (row_cnt % reduce_factor) {
701 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
702 row_cnt, reduce_factor);
703 printf("BUG in reduce_data()\n");
704 exit(1);
705 }
707 /* Now combine reduce_factor intervals at a time
708 ** into one interval for the destination.
709 */
711 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
712 for (col = 0; col < (*ds_cnt); col++) {
713 rrd_value_t newval = DNAN;
714 unsigned long validval = 0;
716 for (i = 0; i < reduce_factor; i++) {
717 if (isnan(srcptr[i * (*ds_cnt) + col])) {
718 continue;
719 }
720 validval++;
721 if (isnan(newval))
722 newval = srcptr[i * (*ds_cnt) + col];
723 else {
724 switch (cf) {
725 case CF_HWPREDICT:
726 case CF_MHWPREDICT:
727 case CF_DEVSEASONAL:
728 case CF_DEVPREDICT:
729 case CF_SEASONAL:
730 case CF_AVERAGE:
731 newval += srcptr[i * (*ds_cnt) + col];
732 break;
733 case CF_MINIMUM:
734 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
735 break;
736 case CF_FAILURES:
737 /* an interval contains a failure if any subintervals contained a failure */
738 case CF_MAXIMUM:
739 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
740 break;
741 case CF_LAST:
742 newval = srcptr[i * (*ds_cnt) + col];
743 break;
744 }
745 }
746 }
747 if (validval == 0) {
748 newval = DNAN;
749 } else {
750 switch (cf) {
751 case CF_HWPREDICT:
752 case CF_MHWPREDICT:
753 case CF_DEVSEASONAL:
754 case CF_DEVPREDICT:
755 case CF_SEASONAL:
756 case CF_AVERAGE:
757 newval /= validval;
758 break;
759 case CF_MINIMUM:
760 case CF_FAILURES:
761 case CF_MAXIMUM:
762 case CF_LAST:
763 break;
764 }
765 }
766 *dstptr++ = newval;
767 }
768 srcptr += (*ds_cnt) * reduce_factor;
769 row_cnt -= reduce_factor;
770 }
771 /* If we had to alter the endtime, we didn't have enough
772 ** source rows to fill the last row. Fill it with NaN.
773 */
774 if (end_offset)
775 for (col = 0; col < (*ds_cnt); col++)
776 *dstptr++ = DNAN;
777 #ifdef DEBUG_REDUCE
778 row_cnt = ((*end) - (*start)) / *step;
779 srcptr = *data;
780 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
781 row_cnt, *start, *end, *step);
782 for (col = 0; col < row_cnt; col++) {
783 printf("time %10lu: ", *start + (col + 1) * (*step));
784 for (i = 0; i < *ds_cnt; i++)
785 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
786 printf("\n");
787 }
788 #endif
789 }
792 /* get the data required for the graphs from the
793 relevant rrds ... */
795 int data_fetch(
796 image_desc_t *im)
797 {
798 int i, ii;
799 int skip;
801 /* pull the data from the rrd files ... */
802 for (i = 0; i < (int) im->gdes_c; i++) {
803 /* only GF_DEF elements fetch data */
804 if (im->gdes[i].gf != GF_DEF)
805 continue;
807 skip = 0;
808 /* do we have it already ? */
809 for (ii = 0; ii < i; ii++) {
810 if (im->gdes[ii].gf != GF_DEF)
811 continue;
812 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
813 && (im->gdes[i].cf == im->gdes[ii].cf)
814 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
815 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
816 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
817 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
818 /* OK, the data is already there.
819 ** Just copy the header portion
820 */
821 im->gdes[i].start = im->gdes[ii].start;
822 im->gdes[i].end = im->gdes[ii].end;
823 im->gdes[i].step = im->gdes[ii].step;
824 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
825 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
826 im->gdes[i].data = im->gdes[ii].data;
827 im->gdes[i].data_first = 0;
828 skip = 1;
829 }
830 if (skip)
831 break;
832 }
833 if (!skip) {
834 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
836 if ((rrd_fetch_fn(im->gdes[i].rrd,
837 im->gdes[i].cf,
838 &im->gdes[i].start,
839 &im->gdes[i].end,
840 &ft_step,
841 &im->gdes[i].ds_cnt,
842 &im->gdes[i].ds_namv,
843 &im->gdes[i].data)) == -1) {
844 return -1;
845 }
846 im->gdes[i].data_first = 1;
848 if (ft_step < im->gdes[i].step) {
849 reduce_data(im->gdes[i].cf_reduce,
850 ft_step,
851 &im->gdes[i].start,
852 &im->gdes[i].end,
853 &im->gdes[i].step,
854 &im->gdes[i].ds_cnt, &im->gdes[i].data);
855 } else {
856 im->gdes[i].step = ft_step;
857 }
858 }
860 /* lets see if the required data source is really there */
861 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
862 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
863 im->gdes[i].ds = ii;
864 }
865 }
866 if (im->gdes[i].ds == -1) {
867 rrd_set_error("No DS called '%s' in '%s'",
868 im->gdes[i].ds_nam, im->gdes[i].rrd);
869 return -1;
870 }
872 }
873 return 0;
874 }
876 /* evaluate the expressions in the CDEF functions */
878 /*************************************************************
879 * CDEF stuff
880 *************************************************************/
882 long find_var_wrapper(
883 void *arg1,
884 char *key)
885 {
886 return find_var((image_desc_t *) arg1, key);
887 }
889 /* find gdes containing var*/
890 long find_var(
891 image_desc_t *im,
892 char *key)
893 {
894 long ii;
896 for (ii = 0; ii < im->gdes_c - 1; ii++) {
897 if ((im->gdes[ii].gf == GF_DEF
898 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
899 && (strcmp(im->gdes[ii].vname, key) == 0)) {
900 return ii;
901 }
902 }
903 return -1;
904 }
906 /* find the largest common denominator for all the numbers
907 in the 0 terminated num array */
908 long lcd(
909 long *num)
910 {
911 long rest;
912 int i;
914 for (i = 0; num[i + 1] != 0; i++) {
915 do {
916 rest = num[i] % num[i + 1];
917 num[i] = num[i + 1];
918 num[i + 1] = rest;
919 } while (rest != 0);
920 num[i + 1] = num[i];
921 }
922 /* return i==0?num[i]:num[i-1]; */
923 return num[i];
924 }
926 /* run the rpn calculator on all the VDEF and CDEF arguments */
927 int data_calc(
928 image_desc_t *im)
929 {
931 int gdi;
932 int dataidx;
933 long *steparray, rpi;
934 int stepcnt;
935 time_t now;
936 rpnstack_t rpnstack;
938 rpnstack_init(&rpnstack);
940 for (gdi = 0; gdi < im->gdes_c; gdi++) {
941 /* Look for GF_VDEF and GF_CDEF in the same loop,
942 * so CDEFs can use VDEFs and vice versa
943 */
944 switch (im->gdes[gdi].gf) {
945 case GF_XPORT:
946 break;
947 case GF_SHIFT:{
948 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
950 /* remove current shift */
951 vdp->start -= vdp->shift;
952 vdp->end -= vdp->shift;
954 /* vdef */
955 if (im->gdes[gdi].shidx >= 0)
956 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
957 /* constant */
958 else
959 vdp->shift = im->gdes[gdi].shval;
961 /* normalize shift to multiple of consolidated step */
962 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
964 /* apply shift */
965 vdp->start += vdp->shift;
966 vdp->end += vdp->shift;
967 break;
968 }
969 case GF_VDEF:
970 /* A VDEF has no DS. This also signals other parts
971 * of rrdtool that this is a VDEF value, not a CDEF.
972 */
973 im->gdes[gdi].ds_cnt = 0;
974 if (vdef_calc(im, gdi)) {
975 rrd_set_error("Error processing VDEF '%s'",
976 im->gdes[gdi].vname);
977 rpnstack_free(&rpnstack);
978 return -1;
979 }
980 break;
981 case GF_CDEF:
982 im->gdes[gdi].ds_cnt = 1;
983 im->gdes[gdi].ds = 0;
984 im->gdes[gdi].data_first = 1;
985 im->gdes[gdi].start = 0;
986 im->gdes[gdi].end = 0;
987 steparray = NULL;
988 stepcnt = 0;
989 dataidx = -1;
991 /* Find the variables in the expression.
992 * - VDEF variables are substituted by their values
993 * and the opcode is changed into OP_NUMBER.
994 * - CDEF variables are analized for their step size,
995 * the lowest common denominator of all the step
996 * sizes of the data sources involved is calculated
997 * and the resulting number is the step size for the
998 * resulting data source.
999 */
1000 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1001 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1002 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1003 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1005 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1006 #if 0
1007 printf
1008 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1009 im->gdes[gdi].vname, im->gdes[ptr].vname);
1010 printf("DEBUG: value from vdef is %f\n",
1011 im->gdes[ptr].vf.val);
1012 #endif
1013 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1014 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1015 } else { /* normal variables and PREF(variables) */
1017 /* add one entry to the array that keeps track of the step sizes of the
1018 * data sources going into the CDEF. */
1019 if ((steparray =
1020 rrd_realloc(steparray,
1021 (++stepcnt +
1022 1) * sizeof(*steparray))) == NULL) {
1023 rrd_set_error("realloc steparray");
1024 rpnstack_free(&rpnstack);
1025 return -1;
1026 };
1028 steparray[stepcnt - 1] = im->gdes[ptr].step;
1030 /* adjust start and end of cdef (gdi) so
1031 * that it runs from the latest start point
1032 * to the earliest endpoint of any of the
1033 * rras involved (ptr)
1034 */
1036 if (im->gdes[gdi].start < im->gdes[ptr].start)
1037 im->gdes[gdi].start = im->gdes[ptr].start;
1039 if (im->gdes[gdi].end == 0 ||
1040 im->gdes[gdi].end > im->gdes[ptr].end)
1041 im->gdes[gdi].end = im->gdes[ptr].end;
1043 /* store pointer to the first element of
1044 * the rra providing data for variable,
1045 * further save step size and data source
1046 * count of this rra
1047 */
1048 im->gdes[gdi].rpnp[rpi].data =
1049 im->gdes[ptr].data + im->gdes[ptr].ds;
1050 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1051 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1053 /* backoff the *.data ptr; this is done so
1054 * rpncalc() function doesn't have to treat
1055 * the first case differently
1056 */
1057 } /* if ds_cnt != 0 */
1058 } /* if OP_VARIABLE */
1059 } /* loop through all rpi */
1061 /* move the data pointers to the correct period */
1062 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1063 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1064 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1065 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1066 long diff =
1067 im->gdes[gdi].start - im->gdes[ptr].start;
1069 if (diff > 0)
1070 im->gdes[gdi].rpnp[rpi].data +=
1071 (diff / im->gdes[ptr].step) *
1072 im->gdes[ptr].ds_cnt;
1073 }
1074 }
1076 if (steparray == NULL) {
1077 rrd_set_error("rpn expressions without DEF"
1078 " or CDEF variables are not supported");
1079 rpnstack_free(&rpnstack);
1080 return -1;
1081 }
1082 steparray[stepcnt] = 0;
1083 /* Now find the resulting step. All steps in all
1084 * used RRAs have to be visited
1085 */
1086 im->gdes[gdi].step = lcd(steparray);
1087 free(steparray);
1088 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1089 im->gdes[gdi].start)
1090 / im->gdes[gdi].step)
1091 * sizeof(double))) == NULL) {
1092 rrd_set_error("malloc im->gdes[gdi].data");
1093 rpnstack_free(&rpnstack);
1094 return -1;
1095 }
1097 /* Step through the new cdef results array and
1098 * calculate the values
1099 */
1100 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1101 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1102 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1104 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1105 * in this case we are advancing by timesteps;
1106 * we use the fact that time_t is a synonym for long
1107 */
1108 if (rpn_calc(rpnp, &rpnstack, (long) now,
1109 im->gdes[gdi].data, ++dataidx) == -1) {
1110 /* rpn_calc sets the error string */
1111 rpnstack_free(&rpnstack);
1112 return -1;
1113 }
1114 } /* enumerate over time steps within a CDEF */
1115 break;
1116 default:
1117 continue;
1118 }
1119 } /* enumerate over CDEFs */
1120 rpnstack_free(&rpnstack);
1121 return 0;
1122 }
1124 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1125 /* yes we are loosing precision by doing tos with floats instead of doubles
1126 but it seems more stable this way. */
1128 static int AlmostEqual2sComplement(
1129 float A,
1130 float B,
1131 int maxUlps)
1132 {
1134 int aInt = *(int *) &A;
1135 int bInt = *(int *) &B;
1136 int intDiff;
1138 /* Make sure maxUlps is non-negative and small enough that the
1139 default NAN won't compare as equal to anything. */
1141 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1143 /* Make aInt lexicographically ordered as a twos-complement int */
1145 if (aInt < 0)
1146 aInt = 0x80000000l - aInt;
1148 /* Make bInt lexicographically ordered as a twos-complement int */
1150 if (bInt < 0)
1151 bInt = 0x80000000l - bInt;
1153 intDiff = abs(aInt - bInt);
1155 if (intDiff <= maxUlps)
1156 return 1;
1158 return 0;
1159 }
1161 /* massage data so, that we get one value for each x coordinate in the graph */
1162 int data_proc(
1163 image_desc_t *im)
1164 {
1165 long i, ii;
1166 double pixstep = (double) (im->end - im->start)
1167 / (double) im->xsize; /* how much time
1168 passes in one pixel */
1169 double paintval;
1170 double minval = DNAN, maxval = DNAN;
1172 unsigned long gr_time;
1174 /* memory for the processed data */
1175 for (i = 0; i < im->gdes_c; i++) {
1176 if ((im->gdes[i].gf == GF_LINE) ||
1177 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1178 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1179 * sizeof(rrd_value_t))) == NULL) {
1180 rrd_set_error("malloc data_proc");
1181 return -1;
1182 }
1183 }
1184 }
1186 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1187 long vidx;
1189 gr_time = im->start + pixstep * i; /* time of the current step */
1190 paintval = 0.0;
1192 for (ii = 0; ii < im->gdes_c; ii++) {
1193 double value;
1195 switch (im->gdes[ii].gf) {
1196 case GF_LINE:
1197 case GF_AREA:
1198 case GF_TICK:
1199 if (!im->gdes[ii].stack)
1200 paintval = 0.0;
1201 value = im->gdes[ii].yrule;
1202 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1203 /* The time of the data doesn't necessarily match
1204 ** the time of the graph. Beware.
1205 */
1206 vidx = im->gdes[ii].vidx;
1207 if (im->gdes[vidx].gf == GF_VDEF) {
1208 value = im->gdes[vidx].vf.val;
1209 } else
1210 if (((long int) gr_time >=
1211 (long int) im->gdes[vidx].start)
1212 && ((long int) gr_time <=
1213 (long int) im->gdes[vidx].end)) {
1214 value = im->gdes[vidx].data[(unsigned long)
1215 floor((double)
1216 (gr_time -
1217 im->gdes[vidx].
1218 start)
1219 /
1220 im->gdes[vidx].step)
1221 * im->gdes[vidx].ds_cnt +
1222 im->gdes[vidx].ds];
1223 } else {
1224 value = DNAN;
1225 }
1226 };
1228 if (!isnan(value)) {
1229 paintval += value;
1230 im->gdes[ii].p_data[i] = paintval;
1231 /* GF_TICK: the data values are not
1232 ** relevant for min and max
1233 */
1234 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1235 if ((isnan(minval) || paintval < minval) &&
1236 !(im->logarithmic && paintval <= 0.0))
1237 minval = paintval;
1238 if (isnan(maxval) || paintval > maxval)
1239 maxval = paintval;
1240 }
1241 } else {
1242 im->gdes[ii].p_data[i] = DNAN;
1243 }
1244 break;
1245 case GF_STACK:
1246 rrd_set_error
1247 ("STACK should already be turned into LINE or AREA here");
1248 return -1;
1249 break;
1250 default:
1251 break;
1252 }
1253 }
1254 }
1256 /* if min or max have not been asigned a value this is because
1257 there was no data in the graph ... this is not good ...
1258 lets set these to dummy values then ... */
1260 if (im->logarithmic) {
1261 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1262 minval = 0.0; /* catching this right away below */
1263 maxval = 5.1;
1264 }
1265 /* in logarithm mode, where minval is smaller or equal
1266 to 0 make the beast just way smaller than maxval */
1267 if (minval <= 0) {
1268 minval = maxval / 10e8;
1269 }
1270 } else {
1271 if (isnan(minval) || isnan(maxval)) {
1272 minval = 0.0;
1273 maxval = 1.0;
1274 }
1275 }
1277 /* adjust min and max values given by the user */
1278 /* for logscale we add something on top */
1279 if (isnan(im->minval)
1280 || ((!im->rigid) && im->minval > minval)
1281 ) {
1282 if (im->logarithmic)
1283 im->minval = minval / 2.0;
1284 else
1285 im->minval = minval;
1286 }
1287 if (isnan(im->maxval)
1288 || (!im->rigid && im->maxval < maxval)
1289 ) {
1290 if (im->logarithmic)
1291 im->maxval = maxval * 2.0;
1292 else
1293 im->maxval = maxval;
1294 }
1296 /* make sure min is smaller than max */
1297 if (im->minval > im->maxval) {
1298 if (im->minval > 0)
1299 im->minval = 0.99 * im->maxval;
1300 else
1301 im->minval = 1.01 * im->maxval;
1302 }
1304 /* make sure min and max are not equal */
1305 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1306 if (im->maxval > 0)
1307 im->maxval *= 1.01;
1308 else
1309 im->maxval *= 0.99;
1311 /* make sure min and max are not both zero */
1312 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1313 im->maxval = 1.0;
1314 }
1315 }
1316 return 0;
1317 }
1321 /* identify the point where the first gridline, label ... gets placed */
1323 time_t find_first_time(
1324 time_t start, /* what is the initial time */
1325 enum tmt_en baseint, /* what is the basic interval */
1326 long basestep /* how many if these do we jump a time */
1327 )
1328 {
1329 struct tm tm;
1331 localtime_r(&start, &tm);
1333 switch (baseint) {
1334 case TMT_SECOND:
1335 tm. tm_sec -= tm.tm_sec % basestep;
1337 break;
1338 case TMT_MINUTE:
1339 tm. tm_sec = 0;
1340 tm. tm_min -= tm.tm_min % basestep;
1342 break;
1343 case TMT_HOUR:
1344 tm. tm_sec = 0;
1345 tm. tm_min = 0;
1346 tm. tm_hour -= tm.tm_hour % basestep;
1348 break;
1349 case TMT_DAY:
1350 /* we do NOT look at the basestep for this ... */
1351 tm. tm_sec = 0;
1352 tm. tm_min = 0;
1353 tm. tm_hour = 0;
1355 break;
1356 case TMT_WEEK:
1357 /* we do NOT look at the basestep for this ... */
1358 tm. tm_sec = 0;
1359 tm. tm_min = 0;
1360 tm. tm_hour = 0;
1361 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1363 if (tm.tm_wday == 0)
1364 tm. tm_mday -= 7; /* we want the *previous* monday */
1366 break;
1367 case TMT_MONTH:
1368 tm. tm_sec = 0;
1369 tm. tm_min = 0;
1370 tm. tm_hour = 0;
1371 tm. tm_mday = 1;
1372 tm. tm_mon -= tm.tm_mon % basestep;
1374 break;
1376 case TMT_YEAR:
1377 tm. tm_sec = 0;
1378 tm. tm_min = 0;
1379 tm. tm_hour = 0;
1380 tm. tm_mday = 1;
1381 tm. tm_mon = 0;
1382 tm. tm_year -= (
1383 tm.tm_year + 1900) %basestep;
1385 }
1386 return mktime(&tm);
1387 }
1389 /* identify the point where the next gridline, label ... gets placed */
1390 time_t find_next_time(
1391 time_t current, /* what is the initial time */
1392 enum tmt_en baseint, /* what is the basic interval */
1393 long basestep /* how many if these do we jump a time */
1394 )
1395 {
1396 struct tm tm;
1397 time_t madetime;
1399 localtime_r(¤t, &tm);
1401 do {
1402 switch (baseint) {
1403 case TMT_SECOND:
1404 tm. tm_sec += basestep;
1406 break;
1407 case TMT_MINUTE:
1408 tm. tm_min += basestep;
1410 break;
1411 case TMT_HOUR:
1412 tm. tm_hour += basestep;
1414 break;
1415 case TMT_DAY:
1416 tm. tm_mday += basestep;
1418 break;
1419 case TMT_WEEK:
1420 tm. tm_mday += 7 * basestep;
1422 break;
1423 case TMT_MONTH:
1424 tm. tm_mon += basestep;
1426 break;
1427 case TMT_YEAR:
1428 tm. tm_year += basestep;
1429 }
1430 madetime = mktime(&tm);
1431 } while (madetime == -1); /* this is necessary to skip impssible times
1432 like the daylight saving time skips */
1433 return madetime;
1435 }
1438 /* calculate values required for PRINT and GPRINT functions */
1440 int print_calc(
1441 image_desc_t *im)
1442 {
1443 long i, ii, validsteps;
1444 double printval;
1445 struct tm tmvdef;
1446 int graphelement = 0;
1447 long vidx;
1448 int max_ii;
1449 double magfact = -1;
1450 char *si_symb = "";
1451 char *percent_s;
1452 int prline_cnt = 0;
1454 /* wow initializing tmvdef is quite a task :-) */
1455 time_t now = time(NULL);
1457 localtime_r(&now, &tmvdef);
1458 for (i = 0; i < im->gdes_c; i++) {
1459 vidx = im->gdes[i].vidx;
1460 switch (im->gdes[i].gf) {
1461 case GF_PRINT:
1462 case GF_GPRINT:
1463 /* PRINT and GPRINT can now print VDEF generated values.
1464 * There's no need to do any calculations on them as these
1465 * calculations were already made.
1466 */
1467 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1468 printval = im->gdes[vidx].vf.val;
1469 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1470 } else { /* need to calculate max,min,avg etcetera */
1471 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1472 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1473 printval = DNAN;
1474 validsteps = 0;
1475 for (ii = im->gdes[vidx].ds;
1476 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1477 if (!finite(im->gdes[vidx].data[ii]))
1478 continue;
1479 if (isnan(printval)) {
1480 printval = im->gdes[vidx].data[ii];
1481 validsteps++;
1482 continue;
1483 }
1485 switch (im->gdes[i].cf) {
1486 case CF_HWPREDICT:
1487 case CF_MHWPREDICT:
1488 case CF_DEVPREDICT:
1489 case CF_DEVSEASONAL:
1490 case CF_SEASONAL:
1491 case CF_AVERAGE:
1492 validsteps++;
1493 printval += im->gdes[vidx].data[ii];
1494 break;
1495 case CF_MINIMUM:
1496 printval = min(printval, im->gdes[vidx].data[ii]);
1497 break;
1498 case CF_FAILURES:
1499 case CF_MAXIMUM:
1500 printval = max(printval, im->gdes[vidx].data[ii]);
1501 break;
1502 case CF_LAST:
1503 printval = im->gdes[vidx].data[ii];
1504 }
1505 }
1506 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1507 if (validsteps > 1) {
1508 printval = (printval / validsteps);
1509 }
1510 }
1511 } /* prepare printval */
1513 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1514 /* Magfact is set to -1 upon entry to print_calc. If it
1515 * is still less than 0, then we need to run auto_scale.
1516 * Otherwise, put the value into the correct units. If
1517 * the value is 0, then do not set the symbol or magnification
1518 * so next the calculation will be performed again. */
1519 if (magfact < 0.0) {
1520 auto_scale(im, &printval, &si_symb, &magfact);
1521 if (printval == 0.0)
1522 magfact = -1.0;
1523 } else {
1524 printval /= magfact;
1525 }
1526 *(++percent_s) = 's';
1527 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1528 auto_scale(im, &printval, &si_symb, &magfact);
1529 }
1531 if (im->gdes[i].gf == GF_PRINT) {
1532 rrd_infoval_t prline;
1534 if (im->gdes[i].strftm) {
1535 prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
1536 strftime(prline.u_str,
1537 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1538 } else if (bad_format(im->gdes[i].format)) {
1539 rrd_set_error
1540 ("bad format for PRINT in '%s'", im->gdes[i].format);
1541 return -1;
1542 } else {
1543 prline.u_str =
1544 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1545 }
1546 grinfo_push(im,
1547 sprintf_alloc
1548 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1549 free(prline.u_str);
1550 } else {
1551 /* GF_GPRINT */
1553 if (im->gdes[i].strftm) {
1554 strftime(im->gdes[i].legend,
1555 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1556 } else {
1557 if (bad_format(im->gdes[i].format)) {
1558 rrd_set_error
1559 ("bad format for GPRINT in '%s'",
1560 im->gdes[i].format);
1561 return -1;
1562 }
1563 #ifdef HAVE_SNPRINTF
1564 snprintf(im->gdes[i].legend,
1565 FMT_LEG_LEN - 2,
1566 im->gdes[i].format, printval, si_symb);
1567 #else
1568 sprintf(im->gdes[i].legend,
1569 im->gdes[i].format, printval, si_symb);
1570 #endif
1571 }
1572 graphelement = 1;
1573 }
1574 break;
1575 case GF_LINE:
1576 case GF_AREA:
1577 case GF_TICK:
1578 graphelement = 1;
1579 break;
1580 case GF_HRULE:
1581 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1582 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1583 };
1584 graphelement = 1;
1585 break;
1586 case GF_VRULE:
1587 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1588 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1589 };
1590 graphelement = 1;
1591 break;
1592 case GF_COMMENT:
1593 case GF_TEXTALIGN:
1594 case GF_DEF:
1595 case GF_CDEF:
1596 case GF_VDEF:
1597 #ifdef WITH_PIECHART
1598 case GF_PART:
1599 #endif
1600 case GF_SHIFT:
1601 case GF_XPORT:
1602 break;
1603 case GF_STACK:
1604 rrd_set_error
1605 ("STACK should already be turned into LINE or AREA here");
1606 return -1;
1607 break;
1608 }
1609 }
1610 return graphelement;
1611 }
1614 /* place legends with color spots */
1615 int leg_place(
1616 image_desc_t *im,
1617 int *gY)
1618 {
1619 /* graph labels */
1620 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1621 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1622 int fill = 0, fill_last;
1623 int leg_c = 0;
1624 int leg_x = border;
1625 int leg_y = im->yimg;
1626 int leg_y_prev = im->yimg;
1627 int leg_cc;
1628 int glue = 0;
1629 int i, ii, mark = 0;
1630 char prt_fctn; /*special printfunctions */
1631 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1632 int *legspace;
1633 char *tab;
1635 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1636 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1637 rrd_set_error("malloc for legspace");
1638 return -1;
1639 }
1641 if (im->extra_flags & FULL_SIZE_MODE)
1642 leg_y = leg_y_prev =
1643 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1644 for (i = 0; i < im->gdes_c; i++) {
1645 fill_last = fill;
1646 /* hide legends for rules which are not displayed */
1647 if (im->gdes[i].gf == GF_TEXTALIGN) {
1648 default_txtalign = im->gdes[i].txtalign;
1649 }
1651 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1652 if (im->gdes[i].gf == GF_HRULE
1653 && (im->gdes[i].yrule <
1654 im->minval || im->gdes[i].yrule > im->maxval))
1655 im->gdes[i].legend[0] = '\0';
1656 if (im->gdes[i].gf == GF_VRULE
1657 && (im->gdes[i].xrule <
1658 im->start || im->gdes[i].xrule > im->end))
1659 im->gdes[i].legend[0] = '\0';
1660 }
1662 /* turn \\t into tab */
1663 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1664 memmove(tab, tab + 1, strlen(tab));
1665 tab[0] = (char) 9;
1666 }
1667 leg_cc = strlen(im->gdes[i].legend);
1668 /* is there a controle code ant the end of the legend string ? */
1669 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1670 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1671 leg_cc -= 2;
1672 im->gdes[i].legend[leg_cc] = '\0';
1673 } else {
1674 prt_fctn = '\0';
1675 }
1676 /* only valid control codes */
1677 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1678 prt_fctn != 'r' &&
1679 prt_fctn != 'j' &&
1680 prt_fctn != 'c' &&
1681 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1682 free(legspace);
1683 rrd_set_error
1684 ("Unknown control code at the end of '%s\\%c'",
1685 im->gdes[i].legend, prt_fctn);
1686 return -1;
1687 }
1688 /* \n -> \l */
1689 if (prt_fctn == 'n') {
1690 prt_fctn = 'l';
1691 }
1693 /* remove exess space from the end of the legend for \g */
1694 while (prt_fctn == 'g' &&
1695 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1696 leg_cc--;
1697 im->gdes[i].legend[leg_cc] = '\0';
1698 }
1700 if (leg_cc != 0) {
1702 /* no interleg space if string ends in \g */
1703 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1704 if (fill > 0) {
1705 fill += legspace[i];
1706 }
1707 fill +=
1708 gfx_get_text_width(im,
1709 fill + border,
1710 im->
1711 text_prop
1712 [TEXT_PROP_LEGEND].
1713 font_desc,
1714 im->tabwidth, im->gdes[i].legend);
1715 leg_c++;
1716 } else {
1717 legspace[i] = 0;
1718 }
1719 /* who said there was a special tag ... ? */
1720 if (prt_fctn == 'g') {
1721 prt_fctn = '\0';
1722 }
1724 if (prt_fctn == '\0') {
1725 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1726 /* just one legend item is left right or center */
1727 switch (default_txtalign) {
1728 case TXA_RIGHT:
1729 prt_fctn = 'r';
1730 break;
1731 case TXA_CENTER:
1732 prt_fctn = 'c';
1733 break;
1734 case TXA_JUSTIFIED:
1735 prt_fctn = 'j';
1736 break;
1737 default:
1738 prt_fctn = 'l';
1739 break;
1740 }
1741 }
1742 /* is it time to place the legends ? */
1743 if (fill > im->ximg - 2 * border) {
1744 if (leg_c > 1) {
1745 /* go back one */
1746 i--;
1747 fill = fill_last;
1748 leg_c--;
1749 }
1750 }
1751 if (leg_c == 1 && prt_fctn == 'j') {
1752 prt_fctn = 'l';
1753 }
1754 }
1757 if (prt_fctn != '\0') {
1758 leg_x = border;
1759 if (leg_c >= 2 && prt_fctn == 'j') {
1760 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1761 } else {
1762 glue = 0;
1763 }
1764 if (prt_fctn == 'c')
1765 leg_x = (im->ximg - fill) / 2.0;
1766 if (prt_fctn == 'r')
1767 leg_x = im->ximg - fill - border;
1768 for (ii = mark; ii <= i; ii++) {
1769 if (im->gdes[ii].legend[0] == '\0')
1770 continue; /* skip empty legends */
1771 im->gdes[ii].leg_x = leg_x;
1772 im->gdes[ii].leg_y = leg_y;
1773 leg_x +=
1774 gfx_get_text_width(im, leg_x,
1775 im->
1776 text_prop
1777 [TEXT_PROP_LEGEND].
1778 font_desc,
1779 im->tabwidth, im->gdes[ii].legend)
1780 + legspace[ii]
1781 + glue;
1782 }
1783 leg_y_prev = leg_y;
1784 if (im->extra_flags & FULL_SIZE_MODE) {
1785 /* only add y space if there was text on the line */
1786 if (leg_x > border || prt_fctn == 's')
1787 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1788 if (prt_fctn == 's')
1789 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1790 } else {
1791 if (leg_x > border || prt_fctn == 's')
1792 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1793 if (prt_fctn == 's')
1794 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1795 }
1796 fill = 0;
1797 leg_c = 0;
1798 mark = ii;
1799 }
1800 }
1802 if (im->extra_flags & FULL_SIZE_MODE) {
1803 if (leg_y != leg_y_prev) {
1804 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1805 im->yorigin =
1806 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1807 }
1808 } else {
1809 im->yimg =
1810 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1811 border * 0.6;
1812 }
1813 free(legspace);
1814 }
1815 return 0;
1816 }
1818 /* create a grid on the graph. it determines what to do
1819 from the values of xsize, start and end */
1821 /* the xaxis labels are determined from the number of seconds per pixel
1822 in the requested graph */
1824 int calc_horizontal_grid(
1825 image_desc_t
1826 *im)
1827 {
1828 double range;
1829 double scaledrange;
1830 int pixel, i;
1831 int gridind = 0;
1832 int decimals, fractionals;
1834 im->ygrid_scale.labfact = 2;
1835 range = im->maxval - im->minval;
1836 scaledrange = range / im->magfact;
1837 /* does the scale of this graph make it impossible to put lines
1838 on it? If so, give up. */
1839 if (isnan(scaledrange)) {
1840 return 0;
1841 }
1843 /* find grid spaceing */
1844 pixel = 1;
1845 if (isnan(im->ygridstep)) {
1846 if (im->extra_flags & ALTYGRID) {
1847 /* find the value with max number of digits. Get number of digits */
1848 decimals =
1849 ceil(log10
1850 (max(fabs(im->maxval), fabs(im->minval)) *
1851 im->viewfactor / im->magfact));
1852 if (decimals <= 0) /* everything is small. make place for zero */
1853 decimals = 1;
1854 im->ygrid_scale.gridstep =
1855 pow((double) 10,
1856 floor(log10(range * im->viewfactor / im->magfact))) /
1857 im->viewfactor * im->magfact;
1858 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1859 im->ygrid_scale.gridstep = 0.1;
1860 /* should have at least 5 lines but no more then 15 */
1861 if (range / im->ygrid_scale.gridstep < 5
1862 && im->ygrid_scale.gridstep >= 30)
1863 im->ygrid_scale.gridstep /= 10;
1864 if (range / im->ygrid_scale.gridstep > 15)
1865 im->ygrid_scale.gridstep *= 10;
1866 if (range / im->ygrid_scale.gridstep > 5) {
1867 im->ygrid_scale.labfact = 1;
1868 if (range / im->ygrid_scale.gridstep > 8
1869 || im->ygrid_scale.gridstep <
1870 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1871 im->ygrid_scale.labfact = 2;
1872 } else {
1873 im->ygrid_scale.gridstep /= 5;
1874 im->ygrid_scale.labfact = 5;
1875 }
1876 fractionals =
1877 floor(log10
1878 (im->ygrid_scale.gridstep *
1879 (double) im->ygrid_scale.labfact * im->viewfactor /
1880 im->magfact));
1881 if (fractionals < 0) { /* small amplitude. */
1882 int len = decimals - fractionals + 1;
1884 if (im->unitslength < len + 2)
1885 im->unitslength = len + 2;
1886 sprintf(im->ygrid_scale.labfmt,
1887 "%%%d.%df%s", len,
1888 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1889 } else {
1890 int len = decimals + 1;
1892 if (im->unitslength < len + 2)
1893 im->unitslength = len + 2;
1894 sprintf(im->ygrid_scale.labfmt,
1895 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1896 }
1897 } else { /* classic rrd grid */
1898 for (i = 0; ylab[i].grid > 0; i++) {
1899 pixel = im->ysize / (scaledrange / ylab[i].grid);
1900 gridind = i;
1901 if (pixel >= 5)
1902 break;
1903 }
1905 for (i = 0; i < 4; i++) {
1906 if (pixel * ylab[gridind].lfac[i] >=
1907 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1908 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1909 break;
1910 }
1911 }
1913 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1914 }
1915 } else {
1916 im->ygrid_scale.gridstep = im->ygridstep;
1917 im->ygrid_scale.labfact = im->ylabfact;
1918 }
1919 return 1;
1920 }
1922 int draw_horizontal_grid(
1923 image_desc_t
1924 *im)
1925 {
1926 int i;
1927 double scaledstep;
1928 char graph_label[100];
1929 int nlabels = 0;
1930 double X0 = im->xorigin;
1931 double X1 = im->xorigin + im->xsize;
1932 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1933 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1934 double MaxY;
1936 scaledstep =
1937 im->ygrid_scale.gridstep /
1938 (double) im->magfact * (double) im->viewfactor;
1939 MaxY = scaledstep * (double) egrid;
1940 for (i = sgrid; i <= egrid; i++) {
1941 double Y0 = ytr(im,
1942 im->ygrid_scale.gridstep * i);
1943 double YN = ytr(im,
1944 im->ygrid_scale.gridstep * (i + 1));
1946 if (floor(Y0 + 0.5) >=
1947 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1948 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1949 with the chosen settings. Add a label if required by settings, or if
1950 there is only one label so far and the next grid line is out of bounds. */
1951 if (i % im->ygrid_scale.labfact == 0
1952 || (nlabels == 1
1953 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1954 if (im->symbol == ' ') {
1955 if (im->extra_flags & ALTYGRID) {
1956 sprintf(graph_label,
1957 im->ygrid_scale.labfmt,
1958 scaledstep * (double) i);
1959 } else {
1960 if (MaxY < 10) {
1961 sprintf(graph_label, "%4.1f",
1962 scaledstep * (double) i);
1963 } else {
1964 sprintf(graph_label, "%4.0f",
1965 scaledstep * (double) i);
1966 }
1967 }
1968 } else {
1969 char sisym = (i == 0 ? ' ' : im->symbol);
1971 if (im->extra_flags & ALTYGRID) {
1972 sprintf(graph_label,
1973 im->ygrid_scale.labfmt,
1974 scaledstep * (double) i, sisym);
1975 } else {
1976 if (MaxY < 10) {
1977 sprintf(graph_label, "%4.1f %c",
1978 scaledstep * (double) i, sisym);
1979 } else {
1980 sprintf(graph_label, "%4.0f %c",
1981 scaledstep * (double) i, sisym);
1982 }
1983 }
1984 }
1985 nlabels++;
1986 gfx_text(im,
1987 X0 -
1988 im->
1989 text_prop[TEXT_PROP_AXIS].
1990 size, Y0,
1991 im->graph_col[GRC_FONT],
1992 im->
1993 text_prop[TEXT_PROP_AXIS].
1994 font_desc,
1995 im->tabwidth, 0.0,
1996 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
1997 gfx_line(im, X0 - 2, Y0, X0, Y0,
1998 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1999 gfx_line(im, X1, Y0, X1 + 2, Y0,
2000 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2001 gfx_dashed_line(im, X0 - 2, Y0,
2002 X1 + 2, Y0,
2003 MGRIDWIDTH,
2004 im->
2005 graph_col
2006 [GRC_MGRID],
2007 im->grid_dash_on, im->grid_dash_off);
2008 } else if (!(im->extra_flags & NOMINOR)) {
2009 gfx_line(im,
2010 X0 - 2, Y0,
2011 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2012 gfx_line(im, X1, Y0, X1 + 2, Y0,
2013 GRIDWIDTH, im->graph_col[GRC_GRID]);
2014 gfx_dashed_line(im, X0 - 1, Y0,
2015 X1 + 1, Y0,
2016 GRIDWIDTH,
2017 im->
2018 graph_col[GRC_GRID],
2019 im->grid_dash_on, im->grid_dash_off);
2020 }
2021 }
2022 }
2023 return 1;
2024 }
2026 /* this is frexp for base 10 */
2027 double frexp10(
2028 double,
2029 double *);
2030 double frexp10(
2031 double x,
2032 double *e)
2033 {
2034 double mnt;
2035 int iexp;
2037 iexp = floor(log(fabs(x)) / log(10));
2038 mnt = x / pow(10.0, iexp);
2039 if (mnt >= 10.0) {
2040 iexp++;
2041 mnt = x / pow(10.0, iexp);
2042 }
2043 *e = iexp;
2044 return mnt;
2045 }
2048 /* logaritmic horizontal grid */
2049 int horizontal_log_grid(
2050 image_desc_t
2051 *im)
2052 {
2053 double yloglab[][10] = {
2054 {
2055 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2056 0.0, 0.0, 0.0}, {
2057 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2058 0.0, 0.0, 0.0}, {
2059 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2060 0.0, 0.0, 0.0}, {
2061 1.0, 2.0, 4.0,
2062 6.0, 8.0, 10.,
2063 0.0,
2064 0.0, 0.0, 0.0}, {
2065 1.0,
2066 2.0,
2067 3.0,
2068 4.0,
2069 5.0,
2070 6.0,
2071 7.0,
2072 8.0,
2073 9.0,
2074 10.},
2075 {
2076 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2077 };
2078 int i, j, val_exp, min_exp;
2079 double nex; /* number of decades in data */
2080 double logscale; /* scale in logarithmic space */
2081 int exfrac = 1; /* decade spacing */
2082 int mid = -1; /* row in yloglab for major grid */
2083 double mspac; /* smallest major grid spacing (pixels) */
2084 int flab; /* first value in yloglab to use */
2085 double value, tmp, pre_value;
2086 double X0, X1, Y0;
2087 char graph_label[100];
2089 nex = log10(im->maxval / im->minval);
2090 logscale = im->ysize / nex;
2091 /* major spacing for data with high dynamic range */
2092 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2093 if (exfrac == 1)
2094 exfrac = 3;
2095 else
2096 exfrac += 3;
2097 }
2099 /* major spacing for less dynamic data */
2100 do {
2101 /* search best row in yloglab */
2102 mid++;
2103 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2104 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2105 }
2106 while (mspac >
2107 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2108 if (mid)
2109 mid--;
2110 /* find first value in yloglab */
2111 for (flab = 0;
2112 yloglab[mid][flab] < 10
2113 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2114 if (yloglab[mid][flab] == 10.0) {
2115 tmp += 1.0;
2116 flab = 0;
2117 }
2118 val_exp = tmp;
2119 if (val_exp % exfrac)
2120 val_exp += abs(-val_exp % exfrac);
2121 X0 = im->xorigin;
2122 X1 = im->xorigin + im->xsize;
2123 /* draw grid */
2124 pre_value = DNAN;
2125 while (1) {
2127 value = yloglab[mid][flab] * pow(10.0, val_exp);
2128 if (AlmostEqual2sComplement(value, pre_value, 4))
2129 break; /* it seems we are not converging */
2130 pre_value = value;
2131 Y0 = ytr(im, value);
2132 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2133 break;
2134 /* major grid line */
2135 gfx_line(im,
2136 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2137 gfx_line(im, X1, Y0, X1 + 2, Y0,
2138 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2139 gfx_dashed_line(im, X0 - 2, Y0,
2140 X1 + 2, Y0,
2141 MGRIDWIDTH,
2142 im->
2143 graph_col
2144 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2145 /* label */
2146 if (im->extra_flags & FORCE_UNITS_SI) {
2147 int scale;
2148 double pvalue;
2149 char symbol;
2151 scale = floor(val_exp / 3.0);
2152 if (value >= 1.0)
2153 pvalue = pow(10.0, val_exp % 3);
2154 else
2155 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2156 pvalue *= yloglab[mid][flab];
2157 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2158 && ((scale + si_symbcenter) >= 0))
2159 symbol = si_symbol[scale + si_symbcenter];
2160 else
2161 symbol = '?';
2162 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2163 } else
2164 sprintf(graph_label, "%3.0e", value);
2165 gfx_text(im,
2166 X0 -
2167 im->
2168 text_prop[TEXT_PROP_AXIS].
2169 size, Y0,
2170 im->graph_col[GRC_FONT],
2171 im->
2172 text_prop[TEXT_PROP_AXIS].
2173 font_desc,
2174 im->tabwidth, 0.0,
2175 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2176 /* minor grid */
2177 if (mid < 4 && exfrac == 1) {
2178 /* find first and last minor line behind current major line
2179 * i is the first line and j tha last */
2180 if (flab == 0) {
2181 min_exp = val_exp - 1;
2182 for (i = 1; yloglab[mid][i] < 10.0; i++);
2183 i = yloglab[mid][i - 1] + 1;
2184 j = 10;
2185 } else {
2186 min_exp = val_exp;
2187 i = yloglab[mid][flab - 1] + 1;
2188 j = yloglab[mid][flab];
2189 }
2191 /* draw minor lines below current major line */
2192 for (; i < j; i++) {
2194 value = i * pow(10.0, min_exp);
2195 if (value < im->minval)
2196 continue;
2197 Y0 = ytr(im, value);
2198 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2199 break;
2200 /* draw lines */
2201 gfx_line(im,
2202 X0 - 2, Y0,
2203 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2204 gfx_line(im, X1, Y0, X1 + 2, Y0,
2205 GRIDWIDTH, im->graph_col[GRC_GRID]);
2206 gfx_dashed_line(im, X0 - 1, Y0,
2207 X1 + 1, Y0,
2208 GRIDWIDTH,
2209 im->
2210 graph_col[GRC_GRID],
2211 im->grid_dash_on, im->grid_dash_off);
2212 }
2213 } else if (exfrac > 1) {
2214 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2215 value = pow(10.0, i);
2216 if (value < im->minval)
2217 continue;
2218 Y0 = ytr(im, value);
2219 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2220 break;
2221 /* draw lines */
2222 gfx_line(im,
2223 X0 - 2, Y0,
2224 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2225 gfx_line(im, X1, Y0, X1 + 2, Y0,
2226 GRIDWIDTH, im->graph_col[GRC_GRID]);
2227 gfx_dashed_line(im, X0 - 1, Y0,
2228 X1 + 1, Y0,
2229 GRIDWIDTH,
2230 im->
2231 graph_col[GRC_GRID],
2232 im->grid_dash_on, im->grid_dash_off);
2233 }
2234 }
2236 /* next decade */
2237 if (yloglab[mid][++flab] == 10.0) {
2238 flab = 0;
2239 val_exp += exfrac;
2240 }
2241 }
2243 /* draw minor lines after highest major line */
2244 if (mid < 4 && exfrac == 1) {
2245 /* find first and last minor line below current major line
2246 * i is the first line and j tha last */
2247 if (flab == 0) {
2248 min_exp = val_exp - 1;
2249 for (i = 1; yloglab[mid][i] < 10.0; i++);
2250 i = yloglab[mid][i - 1] + 1;
2251 j = 10;
2252 } else {
2253 min_exp = val_exp;
2254 i = yloglab[mid][flab - 1] + 1;
2255 j = yloglab[mid][flab];
2256 }
2258 /* draw minor lines below current major line */
2259 for (; i < j; i++) {
2261 value = i * pow(10.0, min_exp);
2262 if (value < im->minval)
2263 continue;
2264 Y0 = ytr(im, value);
2265 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2266 break;
2267 /* draw lines */
2268 gfx_line(im,
2269 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2270 gfx_line(im, X1, Y0, X1 + 2, Y0,
2271 GRIDWIDTH, im->graph_col[GRC_GRID]);
2272 gfx_dashed_line(im, X0 - 1, Y0,
2273 X1 + 1, Y0,
2274 GRIDWIDTH,
2275 im->
2276 graph_col[GRC_GRID],
2277 im->grid_dash_on, im->grid_dash_off);
2278 }
2279 }
2280 /* fancy minor gridlines */
2281 else if (exfrac > 1) {
2282 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2283 value = pow(10.0, i);
2284 if (value < im->minval)
2285 continue;
2286 Y0 = ytr(im, value);
2287 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2288 break;
2289 /* draw lines */
2290 gfx_line(im,
2291 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2292 gfx_line(im, X1, Y0, X1 + 2, Y0,
2293 GRIDWIDTH, im->graph_col[GRC_GRID]);
2294 gfx_dashed_line(im, X0 - 1, Y0,
2295 X1 + 1, Y0,
2296 GRIDWIDTH,
2297 im->
2298 graph_col[GRC_GRID],
2299 im->grid_dash_on, im->grid_dash_off);
2300 }
2301 }
2303 return 1;
2304 }
2307 void vertical_grid(
2308 image_desc_t *im)
2309 {
2310 int xlab_sel; /* which sort of label and grid ? */
2311 time_t ti, tilab, timajor;
2312 long factor;
2313 char graph_label[100];
2314 double X0, Y0, Y1; /* points for filled graph and more */
2315 struct tm tm;
2317 /* the type of time grid is determined by finding
2318 the number of seconds per pixel in the graph */
2319 if (im->xlab_user.minsec == -1) {
2320 factor = (im->end - im->start) / im->xsize;
2321 xlab_sel = 0;
2322 while (xlab[xlab_sel + 1].minsec !=
2323 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2324 xlab_sel++;
2325 } /* pick the last one */
2326 while (xlab[xlab_sel - 1].minsec ==
2327 xlab[xlab_sel].minsec
2328 && xlab[xlab_sel].length > (im->end - im->start)) {
2329 xlab_sel--;
2330 } /* go back to the smallest size */
2331 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2332 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2333 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2334 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2335 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2336 im->xlab_user.labst = xlab[xlab_sel].labst;
2337 im->xlab_user.precis = xlab[xlab_sel].precis;
2338 im->xlab_user.stst = xlab[xlab_sel].stst;
2339 }
2341 /* y coords are the same for every line ... */
2342 Y0 = im->yorigin;
2343 Y1 = im->yorigin - im->ysize;
2344 /* paint the minor grid */
2345 if (!(im->extra_flags & NOMINOR)) {
2346 for (ti = find_first_time(im->start,
2347 im->
2348 xlab_user.
2349 gridtm,
2350 im->
2351 xlab_user.
2352 gridst),
2353 timajor =
2354 find_first_time(im->start,
2355 im->xlab_user.
2356 mgridtm,
2357 im->xlab_user.
2358 mgridst);
2359 ti < im->end;
2360 ti =
2361 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2362 ) {
2363 /* are we inside the graph ? */
2364 if (ti < im->start || ti > im->end)
2365 continue;
2366 while (timajor < ti) {
2367 timajor = find_next_time(timajor,
2368 im->
2369 xlab_user.
2370 mgridtm, im->xlab_user.mgridst);
2371 }
2372 if (ti == timajor)
2373 continue; /* skip as falls on major grid line */
2374 X0 = xtr(im, ti);
2375 gfx_line(im, X0, Y1 - 2, X0, Y1,
2376 GRIDWIDTH, im->graph_col[GRC_GRID]);
2377 gfx_line(im, X0, Y0, X0, Y0 + 2,
2378 GRIDWIDTH, im->graph_col[GRC_GRID]);
2379 gfx_dashed_line(im, X0, Y0 + 1, X0,
2380 Y1 - 1, GRIDWIDTH,
2381 im->
2382 graph_col[GRC_GRID],
2383 im->grid_dash_on, im->grid_dash_off);
2384 }
2385 }
2387 /* paint the major grid */
2388 for (ti = find_first_time(im->start,
2389 im->
2390 xlab_user.
2391 mgridtm,
2392 im->
2393 xlab_user.
2394 mgridst);
2395 ti < im->end;
2396 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2397 ) {
2398 /* are we inside the graph ? */
2399 if (ti < im->start || ti > im->end)
2400 continue;
2401 X0 = xtr(im, ti);
2402 gfx_line(im, X0, Y1 - 2, X0, Y1,
2403 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2404 gfx_line(im, X0, Y0, X0, Y0 + 3,
2405 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2406 gfx_dashed_line(im, X0, Y0 + 3, X0,
2407 Y1 - 2, MGRIDWIDTH,
2408 im->
2409 graph_col
2410 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2411 }
2412 /* paint the labels below the graph */
2413 for (ti =
2414 find_first_time(im->start -
2415 im->xlab_user.
2416 precis / 2,
2417 im->xlab_user.
2418 labtm,
2419 im->xlab_user.
2420 labst);
2421 ti <=
2422 im->end -
2423 im->xlab_user.precis / 2;
2424 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2425 ) {
2426 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2427 /* are we inside the graph ? */
2428 if (tilab < im->start || tilab > im->end)
2429 continue;
2430 #if HAVE_STRFTIME
2431 localtime_r(&tilab, &tm);
2432 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2433 #else
2434 # error "your libc has no strftime I guess we'll abort the exercise here."
2435 #endif
2436 gfx_text(im,
2437 xtr(im, tilab),
2438 Y0 + 3,
2439 im->graph_col[GRC_FONT],
2440 im->
2441 text_prop[TEXT_PROP_AXIS].
2442 font_desc,
2443 im->tabwidth, 0.0,
2444 GFX_H_CENTER, GFX_V_TOP, graph_label);
2445 }
2447 }
2450 void axis_paint(
2451 image_desc_t *im)
2452 {
2453 /* draw x and y axis */
2454 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2455 im->xorigin+im->xsize,im->yorigin-im->ysize,
2456 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2458 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2459 im->xorigin+im->xsize,im->yorigin-im->ysize,
2460 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2462 gfx_line(im, im->xorigin - 4,
2463 im->yorigin,
2464 im->xorigin + im->xsize +
2465 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2466 gfx_line(im, im->xorigin,
2467 im->yorigin + 4,
2468 im->xorigin,
2469 im->yorigin - im->ysize -
2470 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2471 /* arrow for X and Y axis direction */
2472 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 */
2473 im->graph_col[GRC_ARROW]);
2474 gfx_close_path(im);
2475 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 */
2476 im->graph_col[GRC_ARROW]);
2477 gfx_close_path(im);
2478 }
2480 void grid_paint(
2481 image_desc_t *im)
2482 {
2483 long i;
2484 int res = 0;
2485 double X0, Y0; /* points for filled graph and more */
2486 struct gfx_color_t water_color;
2488 /* draw 3d border */
2489 gfx_new_area(im, 0, im->yimg,
2490 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2491 gfx_add_point(im, im->ximg - 2, 2);
2492 gfx_add_point(im, im->ximg, 0);
2493 gfx_add_point(im, 0, 0);
2494 gfx_close_path(im);
2495 gfx_new_area(im, 2, im->yimg - 2,
2496 im->ximg - 2,
2497 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2498 gfx_add_point(im, im->ximg, 0);
2499 gfx_add_point(im, im->ximg, im->yimg);
2500 gfx_add_point(im, 0, im->yimg);
2501 gfx_close_path(im);
2502 if (im->draw_x_grid == 1)
2503 vertical_grid(im);
2504 if (im->draw_y_grid == 1) {
2505 if (im->logarithmic) {
2506 res = horizontal_log_grid(im);
2507 } else {
2508 res = draw_horizontal_grid(im);
2509 }
2511 /* dont draw horizontal grid if there is no min and max val */
2512 if (!res) {
2513 char *nodata = "No Data found";
2515 gfx_text(im, im->ximg / 2,
2516 (2 * im->yorigin -
2517 im->ysize) / 2,
2518 im->graph_col[GRC_FONT],
2519 im->
2520 text_prop[TEXT_PROP_AXIS].
2521 font_desc,
2522 im->tabwidth, 0.0,
2523 GFX_H_CENTER, GFX_V_CENTER, nodata);
2524 }
2525 }
2527 /* yaxis unit description */
2528 gfx_text(im,
2529 10,
2530 (im->yorigin -
2531 im->ysize / 2),
2532 im->graph_col[GRC_FONT],
2533 im->
2534 text_prop[TEXT_PROP_UNIT].
2535 font_desc,
2536 im->tabwidth,
2537 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2538 /* graph title */
2539 gfx_text(im,
2540 im->ximg / 2, 6,
2541 im->graph_col[GRC_FONT],
2542 im->
2543 text_prop[TEXT_PROP_TITLE].
2544 font_desc,
2545 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2546 /* rrdtool 'logo' */
2547 water_color = im->graph_col[GRC_FONT];
2548 water_color.alpha = 0.3;
2549 gfx_text(im, im->ximg - 4, 5,
2550 water_color,
2551 im->
2552 text_prop[TEXT_PROP_WATERMARK].
2553 font_desc, im->tabwidth,
2554 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2555 /* graph watermark */
2556 if (im->watermark[0] != '\0') {
2557 gfx_text(im,
2558 im->ximg / 2, im->yimg - 6,
2559 water_color,
2560 im->
2561 text_prop[TEXT_PROP_WATERMARK].
2562 font_desc, im->tabwidth, 0,
2563 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2564 }
2566 /* graph labels */
2567 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2568 for (i = 0; i < im->gdes_c; i++) {
2569 if (im->gdes[i].legend[0] == '\0')
2570 continue;
2571 /* im->gdes[i].leg_y is the bottom of the legend */
2572 X0 = im->gdes[i].leg_x;
2573 Y0 = im->gdes[i].leg_y;
2574 gfx_text(im, X0, Y0,
2575 im->graph_col[GRC_FONT],
2576 im->
2577 text_prop
2578 [TEXT_PROP_LEGEND].font_desc,
2579 im->tabwidth, 0.0,
2580 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2581 /* The legend for GRAPH items starts with "M " to have
2582 enough space for the box */
2583 if (im->gdes[i].gf != GF_PRINT &&
2584 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2585 double boxH, boxV;
2586 double X1, Y1;
2588 boxH = gfx_get_text_width(im, 0,
2589 im->
2590 text_prop
2591 [TEXT_PROP_LEGEND].
2592 font_desc,
2593 im->tabwidth, "o") * 1.2;
2594 boxV = boxH;
2595 /* shift the box up a bit */
2596 Y0 -= boxV * 0.4;
2597 /* make sure transparent colors show up the same way as in the graph */
2598 gfx_new_area(im,
2599 X0, Y0 - boxV,
2600 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2601 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2602 gfx_close_path(im);
2603 gfx_new_area(im, X0, Y0 - boxV, X0,
2604 Y0, X0 + boxH, Y0, im->gdes[i].col);
2605 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2606 gfx_close_path(im);
2607 cairo_save(im->cr);
2608 cairo_new_path(im->cr);
2609 cairo_set_line_width(im->cr, 1.0);
2610 X1 = X0 + boxH;
2611 Y1 = Y0 - boxV;
2612 gfx_line_fit(im, &X0, &Y0);
2613 gfx_line_fit(im, &X1, &Y1);
2614 cairo_move_to(im->cr, X0, Y0);
2615 cairo_line_to(im->cr, X1, Y0);
2616 cairo_line_to(im->cr, X1, Y1);
2617 cairo_line_to(im->cr, X0, Y1);
2618 cairo_close_path(im->cr);
2619 cairo_set_source_rgba(im->cr,
2620 im->
2621 graph_col
2622 [GRC_FRAME].
2623 red,
2624 im->
2625 graph_col
2626 [GRC_FRAME].
2627 green,
2628 im->
2629 graph_col
2630 [GRC_FRAME].
2631 blue, im->graph_col[GRC_FRAME].alpha);
2632 if (im->gdes[i].dash) {
2633 /* make box borders in legend dashed if the graph is dashed */
2634 double dashes[] = {
2635 3.0
2636 };
2637 cairo_set_dash(im->cr, dashes, 1, 0.0);
2638 }
2639 cairo_stroke(im->cr);
2640 cairo_restore(im->cr);
2641 }
2642 }
2643 }
2644 }
2647 /*****************************************************
2648 * lazy check make sure we rely need to create this graph
2649 *****************************************************/
2651 int lazy_check(
2652 image_desc_t *im)
2653 {
2654 FILE *fd = NULL;
2655 int size = 1;
2656 struct stat imgstat;
2658 if (im->lazy == 0)
2659 return 0; /* no lazy option */
2660 if (strlen(im->graphfile) == 0)
2661 return 0; /* inmemory option */
2662 if (stat(im->graphfile, &imgstat) != 0)
2663 return 0; /* can't stat */
2664 /* one pixel in the existing graph is more then what we would
2665 change here ... */
2666 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2667 return 0;
2668 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2669 return 0; /* the file does not exist */
2670 switch (im->imgformat) {
2671 case IF_PNG:
2672 size = PngSize(fd, &(im->ximg), &(im->yimg));
2673 break;
2674 default:
2675 size = 1;
2676 }
2677 fclose(fd);
2678 return size;
2679 }
2682 int graph_size_location(
2683 image_desc_t
2684 *im,
2685 int elements)
2686 {
2687 /* The actual size of the image to draw is determined from
2688 ** several sources. The size given on the command line is
2689 ** the graph area but we need more as we have to draw labels
2690 ** and other things outside the graph area
2691 */
2693 int Xvertical = 0, Ytitle =
2694 0, Xylabel = 0, Xmain = 0, Ymain =
2695 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2697 if (im->extra_flags & ONLY_GRAPH) {
2698 im->xorigin = 0;
2699 im->ximg = im->xsize;
2700 im->yimg = im->ysize;
2701 im->yorigin = im->ysize;
2702 ytr(im, DNAN);
2703 return 0;
2704 }
2706 /** +---+--------------------------------------------+
2707 ** | y |...............graph title..................|
2708 ** | +---+-------------------------------+--------+
2709 ** | a | y | | |
2710 ** | x | | | |
2711 ** | i | a | | pie |
2712 ** | s | x | main graph area | chart |
2713 ** | | i | | area |
2714 ** | t | s | | |
2715 ** | i | | | |
2716 ** | t | l | | |
2717 ** | l | b +-------------------------------+--------+
2718 ** | e | l | x axis labels | |
2719 ** +---+---+-------------------------------+--------+
2720 ** |....................legends.....................|
2721 ** +------------------------------------------------+
2722 ** | watermark |
2723 ** +------------------------------------------------+
2724 */
2726 if (im->ylegend[0] != '\0') {
2727 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2728 }
2730 if (im->title[0] != '\0') {
2731 /* The title is placed "inbetween" two text lines so it
2732 ** automatically has some vertical spacing. The horizontal
2733 ** spacing is added here, on each side.
2734 */
2735 /* if necessary, reduce the font size of the title until it fits the image width */
2736 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2737 }
2739 if (elements) {
2740 if (im->draw_x_grid) {
2741 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2742 }
2743 if (im->draw_y_grid || im->forceleftspace) {
2744 Xylabel =
2745 gfx_get_text_width(im, 0,
2746 im->
2747 text_prop
2748 [TEXT_PROP_AXIS].
2749 font_desc,
2750 im->tabwidth, "0") * im->unitslength;
2751 }
2752 }
2754 if (im->extra_flags & FULL_SIZE_MODE) {
2755 /* The actual size of the image to draw has been determined by the user.
2756 ** The graph area is the space remaining after accounting for the legend,
2757 ** the watermark, the pie chart, the axis labels, and the title.
2758 */
2759 im->xorigin = 0;
2760 im->ximg = im->xsize;
2761 im->yimg = im->ysize;
2762 im->yorigin = im->ysize;
2763 Xmain = im->ximg;
2764 Ymain = im->yimg;
2765 im->yorigin += Ytitle;
2766 /* Now calculate the total size. Insert some spacing where
2767 desired. im->xorigin and im->yorigin need to correspond
2768 with the lower left corner of the main graph area or, if
2769 this one is not set, the imaginary box surrounding the
2770 pie chart area. */
2771 /* Initial size calculation for the main graph area */
2772 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2773 if (Xmain)
2774 Xmain -= Xspacing; /* put space between main graph area and right edge */
2775 im->xorigin = Xspacing + Xylabel;
2776 /* the length of the title should not influence with width of the graph
2777 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2778 if (Xvertical) { /* unit description */
2779 Xmain -= Xvertical;
2780 im->xorigin += Xvertical;
2781 }
2782 im->xsize = Xmain;
2783 xtr(im, 0);
2784 /* The vertical size of the image is known in advance. The main graph area
2785 ** (Ymain) and im->yorigin must be set according to the space requirements
2786 ** of the legend and the axis labels.
2787 */
2788 if (im->extra_flags & NOLEGEND) {
2789 /* set dimensions correctly if using full size mode with no legend */
2790 im->yorigin =
2791 im->yimg -
2792 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2793 Ymain = im->yorigin;
2794 } else {
2795 /* Determine where to place the legends onto the image.
2796 ** Set Ymain and adjust im->yorigin to match the space requirements.
2797 */
2798 if (leg_place(im, &Ymain) == -1)
2799 return -1;
2800 }
2803 /* remove title space *or* some padding above the graph from the main graph area */
2804 if (Ytitle) {
2805 Ymain -= Ytitle;
2806 } else {
2807 Ymain -= 1.5 * Yspacing;
2808 }
2810 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2811 if (im->watermark[0] != '\0') {
2812 Ymain -= Ywatermark;
2813 }
2815 im->ysize = Ymain;
2816 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2818 /* The actual size of the image to draw is determined from
2819 ** several sources. The size given on the command line is
2820 ** the graph area but we need more as we have to draw labels
2821 ** and other things outside the graph area.
2822 */
2824 if (im->ylegend[0] != '\0') {
2825 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2826 }
2829 if (im->title[0] != '\0') {
2830 /* The title is placed "inbetween" two text lines so it
2831 ** automatically has some vertical spacing. The horizontal
2832 ** spacing is added here, on each side.
2833 */
2834 /* don't care for the with of the title
2835 Xtitle = gfx_get_text_width(im->canvas, 0,
2836 im->text_prop[TEXT_PROP_TITLE].font,
2837 im->text_prop[TEXT_PROP_TITLE].size,
2838 im->tabwidth,
2839 im->title, 0) + 2*Xspacing; */
2840 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2841 }
2843 if (elements) {
2844 Xmain = im->xsize;
2845 Ymain = im->ysize;
2846 }
2847 /* Now calculate the total size. Insert some spacing where
2848 desired. im->xorigin and im->yorigin need to correspond
2849 with the lower left corner of the main graph area or, if
2850 this one is not set, the imaginary box surrounding the
2851 pie chart area. */
2853 /* The legend width cannot yet be determined, as a result we
2854 ** have problems adjusting the image to it. For now, we just
2855 ** forget about it at all; the legend will have to fit in the
2856 ** size already allocated.
2857 */
2858 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2859 if (Xmain)
2860 im->ximg += Xspacing;
2861 im->xorigin = Xspacing + Xylabel;
2862 /* the length of the title should not influence with width of the graph
2863 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2864 if (Xvertical) { /* unit description */
2865 im->ximg += Xvertical;
2866 im->xorigin += Xvertical;
2867 }
2868 xtr(im, 0);
2869 /* The vertical size is interesting... we need to compare
2870 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2871 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2872 ** in order to start even thinking about Ylegend or Ywatermark.
2873 **
2874 ** Do it in three portions: First calculate the inner part,
2875 ** then do the legend, then adjust the total height of the img,
2876 ** adding space for a watermark if one exists;
2877 */
2878 /* reserve space for main and/or pie */
2879 im->yimg = Ymain + Yxlabel;
2880 im->yorigin = im->yimg - Yxlabel;
2881 /* reserve space for the title *or* some padding above the graph */
2882 if (Ytitle) {
2883 im->yimg += Ytitle;
2884 im->yorigin += Ytitle;
2885 } else {
2886 im->yimg += 1.5 * Yspacing;
2887 im->yorigin += 1.5 * Yspacing;
2888 }
2889 /* reserve space for padding below the graph */
2890 im->yimg += Yspacing;
2891 /* Determine where to place the legends onto the image.
2892 ** Adjust im->yimg to match the space requirements.
2893 */
2894 if (leg_place(im, 0) == -1)
2895 return -1;
2896 if (im->watermark[0] != '\0') {
2897 im->yimg += Ywatermark;
2898 }
2899 }
2901 ytr(im, DNAN);
2902 return 0;
2903 }
2905 static cairo_status_t cairo_output(
2906 void *closure,
2907 const unsigned char
2908 *data,
2909 unsigned int length)
2910 {
2911 image_desc_t *im = closure;
2913 im->rendered_image =
2914 realloc(im->rendered_image, im->rendered_image_size + length);
2915 if (im->rendered_image == NULL)
2916 return CAIRO_STATUS_WRITE_ERROR;
2917 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2918 im->rendered_image_size += length;
2919 return CAIRO_STATUS_SUCCESS;
2920 }
2922 /* draw that picture thing ... */
2923 int graph_paint(
2924 image_desc_t *im)
2925 {
2926 int i, ii;
2927 int lazy = lazy_check(im);
2928 double areazero = 0.0;
2929 graph_desc_t *lastgdes = NULL;
2930 rrd_infoval_t info;
2932 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
2934 /* if we want and can be lazy ... quit now */
2935 if (lazy) {
2936 info.u_cnt = im->ximg;
2937 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2938 info.u_cnt = im->yimg;
2939 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2940 return 0;
2941 }
2942 /* pull the data from the rrd files ... */
2943 if (data_fetch(im) == -1)
2944 return -1;
2945 /* evaluate VDEF and CDEF operations ... */
2946 if (data_calc(im) == -1)
2947 return -1;
2948 /* calculate and PRINT and GPRINT definitions. We have to do it at
2949 * this point because it will affect the length of the legends
2950 * if there are no graph elements (i==0) we stop here ...
2951 * if we are lazy, try to quit ...
2952 */
2953 i = print_calc(im);
2954 if (i < 0)
2955 return -1;
2957 if ((i == 0) || lazy)
2958 return 0;
2960 /**************************************************************
2961 *** Calculating sizes and locations became a bit confusing ***
2962 *** so I moved this into a separate function. ***
2963 **************************************************************/
2964 if (graph_size_location(im, i) == -1)
2965 return -1;
2967 info.u_cnt = im->xorigin;
2968 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2969 info.u_cnt = im->yorigin - im->ysize;
2970 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2971 info.u_cnt = im->xsize;
2972 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2973 info.u_cnt = im->ysize;
2974 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2975 info.u_cnt = im->ximg;
2976 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2977 info.u_cnt = im->yimg;
2978 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2980 /* get actual drawing data and find min and max values */
2981 if (data_proc(im) == -1)
2982 return -1;
2983 if (!im->logarithmic) {
2984 si_unit(im);
2985 }
2987 /* identify si magnitude Kilo, Mega Giga ? */
2988 if (!im->rigid && !im->logarithmic)
2989 expand_range(im); /* make sure the upper and lower limit are
2990 sensible values */
2992 info.u_val = im->minval;
2993 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
2994 info.u_val = im->maxval;
2995 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
2997 if (!calc_horizontal_grid(im))
2998 return -1;
2999 /* reset precalc */
3000 ytr(im, DNAN);
3001 /* if (im->gridfit)
3002 apply_gridfit(im); */
3003 /* the actual graph is created by going through the individual
3004 graph elements and then drawing them */
3005 cairo_surface_destroy(im->surface);
3006 switch (im->imgformat) {
3007 case IF_PNG:
3008 im->surface =
3009 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3010 im->ximg * im->zoom,
3011 im->yimg * im->zoom);
3012 break;
3013 case IF_PDF:
3014 im->gridfit = 0;
3015 im->surface = strlen(im->graphfile)
3016 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3017 im->yimg * im->zoom)
3018 : cairo_pdf_surface_create_for_stream
3019 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3020 break;
3021 case IF_EPS:
3022 im->gridfit = 0;
3023 im->surface = strlen(im->graphfile)
3024 ?
3025 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3026 im->yimg * im->zoom)
3027 : cairo_ps_surface_create_for_stream
3028 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3029 break;
3030 case IF_SVG:
3031 im->gridfit = 0;
3032 im->surface = strlen(im->graphfile)
3033 ?
3034 cairo_svg_surface_create(im->
3035 graphfile,
3036 im->ximg * im->zoom, im->yimg * im->zoom)
3037 : cairo_svg_surface_create_for_stream
3038 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3039 cairo_svg_surface_restrict_to_version
3040 (im->surface, CAIRO_SVG_VERSION_1_1);
3041 break;
3042 };
3043 cairo_destroy(im->cr);
3044 im->cr = cairo_create(im->surface);
3045 cairo_set_antialias(im->cr, im->graph_antialias);
3046 cairo_scale(im->cr, im->zoom, im->zoom);
3047 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3048 gfx_new_area(im, 0, 0, 0, im->yimg,
3049 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3050 gfx_add_point(im, im->ximg, 0);
3051 gfx_close_path(im);
3052 gfx_new_area(im, im->xorigin,
3053 im->yorigin,
3054 im->xorigin +
3055 im->xsize, im->yorigin,
3056 im->xorigin +
3057 im->xsize,
3058 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3059 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3060 gfx_close_path(im);
3061 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3062 im->xsize, im->ysize + 2.0);
3063 cairo_clip(im->cr);
3064 if (im->minval > 0.0)
3065 areazero = im->minval;
3066 if (im->maxval < 0.0)
3067 areazero = im->maxval;
3068 for (i = 0; i < im->gdes_c; i++) {
3069 switch (im->gdes[i].gf) {
3070 case GF_CDEF:
3071 case GF_VDEF:
3072 case GF_DEF:
3073 case GF_PRINT:
3074 case GF_GPRINT:
3075 case GF_COMMENT:
3076 case GF_TEXTALIGN:
3077 case GF_HRULE:
3078 case GF_VRULE:
3079 case GF_XPORT:
3080 case GF_SHIFT:
3081 break;
3082 case GF_TICK:
3083 for (ii = 0; ii < im->xsize; ii++) {
3084 if (!isnan(im->gdes[i].p_data[ii])
3085 && im->gdes[i].p_data[ii] != 0.0) {
3086 if (im->gdes[i].yrule > 0) {
3087 gfx_line(im,
3088 im->xorigin + ii,
3089 im->yorigin,
3090 im->xorigin + ii,
3091 im->yorigin -
3092 im->gdes[i].yrule *
3093 im->ysize, 1.0, im->gdes[i].col);
3094 } else if (im->gdes[i].yrule < 0) {
3095 gfx_line(im,
3096 im->xorigin + ii,
3097 im->yorigin - im->ysize,
3098 im->xorigin + ii,
3099 im->yorigin - (1 -
3100 im->gdes[i].
3101 yrule) *
3102 im->ysize, 1.0, im->gdes[i].col);
3103 }
3104 }
3105 }
3106 break;
3107 case GF_LINE:
3108 case GF_AREA:
3109 /* fix data points at oo and -oo */
3110 for (ii = 0; ii < im->xsize; ii++) {
3111 if (isinf(im->gdes[i].p_data[ii])) {
3112 if (im->gdes[i].p_data[ii] > 0) {
3113 im->gdes[i].p_data[ii] = im->maxval;
3114 } else {
3115 im->gdes[i].p_data[ii] = im->minval;
3116 }
3118 }
3119 } /* for */
3121 /* *******************************************************
3122 a ___. (a,t)
3123 | | ___
3124 ____| | | |
3125 | |___|
3126 -------|--t-1--t--------------------------------
3128 if we know the value at time t was a then
3129 we draw a square from t-1 to t with the value a.
3131 ********************************************************* */
3132 if (im->gdes[i].col.alpha != 0.0) {
3133 /* GF_LINE and friend */
3134 if (im->gdes[i].gf == GF_LINE) {
3135 double last_y = 0.0;
3136 int draw_on = 0;
3138 cairo_save(im->cr);
3139 cairo_new_path(im->cr);
3140 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3141 if (im->gdes[i].dash) {
3142 cairo_set_dash(im->cr,
3143 im->gdes[i].p_dashes,
3144 im->gdes[i].ndash, im->gdes[i].offset);
3145 }
3147 for (ii = 1; ii < im->xsize; ii++) {
3148 if (isnan(im->gdes[i].p_data[ii])
3149 || (im->slopemode == 1
3150 && isnan(im->gdes[i].p_data[ii - 1]))) {
3151 draw_on = 0;
3152 continue;
3153 }
3154 if (draw_on == 0) {
3155 last_y = ytr(im, im->gdes[i].p_data[ii]);
3156 if (im->slopemode == 0) {
3157 double x = ii - 1 + im->xorigin;
3158 double y = last_y;
3160 gfx_line_fit(im, &x, &y);
3161 cairo_move_to(im->cr, x, y);
3162 x = ii + im->xorigin;
3163 y = last_y;
3164 gfx_line_fit(im, &x, &y);
3165 cairo_line_to(im->cr, x, y);
3166 } else {
3167 double x = ii - 1 + im->xorigin;
3168 double y =
3169 ytr(im, im->gdes[i].p_data[ii - 1]);
3170 gfx_line_fit(im, &x, &y);
3171 cairo_move_to(im->cr, x, y);
3172 x = ii + im->xorigin;
3173 y = last_y;
3174 gfx_line_fit(im, &x, &y);
3175 cairo_line_to(im->cr, x, y);
3176 }
3177 draw_on = 1;
3178 } else {
3179 double x1 = ii + im->xorigin;
3180 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3182 if (im->slopemode == 0
3183 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3184 double x = ii - 1 + im->xorigin;
3185 double y = y1;
3187 gfx_line_fit(im, &x, &y);
3188 cairo_line_to(im->cr, x, y);
3189 };
3190 last_y = y1;
3191 gfx_line_fit(im, &x1, &y1);
3192 cairo_line_to(im->cr, x1, y1);
3193 };
3194 }
3195 cairo_set_source_rgba(im->cr,
3196 im->gdes[i].
3197 col.red,
3198 im->gdes[i].
3199 col.green,
3200 im->gdes[i].
3201 col.blue, im->gdes[i].col.alpha);
3202 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3203 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3204 cairo_stroke(im->cr);
3205 cairo_restore(im->cr);
3206 } else {
3207 int idxI = -1;
3208 double *foreY =
3209 (double *) malloc(sizeof(double) * im->xsize * 2);
3210 double *foreX =
3211 (double *) malloc(sizeof(double) * im->xsize * 2);
3212 double *backY =
3213 (double *) malloc(sizeof(double) * im->xsize * 2);
3214 double *backX =
3215 (double *) malloc(sizeof(double) * im->xsize * 2);
3216 int drawem = 0;
3218 for (ii = 0; ii <= im->xsize; ii++) {
3219 double ybase, ytop;
3221 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3222 int cntI = 1;
3223 int lastI = 0;
3225 while (cntI < idxI
3226 &&
3227 AlmostEqual2sComplement(foreY
3228 [lastI],
3229 foreY[cntI], 4)
3230 &&
3231 AlmostEqual2sComplement(foreY
3232 [lastI],
3233 foreY
3234 [cntI + 1], 4)) {
3235 cntI++;
3236 }
3237 gfx_new_area(im,
3238 backX[0], backY[0],
3239 foreX[0], foreY[0],
3240 foreX[cntI],
3241 foreY[cntI], im->gdes[i].col);
3242 while (cntI < idxI) {
3243 lastI = cntI;
3244 cntI++;
3245 while (cntI < idxI
3246 &&
3247 AlmostEqual2sComplement(foreY
3248 [lastI],
3249 foreY[cntI], 4)
3250 &&
3251 AlmostEqual2sComplement(foreY
3252 [lastI],
3253 foreY
3254 [cntI
3255 + 1], 4)) {
3256 cntI++;
3257 }
3258 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3259 }
3260 gfx_add_point(im, backX[idxI], backY[idxI]);
3261 while (idxI > 1) {
3262 lastI = idxI;
3263 idxI--;
3264 while (idxI > 1
3265 &&
3266 AlmostEqual2sComplement(backY
3267 [lastI],
3268 backY[idxI], 4)
3269 &&
3270 AlmostEqual2sComplement(backY
3271 [lastI],
3272 backY
3273 [idxI
3274 - 1], 4)) {
3275 idxI--;
3276 }
3277 gfx_add_point(im, backX[idxI], backY[idxI]);
3278 }
3279 idxI = -1;
3280 drawem = 0;
3281 gfx_close_path(im);
3282 }
3283 if (drawem != 0) {
3284 drawem = 0;
3285 idxI = -1;
3286 }
3287 if (ii == im->xsize)
3288 break;
3289 if (im->slopemode == 0 && ii == 0) {
3290 continue;
3291 }
3292 if (isnan(im->gdes[i].p_data[ii])) {
3293 drawem = 1;
3294 continue;
3295 }
3296 ytop = ytr(im, im->gdes[i].p_data[ii]);
3297 if (lastgdes && im->gdes[i].stack) {
3298 ybase = ytr(im, lastgdes->p_data[ii]);
3299 } else {
3300 ybase = ytr(im, areazero);
3301 }
3302 if (ybase == ytop) {
3303 drawem = 1;
3304 continue;
3305 }
3307 if (ybase > ytop) {
3308 double extra = ytop;
3310 ytop = ybase;
3311 ybase = extra;
3312 }
3313 if (im->slopemode == 0) {
3314 backY[++idxI] = ybase - 0.2;
3315 backX[idxI] = ii + im->xorigin - 1;
3316 foreY[idxI] = ytop + 0.2;
3317 foreX[idxI] = ii + im->xorigin - 1;
3318 }
3319 backY[++idxI] = ybase - 0.2;
3320 backX[idxI] = ii + im->xorigin;
3321 foreY[idxI] = ytop + 0.2;
3322 foreX[idxI] = ii + im->xorigin;
3323 }
3324 /* close up any remaining area */
3325 free(foreY);
3326 free(foreX);
3327 free(backY);
3328 free(backX);
3329 } /* else GF_LINE */
3330 }
3331 /* if color != 0x0 */
3332 /* make sure we do not run into trouble when stacking on NaN */
3333 for (ii = 0; ii < im->xsize; ii++) {
3334 if (isnan(im->gdes[i].p_data[ii])) {
3335 if (lastgdes && (im->gdes[i].stack)) {
3336 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3337 } else {
3338 im->gdes[i].p_data[ii] = areazero;
3339 }
3340 }
3341 }
3342 lastgdes = &(im->gdes[i]);
3343 break;
3344 case GF_STACK:
3345 rrd_set_error
3346 ("STACK should already be turned into LINE or AREA here");
3347 return -1;
3348 break;
3349 } /* switch */
3350 }
3351 cairo_reset_clip(im->cr);
3353 /* grid_paint also does the text */
3354 if (!(im->extra_flags & ONLY_GRAPH))
3355 grid_paint(im);
3356 if (!(im->extra_flags & ONLY_GRAPH))
3357 axis_paint(im);
3358 /* the RULES are the last thing to paint ... */
3359 for (i = 0; i < im->gdes_c; i++) {
3361 switch (im->gdes[i].gf) {
3362 case GF_HRULE:
3363 if (im->gdes[i].yrule >= im->minval
3364 && im->gdes[i].yrule <= im->maxval) {
3365 cairo_save(im->cr);
3366 if (im->gdes[i].dash) {
3367 cairo_set_dash(im->cr,
3368 im->gdes[i].p_dashes,
3369 im->gdes[i].ndash, im->gdes[i].offset);
3370 }
3371 gfx_line(im, im->xorigin,
3372 ytr(im, im->gdes[i].yrule),
3373 im->xorigin + im->xsize,
3374 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3375 cairo_stroke(im->cr);
3376 cairo_restore(im->cr);
3377 }
3378 break;
3379 case GF_VRULE:
3380 if (im->gdes[i].xrule >= im->start
3381 && im->gdes[i].xrule <= im->end) {
3382 cairo_save(im->cr);
3383 if (im->gdes[i].dash) {
3384 cairo_set_dash(im->cr,
3385 im->gdes[i].p_dashes,
3386 im->gdes[i].ndash, im->gdes[i].offset);
3387 }
3388 gfx_line(im,
3389 xtr(im, im->gdes[i].xrule),
3390 im->yorigin, xtr(im,
3391 im->
3392 gdes[i].
3393 xrule),
3394 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3395 cairo_stroke(im->cr);
3396 cairo_restore(im->cr);
3397 }
3398 break;
3399 default:
3400 break;
3401 }
3402 }
3405 switch (im->imgformat) {
3406 case IF_PNG:
3407 {
3408 cairo_status_t status;
3410 status = strlen(im->graphfile) ?
3411 cairo_surface_write_to_png(im->surface, im->graphfile)
3412 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3413 im);
3415 if (status != CAIRO_STATUS_SUCCESS) {
3416 rrd_set_error("Could not save png to '%s'", im->graphfile);
3417 return 1;
3418 }
3419 break;
3420 }
3421 default:
3422 if (strlen(im->graphfile)) {
3423 cairo_show_page(im->cr);
3424 } else {
3425 cairo_surface_finish(im->surface);
3426 }
3427 break;
3428 }
3430 return 0;
3431 }
3434 /*****************************************************
3435 * graph stuff
3436 *****************************************************/
3438 int gdes_alloc(
3439 image_desc_t *im)
3440 {
3442 im->gdes_c++;
3443 if ((im->gdes = (graph_desc_t *)
3444 rrd_realloc(im->gdes, (im->gdes_c)
3445 * sizeof(graph_desc_t))) == NULL) {
3446 rrd_set_error("realloc graph_descs");
3447 return -1;
3448 }
3451 im->gdes[im->gdes_c - 1].step = im->step;
3452 im->gdes[im->gdes_c - 1].step_orig = im->step;
3453 im->gdes[im->gdes_c - 1].stack = 0;
3454 im->gdes[im->gdes_c - 1].linewidth = 0;
3455 im->gdes[im->gdes_c - 1].debug = 0;
3456 im->gdes[im->gdes_c - 1].start = im->start;
3457 im->gdes[im->gdes_c - 1].start_orig = im->start;
3458 im->gdes[im->gdes_c - 1].end = im->end;
3459 im->gdes[im->gdes_c - 1].end_orig = im->end;
3460 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3461 im->gdes[im->gdes_c - 1].data = NULL;
3462 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3463 im->gdes[im->gdes_c - 1].data_first = 0;
3464 im->gdes[im->gdes_c - 1].p_data = NULL;
3465 im->gdes[im->gdes_c - 1].rpnp = NULL;
3466 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3467 im->gdes[im->gdes_c - 1].shift = 0.0;
3468 im->gdes[im->gdes_c - 1].dash = 0;
3469 im->gdes[im->gdes_c - 1].ndash = 0;
3470 im->gdes[im->gdes_c - 1].offset = 0;
3471 im->gdes[im->gdes_c - 1].col.red = 0.0;
3472 im->gdes[im->gdes_c - 1].col.green = 0.0;
3473 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3474 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3475 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3476 im->gdes[im->gdes_c - 1].format[0] = '\0';
3477 im->gdes[im->gdes_c - 1].strftm = 0;
3478 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3479 im->gdes[im->gdes_c - 1].ds = -1;
3480 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3481 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3482 im->gdes[im->gdes_c - 1].yrule = DNAN;
3483 im->gdes[im->gdes_c - 1].xrule = 0;
3484 return 0;
3485 }
3487 /* copies input untill the first unescaped colon is found
3488 or until input ends. backslashes have to be escaped as well */
3489 int scan_for_col(
3490 const char *const input,
3491 int len,
3492 char *const output)
3493 {
3494 int inp, outp = 0;
3496 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3497 if (input[inp] == '\\'
3498 && input[inp + 1] != '\0'
3499 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3500 output[outp++] = input[++inp];
3501 } else {
3502 output[outp++] = input[inp];
3503 }
3504 }
3505 output[outp] = '\0';
3506 return inp;
3507 }
3509 /* Now just a wrapper around rrd_graph_v */
3510 int rrd_graph(
3511 int argc,
3512 char **argv,
3513 char ***prdata,
3514 int *xsize,
3515 int *ysize,
3516 FILE * stream,
3517 double *ymin,
3518 double *ymax)
3519 {
3520 int prlines = 0;
3521 rrd_info_t *grinfo = NULL;
3522 rrd_info_t *walker;
3524 grinfo = rrd_graph_v(argc, argv);
3525 if (grinfo == NULL)
3526 return -1;
3527 walker = grinfo;
3528 (*prdata) = NULL;
3529 while (walker) {
3530 if (strcmp(walker->key, "image_info") == 0) {
3531 prlines++;
3532 if (((*prdata) =
3533 rrd_realloc((*prdata),
3534 (prlines + 1) * sizeof(char *))) == NULL) {
3535 rrd_set_error("realloc prdata");
3536 return 0;
3537 }
3538 /* imginfo goes to position 0 in the prdata array */
3539 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3540 + 2) * sizeof(char));
3541 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3542 (*prdata)[prlines] = NULL;
3543 }
3544 /* skip anything else */
3545 walker = walker->next;
3546 }
3547 walker = grinfo;
3548 *xsize = 0;
3549 *ysize = 0;
3550 *ymin = 0;
3551 *ymax = 0;
3552 while (walker) {
3553 if (strcmp(walker->key, "image_width") == 0) {
3554 *xsize = walker->value.u_int;
3555 } else if (strcmp(walker->key, "image_height") == 0) {
3556 *ysize = walker->value.u_int;
3557 } else if (strcmp(walker->key, "value_min") == 0) {
3558 *ymin = walker->value.u_val;
3559 } else if (strcmp(walker->key, "value_max") == 0) {
3560 *ymax = walker->value.u_val;
3561 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3562 prlines++;
3563 if (((*prdata) =
3564 rrd_realloc((*prdata),
3565 (prlines + 1) * sizeof(char *))) == NULL) {
3566 rrd_set_error("realloc prdata");
3567 return 0;
3568 }
3569 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3570 + 2) * sizeof(char));
3571 (*prdata)[prlines] = NULL;
3572 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3573 } else if (strcmp(walker->key, "image") == 0) {
3574 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3575 (stream ? stream : stdout));
3576 }
3577 /* skip anything else */
3578 walker = walker->next;
3579 }
3580 rrd_info_free(grinfo);
3581 return 0;
3582 }
3585 /* Some surgery done on this function, it became ridiculously big.
3586 ** Things moved:
3587 ** - initializing now in rrd_graph_init()
3588 ** - options parsing now in rrd_graph_options()
3589 ** - script parsing now in rrd_graph_script()
3590 */
3591 rrd_info_t *rrd_graph_v(
3592 int argc,
3593 char **argv)
3594 {
3595 image_desc_t im;
3596 rrd_info_t *grinfo;
3597 rrd_graph_init(&im);
3598 /* a dummy surface so that we can measure text sizes for placements */
3600 rrd_graph_options(argc, argv, &im);
3601 if (rrd_test_error()) {
3602 rrd_info_free(im.grinfo);
3603 im_free(&im);
3604 return NULL;
3605 }
3607 if (optind >= argc) {
3608 rrd_info_free(im.grinfo);
3609 im_free(&im);
3610 rrd_set_error("missing filename");
3611 return NULL;
3612 }
3614 if (strlen(argv[optind]) >= MAXPATH) {
3615 rrd_set_error("filename (including path) too long");
3616 rrd_info_free(im.grinfo);
3617 im_free(&im);
3618 return NULL;
3619 }
3621 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3622 im.graphfile[MAXPATH - 1] = '\0';
3624 if (strcmp(im.graphfile, "-") == 0) {
3625 im.graphfile[0] = '\0';
3626 }
3628 rrd_graph_script(argc, argv, &im, 1);
3629 if (rrd_test_error()) {
3630 rrd_info_free(im.grinfo);
3631 im_free(&im);
3632 return NULL;
3633 }
3635 /* Everything is now read and the actual work can start */
3637 if (graph_paint(&im) == -1) {
3638 rrd_info_free(im.grinfo);
3639 im_free(&im);
3640 return NULL;
3641 }
3644 /* The image is generated and needs to be output.
3645 ** Also, if needed, print a line with information about the image.
3646 */
3648 if (im.imginfo) {
3649 rrd_infoval_t info;
3650 char *filename;
3652 filename = im.graphfile + strlen(im.graphfile);
3653 while (filename > im.graphfile) {
3654 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3655 break;
3656 filename--;
3657 }
3658 info.u_str =
3659 sprintf_alloc(im.imginfo,
3660 filename,
3661 (long) (im.zoom *
3662 im.ximg), (long) (im.zoom * im.yimg));
3663 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3664 free(info.u_str);
3665 }
3666 if (im.rendered_image) {
3667 rrd_infoval_t img;
3669 img.u_blo.size = im.rendered_image_size;
3670 img.u_blo.ptr = im.rendered_image;
3671 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3672 }
3673 grinfo = im.grinfo;
3674 im_free(&im);
3675 return grinfo;
3676 }
3678 static void
3679 rrd_set_font_desc (
3680 image_desc_t *im,int prop,char *font, double size ){
3681 static text_prop_t tp_cache[] = { {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}, {-1,"",NULL}};
3683 if (tp_cache[prop].font_desc == NULL){
3684 if (prop > 0 && tp_cache[0].font_desc != NULL){
3685 tp_cache[prop].font_desc = pango_font_description_copy (tp_cache[0].font_desc);
3686 strcpy(tp_cache[prop].font,tp_cache[0].font);
3687 tp_cache[prop].size = tp_cache[0].size;
3688 }
3689 else {
3690 tp_cache[prop].font_desc = pango_font_description_new();
3691 }
3692 im->text_prop[prop].font_desc = pango_font_description_copy (tp_cache[prop].font_desc);
3693 }
3695 if (font != NULL && strcmp(tp_cache[prop].font,font) != 0){
3696 pango_font_description_free(tp_cache[prop].font_desc);
3697 pango_font_description_free(im->text_prop[prop].font_desc);
3698 tp_cache[prop].font_desc = pango_font_description_from_string( font );
3699 im->text_prop[prop].font_desc = pango_font_description_copy( tp_cache[prop].font_desc );
3700 strncpy(tp_cache[prop].font, font, sizeof(text_prop[prop].font) - 1);
3701 tp_cache[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3702 strcpy(im->text_prop[prop].font,tp_cache[prop].font);
3703 }
3704 if (size != 0 && size != (tp_cache[prop].size)){
3705 pango_font_description_set_size(tp_cache[prop].font_desc, size * PANGO_SCALE);
3706 pango_font_description_set_size(im->text_prop[prop].font_desc, size * PANGO_SCALE);
3707 im->text_prop[prop].size = size;
3708 tp_cache[prop].size = size;
3709 }
3710 if (im->text_prop[prop].size < 0){
3711 im->text_prop[prop].size = tp_cache[prop].size;
3712 im->text_prop[prop].font_desc = pango_font_description_copy( tp_cache[prop].font_desc );
3713 strcpy(im->text_prop[prop].font,tp_cache[prop].font);
3714 }
3715 // fprintf(stderr,"%d %s\n",prop,pango_font_description_to_string(im->text_prop[prop].font_desc));
3716 }
3718 void rrd_graph_init(
3719 image_desc_t
3720 *im)
3721 {
3722 unsigned int i;
3723 char *deffont = getenv("RRD_DEFAULT_FONT");
3725 #ifdef HAVE_TZSET
3726 tzset();
3727 #endif
3728 #ifdef HAVE_SETLOCALE
3729 setlocale(LC_TIME, "");
3730 #ifdef HAVE_MBSTOWCS
3731 setlocale(LC_CTYPE, "");
3732 #endif
3733 #endif
3734 im->base = 1000;
3735 im->draw_x_grid = 1;
3736 im->draw_y_grid = 1;
3737 im->extra_flags = 0;
3738 im->font_options = cairo_font_options_create();
3739 im->forceleftspace = 0;
3740 im->gdes_c = 0;
3741 im->gdes = NULL;
3742 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3743 im->grid_dash_off = 1;
3744 im->grid_dash_on = 1;
3745 im->gridfit = 1;
3746 im->grinfo = (rrd_info_t *) NULL;
3747 im->grinfo_current = (rrd_info_t *) NULL;
3748 im->imgformat = IF_PNG;
3749 im->imginfo = NULL;
3750 im->lazy = 0;
3751 im->logarithmic = 0;
3752 im->maxval = DNAN;
3753 im->minval = 0;
3754 im->minval = DNAN;
3755 im->prt_c = 0;
3756 im->rigid = 0;
3757 im->rendered_image_size = 0;
3758 im->rendered_image = NULL;
3759 im->slopemode = 0;
3760 im->step = 0;
3761 im->symbol = ' ';
3762 im->tabwidth = 40.0;
3763 im->title[0] = '\0';
3764 im->unitsexponent = 9999;
3765 im->unitslength = 6;
3766 im->viewfactor = 1.0;
3767 im->watermark[0] = '\0';
3768 im->with_markup = 0;
3769 im->ximg = 0;
3770 im->xlab_user.minsec = -1;
3771 im->xorigin = 0;
3772 im->xsize = 400;
3773 im->ygridstep = DNAN;
3774 im->yimg = 0;
3775 im->ylegend[0] = '\0';
3776 im->yorigin = 0;
3777 im->ysize = 100;
3778 im->zoom = 1;
3780 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3781 im->cr = cairo_create(im->surface);
3783 for (i = 0; i < DIM(text_prop); i++) {
3784 im->text_prop[i].size = -1;
3785 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3786 }
3788 im->layout = pango_cairo_create_layout(im->cr);
3789 pango_cairo_context_set_resolution(pango_layout_get_context(im->layout), 100);
3791 cairo_font_options_set_hint_style
3792 (im->font_options, CAIRO_HINT_STYLE_FULL);
3793 cairo_font_options_set_hint_metrics
3794 (im->font_options, CAIRO_HINT_METRICS_ON);
3795 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3799 for (i = 0; i < DIM(graph_col); i++)
3800 im->graph_col[i] = graph_col[i];
3803 }
3806 void rrd_graph_options(
3807 int argc,
3808 char *argv[],
3809 image_desc_t
3810 *im)
3811 {
3812 int stroff;
3813 char *parsetime_error = NULL;
3814 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3815 time_t start_tmp = 0, end_tmp = 0;
3816 long long_tmp;
3817 rrd_time_value_t start_tv, end_tv;
3818 long unsigned int color;
3819 char *old_locale = "";
3821 /* defines for long options without a short equivalent. should be bytes,
3822 and may not collide with (the ASCII value of) short options */
3823 #define LONGOPT_UNITS_SI 255
3825 /* *INDENT-OFF* */
3826 struct option long_options[] = {
3827 { "start", required_argument, 0, 's'},
3828 { "end", required_argument, 0, 'e'},
3829 { "x-grid", required_argument, 0, 'x'},
3830 { "y-grid", required_argument, 0, 'y'},
3831 { "vertical-label", required_argument, 0, 'v'},
3832 { "width", required_argument, 0, 'w'},
3833 { "height", required_argument, 0, 'h'},
3834 { "full-size-mode", no_argument, 0, 'D'},
3835 { "interlaced", no_argument, 0, 'i'},
3836 { "upper-limit", required_argument, 0, 'u'},
3837 { "lower-limit", required_argument, 0, 'l'},
3838 { "rigid", no_argument, 0, 'r'},
3839 { "base", required_argument, 0, 'b'},
3840 { "logarithmic", no_argument, 0, 'o'},
3841 { "color", required_argument, 0, 'c'},
3842 { "font", required_argument, 0, 'n'},
3843 { "title", required_argument, 0, 't'},
3844 { "imginfo", required_argument, 0, 'f'},
3845 { "imgformat", required_argument, 0, 'a'},
3846 { "lazy", no_argument, 0, 'z'},
3847 { "zoom", required_argument, 0, 'm'},
3848 { "no-legend", no_argument, 0, 'g'},
3849 { "force-rules-legend", no_argument, 0, 'F'},
3850 { "only-graph", no_argument, 0, 'j'},
3851 { "alt-y-grid", no_argument, 0, 'Y'},
3852 { "no-minor", no_argument, 0, 'I'},
3853 { "slope-mode", no_argument, 0, 'E'},
3854 { "alt-autoscale", no_argument, 0, 'A'},
3855 { "alt-autoscale-min", no_argument, 0, 'J'},
3856 { "alt-autoscale-max", no_argument, 0, 'M'},
3857 { "no-gridfit", no_argument, 0, 'N'},
3858 { "units-exponent", required_argument, 0, 'X'},
3859 { "units-length", required_argument, 0, 'L'},
3860 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3861 { "step", required_argument, 0, 'S'},
3862 { "tabwidth", required_argument, 0, 'T'},
3863 { "font-render-mode", required_argument, 0, 'R'},
3864 { "graph-render-mode", required_argument, 0, 'G'},
3865 { "font-smoothing-threshold", required_argument, 0, 'B'},
3866 { "watermark", required_argument, 0, 'W'},
3867 { "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 */
3868 { "pango-markup", no_argument, 0, 'P'},
3869 { 0, 0, 0, 0}
3870 };
3871 /* *INDENT-ON* */
3873 optind = 0;
3874 opterr = 0; /* initialize getopt */
3875 rrd_parsetime("end-24h", &start_tv);
3876 rrd_parsetime("now", &end_tv);
3877 while (1) {
3878 int option_index = 0;
3879 int opt;
3880 int col_start, col_end;
3882 opt = getopt_long(argc, argv,
3883 "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:kP",
3884 long_options, &option_index);
3885 if (opt == EOF)
3886 break;
3887 switch (opt) {
3888 case 'I':
3889 im->extra_flags |= NOMINOR;
3890 break;
3891 case 'Y':
3892 im->extra_flags |= ALTYGRID;
3893 break;
3894 case 'A':
3895 im->extra_flags |= ALTAUTOSCALE;
3896 break;
3897 case 'J':
3898 im->extra_flags |= ALTAUTOSCALE_MIN;
3899 break;
3900 case 'M':
3901 im->extra_flags |= ALTAUTOSCALE_MAX;
3902 break;
3903 case 'j':
3904 im->extra_flags |= ONLY_GRAPH;
3905 break;
3906 case 'g':
3907 im->extra_flags |= NOLEGEND;
3908 break;
3909 case 'F':
3910 im->extra_flags |= FORCE_RULES_LEGEND;
3911 break;
3912 case LONGOPT_UNITS_SI:
3913 if (im->extra_flags & FORCE_UNITS) {
3914 rrd_set_error("--units can only be used once!");
3915 setlocale(LC_NUMERIC, old_locale);
3916 return;
3917 }
3918 if (strcmp(optarg, "si") == 0)
3919 im->extra_flags |= FORCE_UNITS_SI;
3920 else {
3921 rrd_set_error("invalid argument for --units: %s", optarg);
3922 return;
3923 }
3924 break;
3925 case 'X':
3926 im->unitsexponent = atoi(optarg);
3927 break;
3928 case 'L':
3929 im->unitslength = atoi(optarg);
3930 im->forceleftspace = 1;
3931 break;
3932 case 'T':
3933 old_locale = setlocale(LC_NUMERIC, "C");
3934 im->tabwidth = atof(optarg);
3935 setlocale(LC_NUMERIC, old_locale);
3936 break;
3937 case 'S':
3938 old_locale = setlocale(LC_NUMERIC, "C");
3939 im->step = atoi(optarg);
3940 setlocale(LC_NUMERIC, old_locale);
3941 break;
3942 case 'N':
3943 im->gridfit = 0;
3944 break;
3945 case 'P':
3946 im->with_markup = 1;
3947 break;
3948 case 's':
3949 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3950 rrd_set_error("start time: %s", parsetime_error);
3951 return;
3952 }
3953 break;
3954 case 'e':
3955 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3956 rrd_set_error("end time: %s", parsetime_error);
3957 return;
3958 }
3959 break;
3960 case 'x':
3961 if (strcmp(optarg, "none") == 0) {
3962 im->draw_x_grid = 0;
3963 break;
3964 };
3965 if (sscanf(optarg,
3966 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3967 scan_gtm,
3968 &im->xlab_user.gridst,
3969 scan_mtm,
3970 &im->xlab_user.mgridst,
3971 scan_ltm,
3972 &im->xlab_user.labst,
3973 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3974 strncpy(im->xlab_form, optarg + stroff,
3975 sizeof(im->xlab_form) - 1);
3976 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3977 if ((int)
3978 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3979 rrd_set_error("unknown keyword %s", scan_gtm);
3980 return;
3981 } else if ((int)
3982 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3983 == -1) {
3984 rrd_set_error("unknown keyword %s", scan_mtm);
3985 return;
3986 } else if ((int)
3987 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
3988 rrd_set_error("unknown keyword %s", scan_ltm);
3989 return;
3990 }
3991 im->xlab_user.minsec = 1;
3992 im->xlab_user.stst = im->xlab_form;
3993 } else {
3994 rrd_set_error("invalid x-grid format");
3995 return;
3996 }
3997 break;
3998 case 'y':
4000 if (strcmp(optarg, "none") == 0) {
4001 im->draw_y_grid = 0;
4002 break;
4003 };
4004 old_locale = setlocale(LC_NUMERIC, "C");
4005 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4006 setlocale(LC_NUMERIC, old_locale);
4007 if (im->ygridstep <= 0) {
4008 rrd_set_error("grid step must be > 0");
4009 return;
4010 } else if (im->ylabfact < 1) {
4011 rrd_set_error("label factor must be > 0");
4012 return;
4013 }
4014 } else {
4015 setlocale(LC_NUMERIC, old_locale);
4016 rrd_set_error("invalid y-grid format");
4017 return;
4018 }
4019 break;
4020 case 'v':
4021 strncpy(im->ylegend, optarg, 150);
4022 im->ylegend[150] = '\0';
4023 break;
4024 case 'u':
4025 old_locale = setlocale(LC_NUMERIC, "C");
4026 im->maxval = atof(optarg);
4027 setlocale(LC_NUMERIC, old_locale);
4028 break;
4029 case 'l':
4030 old_locale = setlocale(LC_NUMERIC, "C");
4031 im->minval = atof(optarg);
4032 setlocale(LC_NUMERIC, old_locale);
4033 break;
4034 case 'b':
4035 im->base = atol(optarg);
4036 if (im->base != 1024 && im->base != 1000) {
4037 rrd_set_error
4038 ("the only sensible value for base apart from 1000 is 1024");
4039 return;
4040 }
4041 break;
4042 case 'w':
4043 long_tmp = atol(optarg);
4044 if (long_tmp < 10) {
4045 rrd_set_error("width below 10 pixels");
4046 return;
4047 }
4048 im->xsize = long_tmp;
4049 break;
4050 case 'h':
4051 long_tmp = atol(optarg);
4052 if (long_tmp < 10) {
4053 rrd_set_error("height below 10 pixels");
4054 return;
4055 }
4056 im->ysize = long_tmp;
4057 break;
4058 case 'D':
4059 im->extra_flags |= FULL_SIZE_MODE;
4060 break;
4061 case 'i':
4062 /* interlaced png not supported at the moment */
4063 break;
4064 case 'r':
4065 im->rigid = 1;
4066 break;
4067 case 'f':
4068 im->imginfo = optarg;
4069 break;
4070 case 'a':
4071 if ((int)
4072 (im->imgformat = if_conv(optarg)) == -1) {
4073 rrd_set_error("unsupported graphics format '%s'", optarg);
4074 return;
4075 }
4076 break;
4077 case 'z':
4078 im->lazy = 1;
4079 break;
4080 case 'E':
4081 im->slopemode = 1;
4082 break;
4083 case 'o':
4084 im->logarithmic = 1;
4085 break;
4086 case 'c':
4087 if (sscanf(optarg,
4088 "%10[A-Z]#%n%8lx%n",
4089 col_nam, &col_start, &color, &col_end) == 2) {
4090 int ci;
4091 int col_len = col_end - col_start;
4093 switch (col_len) {
4094 case 3:
4095 color =
4096 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4097 0x011000) |
4098 ((color & 0x00F)
4099 * 0x001100)
4100 | 0x000000FF);
4101 break;
4102 case 4:
4103 color =
4104 (((color & 0xF000) *
4105 0x11000) | ((color & 0x0F00) *
4106 0x01100) | ((color &
4107 0x00F0) *
4108 0x00110) |
4109 ((color & 0x000F) * 0x00011)
4110 );
4111 break;
4112 case 6:
4113 color = (color << 8) + 0xff /* shift left by 8 */ ;
4114 break;
4115 case 8:
4116 break;
4117 default:
4118 rrd_set_error("the color format is #RRGGBB[AA]");
4119 return;
4120 }
4121 if ((ci = grc_conv(col_nam)) != -1) {
4122 im->graph_col[ci] = gfx_hex_to_col(color);
4123 } else {
4124 rrd_set_error("invalid color name '%s'", col_nam);
4125 return;
4126 }
4127 } else {
4128 rrd_set_error("invalid color def format");
4129 return;
4130 }
4131 break;
4132 case 'n':{
4133 char prop[15];
4134 double size = 1;
4135 int end;
4137 old_locale = setlocale(LC_NUMERIC, "C");
4138 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4139 int sindex, propidx;
4141 setlocale(LC_NUMERIC, old_locale);
4142 if ((sindex = text_prop_conv(prop)) != -1) {
4143 for (propidx = sindex;
4144 propidx < TEXT_PROP_LAST; propidx++) {
4145 if (size > 0) {
4146 rrd_set_font_desc(im,propidx,NULL,size);
4147 }
4148 if ((int) strlen(optarg) > end) {
4149 if (optarg[end] == ':') {
4150 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4151 } else {
4152 rrd_set_error
4153 ("expected : after font size in '%s'",
4154 optarg);
4155 return;
4156 }
4157 }
4158 /* only run the for loop for DEFAULT (0) for
4159 all others, we break here. woodo programming */
4160 if (propidx == sindex && sindex != 0)
4161 break;
4162 }
4163 } else {
4164 rrd_set_error("invalid fonttag '%s'", prop);
4165 return;
4166 }
4167 } else {
4168 setlocale(LC_NUMERIC, old_locale);
4169 rrd_set_error("invalid text property format");
4170 return;
4171 }
4172 break;
4173 }
4174 case 'm':
4175 old_locale = setlocale(LC_NUMERIC, "C");
4176 im->zoom = atof(optarg);
4177 setlocale(LC_NUMERIC, old_locale);
4178 if (im->zoom <= 0.0) {
4179 rrd_set_error("zoom factor must be > 0");
4180 return;
4181 }
4182 break;
4183 case 't':
4184 strncpy(im->title, optarg, 150);
4185 im->title[150] = '\0';
4186 break;
4187 case 'R':
4188 if (strcmp(optarg, "normal") == 0) {
4189 cairo_font_options_set_antialias
4190 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4191 cairo_font_options_set_hint_style
4192 (im->font_options, CAIRO_HINT_STYLE_FULL);
4193 } else if (strcmp(optarg, "light") == 0) {
4194 cairo_font_options_set_antialias
4195 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4196 cairo_font_options_set_hint_style
4197 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4198 } else if (strcmp(optarg, "mono") == 0) {
4199 cairo_font_options_set_antialias
4200 (im->font_options, CAIRO_ANTIALIAS_NONE);
4201 cairo_font_options_set_hint_style
4202 (im->font_options, CAIRO_HINT_STYLE_FULL);
4203 } else {
4204 rrd_set_error("unknown font-render-mode '%s'", optarg);
4205 return;
4206 }
4207 break;
4208 case 'G':
4209 if (strcmp(optarg, "normal") == 0)
4210 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4211 else if (strcmp(optarg, "mono") == 0)
4212 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4213 else {
4214 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4215 return;
4216 }
4217 break;
4218 case 'B':
4219 /* not supported curently */
4220 break;
4221 case 'W':
4222 strncpy(im->watermark, optarg, 100);
4223 im->watermark[99] = '\0';
4224 break;
4225 case '?':
4226 if (optopt != 0)
4227 rrd_set_error("unknown option '%c'", optopt);
4228 else
4229 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4230 return;
4231 }
4232 }
4234 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4235 pango_layout_context_changed(im->layout);
4239 if (im->logarithmic && im->minval <= 0) {
4240 rrd_set_error
4241 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4242 return;
4243 }
4245 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4246 /* error string is set in rrd_parsetime.c */
4247 return;
4248 }
4250 if (start_tmp < 3600 * 24 * 365 * 10) {
4251 rrd_set_error
4252 ("the first entry to fetch should be after 1980 (%ld)",
4253 start_tmp);
4254 return;
4255 }
4257 if (end_tmp < start_tmp) {
4258 rrd_set_error
4259 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4260 return;
4261 }
4263 im->start = start_tmp;
4264 im->end = end_tmp;
4265 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4266 }
4268 int rrd_graph_color(
4269 image_desc_t
4270 *im,
4271 char *var,
4272 char *err,
4273 int optional)
4274 {
4275 char *color;
4276 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4278 color = strstr(var, "#");
4279 if (color == NULL) {
4280 if (optional == 0) {
4281 rrd_set_error("Found no color in %s", err);
4282 return 0;
4283 }
4284 return 0;
4285 } else {
4286 int n = 0;
4287 char *rest;
4288 long unsigned int col;
4290 rest = strstr(color, ":");
4291 if (rest != NULL)
4292 n = rest - color;
4293 else
4294 n = strlen(color);
4295 switch (n) {
4296 case 7:
4297 sscanf(color, "#%6lx%n", &col, &n);
4298 col = (col << 8) + 0xff /* shift left by 8 */ ;
4299 if (n != 7)
4300 rrd_set_error("Color problem in %s", err);
4301 break;
4302 case 9:
4303 sscanf(color, "#%8lx%n", &col, &n);
4304 if (n == 9)
4305 break;
4306 default:
4307 rrd_set_error("Color problem in %s", err);
4308 }
4309 if (rrd_test_error())
4310 return 0;
4311 gdp->col = gfx_hex_to_col(col);
4312 return n;
4313 }
4314 }
4317 int bad_format(
4318 char *fmt)
4319 {
4320 char *ptr;
4321 int n = 0;
4323 ptr = fmt;
4324 while (*ptr != '\0')
4325 if (*ptr++ == '%') {
4327 /* line cannot end with percent char */
4328 if (*ptr == '\0')
4329 return 1;
4330 /* '%s', '%S' and '%%' are allowed */
4331 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4332 ptr++;
4333 /* %c is allowed (but use only with vdef!) */
4334 else if (*ptr == 'c') {
4335 ptr++;
4336 n = 1;
4337 }
4339 /* or else '% 6.2lf' and such are allowed */
4340 else {
4341 /* optional padding character */
4342 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4343 ptr++;
4344 /* This should take care of 'm.n' with all three optional */
4345 while (*ptr >= '0' && *ptr <= '9')
4346 ptr++;
4347 if (*ptr == '.')
4348 ptr++;
4349 while (*ptr >= '0' && *ptr <= '9')
4350 ptr++;
4351 /* Either 'le', 'lf' or 'lg' must follow here */
4352 if (*ptr++ != 'l')
4353 return 1;
4354 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4355 ptr++;
4356 else
4357 return 1;
4358 n++;
4359 }
4360 }
4362 return (n != 1);
4363 }
4366 int vdef_parse(
4367 struct graph_desc_t
4368 *gdes,
4369 const char *const str)
4370 {
4371 /* A VDEF currently is either "func" or "param,func"
4372 * so the parsing is rather simple. Change if needed.
4373 */
4374 double param;
4375 char func[30];
4376 int n;
4377 char *old_locale;
4379 n = 0;
4380 old_locale = setlocale(LC_NUMERIC, "C");
4381 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4382 setlocale(LC_NUMERIC, old_locale);
4383 if (n == (int) strlen(str)) { /* matched */
4384 ;
4385 } else {
4386 n = 0;
4387 sscanf(str, "%29[A-Z]%n", func, &n);
4388 if (n == (int) strlen(str)) { /* matched */
4389 param = DNAN;
4390 } else {
4391 rrd_set_error
4392 ("Unknown function string '%s' in VDEF '%s'",
4393 str, gdes->vname);
4394 return -1;
4395 }
4396 }
4397 if (!strcmp("PERCENT", func))
4398 gdes->vf.op = VDEF_PERCENT;
4399 else if (!strcmp("MAXIMUM", func))
4400 gdes->vf.op = VDEF_MAXIMUM;
4401 else if (!strcmp("AVERAGE", func))
4402 gdes->vf.op = VDEF_AVERAGE;
4403 else if (!strcmp("STDEV", func))
4404 gdes->vf.op = VDEF_STDEV;
4405 else if (!strcmp("MINIMUM", func))
4406 gdes->vf.op = VDEF_MINIMUM;
4407 else if (!strcmp("TOTAL", func))
4408 gdes->vf.op = VDEF_TOTAL;
4409 else if (!strcmp("FIRST", func))
4410 gdes->vf.op = VDEF_FIRST;
4411 else if (!strcmp("LAST", func))
4412 gdes->vf.op = VDEF_LAST;
4413 else if (!strcmp("LSLSLOPE", func))
4414 gdes->vf.op = VDEF_LSLSLOPE;
4415 else if (!strcmp("LSLINT", func))
4416 gdes->vf.op = VDEF_LSLINT;
4417 else if (!strcmp("LSLCORREL", func))
4418 gdes->vf.op = VDEF_LSLCORREL;
4419 else {
4420 rrd_set_error
4421 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4422 return -1;
4423 };
4424 switch (gdes->vf.op) {
4425 case VDEF_PERCENT:
4426 if (isnan(param)) { /* no parameter given */
4427 rrd_set_error
4428 ("Function '%s' needs parameter in VDEF '%s'\n",
4429 func, gdes->vname);
4430 return -1;
4431 };
4432 if (param >= 0.0 && param <= 100.0) {
4433 gdes->vf.param = param;
4434 gdes->vf.val = DNAN; /* undefined */
4435 gdes->vf.when = 0; /* undefined */
4436 } else {
4437 rrd_set_error
4438 ("Parameter '%f' out of range in VDEF '%s'\n",
4439 param, gdes->vname);
4440 return -1;
4441 };
4442 break;
4443 case VDEF_MAXIMUM:
4444 case VDEF_AVERAGE:
4445 case VDEF_STDEV:
4446 case VDEF_MINIMUM:
4447 case VDEF_TOTAL:
4448 case VDEF_FIRST:
4449 case VDEF_LAST:
4450 case VDEF_LSLSLOPE:
4451 case VDEF_LSLINT:
4452 case VDEF_LSLCORREL:
4453 if (isnan(param)) {
4454 gdes->vf.param = DNAN;
4455 gdes->vf.val = DNAN;
4456 gdes->vf.when = 0;
4457 } else {
4458 rrd_set_error
4459 ("Function '%s' needs no parameter in VDEF '%s'\n",
4460 func, gdes->vname);
4461 return -1;
4462 };
4463 break;
4464 };
4465 return 0;
4466 }
4469 int vdef_calc(
4470 image_desc_t *im,
4471 int gdi)
4472 {
4473 graph_desc_t *src, *dst;
4474 rrd_value_t *data;
4475 long step, steps;
4476 unsigned long end;
4478 dst = &im->gdes[gdi];
4479 src = &im->gdes[dst->vidx];
4480 data = src->data + src->ds;
4481 end =
4482 src->end_orig % (long) src->step ==
4483 0 ? src->end_orig : (src->end_orig + (long) src->step -
4484 src->end_orig % (long) src->step);
4486 steps = (end - src->start) / src->step;
4487 #if 0
4488 printf
4489 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4490 src->start, src->end_orig, steps);
4491 #endif
4492 switch (dst->vf.op) {
4493 case VDEF_PERCENT:{
4494 rrd_value_t *array;
4495 int field;
4496 if ((array = malloc(steps * sizeof(double))) == NULL) {
4497 rrd_set_error("malloc VDEV_PERCENT");
4498 return -1;
4499 }
4500 for (step = 0; step < steps; step++) {
4501 array[step] = data[step * src->ds_cnt];
4502 }
4503 qsort(array, step, sizeof(double), vdef_percent_compar);
4504 field = (steps - 1) * dst->vf.param / 100;
4505 dst->vf.val = array[field];
4506 dst->vf.when = 0; /* no time component */
4507 free(array);
4508 #if 0
4509 for (step = 0; step < steps; step++)
4510 printf("DEBUG: %3li:%10.2f %c\n",
4511 step, array[step], step == field ? '*' : ' ');
4512 #endif
4513 }
4514 break;
4515 case VDEF_MAXIMUM:
4516 step = 0;
4517 while (step != steps && isnan(data[step * src->ds_cnt]))
4518 step++;
4519 if (step == steps) {
4520 dst->vf.val = DNAN;
4521 dst->vf.when = 0;
4522 } else {
4523 dst->vf.val = data[step * src->ds_cnt];
4524 dst->vf.when = src->start + (step + 1) * src->step;
4525 }
4526 while (step != steps) {
4527 if (finite(data[step * src->ds_cnt])) {
4528 if (data[step * src->ds_cnt] > dst->vf.val) {
4529 dst->vf.val = data[step * src->ds_cnt];
4530 dst->vf.when = src->start + (step + 1) * src->step;
4531 }
4532 }
4533 step++;
4534 }
4535 break;
4536 case VDEF_TOTAL:
4537 case VDEF_STDEV:
4538 case VDEF_AVERAGE:{
4539 int cnt = 0;
4540 double sum = 0.0;
4541 double average = 0.0;
4543 for (step = 0; step < steps; step++) {
4544 if (finite(data[step * src->ds_cnt])) {
4545 sum += data[step * src->ds_cnt];
4546 cnt++;
4547 };
4548 }
4549 if (cnt) {
4550 if (dst->vf.op == VDEF_TOTAL) {
4551 dst->vf.val = sum * src->step;
4552 dst->vf.when = 0; /* no time component */
4553 } else if (dst->vf.op == VDEF_AVERAGE) {
4554 dst->vf.val = sum / cnt;
4555 dst->vf.when = 0; /* no time component */
4556 } else {
4557 average = sum / cnt;
4558 sum = 0.0;
4559 for (step = 0; step < steps; step++) {
4560 if (finite(data[step * src->ds_cnt])) {
4561 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4562 };
4563 }
4564 dst->vf.val = pow(sum / cnt, 0.5);
4565 dst->vf.when = 0; /* no time component */
4566 };
4567 } else {
4568 dst->vf.val = DNAN;
4569 dst->vf.when = 0;
4570 }
4571 }
4572 break;
4573 case VDEF_MINIMUM:
4574 step = 0;
4575 while (step != steps && isnan(data[step * src->ds_cnt]))
4576 step++;
4577 if (step == steps) {
4578 dst->vf.val = DNAN;
4579 dst->vf.when = 0;
4580 } else {
4581 dst->vf.val = data[step * src->ds_cnt];
4582 dst->vf.when = src->start + (step + 1) * src->step;
4583 }
4584 while (step != steps) {
4585 if (finite(data[step * src->ds_cnt])) {
4586 if (data[step * src->ds_cnt] < dst->vf.val) {
4587 dst->vf.val = data[step * src->ds_cnt];
4588 dst->vf.when = src->start + (step + 1) * src->step;
4589 }
4590 }
4591 step++;
4592 }
4593 break;
4594 case VDEF_FIRST:
4595 /* The time value returned here is one step before the
4596 * actual time value. This is the start of the first
4597 * non-NaN interval.
4598 */
4599 step = 0;
4600 while (step != steps && isnan(data[step * src->ds_cnt]))
4601 step++;
4602 if (step == steps) { /* all entries were NaN */
4603 dst->vf.val = DNAN;
4604 dst->vf.when = 0;
4605 } else {
4606 dst->vf.val = data[step * src->ds_cnt];
4607 dst->vf.when = src->start + step * src->step;
4608 }
4609 break;
4610 case VDEF_LAST:
4611 /* The time value returned here is the
4612 * actual time value. This is the end of the last
4613 * non-NaN interval.
4614 */
4615 step = steps - 1;
4616 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4617 step--;
4618 if (step < 0) { /* all entries were NaN */
4619 dst->vf.val = DNAN;
4620 dst->vf.when = 0;
4621 } else {
4622 dst->vf.val = data[step * src->ds_cnt];
4623 dst->vf.when = src->start + (step + 1) * src->step;
4624 }
4625 break;
4626 case VDEF_LSLSLOPE:
4627 case VDEF_LSLINT:
4628 case VDEF_LSLCORREL:{
4629 /* Bestfit line by linear least squares method */
4631 int cnt = 0;
4632 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4634 SUMx = 0;
4635 SUMy = 0;
4636 SUMxy = 0;
4637 SUMxx = 0;
4638 SUMyy = 0;
4639 for (step = 0; step < steps; step++) {
4640 if (finite(data[step * src->ds_cnt])) {
4641 cnt++;
4642 SUMx += step;
4643 SUMxx += step * step;
4644 SUMxy += step * data[step * src->ds_cnt];
4645 SUMy += data[step * src->ds_cnt];
4646 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4647 };
4648 }
4650 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4651 y_intercept = (SUMy - slope * SUMx) / cnt;
4652 correl =
4653 (SUMxy -
4654 (SUMx * SUMy) / cnt) /
4655 sqrt((SUMxx -
4656 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4657 if (cnt) {
4658 if (dst->vf.op == VDEF_LSLSLOPE) {
4659 dst->vf.val = slope;
4660 dst->vf.when = 0;
4661 } else if (dst->vf.op == VDEF_LSLINT) {
4662 dst->vf.val = y_intercept;
4663 dst->vf.when = 0;
4664 } else if (dst->vf.op == VDEF_LSLCORREL) {
4665 dst->vf.val = correl;
4666 dst->vf.when = 0;
4667 };
4668 } else {
4669 dst->vf.val = DNAN;
4670 dst->vf.when = 0;
4671 }
4672 }
4673 break;
4674 }
4675 return 0;
4676 }
4678 /* NaN < -INF < finite_values < INF */
4679 int vdef_percent_compar(
4680 const void
4681 *a,
4682 const void
4683 *b)
4684 {
4685 /* Equality is not returned; this doesn't hurt except
4686 * (maybe) for a little performance.
4687 */
4689 /* First catch NaN values. They are smallest */
4690 if (isnan(*(double *) a))
4691 return -1;
4692 if (isnan(*(double *) b))
4693 return 1;
4694 /* NaN doesn't reach this part so INF and -INF are extremes.
4695 * The sign from isinf() is compatible with the sign we return
4696 */
4697 if (isinf(*(double *) a))
4698 return isinf(*(double *) a);
4699 if (isinf(*(double *) b))
4700 return isinf(*(double *) b);
4701 /* If we reach this, both values must be finite */
4702 if (*(double *) a < *(double *) b)
4703 return -1;
4704 else
4705 return 1;
4706 }
4708 void grinfo_push(
4709 image_desc_t *im,
4710 char *key,
4711 rrd_info_type_t type,
4712 rrd_infoval_t value)
4713 {
4714 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4715 if (im->grinfo == NULL) {
4716 im->grinfo = im->grinfo_current;
4717 }
4718 }