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