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