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 prt_fctn; /*special printfunctions */
1667 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1668 int *legspace;
1669 char *tab;
1671 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1672 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1673 rrd_set_error("malloc for legspace");
1674 return -1;
1675 }
1677 if (im->extra_flags & FULL_SIZE_MODE)
1678 leg_y = leg_y_prev =
1679 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1680 for (i = 0; i < im->gdes_c; i++) {
1681 fill_last = fill;
1682 /* hide legends for rules which are not displayed */
1683 if (im->gdes[i].gf == GF_TEXTALIGN) {
1684 default_txtalign = im->gdes[i].txtalign;
1685 }
1687 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1688 if (im->gdes[i].gf == GF_HRULE
1689 && (im->gdes[i].yrule <
1690 im->minval || im->gdes[i].yrule > im->maxval))
1691 im->gdes[i].legend[0] = '\0';
1692 if (im->gdes[i].gf == GF_VRULE
1693 && (im->gdes[i].xrule <
1694 im->start || im->gdes[i].xrule > im->end))
1695 im->gdes[i].legend[0] = '\0';
1696 }
1698 /* turn \\t into tab */
1699 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1700 memmove(tab, tab + 1, strlen(tab));
1701 tab[0] = (char) 9;
1702 }
1703 leg_cc = strlen(im->gdes[i].legend);
1704 /* is there a controle code ant the end of the legend string ? */
1705 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1706 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1707 leg_cc -= 2;
1708 im->gdes[i].legend[leg_cc] = '\0';
1709 } else {
1710 prt_fctn = '\0';
1711 }
1712 /* only valid control codes */
1713 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1714 prt_fctn != 'r' &&
1715 prt_fctn != 'j' &&
1716 prt_fctn != 'c' &&
1717 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1718 free(legspace);
1719 rrd_set_error
1720 ("Unknown control code at the end of '%s\\%c'",
1721 im->gdes[i].legend, prt_fctn);
1722 return -1;
1723 }
1724 /* \n -> \l */
1725 if (prt_fctn == 'n') {
1726 prt_fctn = 'l';
1727 }
1729 /* remove exess space from the end of the legend for \g */
1730 while (prt_fctn == 'g' &&
1731 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1732 leg_cc--;
1733 im->gdes[i].legend[leg_cc] = '\0';
1734 }
1736 if (leg_cc != 0) {
1738 /* no interleg space if string ends in \g */
1739 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1740 if (fill > 0) {
1741 fill += legspace[i];
1742 }
1743 fill +=
1744 gfx_get_text_width(im,
1745 fill + border,
1746 im->
1747 text_prop
1748 [TEXT_PROP_LEGEND].
1749 font_desc,
1750 im->tabwidth, im->gdes[i].legend);
1751 leg_c++;
1752 } else {
1753 legspace[i] = 0;
1754 }
1755 /* who said there was a special tag ... ? */
1756 if (prt_fctn == 'g') {
1757 prt_fctn = '\0';
1758 }
1760 if (prt_fctn == '\0') {
1761 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1762 /* just one legend item is left right or center */
1763 switch (default_txtalign) {
1764 case TXA_RIGHT:
1765 prt_fctn = 'r';
1766 break;
1767 case TXA_CENTER:
1768 prt_fctn = 'c';
1769 break;
1770 case TXA_JUSTIFIED:
1771 prt_fctn = 'j';
1772 break;
1773 default:
1774 prt_fctn = 'l';
1775 break;
1776 }
1777 }
1778 /* is it time to place the legends ? */
1779 if (fill > im->ximg - 2 * border) {
1780 if (leg_c > 1) {
1781 /* go back one */
1782 i--;
1783 fill = fill_last;
1784 leg_c--;
1785 }
1786 }
1787 if (leg_c == 1 && prt_fctn == 'j') {
1788 prt_fctn = 'l';
1789 }
1790 }
1793 if (prt_fctn != '\0') {
1794 leg_x = border;
1795 if (leg_c >= 2 && prt_fctn == 'j') {
1796 glue = (double)(im->ximg - fill - 2 * border) / (double)(leg_c - 1);
1797 } else {
1798 glue = 0;
1799 }
1800 if (prt_fctn == 'c')
1801 leg_x = (double)(im->ximg - fill) / 2.0;
1802 if (prt_fctn == 'r')
1803 leg_x = im->ximg - fill - border;
1804 for (ii = mark; ii <= i; ii++) {
1805 if (im->gdes[ii].legend[0] == '\0')
1806 continue; /* skip empty legends */
1807 im->gdes[ii].leg_x = leg_x;
1808 im->gdes[ii].leg_y = leg_y;
1809 leg_x +=
1810 (double)gfx_get_text_width(im, leg_x,
1811 im->
1812 text_prop
1813 [TEXT_PROP_LEGEND].
1814 font_desc,
1815 im->tabwidth, im->gdes[ii].legend)
1816 +(double)legspace[ii]
1817 + glue;
1818 }
1819 leg_y_prev = leg_y;
1820 if (im->extra_flags & FULL_SIZE_MODE) {
1821 /* only add y space if there was text on the line */
1822 if (leg_x > border || prt_fctn == 's')
1823 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1824 if (prt_fctn == 's')
1825 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1826 } else {
1827 if (leg_x > border || prt_fctn == 's')
1828 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1829 if (prt_fctn == 's')
1830 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1831 }
1832 fill = 0;
1833 leg_c = 0;
1834 mark = ii;
1835 }
1836 }
1838 if (im->extra_flags & FULL_SIZE_MODE) {
1839 if (leg_y != leg_y_prev) {
1840 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1841 im->yorigin =
1842 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1843 }
1844 } else {
1845 im->yimg =
1846 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1847 border * 0.6;
1848 }
1849 free(legspace);
1850 }
1851 return 0;
1852 }
1854 /* create a grid on the graph. it determines what to do
1855 from the values of xsize, start and end */
1857 /* the xaxis labels are determined from the number of seconds per pixel
1858 in the requested graph */
1860 int calc_horizontal_grid(
1861 image_desc_t
1862 *im)
1863 {
1864 double range;
1865 double scaledrange;
1866 int pixel, i;
1867 int gridind = 0;
1868 int decimals, fractionals;
1870 im->ygrid_scale.labfact = 2;
1871 range = im->maxval - im->minval;
1872 scaledrange = range / im->magfact;
1873 /* does the scale of this graph make it impossible to put lines
1874 on it? If so, give up. */
1875 if (isnan(scaledrange)) {
1876 return 0;
1877 }
1879 /* find grid spaceing */
1880 pixel = 1;
1881 if (isnan(im->ygridstep)) {
1882 if (im->extra_flags & ALTYGRID) {
1883 /* find the value with max number of digits. Get number of digits */
1884 decimals =
1885 ceil(log10
1886 (max(fabs(im->maxval), fabs(im->minval)) *
1887 im->viewfactor / im->magfact));
1888 if (decimals <= 0) /* everything is small. make place for zero */
1889 decimals = 1;
1890 im->ygrid_scale.gridstep =
1891 pow((double) 10,
1892 floor(log10(range * im->viewfactor / im->magfact))) /
1893 im->viewfactor * im->magfact;
1894 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1895 im->ygrid_scale.gridstep = 0.1;
1896 /* should have at least 5 lines but no more then 15 */
1897 if (range / im->ygrid_scale.gridstep < 5
1898 && im->ygrid_scale.gridstep >= 30)
1899 im->ygrid_scale.gridstep /= 10;
1900 if (range / im->ygrid_scale.gridstep > 15)
1901 im->ygrid_scale.gridstep *= 10;
1902 if (range / im->ygrid_scale.gridstep > 5) {
1903 im->ygrid_scale.labfact = 1;
1904 if (range / im->ygrid_scale.gridstep > 8
1905 || im->ygrid_scale.gridstep <
1906 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1907 im->ygrid_scale.labfact = 2;
1908 } else {
1909 im->ygrid_scale.gridstep /= 5;
1910 im->ygrid_scale.labfact = 5;
1911 }
1912 fractionals =
1913 floor(log10
1914 (im->ygrid_scale.gridstep *
1915 (double) im->ygrid_scale.labfact * im->viewfactor /
1916 im->magfact));
1917 if (fractionals < 0) { /* small amplitude. */
1918 int len = decimals - fractionals + 1;
1920 if (im->unitslength < len + 2)
1921 im->unitslength = len + 2;
1922 sprintf(im->ygrid_scale.labfmt,
1923 "%%%d.%df%s", len,
1924 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1925 } else {
1926 int len = decimals + 1;
1928 if (im->unitslength < len + 2)
1929 im->unitslength = len + 2;
1930 sprintf(im->ygrid_scale.labfmt,
1931 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1932 }
1933 } else { /* classic rrd grid */
1934 for (i = 0; ylab[i].grid > 0; i++) {
1935 pixel = im->ysize / (scaledrange / ylab[i].grid);
1936 gridind = i;
1937 if (pixel >= 5)
1938 break;
1939 }
1941 for (i = 0; i < 4; i++) {
1942 if (pixel * ylab[gridind].lfac[i] >=
1943 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1944 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1945 break;
1946 }
1947 }
1949 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1950 }
1951 } else {
1952 im->ygrid_scale.gridstep = im->ygridstep;
1953 im->ygrid_scale.labfact = im->ylabfact;
1954 }
1955 return 1;
1956 }
1958 int draw_horizontal_grid(
1959 image_desc_t
1960 *im)
1961 {
1962 int i;
1963 double scaledstep;
1964 char graph_label[100];
1965 int nlabels = 0;
1966 double X0 = im->xorigin;
1967 double X1 = im->xorigin + im->xsize;
1968 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1969 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1970 double MaxY;
1972 scaledstep =
1973 im->ygrid_scale.gridstep /
1974 (double) im->magfact * (double) im->viewfactor;
1975 MaxY = scaledstep * (double) egrid;
1976 for (i = sgrid; i <= egrid; i++) {
1977 double Y0 = ytr(im,
1978 im->ygrid_scale.gridstep * i);
1979 double YN = ytr(im,
1980 im->ygrid_scale.gridstep * (i + 1));
1982 if (floor(Y0 + 0.5) >=
1983 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1984 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1985 with the chosen settings. Add a label if required by settings, or if
1986 there is only one label so far and the next grid line is out of bounds. */
1987 if (i % im->ygrid_scale.labfact == 0
1988 || (nlabels == 1
1989 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1990 if (im->symbol == ' ') {
1991 if (im->extra_flags & ALTYGRID) {
1992 sprintf(graph_label,
1993 im->ygrid_scale.labfmt,
1994 scaledstep * (double) i);
1995 } else {
1996 if (MaxY < 10) {
1997 sprintf(graph_label, "%4.1f",
1998 scaledstep * (double) i);
1999 } else {
2000 sprintf(graph_label, "%4.0f",
2001 scaledstep * (double) i);
2002 }
2003 }
2004 } else {
2005 char sisym = (i == 0 ? ' ' : im->symbol);
2007 if (im->extra_flags & ALTYGRID) {
2008 sprintf(graph_label,
2009 im->ygrid_scale.labfmt,
2010 scaledstep * (double) i, sisym);
2011 } else {
2012 if (MaxY < 10) {
2013 sprintf(graph_label, "%4.1f %c",
2014 scaledstep * (double) i, sisym);
2015 } else {
2016 sprintf(graph_label, "%4.0f %c",
2017 scaledstep * (double) i, sisym);
2018 }
2019 }
2020 }
2021 nlabels++;
2022 gfx_text(im,
2023 X0 -
2024 im->
2025 text_prop[TEXT_PROP_AXIS].
2026 size, Y0,
2027 im->graph_col[GRC_FONT],
2028 im->
2029 text_prop[TEXT_PROP_AXIS].
2030 font_desc,
2031 im->tabwidth, 0.0,
2032 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2033 gfx_line(im, X0 - 2, Y0, X0, Y0,
2034 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2035 gfx_line(im, X1, Y0, X1 + 2, Y0,
2036 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2037 gfx_dashed_line(im, X0 - 2, Y0,
2038 X1 + 2, Y0,
2039 MGRIDWIDTH,
2040 im->
2041 graph_col
2042 [GRC_MGRID],
2043 im->grid_dash_on, im->grid_dash_off);
2044 } else if (!(im->extra_flags & NOMINOR)) {
2045 gfx_line(im,
2046 X0 - 2, Y0,
2047 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2048 gfx_line(im, X1, Y0, X1 + 2, Y0,
2049 GRIDWIDTH, im->graph_col[GRC_GRID]);
2050 gfx_dashed_line(im, X0 - 1, Y0,
2051 X1 + 1, Y0,
2052 GRIDWIDTH,
2053 im->
2054 graph_col[GRC_GRID],
2055 im->grid_dash_on, im->grid_dash_off);
2056 }
2057 }
2058 }
2059 return 1;
2060 }
2062 /* this is frexp for base 10 */
2063 double frexp10(
2064 double,
2065 double *);
2066 double frexp10(
2067 double x,
2068 double *e)
2069 {
2070 double mnt;
2071 int iexp;
2073 iexp = floor(log((double)fabs(x)) / log((double)10));
2074 mnt = x / pow(10.0, iexp);
2075 if (mnt >= 10.0) {
2076 iexp++;
2077 mnt = x / pow(10.0, iexp);
2078 }
2079 *e = iexp;
2080 return mnt;
2081 }
2084 /* logaritmic horizontal grid */
2085 int horizontal_log_grid(
2086 image_desc_t
2087 *im)
2088 {
2089 double yloglab[][10] = {
2090 {
2091 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2092 0.0, 0.0, 0.0}, {
2093 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2094 0.0, 0.0, 0.0}, {
2095 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2096 0.0, 0.0, 0.0}, {
2097 1.0, 2.0, 4.0,
2098 6.0, 8.0, 10.,
2099 0.0,
2100 0.0, 0.0, 0.0}, {
2101 1.0,
2102 2.0,
2103 3.0,
2104 4.0,
2105 5.0,
2106 6.0,
2107 7.0,
2108 8.0,
2109 9.0,
2110 10.},
2111 {
2112 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2113 };
2114 int i, j, val_exp, min_exp;
2115 double nex; /* number of decades in data */
2116 double logscale; /* scale in logarithmic space */
2117 int exfrac = 1; /* decade spacing */
2118 int mid = -1; /* row in yloglab for major grid */
2119 double mspac; /* smallest major grid spacing (pixels) */
2120 int flab; /* first value in yloglab to use */
2121 double value, tmp, pre_value;
2122 double X0, X1, Y0;
2123 char graph_label[100];
2125 nex = log10(im->maxval / im->minval);
2126 logscale = im->ysize / nex;
2127 /* major spacing for data with high dynamic range */
2128 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2129 if (exfrac == 1)
2130 exfrac = 3;
2131 else
2132 exfrac += 3;
2133 }
2135 /* major spacing for less dynamic data */
2136 do {
2137 /* search best row in yloglab */
2138 mid++;
2139 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2140 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2141 }
2142 while (mspac >
2143 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2144 if (mid)
2145 mid--;
2146 /* find first value in yloglab */
2147 for (flab = 0;
2148 yloglab[mid][flab] < 10
2149 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2150 if (yloglab[mid][flab] == 10.0) {
2151 tmp += 1.0;
2152 flab = 0;
2153 }
2154 val_exp = tmp;
2155 if (val_exp % exfrac)
2156 val_exp += abs(-val_exp % exfrac);
2157 X0 = im->xorigin;
2158 X1 = im->xorigin + im->xsize;
2159 /* draw grid */
2160 pre_value = DNAN;
2161 while (1) {
2163 value = yloglab[mid][flab] * pow(10.0, val_exp);
2164 if (AlmostEqual2sComplement(value, pre_value, 4))
2165 break; /* it seems we are not converging */
2166 pre_value = value;
2167 Y0 = ytr(im, value);
2168 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2169 break;
2170 /* major grid line */
2171 gfx_line(im,
2172 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2173 gfx_line(im, X1, Y0, X1 + 2, Y0,
2174 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2175 gfx_dashed_line(im, X0 - 2, Y0,
2176 X1 + 2, Y0,
2177 MGRIDWIDTH,
2178 im->
2179 graph_col
2180 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2181 /* label */
2182 if (im->extra_flags & FORCE_UNITS_SI) {
2183 int scale;
2184 double pvalue;
2185 char symbol;
2187 scale = floor(val_exp / 3.0);
2188 if (value >= 1.0)
2189 pvalue = pow(10.0, val_exp % 3);
2190 else
2191 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2192 pvalue *= yloglab[mid][flab];
2193 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2194 && ((scale + si_symbcenter) >= 0))
2195 symbol = si_symbol[scale + si_symbcenter];
2196 else
2197 symbol = '?';
2198 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2199 } else
2200 sprintf(graph_label, "%3.0e", value);
2201 gfx_text(im,
2202 X0 -
2203 im->
2204 text_prop[TEXT_PROP_AXIS].
2205 size, Y0,
2206 im->graph_col[GRC_FONT],
2207 im->
2208 text_prop[TEXT_PROP_AXIS].
2209 font_desc,
2210 im->tabwidth, 0.0,
2211 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2212 /* minor grid */
2213 if (mid < 4 && exfrac == 1) {
2214 /* find first and last minor line behind current major line
2215 * i is the first line and j tha last */
2216 if (flab == 0) {
2217 min_exp = val_exp - 1;
2218 for (i = 1; yloglab[mid][i] < 10.0; i++);
2219 i = yloglab[mid][i - 1] + 1;
2220 j = 10;
2221 } else {
2222 min_exp = val_exp;
2223 i = yloglab[mid][flab - 1] + 1;
2224 j = yloglab[mid][flab];
2225 }
2227 /* draw minor lines below current major line */
2228 for (; i < j; i++) {
2230 value = i * pow(10.0, min_exp);
2231 if (value < im->minval)
2232 continue;
2233 Y0 = ytr(im, value);
2234 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2235 break;
2236 /* draw lines */
2237 gfx_line(im,
2238 X0 - 2, Y0,
2239 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2240 gfx_line(im, X1, Y0, X1 + 2, Y0,
2241 GRIDWIDTH, im->graph_col[GRC_GRID]);
2242 gfx_dashed_line(im, X0 - 1, Y0,
2243 X1 + 1, Y0,
2244 GRIDWIDTH,
2245 im->
2246 graph_col[GRC_GRID],
2247 im->grid_dash_on, im->grid_dash_off);
2248 }
2249 } else if (exfrac > 1) {
2250 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2251 value = pow(10.0, i);
2252 if (value < im->minval)
2253 continue;
2254 Y0 = ytr(im, value);
2255 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2256 break;
2257 /* draw lines */
2258 gfx_line(im,
2259 X0 - 2, Y0,
2260 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2261 gfx_line(im, X1, Y0, X1 + 2, Y0,
2262 GRIDWIDTH, im->graph_col[GRC_GRID]);
2263 gfx_dashed_line(im, X0 - 1, Y0,
2264 X1 + 1, Y0,
2265 GRIDWIDTH,
2266 im->
2267 graph_col[GRC_GRID],
2268 im->grid_dash_on, im->grid_dash_off);
2269 }
2270 }
2272 /* next decade */
2273 if (yloglab[mid][++flab] == 10.0) {
2274 flab = 0;
2275 val_exp += exfrac;
2276 }
2277 }
2279 /* draw minor lines after highest major line */
2280 if (mid < 4 && exfrac == 1) {
2281 /* find first and last minor line below current major line
2282 * i is the first line and j tha last */
2283 if (flab == 0) {
2284 min_exp = val_exp - 1;
2285 for (i = 1; yloglab[mid][i] < 10.0; i++);
2286 i = yloglab[mid][i - 1] + 1;
2287 j = 10;
2288 } else {
2289 min_exp = val_exp;
2290 i = yloglab[mid][flab - 1] + 1;
2291 j = yloglab[mid][flab];
2292 }
2294 /* draw minor lines below current major line */
2295 for (; i < j; i++) {
2297 value = i * pow(10.0, min_exp);
2298 if (value < im->minval)
2299 continue;
2300 Y0 = ytr(im, value);
2301 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2302 break;
2303 /* draw lines */
2304 gfx_line(im,
2305 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2306 gfx_line(im, X1, Y0, X1 + 2, Y0,
2307 GRIDWIDTH, im->graph_col[GRC_GRID]);
2308 gfx_dashed_line(im, X0 - 1, Y0,
2309 X1 + 1, Y0,
2310 GRIDWIDTH,
2311 im->
2312 graph_col[GRC_GRID],
2313 im->grid_dash_on, im->grid_dash_off);
2314 }
2315 }
2316 /* fancy minor gridlines */
2317 else if (exfrac > 1) {
2318 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2319 value = pow(10.0, i);
2320 if (value < im->minval)
2321 continue;
2322 Y0 = ytr(im, value);
2323 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2324 break;
2325 /* draw lines */
2326 gfx_line(im,
2327 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2328 gfx_line(im, X1, Y0, X1 + 2, Y0,
2329 GRIDWIDTH, im->graph_col[GRC_GRID]);
2330 gfx_dashed_line(im, X0 - 1, Y0,
2331 X1 + 1, Y0,
2332 GRIDWIDTH,
2333 im->
2334 graph_col[GRC_GRID],
2335 im->grid_dash_on, im->grid_dash_off);
2336 }
2337 }
2339 return 1;
2340 }
2343 void vertical_grid(
2344 image_desc_t *im)
2345 {
2346 int xlab_sel; /* which sort of label and grid ? */
2347 time_t ti, tilab, timajor;
2348 long factor;
2349 char graph_label[100];
2350 double X0, Y0, Y1; /* points for filled graph and more */
2351 struct tm tm;
2353 /* the type of time grid is determined by finding
2354 the number of seconds per pixel in the graph */
2355 if (im->xlab_user.minsec == -1) {
2356 factor = (im->end - im->start) / im->xsize;
2357 xlab_sel = 0;
2358 while (xlab[xlab_sel + 1].minsec !=
2359 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2360 xlab_sel++;
2361 } /* pick the last one */
2362 while (xlab[xlab_sel - 1].minsec ==
2363 xlab[xlab_sel].minsec
2364 && xlab[xlab_sel].length > (im->end - im->start)) {
2365 xlab_sel--;
2366 } /* go back to the smallest size */
2367 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2368 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2369 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2370 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2371 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2372 im->xlab_user.labst = xlab[xlab_sel].labst;
2373 im->xlab_user.precis = xlab[xlab_sel].precis;
2374 im->xlab_user.stst = xlab[xlab_sel].stst;
2375 }
2377 /* y coords are the same for every line ... */
2378 Y0 = im->yorigin;
2379 Y1 = im->yorigin - im->ysize;
2380 /* paint the minor grid */
2381 if (!(im->extra_flags & NOMINOR)) {
2382 for (ti = find_first_time(im->start,
2383 im->
2384 xlab_user.
2385 gridtm,
2386 im->
2387 xlab_user.
2388 gridst),
2389 timajor =
2390 find_first_time(im->start,
2391 im->xlab_user.
2392 mgridtm,
2393 im->xlab_user.
2394 mgridst);
2395 ti < im->end;
2396 ti =
2397 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2398 ) {
2399 /* are we inside the graph ? */
2400 if (ti < im->start || ti > im->end)
2401 continue;
2402 while (timajor < ti) {
2403 timajor = find_next_time(timajor,
2404 im->
2405 xlab_user.
2406 mgridtm, im->xlab_user.mgridst);
2407 }
2408 if (ti == timajor)
2409 continue; /* skip as falls on major grid line */
2410 X0 = xtr(im, ti);
2411 gfx_line(im, X0, Y1 - 2, X0, Y1,
2412 GRIDWIDTH, im->graph_col[GRC_GRID]);
2413 gfx_line(im, X0, Y0, X0, Y0 + 2,
2414 GRIDWIDTH, im->graph_col[GRC_GRID]);
2415 gfx_dashed_line(im, X0, Y0 + 1, X0,
2416 Y1 - 1, GRIDWIDTH,
2417 im->
2418 graph_col[GRC_GRID],
2419 im->grid_dash_on, im->grid_dash_off);
2420 }
2421 }
2423 /* paint the major grid */
2424 for (ti = find_first_time(im->start,
2425 im->
2426 xlab_user.
2427 mgridtm,
2428 im->
2429 xlab_user.
2430 mgridst);
2431 ti < im->end;
2432 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2433 ) {
2434 /* are we inside the graph ? */
2435 if (ti < im->start || ti > im->end)
2436 continue;
2437 X0 = xtr(im, ti);
2438 gfx_line(im, X0, Y1 - 2, X0, Y1,
2439 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2440 gfx_line(im, X0, Y0, X0, Y0 + 3,
2441 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2442 gfx_dashed_line(im, X0, Y0 + 3, X0,
2443 Y1 - 2, MGRIDWIDTH,
2444 im->
2445 graph_col
2446 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2447 }
2448 /* paint the labels below the graph */
2449 for (ti =
2450 find_first_time(im->start -
2451 im->xlab_user.
2452 precis / 2,
2453 im->xlab_user.
2454 labtm,
2455 im->xlab_user.
2456 labst);
2457 ti <=
2458 im->end -
2459 im->xlab_user.precis / 2;
2460 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2461 ) {
2462 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2463 /* are we inside the graph ? */
2464 if (tilab < im->start || tilab > im->end)
2465 continue;
2466 #if HAVE_STRFTIME
2467 localtime_r(&tilab, &tm);
2468 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2469 #else
2470 # error "your libc has no strftime I guess we'll abort the exercise here."
2471 #endif
2472 gfx_text(im,
2473 xtr(im, tilab),
2474 Y0 + 3,
2475 im->graph_col[GRC_FONT],
2476 im->
2477 text_prop[TEXT_PROP_AXIS].
2478 font_desc,
2479 im->tabwidth, 0.0,
2480 GFX_H_CENTER, GFX_V_TOP, graph_label);
2481 }
2483 }
2486 void axis_paint(
2487 image_desc_t *im)
2488 {
2489 /* draw x and y axis */
2490 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2491 im->xorigin+im->xsize,im->yorigin-im->ysize,
2492 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2494 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2495 im->xorigin+im->xsize,im->yorigin-im->ysize,
2496 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2498 gfx_line(im, im->xorigin - 4,
2499 im->yorigin,
2500 im->xorigin + im->xsize +
2501 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2502 gfx_line(im, im->xorigin,
2503 im->yorigin + 4,
2504 im->xorigin,
2505 im->yorigin - im->ysize -
2506 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2507 /* arrow for X and Y axis direction */
2508 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 */
2509 im->graph_col[GRC_ARROW]);
2510 gfx_close_path(im);
2511 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 */
2512 im->graph_col[GRC_ARROW]);
2513 gfx_close_path(im);
2514 }
2516 void grid_paint(
2517 image_desc_t *im)
2518 {
2519 long i;
2520 int res = 0;
2521 double X0, Y0; /* points for filled graph and more */
2522 struct gfx_color_t water_color;
2524 /* draw 3d border */
2525 gfx_new_area(im, 0, im->yimg,
2526 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2527 gfx_add_point(im, im->ximg - 2, 2);
2528 gfx_add_point(im, im->ximg, 0);
2529 gfx_add_point(im, 0, 0);
2530 gfx_close_path(im);
2531 gfx_new_area(im, 2, im->yimg - 2,
2532 im->ximg - 2,
2533 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2534 gfx_add_point(im, im->ximg, 0);
2535 gfx_add_point(im, im->ximg, im->yimg);
2536 gfx_add_point(im, 0, im->yimg);
2537 gfx_close_path(im);
2538 if (im->draw_x_grid == 1)
2539 vertical_grid(im);
2540 if (im->draw_y_grid == 1) {
2541 if (im->logarithmic) {
2542 res = horizontal_log_grid(im);
2543 } else {
2544 res = draw_horizontal_grid(im);
2545 }
2547 /* dont draw horizontal grid if there is no min and max val */
2548 if (!res) {
2549 char *nodata = "No Data found";
2551 gfx_text(im, im->ximg / 2,
2552 (2 * im->yorigin -
2553 im->ysize) / 2,
2554 im->graph_col[GRC_FONT],
2555 im->
2556 text_prop[TEXT_PROP_AXIS].
2557 font_desc,
2558 im->tabwidth, 0.0,
2559 GFX_H_CENTER, GFX_V_CENTER, nodata);
2560 }
2561 }
2563 /* yaxis unit description */
2564 gfx_text(im,
2565 10,
2566 (im->yorigin -
2567 im->ysize / 2),
2568 im->graph_col[GRC_FONT],
2569 im->
2570 text_prop[TEXT_PROP_UNIT].
2571 font_desc,
2572 im->tabwidth,
2573 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2574 /* graph title */
2575 gfx_text(im,
2576 im->ximg / 2, 6,
2577 im->graph_col[GRC_FONT],
2578 im->
2579 text_prop[TEXT_PROP_TITLE].
2580 font_desc,
2581 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2582 /* rrdtool 'logo' */
2583 water_color = im->graph_col[GRC_FONT];
2584 water_color.alpha = 0.3;
2585 gfx_text(im, im->ximg - 4, 5,
2586 water_color,
2587 im->
2588 text_prop[TEXT_PROP_WATERMARK].
2589 font_desc, im->tabwidth,
2590 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2591 /* graph watermark */
2592 if (im->watermark[0] != '\0') {
2593 gfx_text(im,
2594 im->ximg / 2, im->yimg - 6,
2595 water_color,
2596 im->
2597 text_prop[TEXT_PROP_WATERMARK].
2598 font_desc, im->tabwidth, 0,
2599 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2600 }
2602 /* graph labels */
2603 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2604 for (i = 0; i < im->gdes_c; i++) {
2605 if (im->gdes[i].legend[0] == '\0')
2606 continue;
2607 /* im->gdes[i].leg_y is the bottom of the legend */
2608 X0 = im->gdes[i].leg_x;
2609 Y0 = im->gdes[i].leg_y;
2610 gfx_text(im, X0, Y0,
2611 im->graph_col[GRC_FONT],
2612 im->
2613 text_prop
2614 [TEXT_PROP_LEGEND].font_desc,
2615 im->tabwidth, 0.0,
2616 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2617 /* The legend for GRAPH items starts with "M " to have
2618 enough space for the box */
2619 if (im->gdes[i].gf != GF_PRINT &&
2620 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2621 double boxH, boxV;
2622 double X1, Y1;
2624 boxH = gfx_get_text_width(im, 0,
2625 im->
2626 text_prop
2627 [TEXT_PROP_LEGEND].
2628 font_desc,
2629 im->tabwidth, "o") * 1.2;
2630 boxV = boxH;
2631 /* shift the box up a bit */
2632 Y0 -= boxV * 0.4;
2633 /* make sure transparent colors show up the same way as in the graph */
2634 gfx_new_area(im,
2635 X0, Y0 - boxV,
2636 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2637 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2638 gfx_close_path(im);
2639 gfx_new_area(im, X0, Y0 - boxV, X0,
2640 Y0, X0 + boxH, Y0, im->gdes[i].col);
2641 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2642 gfx_close_path(im);
2643 cairo_save(im->cr);
2644 cairo_new_path(im->cr);
2645 cairo_set_line_width(im->cr, 1.0);
2646 X1 = X0 + boxH;
2647 Y1 = Y0 - boxV;
2648 gfx_line_fit(im, &X0, &Y0);
2649 gfx_line_fit(im, &X1, &Y1);
2650 cairo_move_to(im->cr, X0, Y0);
2651 cairo_line_to(im->cr, X1, Y0);
2652 cairo_line_to(im->cr, X1, Y1);
2653 cairo_line_to(im->cr, X0, Y1);
2654 cairo_close_path(im->cr);
2655 cairo_set_source_rgba(im->cr,
2656 im->
2657 graph_col
2658 [GRC_FRAME].
2659 red,
2660 im->
2661 graph_col
2662 [GRC_FRAME].
2663 green,
2664 im->
2665 graph_col
2666 [GRC_FRAME].
2667 blue, im->graph_col[GRC_FRAME].alpha);
2668 if (im->gdes[i].dash) {
2669 /* make box borders in legend dashed if the graph is dashed */
2670 double dashes[] = {
2671 3.0
2672 };
2673 cairo_set_dash(im->cr, dashes, 1, 0.0);
2674 }
2675 cairo_stroke(im->cr);
2676 cairo_restore(im->cr);
2677 }
2678 }
2679 }
2680 }
2683 /*****************************************************
2684 * lazy check make sure we rely need to create this graph
2685 *****************************************************/
2687 int lazy_check(
2688 image_desc_t *im)
2689 {
2690 FILE *fd = NULL;
2691 int size = 1;
2692 struct stat imgstat;
2694 if (im->lazy == 0)
2695 return 0; /* no lazy option */
2696 if (strlen(im->graphfile) == 0)
2697 return 0; /* inmemory option */
2698 if (stat(im->graphfile, &imgstat) != 0)
2699 return 0; /* can't stat */
2700 /* one pixel in the existing graph is more then what we would
2701 change here ... */
2702 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2703 return 0;
2704 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2705 return 0; /* the file does not exist */
2706 switch (im->imgformat) {
2707 case IF_PNG:
2708 size = PngSize(fd, &(im->ximg), &(im->yimg));
2709 break;
2710 default:
2711 size = 1;
2712 }
2713 fclose(fd);
2714 return size;
2715 }
2718 int graph_size_location(
2719 image_desc_t
2720 *im,
2721 int elements)
2722 {
2723 /* The actual size of the image to draw is determined from
2724 ** several sources. The size given on the command line is
2725 ** the graph area but we need more as we have to draw labels
2726 ** and other things outside the graph area
2727 */
2729 int Xvertical = 0, Ytitle =
2730 0, Xylabel = 0, Xmain = 0, Ymain =
2731 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2733 if (im->extra_flags & ONLY_GRAPH) {
2734 im->xorigin = 0;
2735 im->ximg = im->xsize;
2736 im->yimg = im->ysize;
2737 im->yorigin = im->ysize;
2738 ytr(im, DNAN);
2739 return 0;
2740 }
2742 /** +---+--------------------------------------------+
2743 ** | y |...............graph title..................|
2744 ** | +---+-------------------------------+--------+
2745 ** | a | y | | |
2746 ** | x | | | |
2747 ** | i | a | | pie |
2748 ** | s | x | main graph area | chart |
2749 ** | | i | | area |
2750 ** | t | s | | |
2751 ** | i | | | |
2752 ** | t | l | | |
2753 ** | l | b +-------------------------------+--------+
2754 ** | e | l | x axis labels | |
2755 ** +---+---+-------------------------------+--------+
2756 ** |....................legends.....................|
2757 ** +------------------------------------------------+
2758 ** | watermark |
2759 ** +------------------------------------------------+
2760 */
2762 if (im->ylegend[0] != '\0') {
2763 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2764 }
2766 if (im->title[0] != '\0') {
2767 /* The title is placed "inbetween" two text lines so it
2768 ** automatically has some vertical spacing. The horizontal
2769 ** spacing is added here, on each side.
2770 */
2771 /* if necessary, reduce the font size of the title until it fits the image width */
2772 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2773 }
2775 if (elements) {
2776 if (im->draw_x_grid) {
2777 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2778 }
2779 if (im->draw_y_grid || im->forceleftspace) {
2780 Xylabel =
2781 gfx_get_text_width(im, 0,
2782 im->
2783 text_prop
2784 [TEXT_PROP_AXIS].
2785 font_desc,
2786 im->tabwidth, "0") * im->unitslength;
2787 }
2788 }
2790 if (im->extra_flags & FULL_SIZE_MODE) {
2791 /* The actual size of the image to draw has been determined by the user.
2792 ** The graph area is the space remaining after accounting for the legend,
2793 ** the watermark, the pie chart, the axis labels, and the title.
2794 */
2795 im->xorigin = 0;
2796 im->ximg = im->xsize;
2797 im->yimg = im->ysize;
2798 im->yorigin = im->ysize;
2799 Xmain = im->ximg;
2800 Ymain = im->yimg;
2801 im->yorigin += Ytitle;
2802 /* Now calculate the total size. Insert some spacing where
2803 desired. im->xorigin and im->yorigin need to correspond
2804 with the lower left corner of the main graph area or, if
2805 this one is not set, the imaginary box surrounding the
2806 pie chart area. */
2807 /* Initial size calculation for the main graph area */
2808 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2809 if (Xmain)
2810 Xmain -= Xspacing; /* put space between main graph area and right edge */
2811 im->xorigin = Xspacing + Xylabel;
2812 /* the length of the title should not influence with width of the graph
2813 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2814 if (Xvertical) { /* unit description */
2815 Xmain -= Xvertical;
2816 im->xorigin += Xvertical;
2817 }
2818 im->xsize = Xmain;
2819 xtr(im, 0);
2820 /* The vertical size of the image is known in advance. The main graph area
2821 ** (Ymain) and im->yorigin must be set according to the space requirements
2822 ** of the legend and the axis labels.
2823 */
2824 if (im->extra_flags & NOLEGEND) {
2825 /* set dimensions correctly if using full size mode with no legend */
2826 im->yorigin =
2827 im->yimg -
2828 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2829 Ymain = im->yorigin;
2830 } else {
2831 /* Determine where to place the legends onto the image.
2832 ** Set Ymain and adjust im->yorigin to match the space requirements.
2833 */
2834 if (leg_place(im, &Ymain) == -1)
2835 return -1;
2836 }
2839 /* remove title space *or* some padding above the graph from the main graph area */
2840 if (Ytitle) {
2841 Ymain -= Ytitle;
2842 } else {
2843 Ymain -= 1.5 * Yspacing;
2844 }
2846 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2847 if (im->watermark[0] != '\0') {
2848 Ymain -= Ywatermark;
2849 }
2851 im->ysize = Ymain;
2852 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2854 /* The actual size of the image to draw is determined from
2855 ** several sources. The size given on the command line is
2856 ** the graph area but we need more as we have to draw labels
2857 ** and other things outside the graph area.
2858 */
2860 if (im->ylegend[0] != '\0') {
2861 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2862 }
2865 if (im->title[0] != '\0') {
2866 /* The title is placed "inbetween" two text lines so it
2867 ** automatically has some vertical spacing. The horizontal
2868 ** spacing is added here, on each side.
2869 */
2870 /* don't care for the with of the title
2871 Xtitle = gfx_get_text_width(im->canvas, 0,
2872 im->text_prop[TEXT_PROP_TITLE].font,
2873 im->text_prop[TEXT_PROP_TITLE].size,
2874 im->tabwidth,
2875 im->title, 0) + 2*Xspacing; */
2876 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2877 }
2879 if (elements) {
2880 Xmain = im->xsize;
2881 Ymain = im->ysize;
2882 }
2883 /* Now calculate the total size. Insert some spacing where
2884 desired. im->xorigin and im->yorigin need to correspond
2885 with the lower left corner of the main graph area or, if
2886 this one is not set, the imaginary box surrounding the
2887 pie chart area. */
2889 /* The legend width cannot yet be determined, as a result we
2890 ** have problems adjusting the image to it. For now, we just
2891 ** forget about it at all; the legend will have to fit in the
2892 ** size already allocated.
2893 */
2894 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2895 if (Xmain)
2896 im->ximg += Xspacing;
2897 im->xorigin = Xspacing + Xylabel;
2898 /* the length of the title should not influence with width of the graph
2899 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2900 if (Xvertical) { /* unit description */
2901 im->ximg += Xvertical;
2902 im->xorigin += Xvertical;
2903 }
2904 xtr(im, 0);
2905 /* The vertical size is interesting... we need to compare
2906 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2907 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2908 ** in order to start even thinking about Ylegend or Ywatermark.
2909 **
2910 ** Do it in three portions: First calculate the inner part,
2911 ** then do the legend, then adjust the total height of the img,
2912 ** adding space for a watermark if one exists;
2913 */
2914 /* reserve space for main and/or pie */
2915 im->yimg = Ymain + Yxlabel;
2916 im->yorigin = im->yimg - Yxlabel;
2917 /* reserve space for the title *or* some padding above the graph */
2918 if (Ytitle) {
2919 im->yimg += Ytitle;
2920 im->yorigin += Ytitle;
2921 } else {
2922 im->yimg += 1.5 * Yspacing;
2923 im->yorigin += 1.5 * Yspacing;
2924 }
2925 /* reserve space for padding below the graph */
2926 im->yimg += Yspacing;
2927 /* Determine where to place the legends onto the image.
2928 ** Adjust im->yimg to match the space requirements.
2929 */
2930 if (leg_place(im, 0) == -1)
2931 return -1;
2932 if (im->watermark[0] != '\0') {
2933 im->yimg += Ywatermark;
2934 }
2935 }
2937 ytr(im, DNAN);
2938 return 0;
2939 }
2941 static cairo_status_t cairo_output(
2942 void *closure,
2943 const unsigned char
2944 *data,
2945 unsigned int length)
2946 {
2947 image_desc_t *im = (image_desc_t*)closure;
2949 im->rendered_image =
2950 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
2951 if (im->rendered_image == NULL)
2952 return CAIRO_STATUS_WRITE_ERROR;
2953 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2954 im->rendered_image_size += length;
2955 return CAIRO_STATUS_SUCCESS;
2956 }
2958 /* draw that picture thing ... */
2959 int graph_paint(
2960 image_desc_t *im)
2961 {
2962 int i, ii;
2963 int lazy = lazy_check(im);
2964 double areazero = 0.0;
2965 graph_desc_t *lastgdes = NULL;
2966 rrd_infoval_t info;
2968 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
2970 /* if we want and can be lazy ... quit now */
2971 if (lazy) {
2972 info.u_cnt = im->ximg;
2973 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2974 info.u_cnt = im->yimg;
2975 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2976 return 0;
2977 }
2978 /* pull the data from the rrd files ... */
2979 if (data_fetch(im) == -1)
2980 return -1;
2981 /* evaluate VDEF and CDEF operations ... */
2982 if (data_calc(im) == -1)
2983 return -1;
2984 /* calculate and PRINT and GPRINT definitions. We have to do it at
2985 * this point because it will affect the length of the legends
2986 * if there are no graph elements (i==0) we stop here ...
2987 * if we are lazy, try to quit ...
2988 */
2989 i = print_calc(im);
2990 if (i < 0)
2991 return -1;
2993 if ((i == 0) || lazy)
2994 return 0;
2996 /**************************************************************
2997 *** Calculating sizes and locations became a bit confusing ***
2998 *** so I moved this into a separate function. ***
2999 **************************************************************/
3000 if (graph_size_location(im, i) == -1)
3001 return -1;
3003 info.u_cnt = im->xorigin;
3004 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3005 info.u_cnt = im->yorigin - im->ysize;
3006 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3007 info.u_cnt = im->xsize;
3008 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3009 info.u_cnt = im->ysize;
3010 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3011 info.u_cnt = im->ximg;
3012 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3013 info.u_cnt = im->yimg;
3014 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3016 /* get actual drawing data and find min and max values */
3017 if (data_proc(im) == -1)
3018 return -1;
3019 if (!im->logarithmic) {
3020 si_unit(im);
3021 }
3023 /* identify si magnitude Kilo, Mega Giga ? */
3024 if (!im->rigid && !im->logarithmic)
3025 expand_range(im); /* make sure the upper and lower limit are
3026 sensible values */
3028 info.u_val = im->minval;
3029 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3030 info.u_val = im->maxval;
3031 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3033 if (!calc_horizontal_grid(im))
3034 return -1;
3035 /* reset precalc */
3036 ytr(im, DNAN);
3037 /* if (im->gridfit)
3038 apply_gridfit(im); */
3039 /* the actual graph is created by going through the individual
3040 graph elements and then drawing them */
3041 cairo_surface_destroy(im->surface);
3042 switch (im->imgformat) {
3043 case IF_PNG:
3044 im->surface =
3045 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3046 im->ximg * im->zoom,
3047 im->yimg * im->zoom);
3048 break;
3049 case IF_PDF:
3050 im->gridfit = 0;
3051 im->surface = strlen(im->graphfile)
3052 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3053 im->yimg * im->zoom)
3054 : cairo_pdf_surface_create_for_stream
3055 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3056 break;
3057 case IF_EPS:
3058 im->gridfit = 0;
3059 im->surface = strlen(im->graphfile)
3060 ?
3061 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3062 im->yimg * im->zoom)
3063 : cairo_ps_surface_create_for_stream
3064 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3065 break;
3066 case IF_SVG:
3067 im->gridfit = 0;
3068 im->surface = strlen(im->graphfile)
3069 ?
3070 cairo_svg_surface_create(im->
3071 graphfile,
3072 im->ximg * im->zoom, im->yimg * im->zoom)
3073 : cairo_svg_surface_create_for_stream
3074 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3075 cairo_svg_surface_restrict_to_version
3076 (im->surface, CAIRO_SVG_VERSION_1_1);
3077 break;
3078 };
3079 cairo_destroy(im->cr);
3080 im->cr = cairo_create(im->surface);
3081 cairo_set_antialias(im->cr, im->graph_antialias);
3082 cairo_scale(im->cr, im->zoom, im->zoom);
3083 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3084 gfx_new_area(im, 0, 0, 0, im->yimg,
3085 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3086 gfx_add_point(im, im->ximg, 0);
3087 gfx_close_path(im);
3088 gfx_new_area(im, im->xorigin,
3089 im->yorigin,
3090 im->xorigin +
3091 im->xsize, im->yorigin,
3092 im->xorigin +
3093 im->xsize,
3094 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3095 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3096 gfx_close_path(im);
3097 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3098 im->xsize, im->ysize + 2.0);
3099 cairo_clip(im->cr);
3100 if (im->minval > 0.0)
3101 areazero = im->minval;
3102 if (im->maxval < 0.0)
3103 areazero = im->maxval;
3104 for (i = 0; i < im->gdes_c; i++) {
3105 switch (im->gdes[i].gf) {
3106 case GF_CDEF:
3107 case GF_VDEF:
3108 case GF_DEF:
3109 case GF_PRINT:
3110 case GF_GPRINT:
3111 case GF_COMMENT:
3112 case GF_TEXTALIGN:
3113 case GF_HRULE:
3114 case GF_VRULE:
3115 case GF_XPORT:
3116 case GF_SHIFT:
3117 break;
3118 case GF_TICK:
3119 for (ii = 0; ii < im->xsize; ii++) {
3120 if (!isnan(im->gdes[i].p_data[ii])
3121 && im->gdes[i].p_data[ii] != 0.0) {
3122 if (im->gdes[i].yrule > 0) {
3123 gfx_line(im,
3124 im->xorigin + ii,
3125 im->yorigin,
3126 im->xorigin + ii,
3127 im->yorigin -
3128 im->gdes[i].yrule *
3129 im->ysize, 1.0, im->gdes[i].col);
3130 } else if (im->gdes[i].yrule < 0) {
3131 gfx_line(im,
3132 im->xorigin + ii,
3133 im->yorigin - im->ysize,
3134 im->xorigin + ii,
3135 im->yorigin - (1 -
3136 im->gdes[i].
3137 yrule) *
3138 im->ysize, 1.0, im->gdes[i].col);
3139 }
3140 }
3141 }
3142 break;
3143 case GF_LINE:
3144 case GF_AREA:
3145 /* fix data points at oo and -oo */
3146 for (ii = 0; ii < im->xsize; ii++) {
3147 if (isinf(im->gdes[i].p_data[ii])) {
3148 if (im->gdes[i].p_data[ii] > 0) {
3149 im->gdes[i].p_data[ii] = im->maxval;
3150 } else {
3151 im->gdes[i].p_data[ii] = im->minval;
3152 }
3154 }
3155 } /* for */
3157 /* *******************************************************
3158 a ___. (a,t)
3159 | | ___
3160 ____| | | |
3161 | |___|
3162 -------|--t-1--t--------------------------------
3164 if we know the value at time t was a then
3165 we draw a square from t-1 to t with the value a.
3167 ********************************************************* */
3168 if (im->gdes[i].col.alpha != 0.0) {
3169 /* GF_LINE and friend */
3170 if (im->gdes[i].gf == GF_LINE) {
3171 double last_y = 0.0;
3172 int draw_on = 0;
3174 cairo_save(im->cr);
3175 cairo_new_path(im->cr);
3176 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3177 if (im->gdes[i].dash) {
3178 cairo_set_dash(im->cr,
3179 im->gdes[i].p_dashes,
3180 im->gdes[i].ndash, im->gdes[i].offset);
3181 }
3183 for (ii = 1; ii < im->xsize; ii++) {
3184 if (isnan(im->gdes[i].p_data[ii])
3185 || (im->slopemode == 1
3186 && isnan(im->gdes[i].p_data[ii - 1]))) {
3187 draw_on = 0;
3188 continue;
3189 }
3190 if (draw_on == 0) {
3191 last_y = ytr(im, im->gdes[i].p_data[ii]);
3192 if (im->slopemode == 0) {
3193 double x = ii - 1 + im->xorigin;
3194 double y = last_y;
3196 gfx_line_fit(im, &x, &y);
3197 cairo_move_to(im->cr, x, y);
3198 x = ii + im->xorigin;
3199 y = last_y;
3200 gfx_line_fit(im, &x, &y);
3201 cairo_line_to(im->cr, x, y);
3202 } else {
3203 double x = ii - 1 + im->xorigin;
3204 double y =
3205 ytr(im, im->gdes[i].p_data[ii - 1]);
3206 gfx_line_fit(im, &x, &y);
3207 cairo_move_to(im->cr, x, y);
3208 x = ii + im->xorigin;
3209 y = last_y;
3210 gfx_line_fit(im, &x, &y);
3211 cairo_line_to(im->cr, x, y);
3212 }
3213 draw_on = 1;
3214 } else {
3215 double x1 = ii + im->xorigin;
3216 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3218 if (im->slopemode == 0
3219 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3220 double x = ii - 1 + im->xorigin;
3221 double y = y1;
3223 gfx_line_fit(im, &x, &y);
3224 cairo_line_to(im->cr, x, y);
3225 };
3226 last_y = y1;
3227 gfx_line_fit(im, &x1, &y1);
3228 cairo_line_to(im->cr, x1, y1);
3229 };
3230 }
3231 cairo_set_source_rgba(im->cr,
3232 im->gdes[i].
3233 col.red,
3234 im->gdes[i].
3235 col.green,
3236 im->gdes[i].
3237 col.blue, im->gdes[i].col.alpha);
3238 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3239 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3240 cairo_stroke(im->cr);
3241 cairo_restore(im->cr);
3242 } else {
3243 int idxI = -1;
3244 double *foreY =
3245 (double *) malloc(sizeof(double) * im->xsize * 2);
3246 double *foreX =
3247 (double *) malloc(sizeof(double) * im->xsize * 2);
3248 double *backY =
3249 (double *) malloc(sizeof(double) * im->xsize * 2);
3250 double *backX =
3251 (double *) malloc(sizeof(double) * im->xsize * 2);
3252 int drawem = 0;
3254 for (ii = 0; ii <= im->xsize; ii++) {
3255 double ybase, ytop;
3257 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3258 int cntI = 1;
3259 int lastI = 0;
3261 while (cntI < idxI
3262 &&
3263 AlmostEqual2sComplement(foreY
3264 [lastI],
3265 foreY[cntI], 4)
3266 &&
3267 AlmostEqual2sComplement(foreY
3268 [lastI],
3269 foreY
3270 [cntI + 1], 4)) {
3271 cntI++;
3272 }
3273 gfx_new_area(im,
3274 backX[0], backY[0],
3275 foreX[0], foreY[0],
3276 foreX[cntI],
3277 foreY[cntI], im->gdes[i].col);
3278 while (cntI < idxI) {
3279 lastI = cntI;
3280 cntI++;
3281 while (cntI < idxI
3282 &&
3283 AlmostEqual2sComplement(foreY
3284 [lastI],
3285 foreY[cntI], 4)
3286 &&
3287 AlmostEqual2sComplement(foreY
3288 [lastI],
3289 foreY
3290 [cntI
3291 + 1], 4)) {
3292 cntI++;
3293 }
3294 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3295 }
3296 gfx_add_point(im, backX[idxI], backY[idxI]);
3297 while (idxI > 1) {
3298 lastI = idxI;
3299 idxI--;
3300 while (idxI > 1
3301 &&
3302 AlmostEqual2sComplement(backY
3303 [lastI],
3304 backY[idxI], 4)
3305 &&
3306 AlmostEqual2sComplement(backY
3307 [lastI],
3308 backY
3309 [idxI
3310 - 1], 4)) {
3311 idxI--;
3312 }
3313 gfx_add_point(im, backX[idxI], backY[idxI]);
3314 }
3315 idxI = -1;
3316 drawem = 0;
3317 gfx_close_path(im);
3318 }
3319 if (drawem != 0) {
3320 drawem = 0;
3321 idxI = -1;
3322 }
3323 if (ii == im->xsize)
3324 break;
3325 if (im->slopemode == 0 && ii == 0) {
3326 continue;
3327 }
3328 if (isnan(im->gdes[i].p_data[ii])) {
3329 drawem = 1;
3330 continue;
3331 }
3332 ytop = ytr(im, im->gdes[i].p_data[ii]);
3333 if (lastgdes && im->gdes[i].stack) {
3334 ybase = ytr(im, lastgdes->p_data[ii]);
3335 } else {
3336 ybase = ytr(im, areazero);
3337 }
3338 if (ybase == ytop) {
3339 drawem = 1;
3340 continue;
3341 }
3343 if (ybase > ytop) {
3344 double extra = ytop;
3346 ytop = ybase;
3347 ybase = extra;
3348 }
3349 if (im->slopemode == 0) {
3350 backY[++idxI] = ybase - 0.2;
3351 backX[idxI] = ii + im->xorigin - 1;
3352 foreY[idxI] = ytop + 0.2;
3353 foreX[idxI] = ii + im->xorigin - 1;
3354 }
3355 backY[++idxI] = ybase - 0.2;
3356 backX[idxI] = ii + im->xorigin;
3357 foreY[idxI] = ytop + 0.2;
3358 foreX[idxI] = ii + im->xorigin;
3359 }
3360 /* close up any remaining area */
3361 free(foreY);
3362 free(foreX);
3363 free(backY);
3364 free(backX);
3365 } /* else GF_LINE */
3366 }
3367 /* if color != 0x0 */
3368 /* make sure we do not run into trouble when stacking on NaN */
3369 for (ii = 0; ii < im->xsize; ii++) {
3370 if (isnan(im->gdes[i].p_data[ii])) {
3371 if (lastgdes && (im->gdes[i].stack)) {
3372 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3373 } else {
3374 im->gdes[i].p_data[ii] = areazero;
3375 }
3376 }
3377 }
3378 lastgdes = &(im->gdes[i]);
3379 break;
3380 case GF_STACK:
3381 rrd_set_error
3382 ("STACK should already be turned into LINE or AREA here");
3383 return -1;
3384 break;
3385 } /* switch */
3386 }
3387 cairo_reset_clip(im->cr);
3389 /* grid_paint also does the text */
3390 if (!(im->extra_flags & ONLY_GRAPH))
3391 grid_paint(im);
3392 if (!(im->extra_flags & ONLY_GRAPH))
3393 axis_paint(im);
3394 /* the RULES are the last thing to paint ... */
3395 for (i = 0; i < im->gdes_c; i++) {
3397 switch (im->gdes[i].gf) {
3398 case GF_HRULE:
3399 if (im->gdes[i].yrule >= im->minval
3400 && im->gdes[i].yrule <= im->maxval) {
3401 cairo_save(im->cr);
3402 if (im->gdes[i].dash) {
3403 cairo_set_dash(im->cr,
3404 im->gdes[i].p_dashes,
3405 im->gdes[i].ndash, im->gdes[i].offset);
3406 }
3407 gfx_line(im, im->xorigin,
3408 ytr(im, im->gdes[i].yrule),
3409 im->xorigin + im->xsize,
3410 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3411 cairo_stroke(im->cr);
3412 cairo_restore(im->cr);
3413 }
3414 break;
3415 case GF_VRULE:
3416 if (im->gdes[i].xrule >= im->start
3417 && im->gdes[i].xrule <= im->end) {
3418 cairo_save(im->cr);
3419 if (im->gdes[i].dash) {
3420 cairo_set_dash(im->cr,
3421 im->gdes[i].p_dashes,
3422 im->gdes[i].ndash, im->gdes[i].offset);
3423 }
3424 gfx_line(im,
3425 xtr(im, im->gdes[i].xrule),
3426 im->yorigin, xtr(im,
3427 im->
3428 gdes[i].
3429 xrule),
3430 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3431 cairo_stroke(im->cr);
3432 cairo_restore(im->cr);
3433 }
3434 break;
3435 default:
3436 break;
3437 }
3438 }
3441 switch (im->imgformat) {
3442 case IF_PNG:
3443 {
3444 cairo_status_t status;
3446 status = strlen(im->graphfile) ?
3447 cairo_surface_write_to_png(im->surface, im->graphfile)
3448 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3449 im);
3451 if (status != CAIRO_STATUS_SUCCESS) {
3452 rrd_set_error("Could not save png to '%s'", im->graphfile);
3453 return 1;
3454 }
3455 break;
3456 }
3457 default:
3458 if (strlen(im->graphfile)) {
3459 cairo_show_page(im->cr);
3460 } else {
3461 cairo_surface_finish(im->surface);
3462 }
3463 break;
3464 }
3466 return 0;
3467 }
3470 /*****************************************************
3471 * graph stuff
3472 *****************************************************/
3474 int gdes_alloc(
3475 image_desc_t *im)
3476 {
3478 im->gdes_c++;
3479 if ((im->gdes = (graph_desc_t *)
3480 rrd_realloc(im->gdes, (im->gdes_c)
3481 * sizeof(graph_desc_t))) == NULL) {
3482 rrd_set_error("realloc graph_descs");
3483 return -1;
3484 }
3487 im->gdes[im->gdes_c - 1].step = im->step;
3488 im->gdes[im->gdes_c - 1].step_orig = im->step;
3489 im->gdes[im->gdes_c - 1].stack = 0;
3490 im->gdes[im->gdes_c - 1].linewidth = 0;
3491 im->gdes[im->gdes_c - 1].debug = 0;
3492 im->gdes[im->gdes_c - 1].start = im->start;
3493 im->gdes[im->gdes_c - 1].start_orig = im->start;
3494 im->gdes[im->gdes_c - 1].end = im->end;
3495 im->gdes[im->gdes_c - 1].end_orig = im->end;
3496 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3497 im->gdes[im->gdes_c - 1].data = NULL;
3498 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3499 im->gdes[im->gdes_c - 1].data_first = 0;
3500 im->gdes[im->gdes_c - 1].p_data = NULL;
3501 im->gdes[im->gdes_c - 1].rpnp = NULL;
3502 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3503 im->gdes[im->gdes_c - 1].shift = 0.0;
3504 im->gdes[im->gdes_c - 1].dash = 0;
3505 im->gdes[im->gdes_c - 1].ndash = 0;
3506 im->gdes[im->gdes_c - 1].offset = 0;
3507 im->gdes[im->gdes_c - 1].col.red = 0.0;
3508 im->gdes[im->gdes_c - 1].col.green = 0.0;
3509 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3510 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3511 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3512 im->gdes[im->gdes_c - 1].format[0] = '\0';
3513 im->gdes[im->gdes_c - 1].strftm = 0;
3514 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3515 im->gdes[im->gdes_c - 1].ds = -1;
3516 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3517 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3518 im->gdes[im->gdes_c - 1].yrule = DNAN;
3519 im->gdes[im->gdes_c - 1].xrule = 0;
3520 return 0;
3521 }
3523 /* copies input untill the first unescaped colon is found
3524 or until input ends. backslashes have to be escaped as well */
3525 int scan_for_col(
3526 const char *const input,
3527 int len,
3528 char *const output)
3529 {
3530 int inp, outp = 0;
3532 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3533 if (input[inp] == '\\'
3534 && input[inp + 1] != '\0'
3535 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3536 output[outp++] = input[++inp];
3537 } else {
3538 output[outp++] = input[inp];
3539 }
3540 }
3541 output[outp] = '\0';
3542 return inp;
3543 }
3545 /* Now just a wrapper around rrd_graph_v */
3546 int rrd_graph(
3547 int argc,
3548 char **argv,
3549 char ***prdata,
3550 int *xsize,
3551 int *ysize,
3552 FILE * stream,
3553 double *ymin,
3554 double *ymax)
3555 {
3556 int prlines = 0;
3557 rrd_info_t *grinfo = NULL;
3558 rrd_info_t *walker;
3560 grinfo = rrd_graph_v(argc, argv);
3561 if (grinfo == NULL)
3562 return -1;
3563 walker = grinfo;
3564 (*prdata) = NULL;
3565 while (walker) {
3566 if (strcmp(walker->key, "image_info") == 0) {
3567 prlines++;
3568 if (((*prdata) =
3569 (char**)rrd_realloc((*prdata),
3570 (prlines + 1) * sizeof(char *))) == NULL) {
3571 rrd_set_error("realloc prdata");
3572 return 0;
3573 }
3574 /* imginfo goes to position 0 in the prdata array */
3575 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3576 + 2) * sizeof(char));
3577 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3578 (*prdata)[prlines] = NULL;
3579 }
3580 /* skip anything else */
3581 walker = walker->next;
3582 }
3583 walker = grinfo;
3584 *xsize = 0;
3585 *ysize = 0;
3586 *ymin = 0;
3587 *ymax = 0;
3588 while (walker) {
3589 if (strcmp(walker->key, "image_width") == 0) {
3590 *xsize = walker->value.u_int;
3591 } else if (strcmp(walker->key, "image_height") == 0) {
3592 *ysize = walker->value.u_int;
3593 } else if (strcmp(walker->key, "value_min") == 0) {
3594 *ymin = walker->value.u_val;
3595 } else if (strcmp(walker->key, "value_max") == 0) {
3596 *ymax = walker->value.u_val;
3597 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3598 prlines++;
3599 if (((*prdata) =
3600 (char**)rrd_realloc((*prdata),
3601 (prlines + 1) * sizeof(char *))) == NULL) {
3602 rrd_set_error("realloc prdata");
3603 return 0;
3604 }
3605 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3606 + 2) * sizeof(char));
3607 (*prdata)[prlines] = NULL;
3608 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3609 } else if (strcmp(walker->key, "image") == 0) {
3610 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3611 (stream ? stream : stdout));
3612 }
3613 /* skip anything else */
3614 walker = walker->next;
3615 }
3616 rrd_info_free(grinfo);
3617 return 0;
3618 }
3621 /* Some surgery done on this function, it became ridiculously big.
3622 ** Things moved:
3623 ** - initializing now in rrd_graph_init()
3624 ** - options parsing now in rrd_graph_options()
3625 ** - script parsing now in rrd_graph_script()
3626 */
3627 rrd_info_t *rrd_graph_v(
3628 int argc,
3629 char **argv)
3630 {
3631 image_desc_t im;
3632 rrd_info_t *grinfo;
3633 rrd_graph_init(&im);
3634 /* a dummy surface so that we can measure text sizes for placements */
3636 rrd_graph_options(argc, argv, &im);
3637 if (rrd_test_error()) {
3638 rrd_info_free(im.grinfo);
3639 im_free(&im);
3640 return NULL;
3641 }
3643 if (optind >= argc) {
3644 rrd_info_free(im.grinfo);
3645 im_free(&im);
3646 rrd_set_error("missing filename");
3647 return NULL;
3648 }
3650 if (strlen(argv[optind]) >= MAXPATH) {
3651 rrd_set_error("filename (including path) too long");
3652 rrd_info_free(im.grinfo);
3653 im_free(&im);
3654 return NULL;
3655 }
3657 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3658 im.graphfile[MAXPATH - 1] = '\0';
3660 if (strcmp(im.graphfile, "-") == 0) {
3661 im.graphfile[0] = '\0';
3662 }
3664 rrd_graph_script(argc, argv, &im, 1);
3665 if (rrd_test_error()) {
3666 rrd_info_free(im.grinfo);
3667 im_free(&im);
3668 return NULL;
3669 }
3671 /* Everything is now read and the actual work can start */
3673 if (graph_paint(&im) == -1) {
3674 rrd_info_free(im.grinfo);
3675 im_free(&im);
3676 return NULL;
3677 }
3680 /* The image is generated and needs to be output.
3681 ** Also, if needed, print a line with information about the image.
3682 */
3684 if (im.imginfo) {
3685 rrd_infoval_t info;
3686 char *path;
3687 char *filename;
3689 path = strdup(im.graphfile);
3690 filename = basename(path);
3691 info.u_str =
3692 sprintf_alloc(im.imginfo,
3693 filename,
3694 (long) (im.zoom *
3695 im.ximg), (long) (im.zoom * im.yimg));
3696 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3697 free(info.u_str);
3698 free(path);
3699 }
3700 if (im.rendered_image) {
3701 rrd_infoval_t img;
3703 img.u_blo.size = im.rendered_image_size;
3704 img.u_blo.ptr = im.rendered_image;
3705 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3706 }
3707 grinfo = im.grinfo;
3708 im_free(&im);
3709 return grinfo;
3710 }
3712 static void
3713 rrd_set_font_desc (
3714 image_desc_t *im,int prop,char *font, double size ){
3715 if (font){
3716 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3717 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3718 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3719 };
3720 if (size > 0){
3721 im->text_prop[prop].size = size;
3722 };
3723 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3724 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3725 };
3726 }
3728 void rrd_graph_init(
3729 image_desc_t
3730 *im)
3731 {
3732 unsigned int i;
3733 char *deffont = getenv("RRD_DEFAULT_FONT");
3734 static PangoFontMap *fontmap = NULL;
3735 PangoContext *context;
3737 #ifdef HAVE_TZSET
3738 tzset();
3739 #endif
3740 #ifdef HAVE_SETLOCALE
3741 setlocale(LC_TIME, "");
3742 #ifdef HAVE_MBSTOWCS
3743 setlocale(LC_CTYPE, "");
3744 #endif
3745 #endif
3746 im->base = 1000;
3747 im->daemon_addr = NULL;
3748 im->draw_x_grid = 1;
3749 im->draw_y_grid = 1;
3750 im->extra_flags = 0;
3751 im->font_options = cairo_font_options_create();
3752 im->forceleftspace = 0;
3753 im->gdes_c = 0;
3754 im->gdes = NULL;
3755 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3756 im->grid_dash_off = 1;
3757 im->grid_dash_on = 1;
3758 im->gridfit = 1;
3759 im->grinfo = (rrd_info_t *) NULL;
3760 im->grinfo_current = (rrd_info_t *) NULL;
3761 im->imgformat = IF_PNG;
3762 im->imginfo = NULL;
3763 im->lazy = 0;
3764 im->logarithmic = 0;
3765 im->maxval = DNAN;
3766 im->minval = 0;
3767 im->minval = DNAN;
3768 im->prt_c = 0;
3769 im->rigid = 0;
3770 im->rendered_image_size = 0;
3771 im->rendered_image = NULL;
3772 im->slopemode = 0;
3773 im->step = 0;
3774 im->symbol = ' ';
3775 im->tabwidth = 40.0;
3776 im->title[0] = '\0';
3777 im->unitsexponent = 9999;
3778 im->unitslength = 6;
3779 im->viewfactor = 1.0;
3780 im->watermark[0] = '\0';
3781 im->with_markup = 0;
3782 im->ximg = 0;
3783 im->xlab_user.minsec = -1;
3784 im->xorigin = 0;
3785 im->xsize = 400;
3786 im->ygridstep = DNAN;
3787 im->yimg = 0;
3788 im->ylegend[0] = '\0';
3789 im->yorigin = 0;
3790 im->ysize = 100;
3791 im->zoom = 1;
3793 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3794 im->cr = cairo_create(im->surface);
3796 for (i = 0; i < DIM(text_prop); i++) {
3797 im->text_prop[i].size = -1;
3798 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3799 }
3801 if (fontmap == NULL){
3802 fontmap = pango_cairo_font_map_get_default();
3803 }
3805 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3807 pango_cairo_context_set_resolution(context, 100);
3809 pango_cairo_update_context(im->cr,context);
3811 im->layout = pango_layout_new(context);
3813 // im->layout = pango_cairo_create_layout(im->cr);
3816 cairo_font_options_set_hint_style
3817 (im->font_options, CAIRO_HINT_STYLE_FULL);
3818 cairo_font_options_set_hint_metrics
3819 (im->font_options, CAIRO_HINT_METRICS_ON);
3820 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3824 for (i = 0; i < DIM(graph_col); i++)
3825 im->graph_col[i] = graph_col[i];
3828 }
3831 void rrd_graph_options(
3832 int argc,
3833 char *argv[],
3834 image_desc_t
3835 *im)
3836 {
3837 int stroff;
3838 char *parsetime_error = NULL;
3839 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3840 time_t start_tmp = 0, end_tmp = 0;
3841 long long_tmp;
3842 rrd_time_value_t start_tv, end_tv;
3843 long unsigned int color;
3844 char *old_locale = "";
3846 /* defines for long options without a short equivalent. should be bytes,
3847 and may not collide with (the ASCII value of) short options */
3848 #define LONGOPT_UNITS_SI 255
3850 /* *INDENT-OFF* */
3851 struct option long_options[] = {
3852 { "start", required_argument, 0, 's'},
3853 { "end", required_argument, 0, 'e'},
3854 { "x-grid", required_argument, 0, 'x'},
3855 { "y-grid", required_argument, 0, 'y'},
3856 { "vertical-label", required_argument, 0, 'v'},
3857 { "width", required_argument, 0, 'w'},
3858 { "height", required_argument, 0, 'h'},
3859 { "full-size-mode", no_argument, 0, 'D'},
3860 { "interlaced", no_argument, 0, 'i'},
3861 { "upper-limit", required_argument, 0, 'u'},
3862 { "lower-limit", required_argument, 0, 'l'},
3863 { "rigid", no_argument, 0, 'r'},
3864 { "base", required_argument, 0, 'b'},
3865 { "logarithmic", no_argument, 0, 'o'},
3866 { "color", required_argument, 0, 'c'},
3867 { "font", required_argument, 0, 'n'},
3868 { "title", required_argument, 0, 't'},
3869 { "imginfo", required_argument, 0, 'f'},
3870 { "imgformat", required_argument, 0, 'a'},
3871 { "lazy", no_argument, 0, 'z'},
3872 { "zoom", required_argument, 0, 'm'},
3873 { "no-legend", no_argument, 0, 'g'},
3874 { "force-rules-legend", no_argument, 0, 'F'},
3875 { "only-graph", no_argument, 0, 'j'},
3876 { "alt-y-grid", no_argument, 0, 'Y'},
3877 { "no-minor", no_argument, 0, 'I'},
3878 { "slope-mode", no_argument, 0, 'E'},
3879 { "alt-autoscale", no_argument, 0, 'A'},
3880 { "alt-autoscale-min", no_argument, 0, 'J'},
3881 { "alt-autoscale-max", no_argument, 0, 'M'},
3882 { "no-gridfit", no_argument, 0, 'N'},
3883 { "units-exponent", required_argument, 0, 'X'},
3884 { "units-length", required_argument, 0, 'L'},
3885 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3886 { "step", required_argument, 0, 'S'},
3887 { "tabwidth", required_argument, 0, 'T'},
3888 { "font-render-mode", required_argument, 0, 'R'},
3889 { "graph-render-mode", required_argument, 0, 'G'},
3890 { "font-smoothing-threshold", required_argument, 0, 'B'},
3891 { "watermark", required_argument, 0, 'W'},
3892 { "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 */
3893 { "pango-markup", no_argument, 0, 'P'},
3894 { "daemon", required_argument, 0, 'd'},
3895 { 0, 0, 0, 0}
3896 };
3897 /* *INDENT-ON* */
3899 optind = 0;
3900 opterr = 0; /* initialize getopt */
3901 rrd_parsetime("end-24h", &start_tv);
3902 rrd_parsetime("now", &end_tv);
3903 while (1) {
3904 int option_index = 0;
3905 int opt;
3906 int col_start, col_end;
3908 opt = getopt_long(argc, argv,
3909 "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:",
3910 long_options, &option_index);
3911 if (opt == EOF)
3912 break;
3913 switch (opt) {
3914 case 'I':
3915 im->extra_flags |= NOMINOR;
3916 break;
3917 case 'Y':
3918 im->extra_flags |= ALTYGRID;
3919 break;
3920 case 'A':
3921 im->extra_flags |= ALTAUTOSCALE;
3922 break;
3923 case 'J':
3924 im->extra_flags |= ALTAUTOSCALE_MIN;
3925 break;
3926 case 'M':
3927 im->extra_flags |= ALTAUTOSCALE_MAX;
3928 break;
3929 case 'j':
3930 im->extra_flags |= ONLY_GRAPH;
3931 break;
3932 case 'g':
3933 im->extra_flags |= NOLEGEND;
3934 break;
3935 case 'F':
3936 im->extra_flags |= FORCE_RULES_LEGEND;
3937 break;
3938 case LONGOPT_UNITS_SI:
3939 if (im->extra_flags & FORCE_UNITS) {
3940 rrd_set_error("--units can only be used once!");
3941 setlocale(LC_NUMERIC, old_locale);
3942 return;
3943 }
3944 if (strcmp(optarg, "si") == 0)
3945 im->extra_flags |= FORCE_UNITS_SI;
3946 else {
3947 rrd_set_error("invalid argument for --units: %s", optarg);
3948 return;
3949 }
3950 break;
3951 case 'X':
3952 im->unitsexponent = atoi(optarg);
3953 break;
3954 case 'L':
3955 im->unitslength = atoi(optarg);
3956 im->forceleftspace = 1;
3957 break;
3958 case 'T':
3959 old_locale = setlocale(LC_NUMERIC, "C");
3960 im->tabwidth = atof(optarg);
3961 setlocale(LC_NUMERIC, old_locale);
3962 break;
3963 case 'S':
3964 old_locale = setlocale(LC_NUMERIC, "C");
3965 im->step = atoi(optarg);
3966 setlocale(LC_NUMERIC, old_locale);
3967 break;
3968 case 'N':
3969 im->gridfit = 0;
3970 break;
3971 case 'P':
3972 im->with_markup = 1;
3973 break;
3974 case 's':
3975 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3976 rrd_set_error("start time: %s", parsetime_error);
3977 return;
3978 }
3979 break;
3980 case 'e':
3981 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3982 rrd_set_error("end time: %s", parsetime_error);
3983 return;
3984 }
3985 break;
3986 case 'x':
3987 if (strcmp(optarg, "none") == 0) {
3988 im->draw_x_grid = 0;
3989 break;
3990 };
3991 if (sscanf(optarg,
3992 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3993 scan_gtm,
3994 &im->xlab_user.gridst,
3995 scan_mtm,
3996 &im->xlab_user.mgridst,
3997 scan_ltm,
3998 &im->xlab_user.labst,
3999 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4000 strncpy(im->xlab_form, optarg + stroff,
4001 sizeof(im->xlab_form) - 1);
4002 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4003 if ((int)
4004 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4005 rrd_set_error("unknown keyword %s", scan_gtm);
4006 return;
4007 } else if ((int)
4008 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4009 == -1) {
4010 rrd_set_error("unknown keyword %s", scan_mtm);
4011 return;
4012 } else if ((int)
4013 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4014 rrd_set_error("unknown keyword %s", scan_ltm);
4015 return;
4016 }
4017 im->xlab_user.minsec = 1;
4018 im->xlab_user.stst = im->xlab_form;
4019 } else {
4020 rrd_set_error("invalid x-grid format");
4021 return;
4022 }
4023 break;
4024 case 'y':
4026 if (strcmp(optarg, "none") == 0) {
4027 im->draw_y_grid = 0;
4028 break;
4029 };
4030 old_locale = setlocale(LC_NUMERIC, "C");
4031 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4032 setlocale(LC_NUMERIC, old_locale);
4033 if (im->ygridstep <= 0) {
4034 rrd_set_error("grid step must be > 0");
4035 return;
4036 } else if (im->ylabfact < 1) {
4037 rrd_set_error("label factor must be > 0");
4038 return;
4039 }
4040 } else {
4041 setlocale(LC_NUMERIC, old_locale);
4042 rrd_set_error("invalid y-grid format");
4043 return;
4044 }
4045 break;
4046 case 'v':
4047 strncpy(im->ylegend, optarg, 150);
4048 im->ylegend[150] = '\0';
4049 break;
4050 case 'u':
4051 old_locale = setlocale(LC_NUMERIC, "C");
4052 im->maxval = atof(optarg);
4053 setlocale(LC_NUMERIC, old_locale);
4054 break;
4055 case 'l':
4056 old_locale = setlocale(LC_NUMERIC, "C");
4057 im->minval = atof(optarg);
4058 setlocale(LC_NUMERIC, old_locale);
4059 break;
4060 case 'b':
4061 im->base = atol(optarg);
4062 if (im->base != 1024 && im->base != 1000) {
4063 rrd_set_error
4064 ("the only sensible value for base apart from 1000 is 1024");
4065 return;
4066 }
4067 break;
4068 case 'w':
4069 long_tmp = atol(optarg);
4070 if (long_tmp < 10) {
4071 rrd_set_error("width below 10 pixels");
4072 return;
4073 }
4074 im->xsize = long_tmp;
4075 break;
4076 case 'h':
4077 long_tmp = atol(optarg);
4078 if (long_tmp < 10) {
4079 rrd_set_error("height below 10 pixels");
4080 return;
4081 }
4082 im->ysize = long_tmp;
4083 break;
4084 case 'D':
4085 im->extra_flags |= FULL_SIZE_MODE;
4086 break;
4087 case 'i':
4088 /* interlaced png not supported at the moment */
4089 break;
4090 case 'r':
4091 im->rigid = 1;
4092 break;
4093 case 'f':
4094 im->imginfo = optarg;
4095 break;
4096 case 'a':
4097 if ((int)
4098 (im->imgformat = if_conv(optarg)) == -1) {
4099 rrd_set_error("unsupported graphics format '%s'", optarg);
4100 return;
4101 }
4102 break;
4103 case 'z':
4104 im->lazy = 1;
4105 break;
4106 case 'E':
4107 im->slopemode = 1;
4108 break;
4109 case 'o':
4110 im->logarithmic = 1;
4111 break;
4112 case 'c':
4113 if (sscanf(optarg,
4114 "%10[A-Z]#%n%8lx%n",
4115 col_nam, &col_start, &color, &col_end) == 2) {
4116 int ci;
4117 int col_len = col_end - col_start;
4119 switch (col_len) {
4120 case 3:
4121 color =
4122 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4123 0x011000) |
4124 ((color & 0x00F)
4125 * 0x001100)
4126 | 0x000000FF);
4127 break;
4128 case 4:
4129 color =
4130 (((color & 0xF000) *
4131 0x11000) | ((color & 0x0F00) *
4132 0x01100) | ((color &
4133 0x00F0) *
4134 0x00110) |
4135 ((color & 0x000F) * 0x00011)
4136 );
4137 break;
4138 case 6:
4139 color = (color << 8) + 0xff /* shift left by 8 */ ;
4140 break;
4141 case 8:
4142 break;
4143 default:
4144 rrd_set_error("the color format is #RRGGBB[AA]");
4145 return;
4146 }
4147 if ((ci = grc_conv(col_nam)) != -1) {
4148 im->graph_col[ci] = gfx_hex_to_col(color);
4149 } else {
4150 rrd_set_error("invalid color name '%s'", col_nam);
4151 return;
4152 }
4153 } else {
4154 rrd_set_error("invalid color def format");
4155 return;
4156 }
4157 break;
4158 case 'n':{
4159 char prop[15];
4160 double size = 1;
4161 int end;
4163 old_locale = setlocale(LC_NUMERIC, "C");
4164 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4165 int sindex, propidx;
4167 setlocale(LC_NUMERIC, old_locale);
4168 if ((sindex = text_prop_conv(prop)) != -1) {
4169 for (propidx = sindex;
4170 propidx < TEXT_PROP_LAST; propidx++) {
4171 if (size > 0) {
4172 rrd_set_font_desc(im,propidx,NULL,size);
4173 }
4174 if ((int) strlen(optarg) > end+2) {
4175 if (optarg[end] == ':') {
4176 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4177 } else {
4178 rrd_set_error
4179 ("expected : after font size in '%s'",
4180 optarg);
4181 return;
4182 }
4183 }
4184 /* only run the for loop for DEFAULT (0) for
4185 all others, we break here. woodo programming */
4186 if (propidx == sindex && sindex != 0)
4187 break;
4188 }
4189 } else {
4190 rrd_set_error("invalid fonttag '%s'", prop);
4191 return;
4192 }
4193 } else {
4194 setlocale(LC_NUMERIC, old_locale);
4195 rrd_set_error("invalid text property format");
4196 return;
4197 }
4198 break;
4199 }
4200 case 'm':
4201 old_locale = setlocale(LC_NUMERIC, "C");
4202 im->zoom = atof(optarg);
4203 setlocale(LC_NUMERIC, old_locale);
4204 if (im->zoom <= 0.0) {
4205 rrd_set_error("zoom factor must be > 0");
4206 return;
4207 }
4208 break;
4209 case 't':
4210 strncpy(im->title, optarg, 150);
4211 im->title[150] = '\0';
4212 break;
4213 case 'R':
4214 if (strcmp(optarg, "normal") == 0) {
4215 cairo_font_options_set_antialias
4216 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4217 cairo_font_options_set_hint_style
4218 (im->font_options, CAIRO_HINT_STYLE_FULL);
4219 } else if (strcmp(optarg, "light") == 0) {
4220 cairo_font_options_set_antialias
4221 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4222 cairo_font_options_set_hint_style
4223 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4224 } else if (strcmp(optarg, "mono") == 0) {
4225 cairo_font_options_set_antialias
4226 (im->font_options, CAIRO_ANTIALIAS_NONE);
4227 cairo_font_options_set_hint_style
4228 (im->font_options, CAIRO_HINT_STYLE_FULL);
4229 } else {
4230 rrd_set_error("unknown font-render-mode '%s'", optarg);
4231 return;
4232 }
4233 break;
4234 case 'G':
4235 if (strcmp(optarg, "normal") == 0)
4236 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4237 else if (strcmp(optarg, "mono") == 0)
4238 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4239 else {
4240 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4241 return;
4242 }
4243 break;
4244 case 'B':
4245 /* not supported curently */
4246 break;
4247 case 'W':
4248 strncpy(im->watermark, optarg, 100);
4249 im->watermark[99] = '\0';
4250 break;
4251 case 'd':
4252 {
4253 if (im->daemon_addr != NULL)
4254 {
4255 rrd_set_error ("You cannot specify --daemon "
4256 "more than once.");
4257 return;
4258 }
4260 im->daemon_addr = strdup(optarg);
4261 if (im->daemon_addr == NULL)
4262 {
4263 rrd_set_error("strdup failed");
4264 return;
4265 }
4267 break;
4268 }
4269 case '?':
4270 if (optopt != 0)
4271 rrd_set_error("unknown option '%c'", optopt);
4272 else
4273 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4274 return;
4275 }
4276 } /* while (1) */
4278 { /* try to connect to rrdcached */
4279 int status = rrdc_connect(im->daemon_addr);
4280 if (status != 0) return;
4281 }
4283 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4284 pango_layout_context_changed(im->layout);
4288 if (im->logarithmic && im->minval <= 0) {
4289 rrd_set_error
4290 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4291 return;
4292 }
4294 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4295 /* error string is set in rrd_parsetime.c */
4296 return;
4297 }
4299 if (start_tmp < 3600 * 24 * 365 * 10) {
4300 rrd_set_error
4301 ("the first entry to fetch should be after 1980 (%ld)",
4302 start_tmp);
4303 return;
4304 }
4306 if (end_tmp < start_tmp) {
4307 rrd_set_error
4308 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4309 return;
4310 }
4312 im->start = start_tmp;
4313 im->end = end_tmp;
4314 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4315 }
4317 int rrd_graph_color(
4318 image_desc_t
4319 *im,
4320 char *var,
4321 char *err,
4322 int optional)
4323 {
4324 char *color;
4325 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4327 color = strstr(var, "#");
4328 if (color == NULL) {
4329 if (optional == 0) {
4330 rrd_set_error("Found no color in %s", err);
4331 return 0;
4332 }
4333 return 0;
4334 } else {
4335 int n = 0;
4336 char *rest;
4337 long unsigned int col;
4339 rest = strstr(color, ":");
4340 if (rest != NULL)
4341 n = rest - color;
4342 else
4343 n = strlen(color);
4344 switch (n) {
4345 case 7:
4346 sscanf(color, "#%6lx%n", &col, &n);
4347 col = (col << 8) + 0xff /* shift left by 8 */ ;
4348 if (n != 7)
4349 rrd_set_error("Color problem in %s", err);
4350 break;
4351 case 9:
4352 sscanf(color, "#%8lx%n", &col, &n);
4353 if (n == 9)
4354 break;
4355 default:
4356 rrd_set_error("Color problem in %s", err);
4357 }
4358 if (rrd_test_error())
4359 return 0;
4360 gdp->col = gfx_hex_to_col(col);
4361 return n;
4362 }
4363 }
4366 int bad_format(
4367 char *fmt)
4368 {
4369 char *ptr;
4370 int n = 0;
4372 ptr = fmt;
4373 while (*ptr != '\0')
4374 if (*ptr++ == '%') {
4376 /* line cannot end with percent char */
4377 if (*ptr == '\0')
4378 return 1;
4379 /* '%s', '%S' and '%%' are allowed */
4380 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4381 ptr++;
4382 /* %c is allowed (but use only with vdef!) */
4383 else if (*ptr == 'c') {
4384 ptr++;
4385 n = 1;
4386 }
4388 /* or else '% 6.2lf' and such are allowed */
4389 else {
4390 /* optional padding character */
4391 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4392 ptr++;
4393 /* This should take care of 'm.n' with all three optional */
4394 while (*ptr >= '0' && *ptr <= '9')
4395 ptr++;
4396 if (*ptr == '.')
4397 ptr++;
4398 while (*ptr >= '0' && *ptr <= '9')
4399 ptr++;
4400 /* Either 'le', 'lf' or 'lg' must follow here */
4401 if (*ptr++ != 'l')
4402 return 1;
4403 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4404 ptr++;
4405 else
4406 return 1;
4407 n++;
4408 }
4409 }
4411 return (n != 1);
4412 }
4415 int vdef_parse(
4416 struct graph_desc_t
4417 *gdes,
4418 const char *const str)
4419 {
4420 /* A VDEF currently is either "func" or "param,func"
4421 * so the parsing is rather simple. Change if needed.
4422 */
4423 double param;
4424 char func[30];
4425 int n;
4426 char *old_locale;
4428 n = 0;
4429 old_locale = setlocale(LC_NUMERIC, "C");
4430 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4431 setlocale(LC_NUMERIC, old_locale);
4432 if (n == (int) strlen(str)) { /* matched */
4433 ;
4434 } else {
4435 n = 0;
4436 sscanf(str, "%29[A-Z]%n", func, &n);
4437 if (n == (int) strlen(str)) { /* matched */
4438 param = DNAN;
4439 } else {
4440 rrd_set_error
4441 ("Unknown function string '%s' in VDEF '%s'",
4442 str, gdes->vname);
4443 return -1;
4444 }
4445 }
4446 if (!strcmp("PERCENT", func))
4447 gdes->vf.op = VDEF_PERCENT;
4448 else if (!strcmp("MAXIMUM", func))
4449 gdes->vf.op = VDEF_MAXIMUM;
4450 else if (!strcmp("AVERAGE", func))
4451 gdes->vf.op = VDEF_AVERAGE;
4452 else if (!strcmp("STDEV", func))
4453 gdes->vf.op = VDEF_STDEV;
4454 else if (!strcmp("MINIMUM", func))
4455 gdes->vf.op = VDEF_MINIMUM;
4456 else if (!strcmp("TOTAL", func))
4457 gdes->vf.op = VDEF_TOTAL;
4458 else if (!strcmp("FIRST", func))
4459 gdes->vf.op = VDEF_FIRST;
4460 else if (!strcmp("LAST", func))
4461 gdes->vf.op = VDEF_LAST;
4462 else if (!strcmp("LSLSLOPE", func))
4463 gdes->vf.op = VDEF_LSLSLOPE;
4464 else if (!strcmp("LSLINT", func))
4465 gdes->vf.op = VDEF_LSLINT;
4466 else if (!strcmp("LSLCORREL", func))
4467 gdes->vf.op = VDEF_LSLCORREL;
4468 else {
4469 rrd_set_error
4470 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4471 return -1;
4472 };
4473 switch (gdes->vf.op) {
4474 case VDEF_PERCENT:
4475 if (isnan(param)) { /* no parameter given */
4476 rrd_set_error
4477 ("Function '%s' needs parameter in VDEF '%s'\n",
4478 func, gdes->vname);
4479 return -1;
4480 };
4481 if (param >= 0.0 && param <= 100.0) {
4482 gdes->vf.param = param;
4483 gdes->vf.val = DNAN; /* undefined */
4484 gdes->vf.when = 0; /* undefined */
4485 } else {
4486 rrd_set_error
4487 ("Parameter '%f' out of range in VDEF '%s'\n",
4488 param, gdes->vname);
4489 return -1;
4490 };
4491 break;
4492 case VDEF_MAXIMUM:
4493 case VDEF_AVERAGE:
4494 case VDEF_STDEV:
4495 case VDEF_MINIMUM:
4496 case VDEF_TOTAL:
4497 case VDEF_FIRST:
4498 case VDEF_LAST:
4499 case VDEF_LSLSLOPE:
4500 case VDEF_LSLINT:
4501 case VDEF_LSLCORREL:
4502 if (isnan(param)) {
4503 gdes->vf.param = DNAN;
4504 gdes->vf.val = DNAN;
4505 gdes->vf.when = 0;
4506 } else {
4507 rrd_set_error
4508 ("Function '%s' needs no parameter in VDEF '%s'\n",
4509 func, gdes->vname);
4510 return -1;
4511 };
4512 break;
4513 };
4514 return 0;
4515 }
4518 int vdef_calc(
4519 image_desc_t *im,
4520 int gdi)
4521 {
4522 graph_desc_t *src, *dst;
4523 rrd_value_t *data;
4524 long step, steps;
4526 dst = &im->gdes[gdi];
4527 src = &im->gdes[dst->vidx];
4528 data = src->data + src->ds;
4530 steps = (src->end - src->start) / src->step;
4531 #if 0
4532 printf
4533 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4534 src->start, src->end, steps);
4535 #endif
4536 switch (dst->vf.op) {
4537 case VDEF_PERCENT:{
4538 rrd_value_t *array;
4539 int field;
4540 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4541 rrd_set_error("malloc VDEV_PERCENT");
4542 return -1;
4543 }
4544 for (step = 0; step < steps; step++) {
4545 array[step] = data[step * src->ds_cnt];
4546 }
4547 qsort(array, step, sizeof(double), vdef_percent_compar);
4548 field = (steps - 1) * dst->vf.param / 100;
4549 dst->vf.val = array[field];
4550 dst->vf.when = 0; /* no time component */
4551 free(array);
4552 #if 0
4553 for (step = 0; step < steps; step++)
4554 printf("DEBUG: %3li:%10.2f %c\n",
4555 step, array[step], step == field ? '*' : ' ');
4556 #endif
4557 }
4558 break;
4559 case VDEF_MAXIMUM:
4560 step = 0;
4561 while (step != steps && isnan(data[step * src->ds_cnt]))
4562 step++;
4563 if (step == steps) {
4564 dst->vf.val = DNAN;
4565 dst->vf.when = 0;
4566 } else {
4567 dst->vf.val = data[step * src->ds_cnt];
4568 dst->vf.when = src->start + (step + 1) * src->step;
4569 }
4570 while (step != steps) {
4571 if (finite(data[step * src->ds_cnt])) {
4572 if (data[step * src->ds_cnt] > dst->vf.val) {
4573 dst->vf.val = data[step * src->ds_cnt];
4574 dst->vf.when = src->start + (step + 1) * src->step;
4575 }
4576 }
4577 step++;
4578 }
4579 break;
4580 case VDEF_TOTAL:
4581 case VDEF_STDEV:
4582 case VDEF_AVERAGE:{
4583 int cnt = 0;
4584 double sum = 0.0;
4585 double average = 0.0;
4587 for (step = 0; step < steps; step++) {
4588 if (finite(data[step * src->ds_cnt])) {
4589 sum += data[step * src->ds_cnt];
4590 cnt++;
4591 };
4592 }
4593 if (cnt) {
4594 if (dst->vf.op == VDEF_TOTAL) {
4595 dst->vf.val = sum * src->step;
4596 dst->vf.when = 0; /* no time component */
4597 } else if (dst->vf.op == VDEF_AVERAGE) {
4598 dst->vf.val = sum / cnt;
4599 dst->vf.when = 0; /* no time component */
4600 } else {
4601 average = sum / cnt;
4602 sum = 0.0;
4603 for (step = 0; step < steps; step++) {
4604 if (finite(data[step * src->ds_cnt])) {
4605 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4606 };
4607 }
4608 dst->vf.val = pow(sum / cnt, 0.5);
4609 dst->vf.when = 0; /* no time component */
4610 };
4611 } else {
4612 dst->vf.val = DNAN;
4613 dst->vf.when = 0;
4614 }
4615 }
4616 break;
4617 case VDEF_MINIMUM:
4618 step = 0;
4619 while (step != steps && isnan(data[step * src->ds_cnt]))
4620 step++;
4621 if (step == steps) {
4622 dst->vf.val = DNAN;
4623 dst->vf.when = 0;
4624 } else {
4625 dst->vf.val = data[step * src->ds_cnt];
4626 dst->vf.when = src->start + (step + 1) * src->step;
4627 }
4628 while (step != steps) {
4629 if (finite(data[step * src->ds_cnt])) {
4630 if (data[step * src->ds_cnt] < dst->vf.val) {
4631 dst->vf.val = data[step * src->ds_cnt];
4632 dst->vf.when = src->start + (step + 1) * src->step;
4633 }
4634 }
4635 step++;
4636 }
4637 break;
4638 case VDEF_FIRST:
4639 /* The time value returned here is one step before the
4640 * actual time value. This is the start of the first
4641 * non-NaN interval.
4642 */
4643 step = 0;
4644 while (step != steps && isnan(data[step * src->ds_cnt]))
4645 step++;
4646 if (step == steps) { /* all entries were NaN */
4647 dst->vf.val = DNAN;
4648 dst->vf.when = 0;
4649 } else {
4650 dst->vf.val = data[step * src->ds_cnt];
4651 dst->vf.when = src->start + step * src->step;
4652 }
4653 break;
4654 case VDEF_LAST:
4655 /* The time value returned here is the
4656 * actual time value. This is the end of the last
4657 * non-NaN interval.
4658 */
4659 step = steps - 1;
4660 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4661 step--;
4662 if (step < 0) { /* all entries were NaN */
4663 dst->vf.val = DNAN;
4664 dst->vf.when = 0;
4665 } else {
4666 dst->vf.val = data[step * src->ds_cnt];
4667 dst->vf.when = src->start + (step + 1) * src->step;
4668 }
4669 break;
4670 case VDEF_LSLSLOPE:
4671 case VDEF_LSLINT:
4672 case VDEF_LSLCORREL:{
4673 /* Bestfit line by linear least squares method */
4675 int cnt = 0;
4676 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4678 SUMx = 0;
4679 SUMy = 0;
4680 SUMxy = 0;
4681 SUMxx = 0;
4682 SUMyy = 0;
4683 for (step = 0; step < steps; step++) {
4684 if (finite(data[step * src->ds_cnt])) {
4685 cnt++;
4686 SUMx += step;
4687 SUMxx += step * step;
4688 SUMxy += step * data[step * src->ds_cnt];
4689 SUMy += data[step * src->ds_cnt];
4690 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4691 };
4692 }
4694 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4695 y_intercept = (SUMy - slope * SUMx) / cnt;
4696 correl =
4697 (SUMxy -
4698 (SUMx * SUMy) / cnt) /
4699 sqrt((SUMxx -
4700 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4701 if (cnt) {
4702 if (dst->vf.op == VDEF_LSLSLOPE) {
4703 dst->vf.val = slope;
4704 dst->vf.when = 0;
4705 } else if (dst->vf.op == VDEF_LSLINT) {
4706 dst->vf.val = y_intercept;
4707 dst->vf.when = 0;
4708 } else if (dst->vf.op == VDEF_LSLCORREL) {
4709 dst->vf.val = correl;
4710 dst->vf.when = 0;
4711 };
4712 } else {
4713 dst->vf.val = DNAN;
4714 dst->vf.when = 0;
4715 }
4716 }
4717 break;
4718 }
4719 return 0;
4720 }
4722 /* NaN < -INF < finite_values < INF */
4723 int vdef_percent_compar(
4724 const void
4725 *a,
4726 const void
4727 *b)
4728 {
4729 /* Equality is not returned; this doesn't hurt except
4730 * (maybe) for a little performance.
4731 */
4733 /* First catch NaN values. They are smallest */
4734 if (isnan(*(double *) a))
4735 return -1;
4736 if (isnan(*(double *) b))
4737 return 1;
4738 /* NaN doesn't reach this part so INF and -INF are extremes.
4739 * The sign from isinf() is compatible with the sign we return
4740 */
4741 if (isinf(*(double *) a))
4742 return isinf(*(double *) a);
4743 if (isinf(*(double *) b))
4744 return isinf(*(double *) b);
4745 /* If we reach this, both values must be finite */
4746 if (*(double *) a < *(double *) b)
4747 return -1;
4748 else
4749 return 1;
4750 }
4752 void grinfo_push(
4753 image_desc_t *im,
4754 char *key,
4755 rrd_info_type_t type,
4756 rrd_infoval_t value)
4757 {
4758 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4759 if (im->grinfo == NULL) {
4760 im->grinfo = im->grinfo_current;
4761 }
4762 }