d23d071683094225157919f9932eb7acd96a129d
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 return 0;
330 }
332 /* find SI magnitude symbol for the given number*/
333 void auto_scale(
334 image_desc_t *im, /* image description */
335 double *value,
336 char **symb_ptr,
337 double *magfact)
338 {
340 char *symbol[] = { "a", /* 10e-18 Atto */
341 "f", /* 10e-15 Femto */
342 "p", /* 10e-12 Pico */
343 "n", /* 10e-9 Nano */
344 "u", /* 10e-6 Micro */
345 "m", /* 10e-3 Milli */
346 " ", /* Base */
347 "k", /* 10e3 Kilo */
348 "M", /* 10e6 Mega */
349 "G", /* 10e9 Giga */
350 "T", /* 10e12 Tera */
351 "P", /* 10e15 Peta */
352 "E"
353 }; /* 10e18 Exa */
355 int symbcenter = 6;
356 int sindex;
358 if (*value == 0.0 || isnan(*value)) {
359 sindex = 0;
360 *magfact = 1.0;
361 } else {
362 sindex = floor(log(fabs(*value)) / log((double) im->base));
363 *magfact = pow((double) im->base, (double) sindex);
364 (*value) /= (*magfact);
365 }
366 if (sindex <= symbcenter && sindex >= -symbcenter) {
367 (*symb_ptr) = symbol[sindex + symbcenter];
368 } else {
369 (*symb_ptr) = "?";
370 }
371 }
374 static char si_symbol[] = {
375 'a', /* 10e-18 Atto */
376 'f', /* 10e-15 Femto */
377 'p', /* 10e-12 Pico */
378 'n', /* 10e-9 Nano */
379 'u', /* 10e-6 Micro */
380 'm', /* 10e-3 Milli */
381 ' ', /* Base */
382 'k', /* 10e3 Kilo */
383 'M', /* 10e6 Mega */
384 'G', /* 10e9 Giga */
385 'T', /* 10e12 Tera */
386 'P', /* 10e15 Peta */
387 'E', /* 10e18 Exa */
388 };
389 static const int si_symbcenter = 6;
391 /* find SI magnitude symbol for the numbers on the y-axis*/
392 void si_unit(
393 image_desc_t *im /* image description */
394 )
395 {
397 double digits, viewdigits = 0;
399 digits =
400 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
401 log((double) im->base));
403 if (im->unitsexponent != 9999) {
404 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
405 viewdigits = floor(im->unitsexponent / 3);
406 } else {
407 viewdigits = digits;
408 }
410 im->magfact = pow((double) im->base, digits);
412 #ifdef DEBUG
413 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
414 #endif
416 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
418 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
419 ((viewdigits + si_symbcenter) >= 0))
420 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
421 else
422 im->symbol = '?';
423 }
425 /* move min and max values around to become sensible */
427 void expand_range(
428 image_desc_t *im)
429 {
430 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
431 600.0, 500.0, 400.0, 300.0, 250.0,
432 200.0, 125.0, 100.0, 90.0, 80.0,
433 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
434 25.0, 20.0, 10.0, 9.0, 8.0,
435 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
436 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
437 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
438 };
440 double scaled_min, scaled_max;
441 double adj;
442 int i;
446 #ifdef DEBUG
447 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
448 im->minval, im->maxval, im->magfact);
449 #endif
451 if (isnan(im->ygridstep)) {
452 if (im->extra_flags & ALTAUTOSCALE) {
453 /* measure the amplitude of the function. Make sure that
454 graph boundaries are slightly higher then max/min vals
455 so we can see amplitude on the graph */
456 double delt, fact;
458 delt = im->maxval - im->minval;
459 adj = delt * 0.1;
460 fact = 2.0 * pow(10.0,
461 floor(log10
462 (max(fabs(im->minval), fabs(im->maxval)) /
463 im->magfact)) - 2);
464 if (delt < fact) {
465 adj = (fact - delt) * 0.55;
466 #ifdef DEBUG
467 printf
468 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
469 im->minval, im->maxval, delt, fact, adj);
470 #endif
471 }
472 im->minval -= adj;
473 im->maxval += adj;
474 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
475 /* measure the amplitude of the function. Make sure that
476 graph boundaries are slightly lower than min vals
477 so we can see amplitude on the graph */
478 adj = (im->maxval - im->minval) * 0.1;
479 im->minval -= adj;
480 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
481 /* measure the amplitude of the function. Make sure that
482 graph boundaries are slightly higher than max vals
483 so we can see amplitude on the graph */
484 adj = (im->maxval - im->minval) * 0.1;
485 im->maxval += adj;
486 } else {
487 scaled_min = im->minval / im->magfact;
488 scaled_max = im->maxval / im->magfact;
490 for (i = 1; sensiblevalues[i] > 0; i++) {
491 if (sensiblevalues[i - 1] >= scaled_min &&
492 sensiblevalues[i] <= scaled_min)
493 im->minval = sensiblevalues[i] * (im->magfact);
495 if (-sensiblevalues[i - 1] <= scaled_min &&
496 -sensiblevalues[i] >= scaled_min)
497 im->minval = -sensiblevalues[i - 1] * (im->magfact);
499 if (sensiblevalues[i - 1] >= scaled_max &&
500 sensiblevalues[i] <= scaled_max)
501 im->maxval = sensiblevalues[i - 1] * (im->magfact);
503 if (-sensiblevalues[i - 1] <= scaled_max &&
504 -sensiblevalues[i] >= scaled_max)
505 im->maxval = -sensiblevalues[i] * (im->magfact);
506 }
507 }
508 } else {
509 /* adjust min and max to the grid definition if there is one */
510 im->minval = (double) im->ylabfact * im->ygridstep *
511 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
512 im->maxval = (double) im->ylabfact * im->ygridstep *
513 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
514 }
516 #ifdef DEBUG
517 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
518 im->minval, im->maxval, im->magfact);
519 #endif
520 }
523 void apply_gridfit(
524 image_desc_t *im)
525 {
526 if (isnan(im->minval) || isnan(im->maxval))
527 return;
528 ytr(im, DNAN);
529 if (im->logarithmic) {
530 double ya, yb, ypix, ypixfrac;
531 double log10_range = log10(im->maxval) - log10(im->minval);
533 ya = pow((double) 10, floor(log10(im->minval)));
534 while (ya < im->minval)
535 ya *= 10;
536 if (ya > im->maxval)
537 return; /* don't have y=10^x gridline */
538 yb = ya * 10;
539 if (yb <= im->maxval) {
540 /* we have at least 2 y=10^x gridlines.
541 Make sure distance between them in pixels
542 are an integer by expanding im->maxval */
543 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
544 double factor = y_pixel_delta / floor(y_pixel_delta);
545 double new_log10_range = factor * log10_range;
546 double new_ymax_log10 = log10(im->minval) + new_log10_range;
548 im->maxval = pow(10, new_ymax_log10);
549 ytr(im, DNAN); /* reset precalc */
550 log10_range = log10(im->maxval) - log10(im->minval);
551 }
552 /* make sure first y=10^x gridline is located on
553 integer pixel position by moving scale slightly
554 downwards (sub-pixel movement) */
555 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
556 ypixfrac = ypix - floor(ypix);
557 if (ypixfrac > 0 && ypixfrac < 1) {
558 double yfrac = ypixfrac / im->ysize;
560 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
561 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
562 ytr(im, DNAN); /* reset precalc */
563 }
564 } else {
565 /* Make sure we have an integer pixel distance between
566 each minor gridline */
567 double ypos1 = ytr(im, im->minval);
568 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
569 double y_pixel_delta = ypos1 - ypos2;
570 double factor = y_pixel_delta / floor(y_pixel_delta);
571 double new_range = factor * (im->maxval - im->minval);
572 double gridstep = im->ygrid_scale.gridstep;
573 double minor_y, minor_y_px, minor_y_px_frac;
575 if (im->maxval > 0.0)
576 im->maxval = im->minval + new_range;
577 else
578 im->minval = im->maxval - new_range;
579 ytr(im, DNAN); /* reset precalc */
580 /* make sure first minor gridline is on integer pixel y coord */
581 minor_y = gridstep * floor(im->minval / gridstep);
582 while (minor_y < im->minval)
583 minor_y += gridstep;
584 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
585 minor_y_px_frac = minor_y_px - floor(minor_y_px);
586 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
587 double yfrac = minor_y_px_frac / im->ysize;
588 double range = im->maxval - im->minval;
590 im->minval = im->minval - yfrac * range;
591 im->maxval = im->maxval - yfrac * range;
592 ytr(im, DNAN); /* reset precalc */
593 }
594 calc_horizontal_grid(im); /* recalc with changed im->maxval */
595 }
596 }
598 /* reduce data reimplementation by Alex */
600 void reduce_data(
601 enum cf_en cf, /* which consolidation function ? */
602 unsigned long cur_step, /* step the data currently is in */
603 time_t *start, /* start, end and step as requested ... */
604 time_t *end, /* ... by the application will be ... */
605 unsigned long *step, /* ... adjusted to represent reality */
606 unsigned long *ds_cnt, /* number of data sources in file */
607 rrd_value_t **data)
608 { /* two dimensional array containing the data */
609 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
610 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
611 0;
612 rrd_value_t *srcptr, *dstptr;
614 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
615 dstptr = *data;
616 srcptr = *data;
617 row_cnt = ((*end) - (*start)) / cur_step;
619 #ifdef DEBUG
620 #define DEBUG_REDUCE
621 #endif
622 #ifdef DEBUG_REDUCE
623 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
624 row_cnt, reduce_factor, *start, *end, cur_step);
625 for (col = 0; col < row_cnt; col++) {
626 printf("time %10lu: ", *start + (col + 1) * cur_step);
627 for (i = 0; i < *ds_cnt; i++)
628 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
629 printf("\n");
630 }
631 #endif
633 /* We have to combine [reduce_factor] rows of the source
634 ** into one row for the destination. Doing this we also
635 ** need to take care to combine the correct rows. First
636 ** alter the start and end time so that they are multiples
637 ** of the new step time. We cannot reduce the amount of
638 ** time so we have to move the end towards the future and
639 ** the start towards the past.
640 */
641 end_offset = (*end) % (*step);
642 start_offset = (*start) % (*step);
644 /* If there is a start offset (which cannot be more than
645 ** one destination row), skip the appropriate number of
646 ** source rows and one destination row. The appropriate
647 ** number is what we do know (start_offset/cur_step) of
648 ** the new interval (*step/cur_step aka reduce_factor).
649 */
650 #ifdef DEBUG_REDUCE
651 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
652 printf("row_cnt before: %lu\n", row_cnt);
653 #endif
654 if (start_offset) {
655 (*start) = (*start) - start_offset;
656 skiprows = reduce_factor - start_offset / cur_step;
657 srcptr += skiprows * *ds_cnt;
658 for (col = 0; col < (*ds_cnt); col++)
659 *dstptr++ = DNAN;
660 row_cnt -= skiprows;
661 }
662 #ifdef DEBUG_REDUCE
663 printf("row_cnt between: %lu\n", row_cnt);
664 #endif
666 /* At the end we have some rows that are not going to be
667 ** used, the amount is end_offset/cur_step
668 */
669 if (end_offset) {
670 (*end) = (*end) - end_offset + (*step);
671 skiprows = end_offset / cur_step;
672 row_cnt -= skiprows;
673 }
674 #ifdef DEBUG_REDUCE
675 printf("row_cnt after: %lu\n", row_cnt);
676 #endif
678 /* Sanity check: row_cnt should be multiple of reduce_factor */
679 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
681 if (row_cnt % reduce_factor) {
682 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
683 row_cnt, reduce_factor);
684 printf("BUG in reduce_data()\n");
685 exit(1);
686 }
688 /* Now combine reduce_factor intervals at a time
689 ** into one interval for the destination.
690 */
692 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
693 for (col = 0; col < (*ds_cnt); col++) {
694 rrd_value_t newval = DNAN;
695 unsigned long validval = 0;
697 for (i = 0; i < reduce_factor; i++) {
698 if (isnan(srcptr[i * (*ds_cnt) + col])) {
699 continue;
700 }
701 validval++;
702 if (isnan(newval))
703 newval = srcptr[i * (*ds_cnt) + col];
704 else {
705 switch (cf) {
706 case CF_HWPREDICT:
707 case CF_DEVSEASONAL:
708 case CF_DEVPREDICT:
709 case CF_SEASONAL:
710 case CF_AVERAGE:
711 newval += srcptr[i * (*ds_cnt) + col];
712 break;
713 case CF_MINIMUM:
714 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
715 break;
716 case CF_FAILURES:
717 /* an interval contains a failure if any subintervals contained a failure */
718 case CF_MAXIMUM:
719 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
720 break;
721 case CF_LAST:
722 newval = srcptr[i * (*ds_cnt) + col];
723 break;
724 }
725 }
726 }
727 if (validval == 0) {
728 newval = DNAN;
729 } else {
730 switch (cf) {
731 case CF_HWPREDICT:
732 case CF_DEVSEASONAL:
733 case CF_DEVPREDICT:
734 case CF_SEASONAL:
735 case CF_AVERAGE:
736 newval /= validval;
737 break;
738 case CF_MINIMUM:
739 case CF_FAILURES:
740 case CF_MAXIMUM:
741 case CF_LAST:
742 break;
743 }
744 }
745 *dstptr++ = newval;
746 }
747 srcptr += (*ds_cnt) * reduce_factor;
748 row_cnt -= reduce_factor;
749 }
750 /* If we had to alter the endtime, we didn't have enough
751 ** source rows to fill the last row. Fill it with NaN.
752 */
753 if (end_offset)
754 for (col = 0; col < (*ds_cnt); col++)
755 *dstptr++ = DNAN;
756 #ifdef DEBUG_REDUCE
757 row_cnt = ((*end) - (*start)) / *step;
758 srcptr = *data;
759 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
760 row_cnt, *start, *end, *step);
761 for (col = 0; col < row_cnt; col++) {
762 printf("time %10lu: ", *start + (col + 1) * (*step));
763 for (i = 0; i < *ds_cnt; i++)
764 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
765 printf("\n");
766 }
767 #endif
768 }
771 /* get the data required for the graphs from the
772 relevant rrds ... */
774 int data_fetch(
775 image_desc_t *im)
776 {
777 int i, ii;
778 int skip;
780 /* pull the data from the rrd files ... */
781 for (i = 0; i < (int) im->gdes_c; i++) {
782 /* only GF_DEF elements fetch data */
783 if (im->gdes[i].gf != GF_DEF)
784 continue;
786 skip = 0;
787 /* do we have it already ? */
788 for (ii = 0; ii < i; ii++) {
789 if (im->gdes[ii].gf != GF_DEF)
790 continue;
791 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
792 && (im->gdes[i].cf == im->gdes[ii].cf)
793 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
794 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
795 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
796 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
797 /* OK, the data is already there.
798 ** Just copy the header portion
799 */
800 im->gdes[i].start = im->gdes[ii].start;
801 im->gdes[i].end = im->gdes[ii].end;
802 im->gdes[i].step = im->gdes[ii].step;
803 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
804 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
805 im->gdes[i].data = im->gdes[ii].data;
806 im->gdes[i].data_first = 0;
807 skip = 1;
808 }
809 if (skip)
810 break;
811 }
812 if (!skip) {
813 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
815 if ((rrd_fetch_fn(im->gdes[i].rrd,
816 im->gdes[i].cf,
817 &im->gdes[i].start,
818 &im->gdes[i].end,
819 &ft_step,
820 &im->gdes[i].ds_cnt,
821 &im->gdes[i].ds_namv,
822 &im->gdes[i].data)) == -1) {
823 return -1;
824 }
825 im->gdes[i].data_first = 1;
827 if (ft_step < im->gdes[i].step) {
828 reduce_data(im->gdes[i].cf_reduce,
829 ft_step,
830 &im->gdes[i].start,
831 &im->gdes[i].end,
832 &im->gdes[i].step,
833 &im->gdes[i].ds_cnt, &im->gdes[i].data);
834 } else {
835 im->gdes[i].step = ft_step;
836 }
837 }
839 /* lets see if the required data source is really there */
840 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
841 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
842 im->gdes[i].ds = ii;
843 }
844 }
845 if (im->gdes[i].ds == -1) {
846 rrd_set_error("No DS called '%s' in '%s'",
847 im->gdes[i].ds_nam, im->gdes[i].rrd);
848 return -1;
849 }
851 }
852 return 0;
853 }
855 /* evaluate the expressions in the CDEF functions */
857 /*************************************************************
858 * CDEF stuff
859 *************************************************************/
861 long find_var_wrapper(
862 void *arg1,
863 char *key)
864 {
865 return find_var((image_desc_t *) arg1, key);
866 }
868 /* find gdes containing var*/
869 long find_var(
870 image_desc_t *im,
871 char *key)
872 {
873 long ii;
875 for (ii = 0; ii < im->gdes_c - 1; ii++) {
876 if ((im->gdes[ii].gf == GF_DEF
877 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
878 && (strcmp(im->gdes[ii].vname, key) == 0)) {
879 return ii;
880 }
881 }
882 return -1;
883 }
885 /* find the largest common denominator for all the numbers
886 in the 0 terminated num array */
887 long lcd(
888 long *num)
889 {
890 long rest;
891 int i;
893 for (i = 0; num[i + 1] != 0; i++) {
894 do {
895 rest = num[i] % num[i + 1];
896 num[i] = num[i + 1];
897 num[i + 1] = rest;
898 } while (rest != 0);
899 num[i + 1] = num[i];
900 }
901 /* return i==0?num[i]:num[i-1]; */
902 return num[i];
903 }
905 /* run the rpn calculator on all the VDEF and CDEF arguments */
906 int data_calc(
907 image_desc_t *im)
908 {
910 int gdi;
911 int dataidx;
912 long *steparray, rpi;
913 int stepcnt;
914 time_t now;
915 rpnstack_t rpnstack;
917 rpnstack_init(&rpnstack);
919 for (gdi = 0; gdi < im->gdes_c; gdi++) {
920 /* Look for GF_VDEF and GF_CDEF in the same loop,
921 * so CDEFs can use VDEFs and vice versa
922 */
923 switch (im->gdes[gdi].gf) {
924 case GF_XPORT:
925 break;
926 case GF_SHIFT:{
927 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
929 /* remove current shift */
930 vdp->start -= vdp->shift;
931 vdp->end -= vdp->shift;
933 /* vdef */
934 if (im->gdes[gdi].shidx >= 0)
935 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
936 /* constant */
937 else
938 vdp->shift = im->gdes[gdi].shval;
940 /* normalize shift to multiple of consolidated step */
941 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
943 /* apply shift */
944 vdp->start += vdp->shift;
945 vdp->end += vdp->shift;
946 break;
947 }
948 case GF_VDEF:
949 /* A VDEF has no DS. This also signals other parts
950 * of rrdtool that this is a VDEF value, not a CDEF.
951 */
952 im->gdes[gdi].ds_cnt = 0;
953 if (vdef_calc(im, gdi)) {
954 rrd_set_error("Error processing VDEF '%s'",
955 im->gdes[gdi].vname);
956 rpnstack_free(&rpnstack);
957 return -1;
958 }
959 break;
960 case GF_CDEF:
961 im->gdes[gdi].ds_cnt = 1;
962 im->gdes[gdi].ds = 0;
963 im->gdes[gdi].data_first = 1;
964 im->gdes[gdi].start = 0;
965 im->gdes[gdi].end = 0;
966 steparray = NULL;
967 stepcnt = 0;
968 dataidx = -1;
970 /* Find the variables in the expression.
971 * - VDEF variables are substituted by their values
972 * and the opcode is changed into OP_NUMBER.
973 * - CDEF variables are analized for their step size,
974 * the lowest common denominator of all the step
975 * sizes of the data sources involved is calculated
976 * and the resulting number is the step size for the
977 * resulting data source.
978 */
979 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
980 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
981 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
982 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
984 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
985 #if 0
986 printf
987 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
988 im->gdes[gdi].vname, im->gdes[ptr].vname);
989 printf("DEBUG: value from vdef is %f\n",
990 im->gdes[ptr].vf.val);
991 #endif
992 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
993 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
994 } else { /* normal variables and PREF(variables) */
996 /* add one entry to the array that keeps track of the step sizes of the
997 * data sources going into the CDEF. */
998 if ((steparray =
999 rrd_realloc(steparray,
1000 (++stepcnt +
1001 1) * sizeof(*steparray))) == NULL) {
1002 rrd_set_error("realloc steparray");
1003 rpnstack_free(&rpnstack);
1004 return -1;
1005 };
1007 steparray[stepcnt - 1] = im->gdes[ptr].step;
1009 /* adjust start and end of cdef (gdi) so
1010 * that it runs from the latest start point
1011 * to the earliest endpoint of any of the
1012 * rras involved (ptr)
1013 */
1015 if (im->gdes[gdi].start < im->gdes[ptr].start)
1016 im->gdes[gdi].start = im->gdes[ptr].start;
1018 if (im->gdes[gdi].end == 0 ||
1019 im->gdes[gdi].end > im->gdes[ptr].end)
1020 im->gdes[gdi].end = im->gdes[ptr].end;
1022 /* store pointer to the first element of
1023 * the rra providing data for variable,
1024 * further save step size and data source
1025 * count of this rra
1026 */
1027 im->gdes[gdi].rpnp[rpi].data =
1028 im->gdes[ptr].data + im->gdes[ptr].ds;
1029 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1030 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1032 /* backoff the *.data ptr; this is done so
1033 * rpncalc() function doesn't have to treat
1034 * the first case differently
1035 */
1036 } /* if ds_cnt != 0 */
1037 } /* if OP_VARIABLE */
1038 } /* loop through all rpi */
1040 /* move the data pointers to the correct period */
1041 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1042 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1043 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1044 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1045 long diff =
1046 im->gdes[gdi].start - im->gdes[ptr].start;
1048 if (diff > 0)
1049 im->gdes[gdi].rpnp[rpi].data +=
1050 (diff / im->gdes[ptr].step) *
1051 im->gdes[ptr].ds_cnt;
1052 }
1053 }
1055 if (steparray == NULL) {
1056 rrd_set_error("rpn expressions without DEF"
1057 " or CDEF variables are not supported");
1058 rpnstack_free(&rpnstack);
1059 return -1;
1060 }
1061 steparray[stepcnt] = 0;
1062 /* Now find the resulting step. All steps in all
1063 * used RRAs have to be visited
1064 */
1065 im->gdes[gdi].step = lcd(steparray);
1066 free(steparray);
1067 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1068 im->gdes[gdi].start)
1069 / im->gdes[gdi].step)
1070 * sizeof(double))) == NULL) {
1071 rrd_set_error("malloc im->gdes[gdi].data");
1072 rpnstack_free(&rpnstack);
1073 return -1;
1074 }
1076 /* Step through the new cdef results array and
1077 * calculate the values
1078 */
1079 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1080 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1081 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1083 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1084 * in this case we are advancing by timesteps;
1085 * we use the fact that time_t is a synonym for long
1086 */
1087 if (rpn_calc(rpnp, &rpnstack, (long) now,
1088 im->gdes[gdi].data, ++dataidx) == -1) {
1089 /* rpn_calc sets the error string */
1090 rpnstack_free(&rpnstack);
1091 return -1;
1092 }
1093 } /* enumerate over time steps within a CDEF */
1094 break;
1095 default:
1096 continue;
1097 }
1098 } /* enumerate over CDEFs */
1099 rpnstack_free(&rpnstack);
1100 return 0;
1101 }
1103 /* massage data so, that we get one value for each x coordinate in the graph */
1104 int data_proc(
1105 image_desc_t *im)
1106 {
1107 long i, ii;
1108 double pixstep = (double) (im->end - im->start)
1109 / (double) im->xsize; /* how much time
1110 passes in one pixel */
1111 double paintval;
1112 double minval = DNAN, maxval = DNAN;
1114 unsigned long gr_time;
1116 /* memory for the processed data */
1117 for (i = 0; i < im->gdes_c; i++) {
1118 if ((im->gdes[i].gf == GF_LINE) ||
1119 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1120 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1121 * sizeof(rrd_value_t))) == NULL) {
1122 rrd_set_error("malloc data_proc");
1123 return -1;
1124 }
1125 }
1126 }
1128 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1129 long vidx;
1131 gr_time = im->start + pixstep * i; /* time of the current step */
1132 paintval = 0.0;
1134 for (ii = 0; ii < im->gdes_c; ii++) {
1135 double value;
1137 switch (im->gdes[ii].gf) {
1138 case GF_LINE:
1139 case GF_AREA:
1140 case GF_TICK:
1141 if (!im->gdes[ii].stack)
1142 paintval = 0.0;
1143 value = im->gdes[ii].yrule;
1144 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1145 /* The time of the data doesn't necessarily match
1146 ** the time of the graph. Beware.
1147 */
1148 vidx = im->gdes[ii].vidx;
1149 if (im->gdes[vidx].gf == GF_VDEF) {
1150 value = im->gdes[vidx].vf.val;
1151 } else
1152 if (((long int) gr_time >=
1153 (long int) im->gdes[vidx].start)
1154 && ((long int) gr_time <=
1155 (long int) im->gdes[vidx].end)) {
1156 value = im->gdes[vidx].data[(unsigned long)
1157 floor((double)
1158 (gr_time -
1159 im->gdes[vidx].
1160 start)
1161 /
1162 im->gdes[vidx].step)
1163 * im->gdes[vidx].ds_cnt +
1164 im->gdes[vidx].ds];
1165 } else {
1166 value = DNAN;
1167 }
1168 };
1170 if (!isnan(value)) {
1171 paintval += value;
1172 im->gdes[ii].p_data[i] = paintval;
1173 /* GF_TICK: the data values are not
1174 ** relevant for min and max
1175 */
1176 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1177 if ((isnan(minval) || paintval < minval) &&
1178 !(im->logarithmic && paintval <= 0.0))
1179 minval = paintval;
1180 if (isnan(maxval) || paintval > maxval)
1181 maxval = paintval;
1182 }
1183 } else {
1184 im->gdes[ii].p_data[i] = DNAN;
1185 }
1186 break;
1187 case GF_STACK:
1188 rrd_set_error
1189 ("STACK should already be turned into LINE or AREA here");
1190 return -1;
1191 break;
1192 default:
1193 break;
1194 }
1195 }
1196 }
1198 /* if min or max have not been asigned a value this is because
1199 there was no data in the graph ... this is not good ...
1200 lets set these to dummy values then ... */
1202 if (im->logarithmic) {
1203 if (isnan(minval))
1204 minval = 0.2;
1205 if (isnan(maxval))
1206 maxval = 5.1;
1207 } else {
1208 if (isnan(minval))
1209 minval = 0.0;
1210 if (isnan(maxval))
1211 maxval = 1.0;
1212 }
1214 /* adjust min and max values */
1215 if (isnan(im->minval)
1216 /* don't adjust low-end with log scale *//* why not? */
1217 || ((!im->rigid) && im->minval > minval)
1218 ) {
1219 if (im->logarithmic)
1220 im->minval = minval * 0.5;
1221 else
1222 im->minval = minval;
1223 }
1224 if (isnan(im->maxval)
1225 || (!im->rigid && im->maxval < maxval)
1226 ) {
1227 if (im->logarithmic)
1228 im->maxval = maxval * 2.0;
1229 else
1230 im->maxval = maxval;
1231 }
1232 /* make sure min is smaller than max */
1233 if (im->minval > im->maxval) {
1234 im->minval = 0.99 * im->maxval;
1235 }
1237 /* make sure min and max are not equal */
1238 if (im->minval == im->maxval) {
1239 im->maxval *= 1.01;
1240 if (!im->logarithmic) {
1241 im->minval *= 0.99;
1242 }
1243 /* make sure min and max are not both zero */
1244 if (im->maxval == 0.0) {
1245 im->maxval = 1.0;
1246 }
1247 }
1248 return 0;
1249 }
1253 /* identify the point where the first gridline, label ... gets placed */
1255 time_t find_first_time(
1256 time_t start, /* what is the initial time */
1257 enum tmt_en baseint, /* what is the basic interval */
1258 long basestep /* how many if these do we jump a time */
1259 )
1260 {
1261 struct tm tm;
1263 localtime_r(&start, &tm);
1265 switch (baseint) {
1266 case TMT_SECOND:
1267 tm. tm_sec -= tm.tm_sec % basestep;
1269 break;
1270 case TMT_MINUTE:
1271 tm. tm_sec = 0;
1272 tm. tm_min -= tm.tm_min % basestep;
1274 break;
1275 case TMT_HOUR:
1276 tm. tm_sec = 0;
1277 tm. tm_min = 0;
1278 tm. tm_hour -= tm.tm_hour % basestep;
1280 break;
1281 case TMT_DAY:
1282 /* we do NOT look at the basestep for this ... */
1283 tm. tm_sec = 0;
1284 tm. tm_min = 0;
1285 tm. tm_hour = 0;
1287 break;
1288 case TMT_WEEK:
1289 /* we do NOT look at the basestep for this ... */
1290 tm. tm_sec = 0;
1291 tm. tm_min = 0;
1292 tm. tm_hour = 0;
1293 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1295 if (tm.tm_wday == 0)
1296 tm. tm_mday -= 7; /* we want the *previous* monday */
1298 break;
1299 case TMT_MONTH:
1300 tm. tm_sec = 0;
1301 tm. tm_min = 0;
1302 tm. tm_hour = 0;
1303 tm. tm_mday = 1;
1304 tm. tm_mon -= tm.tm_mon % basestep;
1306 break;
1308 case TMT_YEAR:
1309 tm. tm_sec = 0;
1310 tm. tm_min = 0;
1311 tm. tm_hour = 0;
1312 tm. tm_mday = 1;
1313 tm. tm_mon = 0;
1314 tm. tm_year -= (
1315 tm.tm_year + 1900) %basestep;
1317 }
1318 return mktime(&tm);
1319 }
1321 /* identify the point where the next gridline, label ... gets placed */
1322 time_t find_next_time(
1323 time_t current, /* what is the initial time */
1324 enum tmt_en baseint, /* what is the basic interval */
1325 long basestep /* how many if these do we jump a time */
1326 )
1327 {
1328 struct tm tm;
1329 time_t madetime;
1331 localtime_r(¤t, &tm);
1333 do {
1334 switch (baseint) {
1335 case TMT_SECOND:
1336 tm. tm_sec += basestep;
1338 break;
1339 case TMT_MINUTE:
1340 tm. tm_min += basestep;
1342 break;
1343 case TMT_HOUR:
1344 tm. tm_hour += basestep;
1346 break;
1347 case TMT_DAY:
1348 tm. tm_mday += basestep;
1350 break;
1351 case TMT_WEEK:
1352 tm. tm_mday += 7 * basestep;
1354 break;
1355 case TMT_MONTH:
1356 tm. tm_mon += basestep;
1358 break;
1359 case TMT_YEAR:
1360 tm. tm_year += basestep;
1361 }
1362 madetime = mktime(&tm);
1363 } while (madetime == -1); /* this is necessary to skip impssible times
1364 like the daylight saving time skips */
1365 return madetime;
1367 }
1370 /* calculate values required for PRINT and GPRINT functions */
1372 int print_calc(
1373 image_desc_t *im,
1374 char ***prdata)
1375 {
1376 long i, ii, validsteps;
1377 double printval;
1378 struct tm tmvdef;
1379 int graphelement = 0;
1380 long vidx;
1381 int max_ii;
1382 double magfact = -1;
1383 char *si_symb = "";
1384 char *percent_s;
1385 int prlines = 1;
1387 /* wow initializing tmvdef is quite a task :-) */
1388 time_t now = time(NULL);
1390 localtime_r(&now, &tmvdef);
1391 if (im->imginfo)
1392 prlines++;
1393 for (i = 0; i < im->gdes_c; i++) {
1394 vidx = im->gdes[i].vidx;
1395 switch (im->gdes[i].gf) {
1396 case GF_PRINT:
1397 prlines++;
1398 if (((*prdata) =
1399 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1400 rrd_set_error("realloc prdata");
1401 return 0;
1402 }
1403 case GF_GPRINT:
1404 /* PRINT and GPRINT can now print VDEF generated values.
1405 * There's no need to do any calculations on them as these
1406 * calculations were already made.
1407 */
1408 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1409 printval = im->gdes[vidx].vf.val;
1410 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1411 } else { /* need to calculate max,min,avg etcetera */
1412 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1413 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1414 printval = DNAN;
1415 validsteps = 0;
1416 for (ii = im->gdes[vidx].ds;
1417 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1418 if (!finite(im->gdes[vidx].data[ii]))
1419 continue;
1420 if (isnan(printval)) {
1421 printval = im->gdes[vidx].data[ii];
1422 validsteps++;
1423 continue;
1424 }
1426 switch (im->gdes[i].cf) {
1427 case CF_HWPREDICT:
1428 case CF_DEVPREDICT:
1429 case CF_DEVSEASONAL:
1430 case CF_SEASONAL:
1431 case CF_AVERAGE:
1432 validsteps++;
1433 printval += im->gdes[vidx].data[ii];
1434 break;
1435 case CF_MINIMUM:
1436 printval = min(printval, im->gdes[vidx].data[ii]);
1437 break;
1438 case CF_FAILURES:
1439 case CF_MAXIMUM:
1440 printval = max(printval, im->gdes[vidx].data[ii]);
1441 break;
1442 case CF_LAST:
1443 printval = im->gdes[vidx].data[ii];
1444 }
1445 }
1446 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1447 if (validsteps > 1) {
1448 printval = (printval / validsteps);
1449 }
1450 }
1451 } /* prepare printval */
1453 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1454 /* Magfact is set to -1 upon entry to print_calc. If it
1455 * is still less than 0, then we need to run auto_scale.
1456 * Otherwise, put the value into the correct units. If
1457 * the value is 0, then do not set the symbol or magnification
1458 * so next the calculation will be performed again. */
1459 if (magfact < 0.0) {
1460 auto_scale(im, &printval, &si_symb, &magfact);
1461 if (printval == 0.0)
1462 magfact = -1.0;
1463 } else {
1464 printval /= magfact;
1465 }
1466 *(++percent_s) = 's';
1467 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1468 auto_scale(im, &printval, &si_symb, &magfact);
1469 }
1471 if (im->gdes[i].gf == GF_PRINT) {
1472 (*prdata)[prlines - 2] =
1473 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1474 (*prdata)[prlines - 1] = NULL;
1475 if (im->gdes[i].strftm) {
1476 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1477 im->gdes[i].format, &tmvdef);
1478 } else {
1479 if (bad_format(im->gdes[i].format)) {
1480 rrd_set_error("bad format for PRINT in '%s'",
1481 im->gdes[i].format);
1482 return -1;
1483 }
1484 #ifdef HAVE_SNPRINTF
1485 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1486 im->gdes[i].format, printval, si_symb);
1487 #else
1488 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1489 printval, si_symb);
1490 #endif
1491 }
1492 } else {
1493 /* GF_GPRINT */
1495 if (im->gdes[i].strftm) {
1496 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1497 im->gdes[i].format, &tmvdef);
1498 } else {
1499 if (bad_format(im->gdes[i].format)) {
1500 rrd_set_error("bad format for GPRINT in '%s'",
1501 im->gdes[i].format);
1502 return -1;
1503 }
1504 #ifdef HAVE_SNPRINTF
1505 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1506 im->gdes[i].format, printval, si_symb);
1507 #else
1508 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1509 si_symb);
1510 #endif
1511 }
1512 graphelement = 1;
1513 }
1514 break;
1515 case GF_LINE:
1516 case GF_AREA:
1517 case GF_TICK:
1518 graphelement = 1;
1519 break;
1520 case GF_HRULE:
1521 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1522 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1523 };
1524 graphelement = 1;
1525 break;
1526 case GF_VRULE:
1527 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1528 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1529 };
1530 graphelement = 1;
1531 break;
1532 case GF_COMMENT:
1533 case GF_DEF:
1534 case GF_CDEF:
1535 case GF_VDEF:
1536 #ifdef WITH_PIECHART
1537 case GF_PART:
1538 #endif
1539 case GF_SHIFT:
1540 case GF_XPORT:
1541 break;
1542 case GF_STACK:
1543 rrd_set_error
1544 ("STACK should already be turned into LINE or AREA here");
1545 return -1;
1546 break;
1547 }
1548 }
1549 return graphelement;
1550 }
1553 /* place legends with color spots */
1554 int leg_place(
1555 image_desc_t *im,
1556 int *gY)
1557 {
1558 /* graph labels */
1559 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1560 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1561 int fill = 0, fill_last;
1562 int leg_c = 0;
1563 int leg_x = border, leg_y = im->yimg;
1564 int leg_y_prev = im->yimg;
1565 int leg_cc;
1566 int glue = 0;
1567 int i, ii, mark = 0;
1568 char prt_fctn; /*special printfunctions */
1569 int *legspace;
1571 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1572 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1573 rrd_set_error("malloc for legspace");
1574 return -1;
1575 }
1577 if (im->extra_flags & FULL_SIZE_MODE)
1578 leg_y = leg_y_prev =
1579 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1581 for (i = 0; i < im->gdes_c; i++) {
1582 fill_last = fill;
1584 /* hide legends for rules which are not displayed */
1586 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1587 if (im->gdes[i].gf == GF_HRULE &&
1588 (im->gdes[i].yrule < im->minval
1589 || im->gdes[i].yrule > im->maxval))
1590 im->gdes[i].legend[0] = '\0';
1592 if (im->gdes[i].gf == GF_VRULE &&
1593 (im->gdes[i].xrule < im->start
1594 || im->gdes[i].xrule > im->end))
1595 im->gdes[i].legend[0] = '\0';
1596 }
1598 leg_cc = strlen(im->gdes[i].legend);
1600 /* is there a controle code ant the end of the legend string ? */
1601 /* and it is not a tab \\t */
1602 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1603 && im->gdes[i].legend[leg_cc - 1] != 't') {
1604 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1605 leg_cc -= 2;
1606 im->gdes[i].legend[leg_cc] = '\0';
1607 } else {
1608 prt_fctn = '\0';
1609 }
1610 /* only valid control codes */
1611 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1612 prt_fctn != 'r' &&
1613 prt_fctn != 'j' &&
1614 prt_fctn != 'c' &&
1615 prt_fctn != 's' &&
1616 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1617 free(legspace);
1618 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1619 im->gdes[i].legend, prt_fctn);
1620 return -1;
1622 }
1624 /* remove exess space */
1625 if (prt_fctn == 'n') {
1626 prt_fctn = 'l';
1627 }
1629 while (prt_fctn == 'g' &&
1630 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1631 leg_cc--;
1632 im->gdes[i].legend[leg_cc] = '\0';
1633 }
1634 if (leg_cc != 0) {
1635 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1637 if (fill > 0) {
1638 /* no interleg space if string ends in \g */
1639 fill += legspace[i];
1640 }
1641 fill += gfx_get_text_width(im->cr, fill + border,
1642 im->text_prop[TEXT_PROP_LEGEND].
1643 font,
1644 im->text_prop[TEXT_PROP_LEGEND].
1645 size, im->tabwidth,
1646 im->gdes[i].legend);
1647 leg_c++;
1648 } else {
1649 legspace[i] = 0;
1650 }
1651 /* who said there was a special tag ... ? */
1652 if (prt_fctn == 'g') {
1653 prt_fctn = '\0';
1654 }
1655 if (prt_fctn == '\0') {
1656 if (i == im->gdes_c - 1)
1657 prt_fctn = 'l';
1659 /* is it time to place the legends ? */
1660 if (fill > im->ximg - 2 * border) {
1661 if (leg_c > 1) {
1662 /* go back one */
1663 i--;
1664 fill = fill_last;
1665 leg_c--;
1666 prt_fctn = 'j';
1667 } else {
1668 prt_fctn = 'l';
1669 }
1671 }
1672 }
1675 if (prt_fctn != '\0') {
1676 leg_x = border;
1677 if (leg_c >= 2 && prt_fctn == 'j') {
1678 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1679 } else {
1680 glue = 0;
1681 }
1682 if (prt_fctn == 'c')
1683 leg_x = (im->ximg - fill) / 2.0;
1684 if (prt_fctn == 'r')
1685 leg_x = im->ximg - fill - border;
1687 for (ii = mark; ii <= i; ii++) {
1688 if (im->gdes[ii].legend[0] == '\0')
1689 continue; /* skip empty legends */
1690 im->gdes[ii].leg_x = leg_x;
1691 im->gdes[ii].leg_y = leg_y;
1692 leg_x +=
1693 gfx_get_text_width(im->cr, leg_x,
1694 im->text_prop[TEXT_PROP_LEGEND].
1695 font,
1696 im->text_prop[TEXT_PROP_LEGEND].
1697 size, im->tabwidth,
1698 im->gdes[ii].legend)
1699 + legspace[ii]
1700 + glue;
1701 }
1702 leg_y_prev = leg_y;
1703 if (im->extra_flags & FULL_SIZE_MODE) {
1704 /* only add y space if there was text on the line */
1705 if (leg_x > border || prt_fctn == 's')
1706 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1707 if (prt_fctn == 's')
1708 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1709 } else {
1710 if (leg_x > border || prt_fctn == 's')
1711 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1712 if (prt_fctn == 's')
1713 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1714 }
1715 fill = 0;
1716 leg_c = 0;
1717 mark = ii;
1718 }
1719 }
1721 if (im->extra_flags & FULL_SIZE_MODE) {
1722 if (leg_y != leg_y_prev) {
1723 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1724 im->yorigin =
1725 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1726 }
1727 } else {
1728 im->yimg = leg_y_prev;
1729 /* if we did place some legends we have to add vertical space */
1730 if (leg_y != im->yimg)
1731 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1732 }
1733 free(legspace);
1734 }
1735 return 0;
1736 }
1738 /* create a grid on the graph. it determines what to do
1739 from the values of xsize, start and end */
1741 /* the xaxis labels are determined from the number of seconds per pixel
1742 in the requested graph */
1746 int calc_horizontal_grid(
1747 image_desc_t *im)
1748 {
1749 double range;
1750 double scaledrange;
1751 int pixel, i;
1752 int gridind = 0;
1753 int decimals, fractionals;
1755 im->ygrid_scale.labfact = 2;
1756 range = im->maxval - im->minval;
1757 scaledrange = range / im->magfact;
1759 /* does the scale of this graph make it impossible to put lines
1760 on it? If so, give up. */
1761 if (isnan(scaledrange)) {
1762 return 0;
1763 }
1765 /* find grid spaceing */
1766 pixel = 1;
1767 if (isnan(im->ygridstep)) {
1768 if (im->extra_flags & ALTYGRID) {
1769 /* find the value with max number of digits. Get number of digits */
1770 decimals =
1771 ceil(log10
1772 (max(fabs(im->maxval), fabs(im->minval)) *
1773 im->viewfactor / im->magfact));
1774 if (decimals <= 0) /* everything is small. make place for zero */
1775 decimals = 1;
1777 im->ygrid_scale.gridstep =
1778 pow((double) 10,
1779 floor(log10(range * im->viewfactor / im->magfact))) /
1780 im->viewfactor * im->magfact;
1782 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1783 im->ygrid_scale.gridstep = 0.1;
1784 /* should have at least 5 lines but no more then 15 */
1785 if (range / im->ygrid_scale.gridstep < 5)
1786 im->ygrid_scale.gridstep /= 10;
1787 if (range / im->ygrid_scale.gridstep > 15)
1788 im->ygrid_scale.gridstep *= 10;
1789 if (range / im->ygrid_scale.gridstep > 5) {
1790 im->ygrid_scale.labfact = 1;
1791 if (range / im->ygrid_scale.gridstep > 8)
1792 im->ygrid_scale.labfact = 2;
1793 } else {
1794 im->ygrid_scale.gridstep /= 5;
1795 im->ygrid_scale.labfact = 5;
1796 }
1797 fractionals =
1798 floor(log10
1799 (im->ygrid_scale.gridstep *
1800 (double) im->ygrid_scale.labfact * im->viewfactor /
1801 im->magfact));
1802 if (fractionals < 0) { /* small amplitude. */
1803 int len = decimals - fractionals + 1;
1805 if (im->unitslength < len + 2)
1806 im->unitslength = len + 2;
1807 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1808 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1809 } else {
1810 int len = decimals + 1;
1812 if (im->unitslength < len + 2)
1813 im->unitslength = len + 2;
1814 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1815 (im->symbol != ' ' ? " %c" : ""));
1816 }
1817 } else {
1818 for (i = 0; ylab[i].grid > 0; i++) {
1819 pixel = im->ysize / (scaledrange / ylab[i].grid);
1820 gridind = i;
1821 if (pixel > 7)
1822 break;
1823 }
1825 for (i = 0; i < 4; i++) {
1826 if (pixel * ylab[gridind].lfac[i] >=
1827 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1828 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1829 break;
1830 }
1831 }
1833 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1834 }
1835 } else {
1836 im->ygrid_scale.gridstep = im->ygridstep;
1837 im->ygrid_scale.labfact = im->ylabfact;
1838 }
1839 return 1;
1840 }
1842 int draw_horizontal_grid(
1843 image_desc_t *im)
1844 {
1845 int i;
1846 double scaledstep;
1847 char graph_label[100];
1848 int nlabels = 0;
1849 double X0 = im->xorigin;
1850 double X1 = im->xorigin + im->xsize;
1852 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1853 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1854 double MaxY;
1856 scaledstep =
1857 im->ygrid_scale.gridstep / (double) im->magfact *
1858 (double) im->viewfactor;
1859 MaxY = scaledstep * (double) egrid;
1860 for (i = sgrid; i <= egrid; i++) {
1861 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1862 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1864 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1865 && floor(Y0 + 0.5) <= im->yorigin) {
1866 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1867 with the chosen settings. Add a label if required by settings, or if
1868 there is only one label so far and the next grid line is out of bounds. */
1869 if (i % im->ygrid_scale.labfact == 0
1870 || (nlabels == 1
1871 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1872 if (im->symbol == ' ') {
1873 if (im->extra_flags & ALTYGRID) {
1874 sprintf(graph_label, im->ygrid_scale.labfmt,
1875 scaledstep * (double) i);
1876 } else {
1877 if (MaxY < 10) {
1878 sprintf(graph_label, "%4.1f",
1879 scaledstep * (double) i);
1880 } else {
1881 sprintf(graph_label, "%4.0f",
1882 scaledstep * (double) i);
1883 }
1884 }
1885 } else {
1886 char sisym = (i == 0 ? ' ' : im->symbol);
1888 if (im->extra_flags & ALTYGRID) {
1889 sprintf(graph_label, im->ygrid_scale.labfmt,
1890 scaledstep * (double) i, sisym);
1891 } else {
1892 if (MaxY < 10) {
1893 sprintf(graph_label, "%4.1f %c",
1894 scaledstep * (double) i, sisym);
1895 } else {
1896 sprintf(graph_label, "%4.0f %c",
1897 scaledstep * (double) i, sisym);
1898 }
1899 }
1900 }
1901 nlabels++;
1903 gfx_text(im->cr,
1904 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1905 im->graph_col[GRC_FONT],
1906 im->text_prop[TEXT_PROP_AXIS].font,
1907 im->text_prop[TEXT_PROP_AXIS].size,
1908 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1909 graph_label);
1910 gfx_line(im->cr,
1911 X0 - 2, Y0,
1912 X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1913 gfx_line(im->cr,
1914 X1, Y0,
1915 X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1916 gfx_dashed_line(im->cr,
1917 X0 - 2, Y0,
1918 X1 + 2, Y0,
1919 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1920 im->grid_dash_on, im->grid_dash_off);
1922 } else if (!(im->extra_flags & NOMINOR)) {
1923 gfx_line(im->cr,
1924 X0 - 2, Y0,
1925 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1926 gfx_line(im->cr,
1927 X1, Y0,
1928 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1929 gfx_dashed_line(im->cr,
1930 X0 - 1, Y0,
1931 X1 + 1, Y0,
1932 GRIDWIDTH, im->graph_col[GRC_GRID],
1933 im->grid_dash_on, im->grid_dash_off);
1935 }
1936 }
1937 }
1938 return 1;
1939 }
1941 /* this is frexp for base 10 */
1942 double frexp10(
1943 double,
1944 double *);
1945 double frexp10(
1946 double x,
1947 double *e)
1948 {
1949 double mnt;
1950 int iexp;
1952 iexp = floor(log(fabs(x)) / log(10));
1953 mnt = x / pow(10.0, iexp);
1954 if (mnt >= 10.0) {
1955 iexp++;
1956 mnt = x / pow(10.0, iexp);
1957 }
1958 *e = iexp;
1959 return mnt;
1960 }
1962 static int AlmostEqual2sComplement(
1963 float A,
1964 float B,
1965 int maxUlps)
1966 {
1968 int aInt = *(int *) &A;
1969 int bInt = *(int *) &B;
1970 int intDiff;
1972 /* Make sure maxUlps is non-negative and small enough that the
1973 default NAN won't compare as equal to anything. */
1975 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1977 /* Make aInt lexicographically ordered as a twos-complement int */
1979 if (aInt < 0)
1980 aInt = 0x80000000l - aInt;
1982 /* Make bInt lexicographically ordered as a twos-complement int */
1984 if (bInt < 0)
1985 bInt = 0x80000000l - bInt;
1987 intDiff = abs(aInt - bInt);
1989 if (intDiff <= maxUlps)
1990 return 1;
1992 return 0;
1993 }
1995 /* logaritmic horizontal grid */
1996 int horizontal_log_grid(
1997 image_desc_t *im)
1998 {
1999 double yloglab[][10] = {
2000 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2001 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2002 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2003 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2004 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2005 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2006 };
2008 int i, j, val_exp, min_exp;
2009 double nex; /* number of decades in data */
2010 double logscale; /* scale in logarithmic space */
2011 int exfrac = 1; /* decade spacing */
2012 int mid = -1; /* row in yloglab for major grid */
2013 double mspac; /* smallest major grid spacing (pixels) */
2014 int flab; /* first value in yloglab to use */
2015 double value, tmp, pre_value;
2016 double X0, X1, Y0;
2017 char graph_label[100];
2019 nex = log10(im->maxval / im->minval);
2020 logscale = im->ysize / nex;
2022 /* major spacing for data with high dynamic range */
2023 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2024 if (exfrac == 1)
2025 exfrac = 3;
2026 else
2027 exfrac += 3;
2028 }
2030 /* major spacing for less dynamic data */
2031 do {
2032 /* search best row in yloglab */
2033 mid++;
2034 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2035 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2036 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2037 && yloglab[mid][0] > 0);
2038 if (mid)
2039 mid--;
2041 /* find first value in yloglab */
2042 for (flab = 0;
2043 yloglab[mid][flab] < 10
2044 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2045 if (yloglab[mid][flab] == 10.0) {
2046 tmp += 1.0;
2047 flab = 0;
2048 }
2049 val_exp = tmp;
2050 if (val_exp % exfrac)
2051 val_exp += abs(-val_exp % exfrac);
2053 X0 = im->xorigin;
2054 X1 = im->xorigin + im->xsize;
2056 /* draw grid */
2057 pre_value = DNAN;
2058 while (1) {
2060 value = yloglab[mid][flab] * pow(10.0, val_exp);
2061 if (AlmostEqual2sComplement(value, pre_value, 4))
2062 break; /* it seems we are not converging */
2064 pre_value = value;
2066 Y0 = ytr(im, value);
2067 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2068 break;
2070 /* major grid line */
2072 gfx_line(im->cr,
2073 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2074 gfx_line(im->cr,
2075 X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2078 gfx_dashed_line(im->cr,
2079 X0 - 2, Y0,
2080 X1 + 2, Y0,
2081 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2082 im->grid_dash_on, im->grid_dash_off);
2084 /* label */
2085 if (im->extra_flags & FORCE_UNITS_SI) {
2086 int scale;
2087 double pvalue;
2088 char symbol;
2090 scale = floor(val_exp / 3.0);
2091 if (value >= 1.0)
2092 pvalue = pow(10.0, val_exp % 3);
2093 else
2094 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2095 pvalue *= yloglab[mid][flab];
2097 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2098 ((scale + si_symbcenter) >= 0))
2099 symbol = si_symbol[scale + si_symbcenter];
2100 else
2101 symbol = '?';
2103 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2104 } else
2105 sprintf(graph_label, "%3.0e", value);
2106 gfx_text(im->cr,
2107 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2108 im->graph_col[GRC_FONT],
2109 im->text_prop[TEXT_PROP_AXIS].font,
2110 im->text_prop[TEXT_PROP_AXIS].size,
2111 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2113 /* minor grid */
2114 if (mid < 4 && exfrac == 1) {
2115 /* find first and last minor line behind current major line
2116 * i is the first line and j tha last */
2117 if (flab == 0) {
2118 min_exp = val_exp - 1;
2119 for (i = 1; yloglab[mid][i] < 10.0; i++);
2120 i = yloglab[mid][i - 1] + 1;
2121 j = 10;
2122 } else {
2123 min_exp = val_exp;
2124 i = yloglab[mid][flab - 1] + 1;
2125 j = yloglab[mid][flab];
2126 }
2128 /* draw minor lines below current major line */
2129 for (; i < j; i++) {
2131 value = i * pow(10.0, min_exp);
2132 if (value < im->minval)
2133 continue;
2135 Y0 = ytr(im, value);
2136 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2137 break;
2139 /* draw lines */
2140 gfx_line(im->cr,
2141 X0 - 2, Y0,
2142 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2143 gfx_line(im->cr,
2144 X1, Y0,
2145 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2146 gfx_dashed_line(im->cr,
2147 X0 - 1, Y0,
2148 X1 + 1, Y0,
2149 GRIDWIDTH, im->graph_col[GRC_GRID],
2150 im->grid_dash_on, im->grid_dash_off);
2151 }
2152 } else if (exfrac > 1) {
2153 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2154 value = pow(10.0, i);
2155 if (value < im->minval)
2156 continue;
2158 Y0 = ytr(im, value);
2159 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2160 break;
2162 /* draw lines */
2163 gfx_line(im->cr,
2164 X0 - 2, Y0,
2165 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2166 gfx_line(im->cr,
2167 X1, Y0,
2168 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2169 gfx_dashed_line(im->cr,
2170 X0 - 1, Y0,
2171 X1 + 1, Y0,
2172 GRIDWIDTH, im->graph_col[GRC_GRID],
2173 im->grid_dash_on, im->grid_dash_off);
2174 }
2175 }
2177 /* next decade */
2178 if (yloglab[mid][++flab] == 10.0) {
2179 flab = 0;
2180 val_exp += exfrac;
2181 }
2182 }
2184 /* draw minor lines after highest major line */
2185 if (mid < 4 && exfrac == 1) {
2186 /* find first and last minor line below current major line
2187 * i is the first line and j tha last */
2188 if (flab == 0) {
2189 min_exp = val_exp - 1;
2190 for (i = 1; yloglab[mid][i] < 10.0; i++);
2191 i = yloglab[mid][i - 1] + 1;
2192 j = 10;
2193 } else {
2194 min_exp = val_exp;
2195 i = yloglab[mid][flab - 1] + 1;
2196 j = yloglab[mid][flab];
2197 }
2199 /* draw minor lines below current major line */
2200 for (; i < j; i++) {
2202 value = i * pow(10.0, min_exp);
2203 if (value < im->minval)
2204 continue;
2206 Y0 = ytr(im, value);
2207 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2208 break;
2210 /* draw lines */
2211 gfx_line(im->cr,
2212 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2213 gfx_line(im->cr,
2214 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2215 gfx_dashed_line(im->cr,
2216 X0 - 1, Y0,
2217 X1 + 1, Y0,
2218 GRIDWIDTH, im->graph_col[GRC_GRID],
2219 im->grid_dash_on, im->grid_dash_off);
2220 }
2221 }
2222 /* fancy minor gridlines */
2223 else if (exfrac > 1) {
2224 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2225 value = pow(10.0, i);
2226 if (value < im->minval)
2227 continue;
2229 Y0 = ytr(im, value);
2230 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2231 break;
2233 /* draw lines */
2234 gfx_line(im->cr,
2235 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2236 gfx_line(im->cr,
2237 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2238 gfx_dashed_line(im->cr,
2239 X0 - 1, Y0,
2240 X1 + 1, Y0,
2241 GRIDWIDTH, im->graph_col[GRC_GRID],
2242 im->grid_dash_on, im->grid_dash_off);
2243 }
2244 }
2246 return 1;
2247 }
2250 void vertical_grid(
2251 image_desc_t *im)
2252 {
2253 int xlab_sel; /* which sort of label and grid ? */
2254 time_t ti, tilab, timajor;
2255 long factor;
2256 char graph_label[100];
2257 double X0, Y0, Y1; /* points for filled graph and more */
2258 struct tm tm;
2260 /* the type of time grid is determined by finding
2261 the number of seconds per pixel in the graph */
2264 if (im->xlab_user.minsec == -1) {
2265 factor = (im->end - im->start) / im->xsize;
2266 xlab_sel = 0;
2267 while (xlab[xlab_sel + 1].minsec != -1
2268 && xlab[xlab_sel + 1].minsec <= factor) {
2269 xlab_sel++;
2270 } /* pick the last one */
2271 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2272 && xlab[xlab_sel].length > (im->end - im->start)) {
2273 xlab_sel--;
2274 } /* go back to the smallest size */
2275 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2276 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2277 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2278 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2279 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2280 im->xlab_user.labst = xlab[xlab_sel].labst;
2281 im->xlab_user.precis = xlab[xlab_sel].precis;
2282 im->xlab_user.stst = xlab[xlab_sel].stst;
2283 }
2285 /* y coords are the same for every line ... */
2286 Y0 = im->yorigin;
2287 Y1 = im->yorigin - im->ysize;
2290 /* paint the minor grid */
2291 if (!(im->extra_flags & NOMINOR)) {
2292 for (ti = find_first_time(im->start,
2293 im->xlab_user.gridtm,
2294 im->xlab_user.gridst),
2295 timajor = find_first_time(im->start,
2296 im->xlab_user.mgridtm,
2297 im->xlab_user.mgridst);
2298 ti < im->end;
2299 ti =
2300 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2301 ) {
2302 /* are we inside the graph ? */
2303 if (ti < im->start || ti > im->end)
2304 continue;
2305 while (timajor < ti) {
2306 timajor = find_next_time(timajor,
2307 im->xlab_user.mgridtm,
2308 im->xlab_user.mgridst);
2309 }
2310 if (ti == timajor)
2311 continue; /* skip as falls on major grid line */
2312 X0 = xtr(im, ti);
2313 gfx_line(im->cr, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2314 im->graph_col[GRC_GRID]);
2315 gfx_line(im->cr, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2316 im->graph_col[GRC_GRID]);
2317 gfx_dashed_line(im->cr, X0, Y1 - 1, X0, Y0 + 1, GRIDWIDTH,
2318 im->graph_col[GRC_GRID],
2319 im->grid_dash_on, im->grid_dash_off);
2321 }
2322 }
2324 /* paint the major grid */
2325 for (ti = find_first_time(im->start,
2326 im->xlab_user.mgridtm,
2327 im->xlab_user.mgridst);
2328 ti < im->end;
2329 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2330 ) {
2331 /* are we inside the graph ? */
2332 if (ti < im->start || ti > im->end)
2333 continue;
2334 X0 = xtr(im, ti);
2335 gfx_line(im->cr, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2336 im->graph_col[GRC_MGRID]);
2337 gfx_line(im->cr, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2338 im->graph_col[GRC_MGRID]);
2339 gfx_dashed_line(im->cr, X0, Y1 - 2, X0, Y0 + 3, MGRIDWIDTH,
2340 im->graph_col[GRC_MGRID],
2341 im->grid_dash_on, im->grid_dash_off);
2343 }
2344 /* paint the labels below the graph */
2345 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2346 im->xlab_user.labtm,
2347 im->xlab_user.labst);
2348 ti <= im->end - im->xlab_user.precis / 2;
2349 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2350 ) {
2351 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2352 /* are we inside the graph ? */
2353 if (tilab < im->start || tilab > im->end)
2354 continue;
2356 #if HAVE_STRFTIME
2357 localtime_r(&tilab, &tm);
2358 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2359 #else
2360 # error "your libc has no strftime I guess we'll abort the exercise here."
2361 #endif
2362 gfx_text(im->cr,
2363 xtr(im, tilab),
2364 Y0 + 1,
2365 im->graph_col[GRC_FONT],
2366 im->text_prop[TEXT_PROP_AXIS].font,
2367 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2368 GFX_H_CENTER, GFX_V_TOP, graph_label);
2370 }
2372 }
2375 void axis_paint(
2376 image_desc_t *im)
2377 {
2378 /* draw x and y axis */
2379 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2380 im->xorigin+im->xsize,im->yorigin-im->ysize,
2381 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2383 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2384 im->xorigin+im->xsize,im->yorigin-im->ysize,
2385 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2387 gfx_line(im->cr, im->xorigin - 4, im->yorigin,
2388 im->xorigin + im->xsize + 4, im->yorigin,
2389 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2391 gfx_line(im->cr, im->xorigin, im->yorigin + 4,
2392 im->xorigin, im->yorigin - im->ysize - 4,
2393 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2396 /* arrow for X and Y axis direction */
2397 gfx_new_area(im->cr, 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 */
2398 im->graph_col[GRC_ARROW]);
2399 gfx_close_path(im->cr);
2401 gfx_new_area(im->cr, 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 */
2402 im->graph_col[GRC_ARROW]);
2403 gfx_close_path(im->cr);
2406 }
2408 void grid_paint(
2409 image_desc_t *im)
2410 {
2411 long i;
2412 int res = 0;
2413 double X0, Y0; /* points for filled graph and more */
2414 struct gfx_color_t water_color;
2416 /* draw 3d border */
2417 gfx_new_area(im->cr, 0, im->yimg,
2418 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2419 gfx_add_point(im->cr, im->ximg - 2, 2);
2420 gfx_add_point(im->cr, im->ximg, 0);
2421 gfx_add_point(im->cr, 0, 0);
2422 gfx_close_path(im->cr);
2424 gfx_new_area(im->cr, 2, im->yimg - 2,
2425 im->ximg - 2, im->yimg - 2,
2426 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2427 gfx_add_point(im->cr, im->ximg, 0);
2428 gfx_add_point(im->cr, im->ximg, im->yimg);
2429 gfx_add_point(im->cr, 0, im->yimg);
2430 gfx_close_path(im->cr);
2433 if (im->draw_x_grid == 1)
2434 vertical_grid(im);
2436 if (im->draw_y_grid == 1) {
2437 if (im->logarithmic) {
2438 res = horizontal_log_grid(im);
2439 } else {
2440 res = draw_horizontal_grid(im);
2441 }
2443 /* dont draw horizontal grid if there is no min and max val */
2444 if (!res) {
2445 char *nodata = "No Data found";
2447 gfx_text(im->cr, im->ximg / 2,
2448 (2 * im->yorigin - im->ysize) / 2,
2449 im->graph_col[GRC_FONT],
2450 im->text_prop[TEXT_PROP_AXIS].font,
2451 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2452 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2453 }
2454 }
2456 /* yaxis unit description */
2457 gfx_text(im->cr,
2458 10, (im->yorigin - im->ysize / 2),
2459 im->graph_col[GRC_FONT],
2460 im->text_prop[TEXT_PROP_UNIT].font,
2461 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2462 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2464 /* graph title */
2465 gfx_text(im->cr,
2466 im->ximg / 2, 4,
2467 im->graph_col[GRC_FONT],
2468 im->text_prop[TEXT_PROP_TITLE].font,
2469 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2470 GFX_H_CENTER, GFX_V_TOP, im->title);
2471 /* rrdtool 'logo' */
2472 water_color = im->graph_col[GRC_FONT];
2473 water_color.alpha = 0.3;
2474 gfx_text(im->cr,
2475 im->ximg - 4, 7,
2476 water_color,
2477 im->text_prop[TEXT_PROP_AXIS].font,
2478 5.5, im->tabwidth, -90,
2479 GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2481 /* graph watermark */
2482 if (im->watermark[0] != '\0') {
2483 gfx_text(im->cr,
2484 im->ximg / 2, im->yimg - 6,
2485 water_color,
2486 im->text_prop[TEXT_PROP_AXIS].font,
2487 5.5, im->tabwidth, 0,
2488 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2489 }
2491 /* graph labels */
2492 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2493 for (i = 0; i < im->gdes_c; i++) {
2494 if (im->gdes[i].legend[0] == '\0')
2495 continue;
2497 /* im->gdes[i].leg_y is the bottom of the legend */
2498 X0 = im->gdes[i].leg_x;
2499 Y0 = im->gdes[i].leg_y;
2500 gfx_text(im->cr, X0, Y0,
2501 im->graph_col[GRC_FONT],
2502 im->text_prop[TEXT_PROP_LEGEND].font,
2503 im->text_prop[TEXT_PROP_LEGEND].size,
2504 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2505 im->gdes[i].legend);
2506 /* The legend for GRAPH items starts with "M " to have
2507 enough space for the box */
2508 if (im->gdes[i].gf != GF_PRINT &&
2509 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2510 int boxH, boxV;
2512 boxH = gfx_get_text_width(im->cr, 0,
2513 im->text_prop[TEXT_PROP_LEGEND].
2514 font,
2515 im->text_prop[TEXT_PROP_LEGEND].
2516 size, im->tabwidth, "o") * 1.2;
2517 boxV = boxH * 1.1;
2519 /* shift the box up a bit */
2520 Y0 -= boxV * 0.3;
2522 /* make sure transparent colors show up the same way as in the graph */
2524 gfx_new_area(im->cr,
2525 X0, Y0 - boxV,
2526 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2527 gfx_add_point(im->cr, X0 + boxH, Y0 - boxV);
2528 gfx_close_path(im->cr);
2530 gfx_new_area(im->cr,
2531 X0, Y0 - boxV,
2532 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2533 gfx_add_point(im->cr, X0 + boxH, Y0 - boxV);
2534 gfx_close_path(im->cr);
2536 gfx_line(im->cr,
2537 X0, Y0 - boxV,
2538 X0, Y0, 1.0, im->graph_col[GRC_FRAME]);
2539 gfx_line(im->cr,
2540 X0, Y0,
2541 X0 + boxH, Y0, 1.0, im->graph_col[GRC_FRAME]);
2542 gfx_line(im->cr,
2543 X0 + boxH, Y0,
2544 X0 + boxH, Y0 - boxV, 1.0, im->graph_col[GRC_FRAME]);
2545 gfx_line(im->cr,
2546 X0 + boxH, Y0 - boxV,
2547 X0, Y0 - boxV, 1.0, im->graph_col[GRC_FRAME]);
2548 }
2549 }
2550 }
2551 }
2554 /*****************************************************
2555 * lazy check make sure we rely need to create this graph
2556 *****************************************************/
2558 int lazy_check(
2559 image_desc_t *im)
2560 {
2561 FILE *fd = NULL;
2562 int size = 1;
2563 struct stat imgstat;
2565 if (im->lazy == 0)
2566 return 0; /* no lazy option */
2567 if (stat(im->graphfile, &imgstat) != 0)
2568 return 0; /* can't stat */
2569 /* one pixel in the existing graph is more then what we would
2570 change here ... */
2571 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2572 return 0;
2573 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2574 return 0; /* the file does not exist */
2575 switch (im->imgformat) {
2576 case IF_PNG:
2577 size = PngSize(fd, &(im->ximg), &(im->yimg));
2578 break;
2579 default:
2580 size = 1;
2581 }
2582 fclose(fd);
2583 return size;
2584 }
2587 int graph_size_location(
2588 image_desc_t *im,
2589 int elements)
2590 {
2591 /* The actual size of the image to draw is determined from
2592 ** several sources. The size given on the command line is
2593 ** the graph area but we need more as we have to draw labels
2594 ** and other things outside the graph area
2595 */
2597 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2598 Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2600 if (im->extra_flags & ONLY_GRAPH) {
2601 im->xorigin = 0;
2602 im->ximg = im->xsize;
2603 im->yimg = im->ysize;
2604 im->yorigin = im->ysize;
2605 ytr(im, DNAN);
2606 return 0;
2607 }
2609 /** +---+--------------------------------------------+
2610 ** | y |...............graph title..................|
2611 ** | +---+-------------------------------+--------+
2612 ** | a | y | | |
2613 ** | x | | | |
2614 ** | i | a | | pie |
2615 ** | s | x | main graph area | chart |
2616 ** | | i | | area |
2617 ** | t | s | | |
2618 ** | i | | | |
2619 ** | t | l | | |
2620 ** | l | b +-------------------------------+--------+
2621 ** | e | l | x axis labels | |
2622 ** +---+---+-------------------------------+--------+
2623 ** |....................legends.....................|
2624 ** +------------------------------------------------+
2625 ** | watermark |
2626 ** +------------------------------------------------+
2627 */
2629 if (im->ylegend[0] != '\0') {
2630 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2631 }
2633 if (im->title[0] != '\0') {
2634 /* The title is placed "inbetween" two text lines so it
2635 ** automatically has some vertical spacing. The horizontal
2636 ** spacing is added here, on each side.
2637 */
2638 /* if necessary, reduce the font size of the title until it fits the image width */
2639 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2640 }
2642 if (elements) {
2643 if (im->draw_x_grid) {
2644 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2645 }
2646 if (im->draw_y_grid || im->forceleftspace) {
2647 Xylabel = gfx_get_text_width(im->cr, 0,
2648 im->text_prop[TEXT_PROP_AXIS].font,
2649 im->text_prop[TEXT_PROP_AXIS].size,
2650 im->tabwidth, "0") * im->unitslength;
2651 }
2652 }
2654 if (im->extra_flags & FULL_SIZE_MODE) {
2655 /* The actual size of the image to draw has been determined by the user.
2656 ** The graph area is the space remaining after accounting for the legend,
2657 ** the watermark, the pie chart, the axis labels, and the title.
2658 */
2659 im->xorigin = 0;
2660 im->ximg = im->xsize;
2661 im->yimg = im->ysize;
2662 im->yorigin = im->ysize;
2663 Xmain = im->ximg;
2664 Ymain = im->yimg;
2666 im->yorigin += Ytitle;
2668 /* Now calculate the total size. Insert some spacing where
2669 desired. im->xorigin and im->yorigin need to correspond
2670 with the lower left corner of the main graph area or, if
2671 this one is not set, the imaginary box surrounding the
2672 pie chart area. */
2674 /* Initial size calculation for the main graph area */
2675 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2676 if (Xmain)
2677 Xmain -= Xspacing; /* put space between main graph area and right edge */
2679 im->xorigin = Xspacing + Xylabel;
2681 /* the length of the title should not influence with width of the graph
2682 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2684 if (Xvertical) { /* unit description */
2685 Xmain -= Xvertical;
2686 im->xorigin += Xvertical;
2687 }
2688 im->xsize = Xmain;
2689 xtr(im, 0);
2691 /* The vertical size of the image is known in advance. The main graph area
2692 ** (Ymain) and im->yorigin must be set according to the space requirements
2693 ** of the legend and the axis labels.
2694 */
2696 if (im->extra_flags & NOLEGEND) {
2697 /* set dimensions correctly if using full size mode with no legend */
2698 im->yorigin =
2699 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2700 Yspacing;
2701 Ymain = im->yorigin;
2702 } else {
2703 /* Determine where to place the legends onto the image.
2704 ** Set Ymain and adjust im->yorigin to match the space requirements.
2705 */
2706 if (leg_place(im, &Ymain) == -1)
2707 return -1;
2708 }
2711 /* remove title space *or* some padding above the graph from the main graph area */
2712 if (Ytitle) {
2713 Ymain -= Ytitle;
2714 } else {
2715 Ymain -= 1.5 * Yspacing;
2716 }
2718 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2719 if (im->watermark[0] != '\0') {
2720 Ymain -= Ywatermark;
2721 }
2723 im->ysize = Ymain;
2725 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2727 /* The actual size of the image to draw is determined from
2728 ** several sources. The size given on the command line is
2729 ** the graph area but we need more as we have to draw labels
2730 ** and other things outside the graph area.
2731 */
2733 if (im->ylegend[0] != '\0') {
2734 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2735 }
2738 if (im->title[0] != '\0') {
2739 /* The title is placed "inbetween" two text lines so it
2740 ** automatically has some vertical spacing. The horizontal
2741 ** spacing is added here, on each side.
2742 */
2743 /* don't care for the with of the title
2744 Xtitle = gfx_get_text_width(im->canvas, 0,
2745 im->text_prop[TEXT_PROP_TITLE].font,
2746 im->text_prop[TEXT_PROP_TITLE].size,
2747 im->tabwidth,
2748 im->title, 0) + 2*Xspacing; */
2749 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2750 }
2752 if (elements) {
2753 Xmain = im->xsize;
2754 Ymain = im->ysize;
2755 }
2756 /* Now calculate the total size. Insert some spacing where
2757 desired. im->xorigin and im->yorigin need to correspond
2758 with the lower left corner of the main graph area or, if
2759 this one is not set, the imaginary box surrounding the
2760 pie chart area. */
2762 /* The legend width cannot yet be determined, as a result we
2763 ** have problems adjusting the image to it. For now, we just
2764 ** forget about it at all; the legend will have to fit in the
2765 ** size already allocated.
2766 */
2767 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2769 if (Xmain)
2770 im->ximg += Xspacing;
2772 im->xorigin = Xspacing + Xylabel;
2774 /* the length of the title should not influence with width of the graph
2775 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2777 if (Xvertical) { /* unit description */
2778 im->ximg += Xvertical;
2779 im->xorigin += Xvertical;
2780 }
2781 xtr(im, 0);
2783 /* The vertical size is interesting... we need to compare
2784 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2785 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2786 ** in order to start even thinking about Ylegend or Ywatermark.
2787 **
2788 ** Do it in three portions: First calculate the inner part,
2789 ** then do the legend, then adjust the total height of the img,
2790 ** adding space for a watermark if one exists;
2791 */
2793 /* reserve space for main and/or pie */
2795 im->yimg = Ymain + Yxlabel;
2798 im->yorigin = im->yimg - Yxlabel;
2800 /* reserve space for the title *or* some padding above the graph */
2801 if (Ytitle) {
2802 im->yimg += Ytitle;
2803 im->yorigin += Ytitle;
2804 } else {
2805 im->yimg += 1.5 * Yspacing;
2806 im->yorigin += 1.5 * Yspacing;
2807 }
2808 /* reserve space for padding below the graph */
2809 im->yimg += Yspacing;
2811 /* Determine where to place the legends onto the image.
2812 ** Adjust im->yimg to match the space requirements.
2813 */
2814 if (leg_place(im, 0) == -1)
2815 return -1;
2817 if (im->watermark[0] != '\0') {
2818 im->yimg += Ywatermark;
2819 }
2820 }
2822 ytr(im, DNAN);
2823 return 0;
2824 }
2826 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2827 /* yes we are loosing precision by doing tos with floats instead of doubles
2828 but it seems more stable this way. */
2831 /* draw that picture thing ... */
2832 int graph_paint(
2833 image_desc_t *im,
2834 char ***calcpr)
2835 {
2836 int i, ii;
2837 int lazy = lazy_check(im);
2839 double areazero = 0.0;
2840 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2842 graph_desc_t *lastgdes = NULL;
2844 /* if we are lazy and there is nothing to PRINT ... quit now */
2845 if (lazy && im->prt_c == 0)
2846 return 0;
2848 /* pull the data from the rrd files ... */
2850 if (data_fetch(im) == -1)
2851 return -1;
2853 /* evaluate VDEF and CDEF operations ... */
2854 if (data_calc(im) == -1)
2855 return -1;
2858 /* calculate and PRINT and GPRINT definitions. We have to do it at
2859 * this point because it will affect the length of the legends
2860 * if there are no graph elements we stop here ...
2861 * if we are lazy, try to quit ...
2862 */
2863 i = print_calc(im, calcpr);
2864 if (i < 0)
2865 return -1;
2866 if ((i == 0) || lazy)
2867 return 0;
2869 /**************************************************************
2870 *** Calculating sizes and locations became a bit confusing ***
2871 *** so I moved this into a separate function. ***
2872 **************************************************************/
2873 if (graph_size_location(im, i) == -1)
2874 return -1;
2876 /* get actual drawing data and find min and max values */
2877 if (data_proc(im) == -1)
2878 return -1;
2880 if (!im->logarithmic) {
2881 si_unit(im);
2882 }
2883 /* identify si magnitude Kilo, Mega Giga ? */
2884 if (!im->rigid && !im->logarithmic)
2885 expand_range(im); /* make sure the upper and lower limit are
2886 sensible values */
2888 if (!calc_horizontal_grid(im))
2889 return -1;
2891 if (im->gridfit)
2892 apply_gridfit(im);
2894 /* the actual graph is created by going through the individual
2895 graph elements and then drawing them */
2896 cairo_surface_destroy(im->surface);
2898 switch (im->imgformat) {
2899 case IF_PNG:
2900 im->surface =
2901 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2902 im->ximg * im->zoom,
2903 im->yimg * im->zoom);
2904 break;
2905 case IF_PDF:
2906 im->surface =
2907 cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2908 im->yimg * im->zoom);
2909 break;
2910 case IF_EPS:
2911 im->surface =
2912 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
2913 im->yimg * im->zoom);
2914 break;
2915 case IF_SVG:
2916 im->surface =
2917 cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
2918 im->yimg * im->zoom);
2919 cairo_svg_surface_restrict_to_version(im->surface,
2920 CAIRO_SVG_VERSION_1_1);
2921 break;
2922 };
2923 im->cr = cairo_create(im->surface);
2924 pango_cairo_font_map_set_resolution(font_map, 100);
2925 cairo_scale(im->cr, im->zoom, im->zoom);
2927 gfx_new_area(im->cr,
2928 0, 0,
2929 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2931 gfx_add_point(im->cr, im->ximg, 0);
2932 gfx_close_path(im->cr);
2934 gfx_new_area(im->cr,
2935 im->xorigin, im->yorigin,
2936 im->xorigin + im->xsize, im->yorigin,
2937 im->xorigin + im->xsize, im->yorigin - im->ysize,
2938 im->graph_col[GRC_CANVAS]);
2940 gfx_add_point(im->cr, im->xorigin, im->yorigin - im->ysize);
2941 gfx_close_path(im->cr);
2943 if (im->minval > 0.0)
2944 areazero = im->minval;
2945 if (im->maxval < 0.0)
2946 areazero = im->maxval;
2948 for (i = 0; i < im->gdes_c; i++) {
2949 switch (im->gdes[i].gf) {
2950 case GF_CDEF:
2951 case GF_VDEF:
2952 case GF_DEF:
2953 case GF_PRINT:
2954 case GF_GPRINT:
2955 case GF_COMMENT:
2956 case GF_HRULE:
2957 case GF_VRULE:
2958 case GF_XPORT:
2959 case GF_SHIFT:
2960 break;
2961 case GF_TICK:
2962 for (ii = 0; ii < im->xsize; ii++) {
2963 if (!isnan(im->gdes[i].p_data[ii]) &&
2964 im->gdes[i].p_data[ii] != 0.0) {
2965 if (im->gdes[i].yrule > 0) {
2966 gfx_line(im->cr,
2967 im->xorigin + ii, im->yorigin,
2968 im->xorigin + ii,
2969 im->yorigin -
2970 im->gdes[i].yrule * im->ysize, 1.0,
2971 im->gdes[i].col);
2972 } else if (im->gdes[i].yrule < 0) {
2973 gfx_line(im->cr,
2974 im->xorigin + ii,
2975 im->yorigin - im->ysize,
2976 im->xorigin + ii,
2977 im->yorigin - (1 -
2978 im->gdes[i].yrule) *
2979 im->ysize, 1.0, im->gdes[i].col);
2981 }
2982 }
2983 }
2984 break;
2985 case GF_LINE:
2986 case GF_AREA:
2987 /* fix data points at oo and -oo */
2988 for (ii = 0; ii < im->xsize; ii++) {
2989 if (isinf(im->gdes[i].p_data[ii])) {
2990 if (im->gdes[i].p_data[ii] > 0) {
2991 im->gdes[i].p_data[ii] = im->maxval;
2992 } else {
2993 im->gdes[i].p_data[ii] = im->minval;
2994 }
2996 }
2997 } /* for */
2999 /* *******************************************************
3000 a ___. (a,t)
3001 | | ___
3002 ____| | | |
3003 | |___|
3004 -------|--t-1--t--------------------------------
3006 if we know the value at time t was a then
3007 we draw a square from t-1 to t with the value a.
3009 ********************************************************* */
3010 if (im->gdes[i].col.alpha != 0.0) {
3011 /* GF_LINE and friend */
3012 if (im->gdes[i].gf == GF_LINE) {
3013 double last_y = 0.0;
3014 int draw_on = 0;
3016 cairo_save(im->cr);
3017 cairo_new_path(im->cr);
3019 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3020 for (ii = 1; ii < im->xsize; ii++) {
3021 if (isnan(im->gdes[i].p_data[ii])
3022 || (im->slopemode == 1
3023 && isnan(im->gdes[i].p_data[ii - 1]))) {
3024 draw_on = 0;
3025 continue;
3026 }
3027 if (draw_on == 0) {
3028 last_y = ytr(im, im->gdes[i].p_data[ii]);
3029 if (im->slopemode == 0) {
3030 double x = ii - 1 + im->xorigin;
3031 double y = last_y;
3033 gfx_line_fit(im->cr, &x, &y);
3034 cairo_move_to(im->cr, x, y);
3035 x = ii + im->xorigin;
3036 y = last_y;
3037 gfx_line_fit(im->cr, &x, &y);
3038 cairo_line_to(im->cr, x, y);
3039 } else {
3040 double x = ii - 1 + im->xorigin;
3041 double y = ytr(im,
3042 im->gdes[i].p_data[ii - 1]);
3044 gfx_line_fit(im->cr, &x, &y);
3045 cairo_move_to(im->cr, x, y);
3046 x = ii + im->xorigin;
3047 y = last_y;
3048 gfx_line_fit(im->cr, &x, &y);
3049 cairo_line_to(im->cr, x, y);
3050 }
3051 draw_on = 1;
3052 } else {
3053 double x1 = ii + im->xorigin;
3054 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3056 if (im->slopemode == 0
3057 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3058 double x = ii - 1 + im->xorigin;
3059 double y = y1;
3061 gfx_line_fit(im->cr, &x, &y);
3062 cairo_line_to(im->cr, x, y);
3063 };
3064 last_y = y1;
3065 gfx_line_fit(im->cr, &x1, &y1);
3066 cairo_line_to(im->cr, x1, y1);
3067 };
3069 }
3070 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3071 im->gdes[i].col.green,
3072 im->gdes[i].col.blue,
3073 im->gdes[i].col.alpha);
3074 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3075 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3076 cairo_stroke(im->cr);
3077 cairo_restore(im->cr);
3078 } else {
3079 int idxI = -1;
3080 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3081 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3082 double *backY = malloc(sizeof(double) * im->xsize * 2);
3083 double *backX = malloc(sizeof(double) * im->xsize * 2);
3084 int drawem = 0;
3086 for (ii = 0; ii <= im->xsize; ii++) {
3087 double ybase, ytop;
3089 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3090 int cntI = 1;
3091 int lastI = 0;
3093 while (cntI < idxI
3094 && AlmostEqual2sComplement(foreY[lastI],
3095 foreY[cntI], 4)
3096 && AlmostEqual2sComplement(foreY[lastI],
3097 foreY[cntI + 1],
3098 4)) {
3099 cntI++;
3100 }
3101 gfx_new_area(im->cr,
3102 backX[0], backY[0],
3103 foreX[0], foreY[0],
3104 foreX[cntI], foreY[cntI],
3105 im->gdes[i].col);
3106 while (cntI < idxI) {
3107 lastI = cntI;
3108 cntI++;
3109 while (cntI < idxI
3110 &&
3111 AlmostEqual2sComplement(foreY[lastI],
3112 foreY[cntI], 4)
3113 &&
3114 AlmostEqual2sComplement(foreY[lastI],
3115 foreY[cntI +
3116 1], 4)) {
3117 cntI++;
3118 }
3119 gfx_add_point(im->cr, foreX[cntI],
3120 foreY[cntI]);
3121 }
3122 gfx_add_point(im->cr, backX[idxI], backY[idxI]);
3123 while (idxI > 1) {
3124 lastI = idxI;
3125 idxI--;
3126 while (idxI > 1
3127 &&
3128 AlmostEqual2sComplement(backY[lastI],
3129 backY[idxI], 4)
3130 &&
3131 AlmostEqual2sComplement(backY[lastI],
3132 backY[idxI -
3133 1], 4)) {
3134 idxI--;
3135 }
3136 gfx_add_point(im->cr, backX[idxI],
3137 backY[idxI]);
3138 }
3139 idxI = -1;
3140 drawem = 0;
3141 gfx_close_path(im->cr);
3142 }
3143 if (drawem != 0) {
3144 drawem = 0;
3145 idxI = -1;
3146 }
3147 if (ii == im->xsize)
3148 break;
3150 if (im->slopemode == 0 && ii == 0) {
3151 continue;
3152 }
3153 if (isnan(im->gdes[i].p_data[ii])) {
3154 drawem = 1;
3155 continue;
3156 }
3157 ytop = ytr(im, im->gdes[i].p_data[ii]);
3158 if (lastgdes && im->gdes[i].stack) {
3159 ybase = ytr(im, lastgdes->p_data[ii]);
3160 } else {
3161 ybase = ytr(im, areazero);
3162 }
3163 if (ybase == ytop) {
3164 drawem = 1;
3165 continue;
3166 }
3168 if (ybase > ytop) {
3169 double extra = ytop;
3171 ytop = ybase;
3172 ybase = extra;
3173 }
3174 if (im->slopemode == 0) {
3175 backY[++idxI] = ybase - 0.2;
3176 backX[idxI] = ii + im->xorigin - 1;
3177 foreY[idxI] = ytop + 0.2;
3178 foreX[idxI] = ii + im->xorigin - 1;
3179 }
3180 backY[++idxI] = ybase - 0.2;
3181 backX[idxI] = ii + im->xorigin;
3182 foreY[idxI] = ytop + 0.2;
3183 foreX[idxI] = ii + im->xorigin;
3184 }
3185 /* close up any remaining area */
3186 free(foreY);
3187 free(foreX);
3188 free(backY);
3189 free(backX);
3190 } /* else GF_LINE */
3191 }
3192 /* if color != 0x0 */
3193 /* make sure we do not run into trouble when stacking on NaN */
3194 for (ii = 0; ii < im->xsize; ii++) {
3195 if (isnan(im->gdes[i].p_data[ii])) {
3196 if (lastgdes && (im->gdes[i].stack)) {
3197 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3198 } else {
3199 im->gdes[i].p_data[ii] = areazero;
3200 }
3201 }
3202 }
3203 lastgdes = &(im->gdes[i]);
3204 break;
3205 case GF_STACK:
3206 rrd_set_error
3207 ("STACK should already be turned into LINE or AREA here");
3208 return -1;
3209 break;
3211 } /* switch */
3212 }
3214 /* grid_paint also does the text */
3215 if (!(im->extra_flags & ONLY_GRAPH))
3216 grid_paint(im);
3219 if (!(im->extra_flags & ONLY_GRAPH))
3220 axis_paint(im);
3222 /* the RULES are the last thing to paint ... */
3223 for (i = 0; i < im->gdes_c; i++) {
3225 switch (im->gdes[i].gf) {
3226 case GF_HRULE:
3227 if (im->gdes[i].yrule >= im->minval
3228 && im->gdes[i].yrule <= im->maxval)
3229 gfx_line(im->cr,
3230 im->xorigin, ytr(im, im->gdes[i].yrule),
3231 im->xorigin + im->xsize, ytr(im,
3232 im->gdes[i].yrule),
3233 1.0, im->gdes[i].col);
3234 break;
3235 case GF_VRULE:
3236 if (im->gdes[i].xrule >= im->start
3237 && im->gdes[i].xrule <= im->end)
3238 gfx_line(im->cr,
3239 xtr(im, im->gdes[i].xrule), im->yorigin,
3240 xtr(im, im->gdes[i].xrule),
3241 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3242 break;
3243 default:
3244 break;
3245 }
3246 }
3249 switch (im->imgformat) {
3250 case IF_PNG:
3251 if (cairo_surface_write_to_png(im->surface, im->graphfile) !=
3252 CAIRO_STATUS_SUCCESS) {
3253 rrd_set_error("Could not save png to '%s'", im->graphfile);
3254 return 1;
3255 }
3256 break;
3257 default:
3258 cairo_show_page(im->cr);
3259 break;
3260 }
3261 return 0;
3262 }
3265 /*****************************************************
3266 * graph stuff
3267 *****************************************************/
3269 int gdes_alloc(
3270 image_desc_t *im)
3271 {
3273 im->gdes_c++;
3274 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3275 * sizeof(graph_desc_t))) ==
3276 NULL) {
3277 rrd_set_error("realloc graph_descs");
3278 return -1;
3279 }
3282 im->gdes[im->gdes_c - 1].step = im->step;
3283 im->gdes[im->gdes_c - 1].step_orig = im->step;
3284 im->gdes[im->gdes_c - 1].stack = 0;
3285 im->gdes[im->gdes_c - 1].linewidth = 0;
3286 im->gdes[im->gdes_c - 1].debug = 0;
3287 im->gdes[im->gdes_c - 1].start = im->start;
3288 im->gdes[im->gdes_c - 1].start_orig = im->start;
3289 im->gdes[im->gdes_c - 1].end = im->end;
3290 im->gdes[im->gdes_c - 1].end_orig = im->end;
3291 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3292 im->gdes[im->gdes_c - 1].data = NULL;
3293 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3294 im->gdes[im->gdes_c - 1].data_first = 0;
3295 im->gdes[im->gdes_c - 1].p_data = NULL;
3296 im->gdes[im->gdes_c - 1].rpnp = NULL;
3297 im->gdes[im->gdes_c - 1].shift = 0.0;
3298 im->gdes[im->gdes_c - 1].col.red = 0.0;
3299 im->gdes[im->gdes_c - 1].col.green = 0.0;
3300 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3301 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3302 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3303 im->gdes[im->gdes_c - 1].format[0] = '\0';
3304 im->gdes[im->gdes_c - 1].strftm = 0;
3305 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3306 im->gdes[im->gdes_c - 1].ds = -1;
3307 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3308 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3309 im->gdes[im->gdes_c - 1].p_data = NULL;
3310 im->gdes[im->gdes_c - 1].yrule = DNAN;
3311 im->gdes[im->gdes_c - 1].xrule = 0;
3312 return 0;
3313 }
3315 /* copies input untill the first unescaped colon is found
3316 or until input ends. backslashes have to be escaped as well */
3317 int scan_for_col(
3318 const char *const input,
3319 int len,
3320 char *const output)
3321 {
3322 int inp, outp = 0;
3324 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3325 if (input[inp] == '\\' &&
3326 input[inp + 1] != '\0' &&
3327 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3328 output[outp++] = input[++inp];
3329 } else {
3330 output[outp++] = input[inp];
3331 }
3332 }
3333 output[outp] = '\0';
3334 return inp;
3335 }
3337 /* Some surgery done on this function, it became ridiculously big.
3338 ** Things moved:
3339 ** - initializing now in rrd_graph_init()
3340 ** - options parsing now in rrd_graph_options()
3341 ** - script parsing now in rrd_graph_script()
3342 */
3343 int rrd_graph(
3344 int argc,
3345 char **argv,
3346 char ***prdata,
3347 int *xsize,
3348 int *ysize,
3349 FILE * stream,
3350 double *ymin,
3351 double *ymax)
3352 {
3353 image_desc_t im;
3355 rrd_graph_init(&im);
3357 /* a dummy surface so that we can measure text sizes for placements */
3358 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3359 im.cr = cairo_create(im.surface);
3362 /* not currently using this ... */
3363 im.graphhandle = stream;
3365 rrd_graph_options(argc, argv, &im);
3366 if (rrd_test_error()) {
3367 im_free(&im);
3368 return -1;
3369 }
3371 if (strlen(argv[optind]) >= MAXPATH) {
3372 rrd_set_error("filename (including path) too long");
3373 im_free(&im);
3374 return -1;
3375 }
3376 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3377 im.graphfile[MAXPATH - 1] = '\0';
3379 rrd_graph_script(argc, argv, &im, 1);
3380 if (rrd_test_error()) {
3381 im_free(&im);
3382 return -1;
3383 }
3385 /* Everything is now read and the actual work can start */
3387 (*prdata) = NULL;
3388 if (graph_paint(&im, prdata) == -1) {
3389 im_free(&im);
3390 return -1;
3391 }
3393 /* The image is generated and needs to be output.
3394 ** Also, if needed, print a line with information about the image.
3395 */
3397 *xsize = im.ximg;
3398 *ysize = im.yimg;
3399 *ymin = im.minval;
3400 *ymax = im.maxval;
3401 if (im.imginfo) {
3402 char *filename;
3404 if (!(*prdata)) {
3405 /* maybe prdata is not allocated yet ... lets do it now */
3406 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3407 rrd_set_error("malloc imginfo");
3408 return -1;
3409 };
3410 }
3411 if (((*prdata)[0] =
3412 malloc((strlen(im.imginfo) + 200 +
3413 strlen(im.graphfile)) * sizeof(char)))
3414 == NULL) {
3415 rrd_set_error("malloc imginfo");
3416 return -1;
3417 }
3418 filename = im.graphfile + strlen(im.graphfile);
3419 while (filename > im.graphfile) {
3420 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3421 break;
3422 filename--;
3423 }
3425 sprintf((*prdata)[0], im.imginfo, filename,
3426 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3427 }
3428 im_free(&im);
3429 return 0;
3430 }
3432 void rrd_graph_init(
3433 image_desc_t *im)
3434 {
3435 unsigned int i;
3437 #ifdef HAVE_TZSET
3438 tzset();
3439 #endif
3440 #ifdef HAVE_SETLOCALE
3441 setlocale(LC_TIME, "");
3442 #ifdef HAVE_MBSTOWCS
3443 setlocale(LC_CTYPE, "");
3444 #endif
3445 #endif
3446 im->yorigin = 0;
3447 im->xorigin = 0;
3448 im->minval = 0;
3449 im->xlab_user.minsec = -1;
3450 im->ximg = 0;
3451 im->yimg = 0;
3452 im->xsize = 400;
3453 im->ysize = 100;
3454 im->step = 0;
3455 im->ylegend[0] = '\0';
3456 im->title[0] = '\0';
3457 im->watermark[0] = '\0';
3458 im->minval = DNAN;
3459 im->maxval = DNAN;
3460 im->unitsexponent = 9999;
3461 im->unitslength = 6;
3462 im->forceleftspace = 0;
3463 im->symbol = ' ';
3464 im->viewfactor = 1.0;
3465 im->imgformat = IF_PNG;
3466 im->cr = NULL;
3467 im->surface = NULL;
3468 im->extra_flags = 0;
3469 im->rigid = 0;
3470 im->gridfit = 1;
3471 im->imginfo = NULL;
3472 im->lazy = 0;
3473 im->slopemode = 0;
3474 im->logarithmic = 0;
3475 im->ygridstep = DNAN;
3476 im->draw_x_grid = 1;
3477 im->draw_y_grid = 1;
3478 im->base = 1000;
3479 im->prt_c = 0;
3480 im->gdes_c = 0;
3481 im->gdes = NULL;
3482 im->grid_dash_on = 1;
3483 im->grid_dash_off = 1;
3484 im->tabwidth = 40.0;
3485 im->zoom = 1;
3487 for (i = 0; i < DIM(graph_col); i++)
3488 im->graph_col[i] = graph_col[i];
3490 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3491 {
3492 char *windir;
3493 char rrd_win_default_font[1000];
3495 windir = getenv("windir");
3496 /* %windir% is something like D:\windows or C:\winnt */
3497 if (windir != NULL) {
3498 strncpy(rrd_win_default_font, windir, 500);
3499 rrd_win_default_font[500] = '\0';
3500 strcat(rrd_win_default_font, "\\fonts\\");
3501 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3502 for (i = 0; i < DIM(text_prop); i++) {
3503 strncpy(text_prop[i].font, rrd_win_default_font,
3504 sizeof(text_prop[i].font) - 1);
3505 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3506 }
3507 }
3508 }
3509 #endif
3510 {
3511 char *deffont;
3513 deffont = getenv("RRD_DEFAULT_FONT");
3514 if (deffont != NULL) {
3515 for (i = 0; i < DIM(text_prop); i++) {
3516 strncpy(text_prop[i].font, deffont,
3517 sizeof(text_prop[i].font) - 1);
3518 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3519 }
3520 }
3521 }
3522 for (i = 0; i < DIM(text_prop); i++) {
3523 im->text_prop[i].size = text_prop[i].size;
3524 strcpy(im->text_prop[i].font, text_prop[i].font);
3525 }
3526 }
3528 void rrd_graph_options(
3529 int argc,
3530 char *argv[],
3531 image_desc_t *im)
3532 {
3533 int stroff;
3534 char *parsetime_error = NULL;
3535 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3536 time_t start_tmp = 0, end_tmp = 0;
3537 long long_tmp;
3538 struct rrd_time_value start_tv, end_tv;
3539 long unsigned int color;
3541 optind = 0;
3542 opterr = 0; /* initialize getopt */
3544 parsetime("end-24h", &start_tv);
3545 parsetime("now", &end_tv);
3547 /* defines for long options without a short equivalent. should be bytes,
3548 and may not collide with (the ASCII value of) short options */
3549 #define LONGOPT_UNITS_SI 255
3551 while (1) {
3552 static struct option long_options[] = {
3553 {"start", required_argument, 0, 's'},
3554 {"end", required_argument, 0, 'e'},
3555 {"x-grid", required_argument, 0, 'x'},
3556 {"y-grid", required_argument, 0, 'y'},
3557 {"vertical-label", required_argument, 0, 'v'},
3558 {"width", required_argument, 0, 'w'},
3559 {"height", required_argument, 0, 'h'},
3560 {"full-size-mode", no_argument, 0, 'D'},
3561 {"interlaced", no_argument, 0, 'i'},
3562 {"upper-limit", required_argument, 0, 'u'},
3563 {"lower-limit", required_argument, 0, 'l'},
3564 {"rigid", no_argument, 0, 'r'},
3565 {"base", required_argument, 0, 'b'},
3566 {"logarithmic", no_argument, 0, 'o'},
3567 {"color", required_argument, 0, 'c'},
3568 {"font", required_argument, 0, 'n'},
3569 {"title", required_argument, 0, 't'},
3570 {"imginfo", required_argument, 0, 'f'},
3571 {"imgformat", required_argument, 0, 'a'},
3572 {"lazy", no_argument, 0, 'z'},
3573 {"zoom", required_argument, 0, 'm'},
3574 {"no-legend", no_argument, 0, 'g'},
3575 {"force-rules-legend", no_argument, 0, 'F'},
3576 {"only-graph", no_argument, 0, 'j'},
3577 {"alt-y-grid", no_argument, 0, 'Y'},
3578 {"no-minor", no_argument, 0, 'I'},
3579 {"slope-mode", no_argument, 0, 'E'},
3580 {"alt-autoscale", no_argument, 0, 'A'},
3581 {"alt-autoscale-min", no_argument, 0, 'J'},
3582 {"alt-autoscale-max", no_argument, 0, 'M'},
3583 {"no-gridfit", no_argument, 0, 'N'},
3584 {"units-exponent", required_argument, 0, 'X'},
3585 {"units-length", required_argument, 0, 'L'},
3586 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3587 {"step", required_argument, 0, 'S'},
3588 {"tabwidth", required_argument, 0, 'T'},
3589 {"font-render-mode", required_argument, 0, 'R'},
3590 {"font-smoothing-threshold", required_argument, 0, 'B'},
3591 {"watermark", required_argument, 0, 'W'},
3592 {"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 */
3593 {0, 0, 0, 0}
3594 };
3595 int option_index = 0;
3596 int opt;
3597 int col_start, col_end;
3599 opt = getopt_long(argc, argv,
3600 "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:",
3601 long_options, &option_index);
3603 if (opt == EOF)
3604 break;
3606 switch (opt) {
3607 case 'I':
3608 im->extra_flags |= NOMINOR;
3609 break;
3610 case 'Y':
3611 im->extra_flags |= ALTYGRID;
3612 break;
3613 case 'A':
3614 im->extra_flags |= ALTAUTOSCALE;
3615 break;
3616 case 'J':
3617 im->extra_flags |= ALTAUTOSCALE_MIN;
3618 break;
3619 case 'M':
3620 im->extra_flags |= ALTAUTOSCALE_MAX;
3621 break;
3622 case 'j':
3623 im->extra_flags |= ONLY_GRAPH;
3624 break;
3625 case 'g':
3626 im->extra_flags |= NOLEGEND;
3627 break;
3628 case 'F':
3629 im->extra_flags |= FORCE_RULES_LEGEND;
3630 break;
3631 case LONGOPT_UNITS_SI:
3632 if (im->extra_flags & FORCE_UNITS) {
3633 rrd_set_error("--units can only be used once!");
3634 return;
3635 }
3636 if (strcmp(optarg, "si") == 0)
3637 im->extra_flags |= FORCE_UNITS_SI;
3638 else {
3639 rrd_set_error("invalid argument for --units: %s", optarg);
3640 return;
3641 }
3642 break;
3643 case 'X':
3644 im->unitsexponent = atoi(optarg);
3645 break;
3646 case 'L':
3647 im->unitslength = atoi(optarg);
3648 im->forceleftspace = 1;
3649 break;
3650 case 'T':
3651 im->tabwidth = atof(optarg);
3652 break;
3653 case 'S':
3654 im->step = atoi(optarg);
3655 break;
3656 case 'N':
3657 im->gridfit = 0;
3658 break;
3659 case 's':
3660 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3661 rrd_set_error("start time: %s", parsetime_error);
3662 return;
3663 }
3664 break;
3665 case 'e':
3666 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3667 rrd_set_error("end time: %s", parsetime_error);
3668 return;
3669 }
3670 break;
3671 case 'x':
3672 if (strcmp(optarg, "none") == 0) {
3673 im->draw_x_grid = 0;
3674 break;
3675 };
3677 if (sscanf(optarg,
3678 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3679 scan_gtm,
3680 &im->xlab_user.gridst,
3681 scan_mtm,
3682 &im->xlab_user.mgridst,
3683 scan_ltm,
3684 &im->xlab_user.labst,
3685 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3686 strncpy(im->xlab_form, optarg + stroff,
3687 sizeof(im->xlab_form) - 1);
3688 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3689 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3690 rrd_set_error("unknown keyword %s", scan_gtm);
3691 return;
3692 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3693 == -1) {
3694 rrd_set_error("unknown keyword %s", scan_mtm);
3695 return;
3696 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3697 -1) {
3698 rrd_set_error("unknown keyword %s", scan_ltm);
3699 return;
3700 }
3701 im->xlab_user.minsec = 1;
3702 im->xlab_user.stst = im->xlab_form;
3703 } else {
3704 rrd_set_error("invalid x-grid format");
3705 return;
3706 }
3707 break;
3708 case 'y':
3710 if (strcmp(optarg, "none") == 0) {
3711 im->draw_y_grid = 0;
3712 break;
3713 };
3715 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3716 if (im->ygridstep <= 0) {
3717 rrd_set_error("grid step must be > 0");
3718 return;
3719 } else if (im->ylabfact < 1) {
3720 rrd_set_error("label factor must be > 0");
3721 return;
3722 }
3723 } else {
3724 rrd_set_error("invalid y-grid format");
3725 return;
3726 }
3727 break;
3728 case 'v':
3729 strncpy(im->ylegend, optarg, 150);
3730 im->ylegend[150] = '\0';
3731 break;
3732 case 'u':
3733 im->maxval = atof(optarg);
3734 break;
3735 case 'l':
3736 im->minval = atof(optarg);
3737 break;
3738 case 'b':
3739 im->base = atol(optarg);
3740 if (im->base != 1024 && im->base != 1000) {
3741 rrd_set_error
3742 ("the only sensible value for base apart from 1000 is 1024");
3743 return;
3744 }
3745 break;
3746 case 'w':
3747 long_tmp = atol(optarg);
3748 if (long_tmp < 10) {
3749 rrd_set_error("width below 10 pixels");
3750 return;
3751 }
3752 im->xsize = long_tmp;
3753 break;
3754 case 'h':
3755 long_tmp = atol(optarg);
3756 if (long_tmp < 10) {
3757 rrd_set_error("height below 10 pixels");
3758 return;
3759 }
3760 im->ysize = long_tmp;
3761 break;
3762 case 'D':
3763 im->extra_flags |= FULL_SIZE_MODE;
3764 break;
3765 case 'i':
3766 /* interlaced png not supported at the moment */
3767 break;
3768 case 'r':
3769 im->rigid = 1;
3770 break;
3771 case 'f':
3772 im->imginfo = optarg;
3773 break;
3774 case 'a':
3775 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3776 rrd_set_error("unsupported graphics format '%s'", optarg);
3777 return;
3778 }
3779 break;
3780 case 'z':
3781 im->lazy = 1;
3782 break;
3783 case 'E':
3784 im->slopemode = 1;
3785 break;
3787 case 'o':
3788 im->logarithmic = 1;
3789 break;
3790 case 'c':
3791 if (sscanf(optarg,
3792 "%10[A-Z]#%n%8lx%n",
3793 col_nam, &col_start, &color, &col_end) == 2) {
3794 int ci;
3795 int col_len = col_end - col_start;
3797 switch (col_len) {
3798 case 3:
3799 color = (((color & 0xF00) * 0x110000) |
3800 ((color & 0x0F0) * 0x011000) |
3801 ((color & 0x00F) * 0x001100) | 0x000000FF);
3802 break;
3803 case 4:
3804 color = (((color & 0xF000) * 0x11000) |
3805 ((color & 0x0F00) * 0x01100) |
3806 ((color & 0x00F0) * 0x00110) |
3807 ((color & 0x000F) * 0x00011)
3808 );
3809 break;
3810 case 6:
3811 color = (color << 8) + 0xff /* shift left by 8 */ ;
3812 break;
3813 case 8:
3814 break;
3815 default:
3816 rrd_set_error("the color format is #RRGGBB[AA]");
3817 return;
3818 }
3819 if ((ci = grc_conv(col_nam)) != -1) {
3820 im->graph_col[ci] = gfx_hex_to_col(color);
3821 } else {
3822 rrd_set_error("invalid color name '%s'", col_nam);
3823 return;
3824 }
3825 } else {
3826 rrd_set_error("invalid color def format");
3827 return;
3828 }
3829 break;
3830 case 'n':{
3831 char prop[15];
3832 double size = 1;
3833 char font[1024] = "";
3835 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3836 int sindex, propidx;
3838 if ((sindex = text_prop_conv(prop)) != -1) {
3839 for (propidx = sindex; propidx < TEXT_PROP_LAST;
3840 propidx++) {
3841 if (size > 0) {
3842 im->text_prop[propidx].size = size;
3843 }
3844 if (strlen(font) > 0) {
3845 strcpy(im->text_prop[propidx].font, font);
3846 }
3847 if (propidx == sindex && sindex != 0)
3848 break;
3849 }
3850 } else {
3851 rrd_set_error("invalid fonttag '%s'", prop);
3852 return;
3853 }
3854 } else {
3855 rrd_set_error("invalid text property format");
3856 return;
3857 }
3858 break;
3859 }
3860 case 'm':
3861 im->zoom = atof(optarg);
3862 if (im->zoom <= 0.0) {
3863 rrd_set_error("zoom factor must be > 0");
3864 return;
3865 }
3866 break;
3867 case 't':
3868 strncpy(im->title, optarg, 150);
3869 im->title[150] = '\0';
3870 break;
3872 case 'R':
3873 /* not supported curently */
3874 break;
3876 case 'B':
3877 /* not supported curently */
3878 break;
3880 case 'W':
3881 strncpy(im->watermark, optarg, 100);
3882 im->watermark[99] = '\0';
3883 break;
3885 case '?':
3886 if (optopt != 0)
3887 rrd_set_error("unknown option '%c'", optopt);
3888 else
3889 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3890 return;
3891 }
3892 }
3894 if (optind >= argc) {
3895 rrd_set_error("missing filename");
3896 return;
3897 }
3899 if (im->logarithmic == 1 && im->minval <= 0) {
3900 rrd_set_error
3901 ("for a logarithmic yaxis you must specify a lower-limit > 0");
3902 return;
3903 }
3905 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3906 /* error string is set in parsetime.c */
3907 return;
3908 }
3910 if (start_tmp < 3600 * 24 * 365 * 10) {
3911 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3912 start_tmp);
3913 return;
3914 }
3916 if (end_tmp < start_tmp) {
3917 rrd_set_error("start (%ld) should be less than end (%ld)",
3918 start_tmp, end_tmp);
3919 return;
3920 }
3922 im->start = start_tmp;
3923 im->end = end_tmp;
3924 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3925 }
3927 int rrd_graph_color(
3928 image_desc_t *im,
3929 char *var,
3930 char *err,
3931 int optional)
3932 {
3933 char *color;
3934 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
3936 color = strstr(var, "#");
3937 if (color == NULL) {
3938 if (optional == 0) {
3939 rrd_set_error("Found no color in %s", err);
3940 return 0;
3941 }
3942 return 0;
3943 } else {
3944 int n = 0;
3945 char *rest;
3946 long unsigned int col;
3948 rest = strstr(color, ":");
3949 if (rest != NULL)
3950 n = rest - color;
3951 else
3952 n = strlen(color);
3954 switch (n) {
3955 case 7:
3956 sscanf(color, "#%6lx%n", &col, &n);
3957 col = (col << 8) + 0xff /* shift left by 8 */ ;
3958 if (n != 7)
3959 rrd_set_error("Color problem in %s", err);
3960 break;
3961 case 9:
3962 sscanf(color, "#%8lx%n", &col, &n);
3963 if (n == 9)
3964 break;
3965 default:
3966 rrd_set_error("Color problem in %s", err);
3967 }
3968 if (rrd_test_error())
3969 return 0;
3970 gdp->col = gfx_hex_to_col(col);
3971 return n;
3972 }
3973 }
3976 int bad_format(
3977 char *fmt)
3978 {
3979 char *ptr;
3980 int n = 0;
3982 ptr = fmt;
3983 while (*ptr != '\0')
3984 if (*ptr++ == '%') {
3986 /* line cannot end with percent char */
3987 if (*ptr == '\0')
3988 return 1;
3990 /* '%s', '%S' and '%%' are allowed */
3991 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
3992 ptr++;
3994 /* %c is allowed (but use only with vdef!) */
3995 else if (*ptr == 'c') {
3996 ptr++;
3997 n = 1;
3998 }
4000 /* or else '% 6.2lf' and such are allowed */
4001 else {
4002 /* optional padding character */
4003 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4004 ptr++;
4006 /* This should take care of 'm.n' with all three optional */
4007 while (*ptr >= '0' && *ptr <= '9')
4008 ptr++;
4009 if (*ptr == '.')
4010 ptr++;
4011 while (*ptr >= '0' && *ptr <= '9')
4012 ptr++;
4014 /* Either 'le', 'lf' or 'lg' must follow here */
4015 if (*ptr++ != 'l')
4016 return 1;
4017 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4018 ptr++;
4019 else
4020 return 1;
4021 n++;
4022 }
4023 }
4025 return (n != 1);
4026 }
4029 int vdef_parse(
4030 gdes,
4031 str)
4032 struct graph_desc_t *gdes;
4033 const char *const str;
4034 {
4035 /* A VDEF currently is either "func" or "param,func"
4036 * so the parsing is rather simple. Change if needed.
4037 */
4038 double param;
4039 char func[30];
4040 int n;
4042 n = 0;
4043 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4044 if (n == (int) strlen(str)) { /* matched */
4045 ;
4046 } else {
4047 n = 0;
4048 sscanf(str, "%29[A-Z]%n", func, &n);
4049 if (n == (int) strlen(str)) { /* matched */
4050 param = DNAN;
4051 } else {
4052 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4053 gdes->vname);
4054 return -1;
4055 }
4056 }
4057 if (!strcmp("PERCENT", func))
4058 gdes->vf.op = VDEF_PERCENT;
4059 else if (!strcmp("MAXIMUM", func))
4060 gdes->vf.op = VDEF_MAXIMUM;
4061 else if (!strcmp("AVERAGE", func))
4062 gdes->vf.op = VDEF_AVERAGE;
4063 else if (!strcmp("MINIMUM", func))
4064 gdes->vf.op = VDEF_MINIMUM;
4065 else if (!strcmp("TOTAL", func))
4066 gdes->vf.op = VDEF_TOTAL;
4067 else if (!strcmp("FIRST", func))
4068 gdes->vf.op = VDEF_FIRST;
4069 else if (!strcmp("LAST", func))
4070 gdes->vf.op = VDEF_LAST;
4071 else if (!strcmp("LSLSLOPE", func))
4072 gdes->vf.op = VDEF_LSLSLOPE;
4073 else if (!strcmp("LSLINT", func))
4074 gdes->vf.op = VDEF_LSLINT;
4075 else if (!strcmp("LSLCORREL", func))
4076 gdes->vf.op = VDEF_LSLCORREL;
4077 else {
4078 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4079 gdes->vname);
4080 return -1;
4081 };
4083 switch (gdes->vf.op) {
4084 case VDEF_PERCENT:
4085 if (isnan(param)) { /* no parameter given */
4086 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4087 func, gdes->vname);
4088 return -1;
4089 };
4090 if (param >= 0.0 && param <= 100.0) {
4091 gdes->vf.param = param;
4092 gdes->vf.val = DNAN; /* undefined */
4093 gdes->vf.when = 0; /* undefined */
4094 } else {
4095 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4096 gdes->vname);
4097 return -1;
4098 };
4099 break;
4100 case VDEF_MAXIMUM:
4101 case VDEF_AVERAGE:
4102 case VDEF_MINIMUM:
4103 case VDEF_TOTAL:
4104 case VDEF_FIRST:
4105 case VDEF_LAST:
4106 case VDEF_LSLSLOPE:
4107 case VDEF_LSLINT:
4108 case VDEF_LSLCORREL:
4109 if (isnan(param)) {
4110 gdes->vf.param = DNAN;
4111 gdes->vf.val = DNAN;
4112 gdes->vf.when = 0;
4113 } else {
4114 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4115 func, gdes->vname);
4116 return -1;
4117 };
4118 break;
4119 };
4120 return 0;
4121 }
4124 int vdef_calc(
4125 im,
4126 gdi)
4127 image_desc_t *im;
4128 int gdi;
4129 {
4130 graph_desc_t *src, *dst;
4131 rrd_value_t *data;
4132 long step, steps;
4134 dst = &im->gdes[gdi];
4135 src = &im->gdes[dst->vidx];
4136 data = src->data + src->ds;
4137 steps = (src->end - src->start) / src->step;
4139 #if 0
4140 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4141 src->end, steps);
4142 #endif
4144 switch (dst->vf.op) {
4145 case VDEF_PERCENT:{
4146 rrd_value_t *array;
4147 int field;
4150 if ((array = malloc(steps * sizeof(double))) == NULL) {
4151 rrd_set_error("malloc VDEV_PERCENT");
4152 return -1;
4153 }
4154 for (step = 0; step < steps; step++) {
4155 array[step] = data[step * src->ds_cnt];
4156 }
4157 qsort(array, step, sizeof(double), vdef_percent_compar);
4159 field = (steps - 1) * dst->vf.param / 100;
4160 dst->vf.val = array[field];
4161 dst->vf.when = 0; /* no time component */
4162 free(array);
4163 #if 0
4164 for (step = 0; step < steps; step++)
4165 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4166 step == field ? '*' : ' ');
4167 #endif
4168 }
4169 break;
4170 case VDEF_MAXIMUM:
4171 step = 0;
4172 while (step != steps && isnan(data[step * src->ds_cnt]))
4173 step++;
4174 if (step == steps) {
4175 dst->vf.val = DNAN;
4176 dst->vf.when = 0;
4177 } else {
4178 dst->vf.val = data[step * src->ds_cnt];
4179 dst->vf.when = src->start + (step + 1) * src->step;
4180 }
4181 while (step != steps) {
4182 if (finite(data[step * src->ds_cnt])) {
4183 if (data[step * src->ds_cnt] > dst->vf.val) {
4184 dst->vf.val = data[step * src->ds_cnt];
4185 dst->vf.when = src->start + (step + 1) * src->step;
4186 }
4187 }
4188 step++;
4189 }
4190 break;
4191 case VDEF_TOTAL:
4192 case VDEF_AVERAGE:{
4193 int cnt = 0;
4194 double sum = 0.0;
4196 for (step = 0; step < steps; step++) {
4197 if (finite(data[step * src->ds_cnt])) {
4198 sum += data[step * src->ds_cnt];
4199 cnt++;
4200 };
4201 }
4202 if (cnt) {
4203 if (dst->vf.op == VDEF_TOTAL) {
4204 dst->vf.val = sum * src->step;
4205 dst->vf.when = 0; /* no time component */
4206 } else {
4207 dst->vf.val = sum / cnt;
4208 dst->vf.when = 0; /* no time component */
4209 };
4210 } else {
4211 dst->vf.val = DNAN;
4212 dst->vf.when = 0;
4213 }
4214 }
4215 break;
4216 case VDEF_MINIMUM:
4217 step = 0;
4218 while (step != steps && isnan(data[step * src->ds_cnt]))
4219 step++;
4220 if (step == steps) {
4221 dst->vf.val = DNAN;
4222 dst->vf.when = 0;
4223 } else {
4224 dst->vf.val = data[step * src->ds_cnt];
4225 dst->vf.when = src->start + (step + 1) * src->step;
4226 }
4227 while (step != steps) {
4228 if (finite(data[step * src->ds_cnt])) {
4229 if (data[step * src->ds_cnt] < dst->vf.val) {
4230 dst->vf.val = data[step * src->ds_cnt];
4231 dst->vf.when = src->start + (step + 1) * src->step;
4232 }
4233 }
4234 step++;
4235 }
4236 break;
4237 case VDEF_FIRST:
4238 /* The time value returned here is one step before the
4239 * actual time value. This is the start of the first
4240 * non-NaN interval.
4241 */
4242 step = 0;
4243 while (step != steps && isnan(data[step * src->ds_cnt]))
4244 step++;
4245 if (step == steps) { /* all entries were NaN */
4246 dst->vf.val = DNAN;
4247 dst->vf.when = 0;
4248 } else {
4249 dst->vf.val = data[step * src->ds_cnt];
4250 dst->vf.when = src->start + step * src->step;
4251 }
4252 break;
4253 case VDEF_LAST:
4254 /* The time value returned here is the
4255 * actual time value. This is the end of the last
4256 * non-NaN interval.
4257 */
4258 step = steps - 1;
4259 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4260 step--;
4261 if (step < 0) { /* all entries were NaN */
4262 dst->vf.val = DNAN;
4263 dst->vf.when = 0;
4264 } else {
4265 dst->vf.val = data[step * src->ds_cnt];
4266 dst->vf.when = src->start + (step + 1) * src->step;
4267 }
4268 break;
4269 case VDEF_LSLSLOPE:
4270 case VDEF_LSLINT:
4271 case VDEF_LSLCORREL:{
4272 /* Bestfit line by linear least squares method */
4274 int cnt = 0;
4275 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4277 SUMx = 0;
4278 SUMy = 0;
4279 SUMxy = 0;
4280 SUMxx = 0;
4281 SUMyy = 0;
4283 for (step = 0; step < steps; step++) {
4284 if (finite(data[step * src->ds_cnt])) {
4285 cnt++;
4286 SUMx += step;
4287 SUMxx += step * step;
4288 SUMxy += step * data[step * src->ds_cnt];
4289 SUMy += data[step * src->ds_cnt];
4290 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4291 };
4292 }
4294 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4295 y_intercept = (SUMy - slope * SUMx) / cnt;
4296 correl =
4297 (SUMxy -
4298 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4299 (SUMx * SUMx) / cnt) * (SUMyy -
4300 (SUMy *
4301 SUMy) /
4302 cnt));
4304 if (cnt) {
4305 if (dst->vf.op == VDEF_LSLSLOPE) {
4306 dst->vf.val = slope;
4307 dst->vf.when = 0;
4308 } else if (dst->vf.op == VDEF_LSLINT) {
4309 dst->vf.val = y_intercept;
4310 dst->vf.when = 0;
4311 } else if (dst->vf.op == VDEF_LSLCORREL) {
4312 dst->vf.val = correl;
4313 dst->vf.when = 0;
4314 };
4316 } else {
4317 dst->vf.val = DNAN;
4318 dst->vf.when = 0;
4319 }
4320 }
4321 break;
4322 }
4323 return 0;
4324 }
4326 /* NaN < -INF < finite_values < INF */
4327 int vdef_percent_compar(
4328 a,
4329 b)
4330 const void *a, *b;
4331 {
4332 /* Equality is not returned; this doesn't hurt except
4333 * (maybe) for a little performance.
4334 */
4336 /* First catch NaN values. They are smallest */
4337 if (isnan(*(double *) a))
4338 return -1;
4339 if (isnan(*(double *) b))
4340 return 1;
4342 /* NaN doesn't reach this part so INF and -INF are extremes.
4343 * The sign from isinf() is compatible with the sign we return
4344 */
4345 if (isinf(*(double *) a))
4346 return isinf(*(double *) a);
4347 if (isinf(*(double *) b))
4348 return isinf(*(double *) b);
4350 /* If we reach this, both values must be finite */
4351 if (*(double *) a < *(double *) b)
4352 return -1;
4353 else
4354 return 1;
4355 }