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 { 0xFFFFFFFF, /* canvas */
144 0xF0F0F0FF, /* background */
145 0xD0D0D0FF, /* shade A */
146 0xA0A0A0FF, /* shade B */
147 0x90909080, /* grid */
148 0xE0505080, /* major grid */
149 0x000000FF, /* font */
150 0x802020FF, /* arrow */
151 0x202020FF, /* axis */
152 0x000000FF /* frame */
153 };
156 /* #define DEBUG */
158 #ifdef DEBUG
159 # define DPRINT(x) (void)(printf x, printf("\n"))
160 #else
161 # define DPRINT(x)
162 #endif
165 /* initialize with xtr(im,0); */
166 int xtr(
167 image_desc_t *im,
168 time_t mytime)
169 {
170 static double pixie;
172 if (mytime == 0) {
173 pixie = (double) im->xsize / (double) (im->end - im->start);
174 return im->xorigin;
175 }
176 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
177 }
179 /* translate data values into y coordinates */
180 double ytr(
181 image_desc_t *im,
182 double value)
183 {
184 static double pixie;
185 double yval;
187 if (isnan(value)) {
188 if (!im->logarithmic)
189 pixie = (double) im->ysize / (im->maxval - im->minval);
190 else
191 pixie =
192 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
193 yval = im->yorigin;
194 } else if (!im->logarithmic) {
195 yval = im->yorigin - pixie * (value - im->minval);
196 } else {
197 if (value < im->minval) {
198 yval = im->yorigin;
199 } else {
200 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
201 }
202 }
203 /* make sure we don't return anything too unreasonable. GD lib can
204 get terribly slow when drawing lines outside its scope. This is
205 especially problematic in connection with the rigid option */
206 if (!im->rigid) {
207 /* keep yval as-is */
208 } else if (yval > im->yorigin) {
209 yval = im->yorigin + 0.00001;
210 } else if (yval < im->yorigin - im->ysize) {
211 yval = im->yorigin - im->ysize - 0.00001;
212 }
213 return yval;
214 }
218 /* conversion function for symbolic entry names */
221 #define conv_if(VV,VVV) \
222 if (strcmp(#VV, string) == 0) return VVV ;
224 enum gf_en gf_conv(
225 char *string)
226 {
228 conv_if(PRINT, GF_PRINT)
229 conv_if(GPRINT, GF_GPRINT)
230 conv_if(COMMENT, GF_COMMENT)
231 conv_if(HRULE, GF_HRULE)
232 conv_if(VRULE, GF_VRULE)
233 conv_if(LINE, GF_LINE)
234 conv_if(AREA, GF_AREA)
235 conv_if(STACK, GF_STACK)
236 conv_if(TICK, GF_TICK)
237 conv_if(DEF, GF_DEF)
238 conv_if(CDEF, GF_CDEF)
239 conv_if(VDEF, GF_VDEF)
240 #ifdef WITH_PIECHART
241 conv_if(PART, GF_PART)
242 #endif
243 conv_if(XPORT, GF_XPORT)
244 conv_if(SHIFT, GF_SHIFT)
246 return (-1);
247 }
249 enum gfx_if_en if_conv(
250 char *string)
251 {
253 conv_if(PNG, IF_PNG)
254 conv_if(SVG, IF_SVG)
255 conv_if(EPS, IF_EPS)
256 conv_if(PDF, IF_PDF)
258 return (-1);
259 }
261 enum tmt_en tmt_conv(
262 char *string)
263 {
265 conv_if(SECOND, TMT_SECOND)
266 conv_if(MINUTE, TMT_MINUTE)
267 conv_if(HOUR, TMT_HOUR)
268 conv_if(DAY, TMT_DAY)
269 conv_if(WEEK, TMT_WEEK)
270 conv_if(MONTH, TMT_MONTH)
271 conv_if(YEAR, TMT_YEAR)
272 return (-1);
273 }
275 enum grc_en grc_conv(
276 char *string)
277 {
279 conv_if(BACK, GRC_BACK)
280 conv_if(CANVAS, GRC_CANVAS)
281 conv_if(SHADEA, GRC_SHADEA)
282 conv_if(SHADEB, GRC_SHADEB)
283 conv_if(GRID, GRC_GRID)
284 conv_if(MGRID, GRC_MGRID)
285 conv_if(FONT, GRC_FONT)
286 conv_if(ARROW, GRC_ARROW)
287 conv_if(AXIS, GRC_AXIS)
288 conv_if(FRAME, GRC_FRAME)
290 return -1;
291 }
293 enum text_prop_en text_prop_conv(
294 char *string)
295 {
297 conv_if(DEFAULT, TEXT_PROP_DEFAULT)
298 conv_if(TITLE, TEXT_PROP_TITLE)
299 conv_if(AXIS, TEXT_PROP_AXIS)
300 conv_if(UNIT, TEXT_PROP_UNIT)
301 conv_if(LEGEND, TEXT_PROP_LEGEND)
302 return -1;
303 }
306 #undef conv_if
308 int im_free(
309 image_desc_t *im)
310 {
311 unsigned long i, ii;
313 if (im == NULL)
314 return 0;
315 for (i = 0; i < (unsigned) im->gdes_c; i++) {
316 if (im->gdes[i].data_first) {
317 /* careful here, because a single pointer can occur several times */
318 free(im->gdes[i].data);
319 if (im->gdes[i].ds_namv) {
320 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
321 free(im->gdes[i].ds_namv[ii]);
322 free(im->gdes[i].ds_namv);
323 }
324 }
325 free(im->gdes[i].p_data);
326 free(im->gdes[i].rpnp);
327 }
328 free(im->gdes);
329 gfx_destroy(im->canvas);
330 return 0;
331 }
333 /* find SI magnitude symbol for the given number*/
334 void auto_scale(
335 image_desc_t *im, /* image description */
336 double *value,
337 char **symb_ptr,
338 double *magfact)
339 {
341 char *symbol[] = { "a", /* 10e-18 Atto */
342 "f", /* 10e-15 Femto */
343 "p", /* 10e-12 Pico */
344 "n", /* 10e-9 Nano */
345 "u", /* 10e-6 Micro */
346 "m", /* 10e-3 Milli */
347 " ", /* Base */
348 "k", /* 10e3 Kilo */
349 "M", /* 10e6 Mega */
350 "G", /* 10e9 Giga */
351 "T", /* 10e12 Tera */
352 "P", /* 10e15 Peta */
353 "E"
354 }; /* 10e18 Exa */
356 int symbcenter = 6;
357 int sindex;
359 if (*value == 0.0 || isnan(*value)) {
360 sindex = 0;
361 *magfact = 1.0;
362 } else {
363 sindex = floor(log(fabs(*value)) / log((double) im->base));
364 *magfact = pow((double) im->base, (double) sindex);
365 (*value) /= (*magfact);
366 }
367 if (sindex <= symbcenter && sindex >= -symbcenter) {
368 (*symb_ptr) = symbol[sindex + symbcenter];
369 } else {
370 (*symb_ptr) = "?";
371 }
372 }
375 static char si_symbol[] = {
376 'a', /* 10e-18 Atto */
377 'f', /* 10e-15 Femto */
378 'p', /* 10e-12 Pico */
379 'n', /* 10e-9 Nano */
380 'u', /* 10e-6 Micro */
381 'm', /* 10e-3 Milli */
382 ' ', /* Base */
383 'k', /* 10e3 Kilo */
384 'M', /* 10e6 Mega */
385 'G', /* 10e9 Giga */
386 'T', /* 10e12 Tera */
387 'P', /* 10e15 Peta */
388 'E', /* 10e18 Exa */
389 };
390 static const int si_symbcenter = 6;
392 /* find SI magnitude symbol for the numbers on the y-axis*/
393 void si_unit(
394 image_desc_t *im /* image description */
395 )
396 {
398 double digits, viewdigits = 0;
400 digits =
401 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
402 log((double) im->base));
404 if (im->unitsexponent != 9999) {
405 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
406 viewdigits = floor(im->unitsexponent / 3);
407 } else {
408 viewdigits = digits;
409 }
411 im->magfact = pow((double) im->base, digits);
413 #ifdef DEBUG
414 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
415 #endif
417 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
419 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
420 ((viewdigits + si_symbcenter) >= 0))
421 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
422 else
423 im->symbol = '?';
424 }
426 /* move min and max values around to become sensible */
428 void expand_range(
429 image_desc_t *im)
430 {
431 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
432 600.0, 500.0, 400.0, 300.0, 250.0,
433 200.0, 125.0, 100.0, 90.0, 80.0,
434 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
435 25.0, 20.0, 10.0, 9.0, 8.0,
436 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
437 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
438 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
439 };
441 double scaled_min, scaled_max;
442 double adj;
443 int i;
447 #ifdef DEBUG
448 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
449 im->minval, im->maxval, im->magfact);
450 #endif
452 if (isnan(im->ygridstep)) {
453 if (im->extra_flags & ALTAUTOSCALE) {
454 /* measure the amplitude of the function. Make sure that
455 graph boundaries are slightly higher then max/min vals
456 so we can see amplitude on the graph */
457 double delt, fact;
459 delt = im->maxval - im->minval;
460 adj = delt * 0.1;
461 fact = 2.0 * pow(10.0,
462 floor(log10
463 (max(fabs(im->minval), fabs(im->maxval)) /
464 im->magfact)) - 2);
465 if (delt < fact) {
466 adj = (fact - delt) * 0.55;
467 #ifdef DEBUG
468 printf
469 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
470 im->minval, im->maxval, delt, fact, adj);
471 #endif
472 }
473 im->minval -= adj;
474 im->maxval += adj;
475 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
476 /* measure the amplitude of the function. Make sure that
477 graph boundaries are slightly lower than min vals
478 so we can see amplitude on the graph */
479 adj = (im->maxval - im->minval) * 0.1;
480 im->minval -= adj;
481 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
482 /* measure the amplitude of the function. Make sure that
483 graph boundaries are slightly higher than max vals
484 so we can see amplitude on the graph */
485 adj = (im->maxval - im->minval) * 0.1;
486 im->maxval += adj;
487 } else {
488 scaled_min = im->minval / im->magfact;
489 scaled_max = im->maxval / im->magfact;
491 for (i = 1; sensiblevalues[i] > 0; i++) {
492 if (sensiblevalues[i - 1] >= scaled_min &&
493 sensiblevalues[i] <= scaled_min)
494 im->minval = sensiblevalues[i] * (im->magfact);
496 if (-sensiblevalues[i - 1] <= scaled_min &&
497 -sensiblevalues[i] >= scaled_min)
498 im->minval = -sensiblevalues[i - 1] * (im->magfact);
500 if (sensiblevalues[i - 1] >= scaled_max &&
501 sensiblevalues[i] <= scaled_max)
502 im->maxval = sensiblevalues[i - 1] * (im->magfact);
504 if (-sensiblevalues[i - 1] <= scaled_max &&
505 -sensiblevalues[i] >= scaled_max)
506 im->maxval = -sensiblevalues[i] * (im->magfact);
507 }
508 }
509 } else {
510 /* adjust min and max to the grid definition if there is one */
511 im->minval = (double) im->ylabfact * im->ygridstep *
512 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
513 im->maxval = (double) im->ylabfact * im->ygridstep *
514 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
515 }
517 #ifdef DEBUG
518 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
519 im->minval, im->maxval, im->magfact);
520 #endif
521 }
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 = leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size*1.8);
1580 for (i = 0; i < im->gdes_c; i++) {
1581 fill_last = fill;
1583 /* hide legends for rules which are not displayed */
1585 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1586 if (im->gdes[i].gf == GF_HRULE &&
1587 (im->gdes[i].yrule < im->minval
1588 || im->gdes[i].yrule > im->maxval))
1589 im->gdes[i].legend[0] = '\0';
1591 if (im->gdes[i].gf == GF_VRULE &&
1592 (im->gdes[i].xrule < im->start
1593 || im->gdes[i].xrule > im->end))
1594 im->gdes[i].legend[0] = '\0';
1595 }
1597 leg_cc = strlen(im->gdes[i].legend);
1599 /* is there a controle code ant the end of the legend string ? */
1600 /* and it is not a tab \\t */
1601 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1602 && im->gdes[i].legend[leg_cc - 1] != 't') {
1603 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1604 leg_cc -= 2;
1605 im->gdes[i].legend[leg_cc] = '\0';
1606 } else {
1607 prt_fctn = '\0';
1608 }
1609 /* only valid control codes */
1610 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1611 prt_fctn != 'r' &&
1612 prt_fctn != 'j' &&
1613 prt_fctn != 'c' &&
1614 prt_fctn != 's' &&
1615 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1616 free(legspace);
1617 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1618 im->gdes[i].legend, prt_fctn);
1619 return -1;
1621 }
1623 /* remove exess space */
1624 if (prt_fctn == 'n') {
1625 prt_fctn = 'l';
1626 }
1628 while (prt_fctn == 'g' &&
1629 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1630 leg_cc--;
1631 im->gdes[i].legend[leg_cc] = '\0';
1632 }
1633 if (leg_cc != 0) {
1634 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1636 if (fill > 0) {
1637 /* no interleg space if string ends in \g */
1638 fill += legspace[i];
1639 }
1640 fill += gfx_get_text_width(im->canvas, fill + border,
1641 im->text_prop[TEXT_PROP_LEGEND].
1642 font,
1643 im->text_prop[TEXT_PROP_LEGEND].
1644 size, im->tabwidth,
1645 im->gdes[i].legend, 0);
1646 leg_c++;
1647 } else {
1648 legspace[i] = 0;
1649 }
1650 /* who said there was a special tag ... ? */
1651 if (prt_fctn == 'g') {
1652 prt_fctn = '\0';
1653 }
1654 if (prt_fctn == '\0') {
1655 if (i == im->gdes_c - 1)
1656 prt_fctn = 'l';
1658 /* is it time to place the legends ? */
1659 if (fill > im->ximg - 2 * border) {
1660 if (leg_c > 1) {
1661 /* go back one */
1662 i--;
1663 fill = fill_last;
1664 leg_c--;
1665 prt_fctn = 'j';
1666 } else {
1667 prt_fctn = 'l';
1668 }
1670 }
1671 }
1674 if (prt_fctn != '\0') {
1675 leg_x = border;
1676 if (leg_c >= 2 && prt_fctn == 'j') {
1677 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1678 } else {
1679 glue = 0;
1680 }
1681 if (prt_fctn == 'c')
1682 leg_x = (im->ximg - fill) / 2.0;
1683 if (prt_fctn == 'r')
1684 leg_x = im->ximg - fill - border;
1686 for (ii = mark; ii <= i; ii++) {
1687 if (im->gdes[ii].legend[0] == '\0')
1688 continue; /* skip empty legends */
1689 im->gdes[ii].leg_x = leg_x;
1690 im->gdes[ii].leg_y = leg_y;
1691 leg_x +=
1692 gfx_get_text_width(im->canvas, leg_x,
1693 im->text_prop[TEXT_PROP_LEGEND].
1694 font,
1695 im->text_prop[TEXT_PROP_LEGEND].
1696 size, im->tabwidth,
1697 im->gdes[ii].legend, 0)
1698 + legspace[ii]
1699 + glue;
1700 }
1701 leg_y_prev = leg_y;
1702 if (im->extra_flags & FULL_SIZE_MODE) {
1703 /* only add y space if there was text on the line */
1704 if (leg_x > border || prt_fctn == 's')
1705 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1706 if (prt_fctn == 's')
1707 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1708 } else {
1709 if (leg_x > border || prt_fctn == 's')
1710 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1711 if (prt_fctn == 's')
1712 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1713 }
1714 fill = 0;
1715 leg_c = 0;
1716 mark = ii;
1717 }
1718 }
1720 if (im->extra_flags & FULL_SIZE_MODE) {
1721 if (leg_y != leg_y_prev) {
1722 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1723 im->yorigin = leg_y - im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1724 }
1725 } else {
1726 im->yimg = leg_y_prev;
1727 /* if we did place some legends we have to add vertical space */
1728 if (leg_y != im->yimg)
1729 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1730 }
1731 free(legspace);
1732 }
1733 return 0;
1734 }
1736 /* create a grid on the graph. it determines what to do
1737 from the values of xsize, start and end */
1739 /* the xaxis labels are determined from the number of seconds per pixel
1740 in the requested graph */
1744 int calc_horizontal_grid(
1745 image_desc_t *im)
1746 {
1747 double range;
1748 double scaledrange;
1749 int pixel, i;
1750 int gridind = 0;
1751 int decimals, fractionals;
1753 im->ygrid_scale.labfact = 2;
1754 range = im->maxval - im->minval;
1755 scaledrange = range / im->magfact;
1757 /* does the scale of this graph make it impossible to put lines
1758 on it? If so, give up. */
1759 if (isnan(scaledrange)) {
1760 return 0;
1761 }
1763 /* find grid spaceing */
1764 pixel = 1;
1765 if (isnan(im->ygridstep)) {
1766 if (im->extra_flags & ALTYGRID) {
1767 /* find the value with max number of digits. Get number of digits */
1768 decimals =
1769 ceil(log10
1770 (max(fabs(im->maxval), fabs(im->minval)) *
1771 im->viewfactor / im->magfact));
1772 if (decimals <= 0) /* everything is small. make place for zero */
1773 decimals = 1;
1775 im->ygrid_scale.gridstep =
1776 pow((double) 10,
1777 floor(log10(range * im->viewfactor / im->magfact))) /
1778 im->viewfactor * im->magfact;
1780 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1781 im->ygrid_scale.gridstep = 0.1;
1782 /* should have at least 5 lines but no more then 15 */
1783 if (range / im->ygrid_scale.gridstep < 5)
1784 im->ygrid_scale.gridstep /= 10;
1785 if (range / im->ygrid_scale.gridstep > 15)
1786 im->ygrid_scale.gridstep *= 10;
1787 if (range / im->ygrid_scale.gridstep > 5) {
1788 im->ygrid_scale.labfact = 1;
1789 if (range / im->ygrid_scale.gridstep > 8)
1790 im->ygrid_scale.labfact = 2;
1791 } else {
1792 im->ygrid_scale.gridstep /= 5;
1793 im->ygrid_scale.labfact = 5;
1794 }
1795 fractionals =
1796 floor(log10
1797 (im->ygrid_scale.gridstep *
1798 (double) im->ygrid_scale.labfact * im->viewfactor /
1799 im->magfact));
1800 if (fractionals < 0) { /* small amplitude. */
1801 int len = decimals - fractionals + 1;
1803 if (im->unitslength < len + 2)
1804 im->unitslength = len + 2;
1805 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1806 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1807 } else {
1808 int len = decimals + 1;
1810 if (im->unitslength < len + 2)
1811 im->unitslength = len + 2;
1812 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1813 (im->symbol != ' ' ? " %c" : ""));
1814 }
1815 } else {
1816 for (i = 0; ylab[i].grid > 0; i++) {
1817 pixel = im->ysize / (scaledrange / ylab[i].grid);
1818 gridind = i;
1819 if (pixel > 7)
1820 break;
1821 }
1823 for (i = 0; i < 4; i++) {
1824 if (pixel * ylab[gridind].lfac[i] >=
1825 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1826 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1827 break;
1828 }
1829 }
1831 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1832 }
1833 } else {
1834 im->ygrid_scale.gridstep = im->ygridstep;
1835 im->ygrid_scale.labfact = im->ylabfact;
1836 }
1837 return 1;
1838 }
1840 int draw_horizontal_grid(
1841 image_desc_t *im)
1842 {
1843 int i;
1844 double scaledstep;
1845 char graph_label[100];
1846 int nlabels = 0;
1847 double X0 = im->xorigin;
1848 double X1 = im->xorigin + im->xsize;
1850 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1851 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1852 double MaxY;
1854 scaledstep =
1855 im->ygrid_scale.gridstep / (double) im->magfact *
1856 (double) im->viewfactor;
1857 MaxY = scaledstep * (double) egrid;
1858 for (i = sgrid; i <= egrid; i++) {
1859 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1860 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1862 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1863 && floor(Y0 + 0.5) <= im->yorigin) {
1864 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1865 with the chosen settings. Add a label if required by settings, or if
1866 there is only one label so far and the next grid line is out of bounds. */
1867 if (i % im->ygrid_scale.labfact == 0
1868 || (nlabels == 1
1869 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1870 if (im->symbol == ' ') {
1871 if (im->extra_flags & ALTYGRID) {
1872 sprintf(graph_label, im->ygrid_scale.labfmt,
1873 scaledstep * (double) i);
1874 } else {
1875 if (MaxY < 10) {
1876 sprintf(graph_label, "%4.1f",
1877 scaledstep * (double) i);
1878 } else {
1879 sprintf(graph_label, "%4.0f",
1880 scaledstep * (double) i);
1881 }
1882 }
1883 } else {
1884 char sisym = (i == 0 ? ' ' : im->symbol);
1886 if (im->extra_flags & ALTYGRID) {
1887 sprintf(graph_label, im->ygrid_scale.labfmt,
1888 scaledstep * (double) i, sisym);
1889 } else {
1890 if (MaxY < 10) {
1891 sprintf(graph_label, "%4.1f %c",
1892 scaledstep * (double) i, sisym);
1893 } else {
1894 sprintf(graph_label, "%4.0f %c",
1895 scaledstep * (double) i, sisym);
1896 }
1897 }
1898 }
1899 nlabels++;
1901 gfx_new_text(im->canvas,
1902 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1903 im->graph_col[GRC_FONT],
1904 im->text_prop[TEXT_PROP_AXIS].font,
1905 im->text_prop[TEXT_PROP_AXIS].size,
1906 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1907 graph_label);
1908 gfx_new_dashed_line(im->canvas,
1909 X0 - 2, Y0,
1910 X1 + 2, Y0,
1911 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1912 im->grid_dash_on, im->grid_dash_off);
1914 } else if (!(im->extra_flags & NOMINOR)) {
1915 gfx_new_dashed_line(im->canvas,
1916 X0 - 1, Y0,
1917 X1 + 1, Y0,
1918 GRIDWIDTH, im->graph_col[GRC_GRID],
1919 im->grid_dash_on, im->grid_dash_off);
1921 }
1922 }
1923 }
1924 return 1;
1925 }
1927 /* this is frexp for base 10 */
1928 double frexp10(
1929 double,
1930 double *);
1931 double frexp10(
1932 double x,
1933 double *e)
1934 {
1935 double mnt;
1936 int iexp;
1938 iexp = floor(log(fabs(x)) / log(10));
1939 mnt = x / pow(10.0, iexp);
1940 if (mnt >= 10.0) {
1941 iexp++;
1942 mnt = x / pow(10.0, iexp);
1943 }
1944 *e = iexp;
1945 return mnt;
1946 }
1948 static int AlmostEqual2sComplement(
1949 float A,
1950 float B,
1951 int maxUlps)
1952 {
1954 int aInt = *(int *) &A;
1955 int bInt = *(int *) &B;
1956 int intDiff;
1958 /* Make sure maxUlps is non-negative and small enough that the
1959 default NAN won't compare as equal to anything. */
1961 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1963 /* Make aInt lexicographically ordered as a twos-complement int */
1965 if (aInt < 0)
1966 aInt = 0x80000000l - aInt;
1968 /* Make bInt lexicographically ordered as a twos-complement int */
1970 if (bInt < 0)
1971 bInt = 0x80000000l - bInt;
1973 intDiff = abs(aInt - bInt);
1975 if (intDiff <= maxUlps)
1976 return 1;
1978 return 0;
1979 }
1981 /* logaritmic horizontal grid */
1982 int horizontal_log_grid(
1983 image_desc_t *im)
1984 {
1985 double yloglab[][10] = {
1986 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1987 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1988 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1989 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1990 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1991 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
1992 };
1994 int i, j, val_exp, min_exp;
1995 double nex; /* number of decades in data */
1996 double logscale; /* scale in logarithmic space */
1997 int exfrac = 1; /* decade spacing */
1998 int mid = -1; /* row in yloglab for major grid */
1999 double mspac; /* smallest major grid spacing (pixels) */
2000 int flab; /* first value in yloglab to use */
2001 double value, tmp, pre_value;
2002 double X0, X1, Y0;
2003 char graph_label[100];
2005 nex = log10(im->maxval / im->minval);
2006 logscale = im->ysize / nex;
2008 /* major spacing for data with high dynamic range */
2009 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2010 if (exfrac == 1)
2011 exfrac = 3;
2012 else
2013 exfrac += 3;
2014 }
2016 /* major spacing for less dynamic data */
2017 do {
2018 /* search best row in yloglab */
2019 mid++;
2020 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2021 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2022 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2023 && yloglab[mid][0] > 0);
2024 if (mid)
2025 mid--;
2027 /* find first value in yloglab */
2028 for (flab = 0;
2029 yloglab[mid][flab] < 10
2030 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2031 if (yloglab[mid][flab] == 10.0) {
2032 tmp += 1.0;
2033 flab = 0;
2034 }
2035 val_exp = tmp;
2036 if (val_exp % exfrac)
2037 val_exp += abs(-val_exp % exfrac);
2039 X0 = im->xorigin;
2040 X1 = im->xorigin + im->xsize;
2042 /* draw grid */
2043 pre_value = DNAN;
2044 while (1) {
2046 value = yloglab[mid][flab] * pow(10.0, val_exp);
2047 if (AlmostEqual2sComplement(value, pre_value, 4))
2048 break; /* it seems we are not converging */
2050 pre_value = value;
2052 Y0 = ytr(im, value);
2053 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2054 break;
2056 /* major grid line */
2057 gfx_new_dashed_line(im->canvas,
2058 X0 - 2, Y0,
2059 X1 + 2, Y0,
2060 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2061 im->grid_dash_on, im->grid_dash_off);
2063 /* label */
2064 if (im->extra_flags & FORCE_UNITS_SI) {
2065 int scale;
2066 double pvalue;
2067 char symbol;
2069 scale = floor(val_exp / 3.0);
2070 if (value >= 1.0)
2071 pvalue = pow(10.0, val_exp % 3);
2072 else
2073 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2074 pvalue *= yloglab[mid][flab];
2076 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2077 ((scale + si_symbcenter) >= 0))
2078 symbol = si_symbol[scale + si_symbcenter];
2079 else
2080 symbol = '?';
2082 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2083 } else
2084 sprintf(graph_label, "%3.0e", value);
2085 gfx_new_text(im->canvas,
2086 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2087 im->graph_col[GRC_FONT],
2088 im->text_prop[TEXT_PROP_AXIS].font,
2089 im->text_prop[TEXT_PROP_AXIS].size,
2090 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
2091 graph_label);
2093 /* minor grid */
2094 if (mid < 4 && exfrac == 1) {
2095 /* find first and last minor line behind current major line
2096 * i is the first line and j tha last */
2097 if (flab == 0) {
2098 min_exp = val_exp - 1;
2099 for (i = 1; yloglab[mid][i] < 10.0; i++);
2100 i = yloglab[mid][i - 1] + 1;
2101 j = 10;
2102 } else {
2103 min_exp = val_exp;
2104 i = yloglab[mid][flab - 1] + 1;
2105 j = yloglab[mid][flab];
2106 }
2108 /* draw minor lines below current major line */
2109 for (; i < j; i++) {
2111 value = i * pow(10.0, min_exp);
2112 if (value < im->minval)
2113 continue;
2115 Y0 = ytr(im, value);
2116 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2117 break;
2119 /* draw lines */
2120 gfx_new_dashed_line(im->canvas,
2121 X0 - 1, Y0,
2122 X1 + 1, Y0,
2123 GRIDWIDTH, im->graph_col[GRC_GRID],
2124 im->grid_dash_on, im->grid_dash_off);
2125 }
2126 } else if (exfrac > 1) {
2127 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2128 value = pow(10.0, i);
2129 if (value < im->minval)
2130 continue;
2132 Y0 = ytr(im, value);
2133 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2134 break;
2136 /* draw lines */
2137 gfx_new_dashed_line(im->canvas,
2138 X0 - 1, Y0,
2139 X1 + 1, Y0,
2140 GRIDWIDTH, im->graph_col[GRC_GRID],
2141 im->grid_dash_on, im->grid_dash_off);
2142 }
2143 }
2145 /* next decade */
2146 if (yloglab[mid][++flab] == 10.0) {
2147 flab = 0;
2148 val_exp += exfrac;
2149 }
2150 }
2152 /* draw minor lines after highest major line */
2153 if (mid < 4 && exfrac == 1) {
2154 /* find first and last minor line below current major line
2155 * i is the first line and j tha last */
2156 if (flab == 0) {
2157 min_exp = val_exp - 1;
2158 for (i = 1; yloglab[mid][i] < 10.0; i++);
2159 i = yloglab[mid][i - 1] + 1;
2160 j = 10;
2161 } else {
2162 min_exp = val_exp;
2163 i = yloglab[mid][flab - 1] + 1;
2164 j = yloglab[mid][flab];
2165 }
2167 /* draw minor lines below current major line */
2168 for (; i < j; i++) {
2170 value = i * pow(10.0, min_exp);
2171 if (value < im->minval)
2172 continue;
2174 Y0 = ytr(im, value);
2175 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2176 break;
2178 /* draw lines */
2179 gfx_new_dashed_line(im->canvas,
2180 X0 - 1, Y0,
2181 X1 + 1, Y0,
2182 GRIDWIDTH, im->graph_col[GRC_GRID],
2183 im->grid_dash_on, im->grid_dash_off);
2184 }
2185 }
2186 /* fancy minor gridlines */
2187 else if (exfrac > 1) {
2188 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2189 value = pow(10.0, i);
2190 if (value < im->minval)
2191 continue;
2193 Y0 = ytr(im, value);
2194 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2195 break;
2197 /* draw lines */
2198 gfx_new_dashed_line(im->canvas,
2199 X0 - 1, Y0,
2200 X1 + 1, Y0,
2201 GRIDWIDTH, im->graph_col[GRC_GRID],
2202 im->grid_dash_on, im->grid_dash_off);
2203 }
2204 }
2206 return 1;
2207 }
2210 void vertical_grid(
2211 image_desc_t *im)
2212 {
2213 int xlab_sel; /* which sort of label and grid ? */
2214 time_t ti, tilab, timajor;
2215 long factor;
2216 char graph_label[100];
2217 double X0, Y0, Y1; /* points for filled graph and more */
2218 struct tm tm;
2220 /* the type of time grid is determined by finding
2221 the number of seconds per pixel in the graph */
2224 if (im->xlab_user.minsec == -1) {
2225 factor = (im->end - im->start) / im->xsize;
2226 xlab_sel = 0;
2227 while (xlab[xlab_sel + 1].minsec != -1
2228 && xlab[xlab_sel + 1].minsec <= factor) {
2229 xlab_sel++;
2230 } /* pick the last one */
2231 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2232 && xlab[xlab_sel].length > (im->end - im->start)) {
2233 xlab_sel--;
2234 } /* go back to the smallest size */
2235 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2236 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2237 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2238 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2239 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2240 im->xlab_user.labst = xlab[xlab_sel].labst;
2241 im->xlab_user.precis = xlab[xlab_sel].precis;
2242 im->xlab_user.stst = xlab[xlab_sel].stst;
2243 }
2245 /* y coords are the same for every line ... */
2246 Y0 = im->yorigin;
2247 Y1 = im->yorigin - im->ysize;
2250 /* paint the minor grid */
2251 if (!(im->extra_flags & NOMINOR)) {
2252 for (ti = find_first_time(im->start,
2253 im->xlab_user.gridtm,
2254 im->xlab_user.gridst),
2255 timajor = find_first_time(im->start,
2256 im->xlab_user.mgridtm,
2257 im->xlab_user.mgridst);
2258 ti < im->end;
2259 ti =
2260 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2261 ) {
2262 /* are we inside the graph ? */
2263 if (ti < im->start || ti > im->end)
2264 continue;
2265 while (timajor < ti) {
2266 timajor = find_next_time(timajor,
2267 im->xlab_user.mgridtm,
2268 im->xlab_user.mgridst);
2269 }
2270 if (ti == timajor)
2271 continue; /* skip as falls on major grid line */
2272 X0 = xtr(im, ti);
2273 gfx_new_dashed_line(im->canvas, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2274 im->graph_col[GRC_GRID],
2275 im->grid_dash_on, im->grid_dash_off);
2277 }
2278 }
2280 /* paint the major grid */
2281 for (ti = find_first_time(im->start,
2282 im->xlab_user.mgridtm,
2283 im->xlab_user.mgridst);
2284 ti < im->end;
2285 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2286 ) {
2287 /* are we inside the graph ? */
2288 if (ti < im->start || ti > im->end)
2289 continue;
2290 X0 = xtr(im, ti);
2291 gfx_new_dashed_line(im->canvas, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2292 im->graph_col[GRC_MGRID],
2293 im->grid_dash_on, im->grid_dash_off);
2295 }
2296 /* paint the labels below the graph */
2297 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2298 im->xlab_user.labtm,
2299 im->xlab_user.labst);
2300 ti <= im->end - im->xlab_user.precis / 2;
2301 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2302 ) {
2303 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2304 /* are we inside the graph ? */
2305 if (tilab < im->start || tilab > im->end)
2306 continue;
2308 #if HAVE_STRFTIME
2309 localtime_r(&tilab, &tm);
2310 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2311 #else
2312 # error "your libc has no strftime I guess we'll abort the exercise here."
2313 #endif
2314 gfx_new_text(im->canvas,
2315 xtr(im, tilab),
2316 Y0 + im->text_prop[TEXT_PROP_AXIS].size * 1.4 + 5,
2317 im->graph_col[GRC_FONT],
2318 im->text_prop[TEXT_PROP_AXIS].font,
2319 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2320 GFX_H_CENTER, GFX_V_BOTTOM, graph_label);
2322 }
2324 }
2327 void axis_paint(
2328 image_desc_t *im)
2329 {
2330 /* draw x and y axis */
2331 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2332 im->xorigin+im->xsize,im->yorigin-im->ysize,
2333 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2335 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2336 im->xorigin+im->xsize,im->yorigin-im->ysize,
2337 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2339 gfx_new_line(im->canvas, im->xorigin - 4, im->yorigin,
2340 im->xorigin + im->xsize + 4, im->yorigin,
2341 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2343 gfx_new_line(im->canvas, im->xorigin, im->yorigin + 4,
2344 im->xorigin, im->yorigin - im->ysize - 4,
2345 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2348 /* arrow for X and Y axis direction */
2349 gfx_new_area(im->canvas, 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 */
2350 im->graph_col[GRC_ARROW]);
2352 gfx_new_area(im->canvas, 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 */
2353 im->graph_col[GRC_ARROW]);
2355 }
2357 void grid_paint(
2358 image_desc_t *im)
2359 {
2360 long i;
2361 int res = 0;
2362 double X0, Y0; /* points for filled graph and more */
2363 gfx_node_t *node;
2365 /* draw 3d border */
2366 node = gfx_new_area(im->canvas, 0, im->yimg,
2367 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2368 gfx_add_point(node, im->ximg - 2, 2);
2369 gfx_add_point(node, im->ximg, 0);
2370 gfx_add_point(node, 0, 0);
2371 /* gfx_add_point( node , 0,im->yimg ); */
2373 node = gfx_new_area(im->canvas, 2, im->yimg - 2,
2374 im->ximg - 2, im->yimg - 2,
2375 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2376 gfx_add_point(node, im->ximg, 0);
2377 gfx_add_point(node, im->ximg, im->yimg);
2378 gfx_add_point(node, 0, im->yimg);
2379 /* gfx_add_point( node , 0,im->yimg ); */
2382 if (im->draw_x_grid == 1)
2383 vertical_grid(im);
2385 if (im->draw_y_grid == 1) {
2386 if (im->logarithmic) {
2387 res = horizontal_log_grid(im);
2388 } else {
2389 res = draw_horizontal_grid(im);
2390 }
2392 /* dont draw horizontal grid if there is no min and max val */
2393 if (!res) {
2394 char *nodata = "No Data found";
2396 gfx_new_text(im->canvas, im->ximg / 2,
2397 (2 * im->yorigin - im->ysize) / 2,
2398 im->graph_col[GRC_FONT],
2399 im->text_prop[TEXT_PROP_AXIS].font,
2400 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2401 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2402 }
2403 }
2405 /* yaxis unit description */
2406 gfx_new_text(im->canvas,
2407 10, (im->yorigin - im->ysize / 2),
2408 im->graph_col[GRC_FONT],
2409 im->text_prop[TEXT_PROP_UNIT].font,
2410 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2411 RRDGRAPH_YLEGEND_ANGLE,
2412 GFX_H_LEFT, GFX_V_CENTER, im->ylegend);
2414 /* graph title */
2415 gfx_new_text(im->canvas,
2416 im->ximg / 2, im->text_prop[TEXT_PROP_TITLE].size * 1.3 + 4,
2417 im->graph_col[GRC_FONT],
2418 im->text_prop[TEXT_PROP_TITLE].font,
2419 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2420 GFX_H_CENTER, GFX_V_CENTER, im->title);
2421 /* rrdtool 'logo' */
2422 gfx_new_text(im->canvas,
2423 im->ximg - 7, 7,
2424 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2425 im->text_prop[TEXT_PROP_AXIS].font,
2426 5.5, im->tabwidth, 270,
2427 GFX_H_RIGHT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2429 /* graph watermark */
2430 if (im->watermark[0] != '\0') {
2431 gfx_new_text(im->canvas,
2432 im->ximg / 2, im->yimg - 6,
2433 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2434 im->text_prop[TEXT_PROP_AXIS].font,
2435 5.5, im->tabwidth, 0,
2436 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2437 }
2439 /* graph labels */
2440 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2441 for (i = 0; i < im->gdes_c; i++) {
2442 if (im->gdes[i].legend[0] == '\0')
2443 continue;
2445 /* im->gdes[i].leg_y is the bottom of the legend */
2446 X0 = im->gdes[i].leg_x;
2447 Y0 = im->gdes[i].leg_y;
2448 gfx_new_text(im->canvas, X0, Y0,
2449 im->graph_col[GRC_FONT],
2450 im->text_prop[TEXT_PROP_LEGEND].font,
2451 im->text_prop[TEXT_PROP_LEGEND].size,
2452 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2453 im->gdes[i].legend);
2454 /* The legend for GRAPH items starts with "M " to have
2455 enough space for the box */
2456 if (im->gdes[i].gf != GF_PRINT &&
2457 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2458 int boxH, boxV;
2460 boxH = gfx_get_text_width(im->canvas, 0,
2461 im->text_prop[TEXT_PROP_LEGEND].
2462 font,
2463 im->text_prop[TEXT_PROP_LEGEND].
2464 size, im->tabwidth, "o", 0) * 1.2;
2465 boxV = boxH * 1.1;
2467 /* make sure transparent colors show up the same way as in the graph */
2468 node = gfx_new_area(im->canvas,
2469 X0, Y0 - boxV,
2470 X0, Y0,
2471 X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2472 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2474 node = gfx_new_area(im->canvas,
2475 X0, Y0 - boxV,
2476 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2477 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2478 node = gfx_new_line(im->canvas,
2479 X0, Y0 - boxV,
2480 X0, Y0, 1.0, im->graph_col[GRC_FRAME]);
2481 gfx_add_point(node, X0 + boxH, Y0);
2482 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2483 gfx_close_path(node);
2484 }
2485 }
2486 }
2487 }
2490 /*****************************************************
2491 * lazy check make sure we rely need to create this graph
2492 *****************************************************/
2494 int lazy_check(
2495 image_desc_t *im)
2496 {
2497 FILE *fd = NULL;
2498 int size = 1;
2499 struct stat imgstat;
2501 if (im->lazy == 0)
2502 return 0; /* no lazy option */
2503 if (stat(im->graphfile, &imgstat) != 0)
2504 return 0; /* can't stat */
2505 /* one pixel in the existing graph is more then what we would
2506 change here ... */
2507 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2508 return 0;
2509 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2510 return 0; /* the file does not exist */
2511 switch (im->canvas->imgformat) {
2512 case IF_PNG:
2513 size = PngSize(fd, &(im->ximg), &(im->yimg));
2514 break;
2515 default:
2516 size = 1;
2517 }
2518 fclose(fd);
2519 return size;
2520 }
2522 #ifdef WITH_PIECHART
2523 void pie_part(
2524 image_desc_t *im,
2525 gfx_color_t color,
2526 double PieCenterX,
2527 double PieCenterY,
2528 double Radius,
2529 double startangle,
2530 double endangle)
2531 {
2532 gfx_node_t *node;
2533 double angle;
2534 double step = M_PI / 50; /* Number of iterations for the circle;
2535 ** 10 is definitely too low, more than
2536 ** 50 seems to be overkill
2537 */
2539 /* Strange but true: we have to work clockwise or else
2540 ** anti aliasing nor transparency don't work.
2541 **
2542 ** This test is here to make sure we do it right, also
2543 ** this makes the for...next loop more easy to implement.
2544 ** The return will occur if the user enters a negative number
2545 ** (which shouldn't be done according to the specs) or if the
2546 ** programmers do something wrong (which, as we all know, never
2547 ** happens anyway :)
2548 */
2549 if (endangle < startangle)
2550 return;
2552 /* Hidden feature: Radius decreases each full circle */
2553 angle = startangle;
2554 while (angle >= 2 * M_PI) {
2555 angle -= 2 * M_PI;
2556 Radius *= 0.8;
2557 }
2559 node = gfx_new_area(im->canvas,
2560 PieCenterX + sin(startangle) * Radius,
2561 PieCenterY - cos(startangle) * Radius,
2562 PieCenterX,
2563 PieCenterY,
2564 PieCenterX + sin(endangle) * Radius,
2565 PieCenterY - cos(endangle) * Radius, color);
2566 for (angle = endangle; angle - startangle >= step; angle -= step) {
2567 gfx_add_point(node,
2568 PieCenterX + sin(angle) * Radius,
2569 PieCenterY - cos(angle) * Radius);
2570 }
2571 }
2573 #endif
2575 int graph_size_location(
2576 image_desc_t *im,
2577 int elements
2578 #ifdef WITH_PIECHART
2579 ,
2580 int piechart
2581 #endif
2582 )
2583 {
2584 /* The actual size of the image to draw is determined from
2585 ** several sources. The size given on the command line is
2586 ** the graph area but we need more as we have to draw labels
2587 ** and other things outside the graph area
2588 */
2590 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2591 #ifdef WITH_PIECHART
2592 Xpie = 0, Ypie = 0,
2593 #endif
2594 Yxlabel = 0,
2595 #if 0
2596 Xlegend = 0, Ylegend = 0,
2597 #endif
2598 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->canvas, 0,
2648 im->text_prop[TEXT_PROP_AXIS].font,
2649 im->text_prop[TEXT_PROP_AXIS].size,
2650 im->tabwidth,
2651 "0", 0) * im->unitslength;
2652 }
2653 }
2655 if (im->extra_flags & FULL_SIZE_MODE) {
2656 /* The actual size of the image to draw has been determined by the user.
2657 ** The graph area is the space remaining after accounting for the legend,
2658 ** the watermark, the pie chart, the axis labels, and the title.
2659 */
2660 im->xorigin =0;
2661 im->ximg = im->xsize;
2662 im->yimg = im->ysize;
2663 im->yorigin = im->ysize;
2664 Xmain=im->ximg;
2665 Ymain=im->yimg;
2667 im->yorigin += Ytitle;
2669 #ifdef WITH_PIECHART
2670 if (piechart) {
2671 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2672 Xpie=im->piesize;
2673 Ypie=im->piesize;
2674 }
2675 #endif
2677 /* Now calculate the total size. Insert some spacing where
2678 desired. im->xorigin and im->yorigin need to correspond
2679 with the lower left corner of the main graph area or, if
2680 this one is not set, the imaginary box surrounding the
2681 pie chart area. */
2683 /* Initial size calculation for the main graph area */
2684 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2685 if (Xmain) Xmain -= Xspacing; /* put space between main graph area and right edge */
2687 #ifdef WITH_PIECHART
2688 Xmain -= Xpie; /* remove pie width from main graph area */
2689 if (Xpie) Xmain -= Xspacing; /* put space between pie and main graph area */
2690 #endif
2692 im->xorigin = Xspacing + Xylabel;
2694 /* the length of the title should not influence with width of the graph
2695 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2697 if (Xvertical) { /* unit description */
2698 Xmain -= Xvertical;
2699 im->xorigin += Xvertical;
2700 }
2701 im->xsize = Xmain;
2702 xtr(im,0);
2704 /* The vertical size of the image is known in advance. The main graph area
2705 ** (Ymain) and im->yorigin must be set according to the space requirements
2706 ** of the legend and the axis labels.
2707 */
2709 /* Determine where to place the legends onto the image.
2710 ** Set Ymain and adjust im->yorigin to match the space requirements.
2711 */
2712 if (leg_place(im,&Ymain)==-1)
2713 return -1;
2715 #ifdef WITH_PIECHART
2716 /* if (im->yimg < Ypie) im->yimg = Ypie; * not sure what do about this */
2717 #endif
2719 /* remove title space *or* some padding above the graph from the main graph area */
2720 if (Ytitle) {
2721 Ymain -= Ytitle;
2722 } else {
2723 Ymain -= 1.5*Yspacing;
2724 }
2726 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2727 if (im->watermark[0] != '\0') {
2728 Ymain -= Ywatermark;
2729 }
2731 im->ysize = Ymain;
2733 } else /* dimension options -width and -height refer to the dimensions of the main graph area */
2734 {
2735 /* The actual size of the image to draw is determined from
2736 ** several sources. The size given on the command line is
2737 ** the graph area but we need more as we have to draw labels
2738 ** and other things outside the graph area.
2739 */
2741 if (im->ylegend[0] != '\0' ) {
2742 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2743 }
2746 if (im->title[0] != '\0') {
2747 /* The title is placed "inbetween" two text lines so it
2748 ** automatically has some vertical spacing. The horizontal
2749 ** spacing is added here, on each side.
2750 */
2751 /* don't care for the with of the title
2752 Xtitle = gfx_get_text_width(im->canvas, 0,
2753 im->text_prop[TEXT_PROP_TITLE].font,
2754 im->text_prop[TEXT_PROP_TITLE].size,
2755 im->tabwidth,
2756 im->title, 0) + 2*Xspacing; */
2757 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2758 }
2760 if (elements) {
2761 Xmain=im->xsize;
2762 Ymain=im->ysize;
2763 }
2765 #ifdef WITH_PIECHART
2766 if (piechart) {
2767 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2768 Xpie=im->piesize;
2769 Ypie=im->piesize;
2770 }
2771 #endif
2773 /* Now calculate the total size. Insert some spacing where
2774 desired. im->xorigin and im->yorigin need to correspond
2775 with the lower left corner of the main graph area or, if
2776 this one is not set, the imaginary box surrounding the
2777 pie chart area. */
2779 /* The legend width cannot yet be determined, as a result we
2780 ** have problems adjusting the image to it. For now, we just
2781 ** forget about it at all; the legend will have to fit in the
2782 ** size already allocated.
2783 */
2784 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2786 #ifdef WITH_PIECHART
2787 im->ximg += Xpie;
2788 #endif
2790 if (Xmain) im->ximg += Xspacing;
2791 #ifdef WITH_PIECHART
2792 if (Xpie) im->ximg += Xspacing;
2793 #endif
2795 im->xorigin = Xspacing + Xylabel;
2797 /* the length of the title should not influence with width of the graph
2798 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2800 if (Xvertical) { /* unit description */
2801 im->ximg += Xvertical;
2802 im->xorigin += Xvertical;
2803 }
2804 xtr(im,0);
2806 /* The vertical size is interesting... we need to compare
2807 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2808 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2809 ** in order to start even thinking about Ylegend or Ywatermark.
2810 **
2811 ** Do it in three portions: First calculate the inner part,
2812 ** then do the legend, then adjust the total height of the img,
2813 ** adding space for a watermark if one exists;
2814 */
2816 /* reserve space for main and/or pie */
2818 im->yimg = Ymain + Yxlabel;
2820 #ifdef WITH_PIECHART
2821 if (im->yimg < Ypie) im->yimg = Ypie;
2822 #endif
2824 im->yorigin = im->yimg - Yxlabel;
2826 /* reserve space for the title *or* some padding above the graph */
2827 if (Ytitle) {
2828 im->yimg += Ytitle;
2829 im->yorigin += Ytitle;
2830 } else {
2831 im->yimg += 1.5*Yspacing;
2832 im->yorigin += 1.5*Yspacing;
2833 }
2834 /* reserve space for padding below the graph */
2835 im->yimg += Yspacing;
2837 /* Determine where to place the legends onto the image.
2838 ** Adjust im->yimg to match the space requirements.
2839 */
2840 if(leg_place(im,0)==-1)
2841 return -1;
2843 if (im->watermark[0] != '\0') {
2844 im->yimg += Ywatermark;
2845 }
2846 }
2848 #if 0
2849 if (Xlegend > im->ximg) {
2850 im->ximg = Xlegend;
2851 /* reposition Pie */
2852 }
2853 #endif
2855 #ifdef WITH_PIECHART
2856 /* The pie is placed in the upper right hand corner,
2857 ** just below the title (if any) and with sufficient
2858 ** padding.
2859 */
2860 if (elements) {
2861 im->pie_x = im->ximg - Xspacing - Xpie/2;
2862 im->pie_y = im->yorigin-Ymain+Ypie/2;
2863 } else {
2864 im->pie_x = im->ximg/2;
2865 im->pie_y = im->yorigin-Ypie/2;
2866 }
2867 #endif
2869 ytr(im,DNAN);
2870 return 0;
2871 }
2873 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2874 /* yes we are loosing precision by doing tos with floats instead of doubles
2875 but it seems more stable this way. */
2878 /* draw that picture thing ... */
2879 int graph_paint(
2880 image_desc_t *im,
2881 char ***calcpr)
2882 {
2883 int i, ii;
2884 int lazy = lazy_check(im);
2886 #ifdef WITH_PIECHART
2887 int piechart = 0;
2888 double PieStart = 0.0;
2889 #endif
2890 FILE *fo;
2891 gfx_node_t *node;
2893 double areazero = 0.0;
2894 graph_desc_t *lastgdes = NULL;
2896 /* if we are lazy and there is nothing to PRINT ... quit now */
2897 if (lazy && im->prt_c == 0)
2898 return 0;
2900 /* pull the data from the rrd files ... */
2902 if (data_fetch(im) == -1)
2903 return -1;
2905 /* evaluate VDEF and CDEF operations ... */
2906 if (data_calc(im) == -1)
2907 return -1;
2909 #ifdef WITH_PIECHART
2910 /* check if we need to draw a piechart */
2911 for (i = 0; i < im->gdes_c; i++) {
2912 if (im->gdes[i].gf == GF_PART) {
2913 piechart = 1;
2914 break;
2915 }
2916 }
2917 #endif
2919 /* calculate and PRINT and GPRINT definitions. We have to do it at
2920 * this point because it will affect the length of the legends
2921 * if there are no graph elements we stop here ...
2922 * if we are lazy, try to quit ...
2923 */
2924 i = print_calc(im, calcpr);
2925 if (i < 0)
2926 return -1;
2927 if (((i == 0)
2928 #ifdef WITH_PIECHART
2929 && (piechart == 0)
2930 #endif
2931 ) || lazy)
2932 return 0;
2934 #ifdef WITH_PIECHART
2935 /* If there's only the pie chart to draw, signal this */
2936 if (i == 0)
2937 piechart = 2;
2938 #endif
2940 /**************************************************************
2941 *** Calculating sizes and locations became a bit confusing ***
2942 *** so I moved this into a separate function. ***
2943 **************************************************************/
2944 if (graph_size_location(im, i
2945 #ifdef WITH_PIECHART
2946 , piechart
2947 #endif
2948 ) == -1)
2949 return -1;
2951 /* get actual drawing data and find min and max values */
2952 if (data_proc(im) == -1)
2953 return -1;
2955 if (!im->logarithmic) {
2956 si_unit(im);
2957 }
2958 /* identify si magnitude Kilo, Mega Giga ? */
2959 if (!im->rigid && !im->logarithmic)
2960 expand_range(im); /* make sure the upper and lower limit are
2961 sensible values */
2963 if (!calc_horizontal_grid(im))
2964 return -1;
2966 if (im->gridfit)
2967 apply_gridfit(im);
2969 /* the actual graph is created by going through the individual
2970 graph elements and then drawing them */
2972 node = gfx_new_area(im->canvas,
2973 0, 0,
2974 0, im->yimg,
2975 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2977 gfx_add_point(node, im->ximg, 0);
2979 #ifdef WITH_PIECHART
2980 if (piechart != 2) {
2981 #endif
2982 node = gfx_new_area(im->canvas,
2983 im->xorigin, im->yorigin,
2984 im->xorigin + im->xsize, im->yorigin,
2985 im->xorigin + im->xsize, im->yorigin - im->ysize,
2986 im->graph_col[GRC_CANVAS]);
2988 gfx_add_point(node, im->xorigin, im->yorigin - im->ysize);
2990 if (im->minval > 0.0)
2991 areazero = im->minval;
2992 if (im->maxval < 0.0)
2993 areazero = im->maxval;
2994 #ifdef WITH_PIECHART
2995 }
2996 #endif
2998 #ifdef WITH_PIECHART
2999 if (piechart) {
3000 pie_part(im, im->graph_col[GRC_CANVAS], im->pie_x, im->pie_y,
3001 im->piesize * 0.5, 0, 2 * M_PI);
3002 }
3003 #endif
3005 for (i = 0; i < im->gdes_c; i++) {
3006 switch (im->gdes[i].gf) {
3007 case GF_CDEF:
3008 case GF_VDEF:
3009 case GF_DEF:
3010 case GF_PRINT:
3011 case GF_GPRINT:
3012 case GF_COMMENT:
3013 case GF_HRULE:
3014 case GF_VRULE:
3015 case GF_XPORT:
3016 case GF_SHIFT:
3017 break;
3018 case GF_TICK:
3019 for (ii = 0; ii < im->xsize; ii++) {
3020 if (!isnan(im->gdes[i].p_data[ii]) &&
3021 im->gdes[i].p_data[ii] != 0.0) {
3022 if (im->gdes[i].yrule > 0) {
3023 gfx_new_line(im->canvas,
3024 im->xorigin + ii, im->yorigin,
3025 im->xorigin + ii,
3026 im->yorigin -
3027 im->gdes[i].yrule * im->ysize, 1.0,
3028 im->gdes[i].col);
3029 } else if (im->gdes[i].yrule < 0) {
3030 gfx_new_line(im->canvas,
3031 im->xorigin + ii,
3032 im->yorigin - im->ysize,
3033 im->xorigin + ii,
3034 im->yorigin - (1 -
3035 im->gdes[i].yrule) *
3036 im->ysize, 1.0, im->gdes[i].col);
3038 }
3039 }
3040 }
3041 break;
3042 case GF_LINE:
3043 case GF_AREA:
3044 /* fix data points at oo and -oo */
3045 for (ii = 0; ii < im->xsize; ii++) {
3046 if (isinf(im->gdes[i].p_data[ii])) {
3047 if (im->gdes[i].p_data[ii] > 0) {
3048 im->gdes[i].p_data[ii] = im->maxval;
3049 } else {
3050 im->gdes[i].p_data[ii] = im->minval;
3051 }
3053 }
3054 } /* for */
3056 /* *******************************************************
3057 a ___. (a,t)
3058 | | ___
3059 ____| | | |
3060 | |___|
3061 -------|--t-1--t--------------------------------
3063 if we know the value at time t was a then
3064 we draw a square from t-1 to t with the value a.
3066 ********************************************************* */
3067 if (im->gdes[i].col != 0x0) {
3068 /* GF_LINE and friend */
3069 if (im->gdes[i].gf == GF_LINE) {
3070 double last_y = 0.0;
3072 node = NULL;
3073 for (ii = 1; ii < im->xsize; ii++) {
3074 if (isnan(im->gdes[i].p_data[ii])
3075 || (im->slopemode == 1
3076 && isnan(im->gdes[i].p_data[ii - 1]))) {
3077 node = NULL;
3078 continue;
3079 }
3080 if (node == NULL) {
3081 last_y = ytr(im, im->gdes[i].p_data[ii]);
3082 if (im->slopemode == 0) {
3083 node = gfx_new_line(im->canvas,
3084 ii - 1 + im->xorigin,
3085 last_y, ii + im->xorigin,
3086 last_y,
3087 im->gdes[i].linewidth,
3088 im->gdes[i].col);
3089 } else {
3090 node = gfx_new_line(im->canvas,
3091 ii - 1 + im->xorigin,
3092 ytr(im,
3093 im->gdes[i].
3094 p_data[ii - 1]),
3095 ii + im->xorigin, last_y,
3096 im->gdes[i].linewidth,
3097 im->gdes[i].col);
3098 }
3099 } else {
3100 double new_y = ytr(im, im->gdes[i].p_data[ii]);
3102 if (im->slopemode == 0
3103 && !AlmostEqual2sComplement(new_y, last_y,
3104 4)) {
3105 gfx_add_point(node, ii - 1 + im->xorigin,
3106 new_y);
3107 };
3108 last_y = new_y;
3109 gfx_add_point(node, ii + im->xorigin, new_y);
3110 };
3112 }
3113 } else {
3114 int idxI = -1;
3115 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3116 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3117 double *backY = malloc(sizeof(double) * im->xsize * 2);
3118 double *backX = malloc(sizeof(double) * im->xsize * 2);
3119 int drawem = 0;
3121 for (ii = 0; ii <= im->xsize; ii++) {
3122 double ybase, ytop;
3124 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3125 int cntI = 1;
3126 int lastI = 0;
3128 while (cntI < idxI
3129 && AlmostEqual2sComplement(foreY[lastI],
3130 foreY[cntI], 4)
3131 && AlmostEqual2sComplement(foreY[lastI],
3132 foreY[cntI + 1],
3133 4)) {
3134 cntI++;
3135 }
3136 node = gfx_new_area(im->canvas,
3137 backX[0], backY[0],
3138 foreX[0], foreY[0],
3139 foreX[cntI], foreY[cntI],
3140 im->gdes[i].col);
3141 while (cntI < idxI) {
3142 lastI = cntI;
3143 cntI++;
3144 while (cntI < idxI
3145 &&
3146 AlmostEqual2sComplement(foreY[lastI],
3147 foreY[cntI], 4)
3148 &&
3149 AlmostEqual2sComplement(foreY[lastI],
3150 foreY[cntI +
3151 1], 4)) {
3152 cntI++;
3153 }
3154 gfx_add_point(node, foreX[cntI], foreY[cntI]);
3155 }
3156 gfx_add_point(node, backX[idxI], backY[idxI]);
3157 while (idxI > 1) {
3158 lastI = idxI;
3159 idxI--;
3160 while (idxI > 1
3161 &&
3162 AlmostEqual2sComplement(backY[lastI],
3163 backY[idxI], 4)
3164 &&
3165 AlmostEqual2sComplement(backY[lastI],
3166 backY[idxI -
3167 1], 4)) {
3168 idxI--;
3169 }
3170 gfx_add_point(node, backX[idxI], backY[idxI]);
3171 }
3172 idxI = -1;
3173 drawem = 0;
3174 }
3175 if (drawem != 0) {
3176 drawem = 0;
3177 idxI = -1;
3178 }
3179 if (ii == im->xsize)
3180 break;
3182 /* keep things simple for now, just draw these bars
3183 do not try to build a big and complex area */
3186 if (im->slopemode == 0 && ii == 0) {
3187 continue;
3188 }
3189 if (isnan(im->gdes[i].p_data[ii])) {
3190 drawem = 1;
3191 continue;
3192 }
3193 ytop = ytr(im, im->gdes[i].p_data[ii]);
3194 if (lastgdes && im->gdes[i].stack) {
3195 ybase = ytr(im, lastgdes->p_data[ii]);
3196 } else {
3197 ybase = ytr(im, areazero);
3198 }
3199 if (ybase == ytop) {
3200 drawem = 1;
3201 continue;
3202 }
3203 /* every area has to be wound clock-wise,
3204 so we have to make sur base remains base */
3205 if (ybase > ytop) {
3206 double extra = ytop;
3208 ytop = ybase;
3209 ybase = extra;
3210 }
3211 if (im->slopemode == 0) {
3212 backY[++idxI] = ybase - 0.2;
3213 backX[idxI] = ii + im->xorigin - 1;
3214 foreY[idxI] = ytop + 0.2;
3215 foreX[idxI] = ii + im->xorigin - 1;
3216 }
3217 backY[++idxI] = ybase - 0.2;
3218 backX[idxI] = ii + im->xorigin;
3219 foreY[idxI] = ytop + 0.2;
3220 foreX[idxI] = ii + im->xorigin;
3221 }
3222 /* close up any remaining area */
3223 free(foreY);
3224 free(foreX);
3225 free(backY);
3226 free(backX);
3227 } /* else GF_LINE */
3228 }
3229 /* if color != 0x0 */
3230 /* make sure we do not run into trouble when stacking on NaN */
3231 for (ii = 0; ii < im->xsize; ii++) {
3232 if (isnan(im->gdes[i].p_data[ii])) {
3233 if (lastgdes && (im->gdes[i].stack)) {
3234 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3235 } else {
3236 im->gdes[i].p_data[ii] = areazero;
3237 }
3238 }
3239 }
3240 lastgdes = &(im->gdes[i]);
3241 break;
3242 #ifdef WITH_PIECHART
3243 case GF_PART:
3244 if (isnan(im->gdes[i].yrule)) /* fetch variable */
3245 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
3247 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
3248 pie_part(im, im->gdes[i].col,
3249 im->pie_x, im->pie_y, im->piesize * 0.4,
3250 M_PI * 2.0 * PieStart / 100.0,
3251 M_PI * 2.0 * (PieStart + im->gdes[i].yrule) / 100.0);
3252 PieStart += im->gdes[i].yrule;
3253 }
3254 break;
3255 #endif
3256 case GF_STACK:
3257 rrd_set_error
3258 ("STACK should already be turned into LINE or AREA here");
3259 return -1;
3260 break;
3262 } /* switch */
3263 }
3264 #ifdef WITH_PIECHART
3265 if (piechart == 2) {
3266 im->draw_x_grid = 0;
3267 im->draw_y_grid = 0;
3268 }
3269 #endif
3272 /* grid_paint also does the text */
3273 if (!(im->extra_flags & ONLY_GRAPH))
3274 grid_paint(im);
3277 if (!(im->extra_flags & ONLY_GRAPH))
3278 axis_paint(im);
3280 /* the RULES are the last thing to paint ... */
3281 for (i = 0; i < im->gdes_c; i++) {
3283 switch (im->gdes[i].gf) {
3284 case GF_HRULE:
3285 if (im->gdes[i].yrule >= im->minval
3286 && im->gdes[i].yrule <= im->maxval)
3287 gfx_new_line(im->canvas,
3288 im->xorigin, ytr(im, im->gdes[i].yrule),
3289 im->xorigin + im->xsize, ytr(im,
3290 im->gdes[i].yrule),
3291 1.0, im->gdes[i].col);
3292 break;
3293 case GF_VRULE:
3294 if (im->gdes[i].xrule >= im->start
3295 && im->gdes[i].xrule <= im->end)
3296 gfx_new_line(im->canvas,
3297 xtr(im, im->gdes[i].xrule), im->yorigin,
3298 xtr(im, im->gdes[i].xrule),
3299 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3300 break;
3301 default:
3302 break;
3303 }
3304 }
3307 if (strcmp(im->graphfile, "-") == 0) {
3308 fo = im->graphhandle ? im->graphhandle : stdout;
3309 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3310 /* Change translation mode for stdout to BINARY */
3311 _setmode(_fileno(fo), O_BINARY);
3312 #endif
3313 } else {
3314 if ((fo = fopen(im->graphfile, "wb")) == NULL) {
3315 rrd_set_error("Opening '%s' for write: %s", im->graphfile,
3316 rrd_strerror(errno));
3317 return (-1);
3318 }
3319 }
3320 gfx_render(im->canvas, im->ximg, im->yimg, 0x00000000, fo);
3321 if (strcmp(im->graphfile, "-") != 0)
3322 fclose(fo);
3323 return 0;
3324 }
3327 /*****************************************************
3328 * graph stuff
3329 *****************************************************/
3331 int gdes_alloc(
3332 image_desc_t *im)
3333 {
3335 im->gdes_c++;
3336 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3337 * sizeof(graph_desc_t))) ==
3338 NULL) {
3339 rrd_set_error("realloc graph_descs");
3340 return -1;
3341 }
3344 im->gdes[im->gdes_c - 1].step = im->step;
3345 im->gdes[im->gdes_c - 1].step_orig = im->step;
3346 im->gdes[im->gdes_c - 1].stack = 0;
3347 im->gdes[im->gdes_c - 1].linewidth = 0;
3348 im->gdes[im->gdes_c - 1].debug = 0;
3349 im->gdes[im->gdes_c - 1].start = im->start;
3350 im->gdes[im->gdes_c - 1].start_orig = im->start;
3351 im->gdes[im->gdes_c - 1].end = im->end;
3352 im->gdes[im->gdes_c - 1].end_orig = im->end;
3353 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3354 im->gdes[im->gdes_c - 1].data = NULL;
3355 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3356 im->gdes[im->gdes_c - 1].data_first = 0;
3357 im->gdes[im->gdes_c - 1].p_data = NULL;
3358 im->gdes[im->gdes_c - 1].rpnp = NULL;
3359 im->gdes[im->gdes_c - 1].shift = 0;
3360 im->gdes[im->gdes_c - 1].col = 0x0;
3361 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3362 im->gdes[im->gdes_c - 1].format[0] = '\0';
3363 im->gdes[im->gdes_c - 1].strftm = 0;
3364 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3365 im->gdes[im->gdes_c - 1].ds = -1;
3366 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3367 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3368 im->gdes[im->gdes_c - 1].p_data = NULL;
3369 im->gdes[im->gdes_c - 1].yrule = DNAN;
3370 im->gdes[im->gdes_c - 1].xrule = 0;
3371 return 0;
3372 }
3374 /* copies input untill the first unescaped colon is found
3375 or until input ends. backslashes have to be escaped as well */
3376 int scan_for_col(
3377 const char *const input,
3378 int len,
3379 char *const output)
3380 {
3381 int inp, outp = 0;
3383 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3384 if (input[inp] == '\\' &&
3385 input[inp + 1] != '\0' &&
3386 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3387 output[outp++] = input[++inp];
3388 } else {
3389 output[outp++] = input[inp];
3390 }
3391 }
3392 output[outp] = '\0';
3393 return inp;
3394 }
3396 /* Some surgery done on this function, it became ridiculously big.
3397 ** Things moved:
3398 ** - initializing now in rrd_graph_init()
3399 ** - options parsing now in rrd_graph_options()
3400 ** - script parsing now in rrd_graph_script()
3401 */
3402 int rrd_graph(
3403 int argc,
3404 char **argv,
3405 char ***prdata,
3406 int *xsize,
3407 int *ysize,
3408 FILE * stream,
3409 double *ymin,
3410 double *ymax)
3411 {
3412 image_desc_t im;
3414 rrd_graph_init(&im);
3415 im.graphhandle = stream;
3417 rrd_graph_options(argc, argv, &im);
3418 if (rrd_test_error()) {
3419 im_free(&im);
3420 return -1;
3421 }
3423 if (strlen(argv[optind]) >= MAXPATH) {
3424 rrd_set_error("filename (including path) too long");
3425 im_free(&im);
3426 return -1;
3427 }
3428 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3429 im.graphfile[MAXPATH - 1] = '\0';
3431 rrd_graph_script(argc, argv, &im, 1);
3432 if (rrd_test_error()) {
3433 im_free(&im);
3434 return -1;
3435 }
3437 /* Everything is now read and the actual work can start */
3439 (*prdata) = NULL;
3440 if (graph_paint(&im, prdata) == -1) {
3441 im_free(&im);
3442 return -1;
3443 }
3445 /* The image is generated and needs to be output.
3446 ** Also, if needed, print a line with information about the image.
3447 */
3449 *xsize = im.ximg;
3450 *ysize = im.yimg;
3451 *ymin = im.minval;
3452 *ymax = im.maxval;
3453 if (im.imginfo) {
3454 char *filename;
3456 if (!(*prdata)) {
3457 /* maybe prdata is not allocated yet ... lets do it now */
3458 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3459 rrd_set_error("malloc imginfo");
3460 return -1;
3461 };
3462 }
3463 if (((*prdata)[0] =
3464 malloc((strlen(im.imginfo) + 200 +
3465 strlen(im.graphfile)) * sizeof(char)))
3466 == NULL) {
3467 rrd_set_error("malloc imginfo");
3468 return -1;
3469 }
3470 filename = im.graphfile + strlen(im.graphfile);
3471 while (filename > im.graphfile) {
3472 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3473 break;
3474 filename--;
3475 }
3477 sprintf((*prdata)[0], im.imginfo, filename,
3478 (long) (im.canvas->zoom * im.ximg),
3479 (long) (im.canvas->zoom * im.yimg));
3480 }
3481 im_free(&im);
3482 return 0;
3483 }
3485 void rrd_graph_init(
3486 image_desc_t *im)
3487 {
3488 unsigned int i;
3490 #ifdef HAVE_TZSET
3491 tzset();
3492 #endif
3493 #ifdef HAVE_SETLOCALE
3494 setlocale(LC_TIME, "");
3495 #ifdef HAVE_MBSTOWCS
3496 setlocale(LC_CTYPE, "");
3497 #endif
3498 #endif
3499 im->yorigin = 0;
3500 im->xorigin = 0;
3501 im->minval = 0;
3502 im->xlab_user.minsec = -1;
3503 im->ximg = 0;
3504 im->yimg = 0;
3505 im->xsize = 400;
3506 im->ysize = 100;
3507 im->step = 0;
3508 im->ylegend[0] = '\0';
3509 im->title[0] = '\0';
3510 im->watermark[0] = '\0';
3511 im->minval = DNAN;
3512 im->maxval = DNAN;
3513 im->unitsexponent = 9999;
3514 im->unitslength = 6;
3515 im->forceleftspace = 0;
3516 im->symbol = ' ';
3517 im->viewfactor = 1.0;
3518 im->extra_flags = 0;
3519 im->rigid = 0;
3520 im->gridfit = 1;
3521 im->imginfo = NULL;
3522 im->lazy = 0;
3523 im->slopemode = 0;
3524 im->logarithmic = 0;
3525 im->ygridstep = DNAN;
3526 im->draw_x_grid = 1;
3527 im->draw_y_grid = 1;
3528 im->base = 1000;
3529 im->prt_c = 0;
3530 im->gdes_c = 0;
3531 im->gdes = NULL;
3532 im->canvas = gfx_new_canvas();
3533 im->grid_dash_on = 1;
3534 im->grid_dash_off = 1;
3535 im->tabwidth = 40.0;
3537 for (i = 0; i < DIM(graph_col); i++)
3538 im->graph_col[i] = graph_col[i];
3540 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3541 {
3542 char *windir;
3543 char rrd_win_default_font[1000];
3545 windir = getenv("windir");
3546 /* %windir% is something like D:\windows or C:\winnt */
3547 if (windir != NULL) {
3548 strncpy(rrd_win_default_font, windir, 500);
3549 rrd_win_default_font[500] = '\0';
3550 strcat(rrd_win_default_font, "\\fonts\\");
3551 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3552 for (i = 0; i < DIM(text_prop); i++) {
3553 strncpy(text_prop[i].font, rrd_win_default_font,
3554 sizeof(text_prop[i].font) - 1);
3555 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3556 }
3557 }
3558 }
3559 #endif
3560 {
3561 char *deffont;
3563 deffont = getenv("RRD_DEFAULT_FONT");
3564 if (deffont != NULL) {
3565 for (i = 0; i < DIM(text_prop); i++) {
3566 strncpy(text_prop[i].font, deffont,
3567 sizeof(text_prop[i].font) - 1);
3568 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3569 }
3570 }
3571 }
3572 for (i = 0; i < DIM(text_prop); i++) {
3573 im->text_prop[i].size = text_prop[i].size;
3574 strcpy(im->text_prop[i].font, text_prop[i].font);
3575 }
3576 }
3578 void rrd_graph_options(
3579 int argc,
3580 char *argv[],
3581 image_desc_t *im)
3582 {
3583 int stroff;
3584 char *parsetime_error = NULL;
3585 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3586 time_t start_tmp = 0, end_tmp = 0;
3587 long long_tmp;
3588 struct rrd_time_value start_tv, end_tv;
3589 gfx_color_t color;
3591 optind = 0;
3592 opterr = 0; /* initialize getopt */
3594 parsetime("end-24h", &start_tv);
3595 parsetime("now", &end_tv);
3597 /* defines for long options without a short equivalent. should be bytes,
3598 and may not collide with (the ASCII value of) short options */
3599 #define LONGOPT_UNITS_SI 255
3601 while (1) {
3602 static struct option long_options[] = {
3603 {"start", required_argument, 0, 's'},
3604 {"end", required_argument, 0, 'e'},
3605 {"x-grid", required_argument, 0, 'x'},
3606 {"y-grid", required_argument, 0, 'y'},
3607 {"vertical-label", required_argument, 0, 'v'},
3608 {"width", required_argument, 0, 'w'},
3609 {"height", required_argument, 0, 'h'},
3610 {"full-size-mode", no_argument, 0, 'D'},
3611 {"interlaced", no_argument, 0, 'i'},
3612 {"upper-limit", required_argument, 0, 'u'},
3613 {"lower-limit", required_argument, 0, 'l'},
3614 {"rigid", no_argument, 0, 'r'},
3615 {"base", required_argument, 0, 'b'},
3616 {"logarithmic", no_argument, 0, 'o'},
3617 {"color", required_argument, 0, 'c'},
3618 {"font", required_argument, 0, 'n'},
3619 {"title", required_argument, 0, 't'},
3620 {"imginfo", required_argument, 0, 'f'},
3621 {"imgformat", required_argument, 0, 'a'},
3622 {"lazy", no_argument, 0, 'z'},
3623 {"zoom", required_argument, 0, 'm'},
3624 {"no-legend", no_argument, 0, 'g'},
3625 {"force-rules-legend", no_argument, 0, 'F'},
3626 {"only-graph", no_argument, 0, 'j'},
3627 {"alt-y-grid", no_argument, 0, 'Y'},
3628 {"no-minor", no_argument, 0, 'I'},
3629 {"slope-mode", no_argument, 0, 'E'},
3630 {"alt-autoscale", no_argument, 0, 'A'},
3631 {"alt-autoscale-min", no_argument, 0, 'J'},
3632 {"alt-autoscale-max", no_argument, 0, 'M'},
3633 {"no-gridfit", no_argument, 0, 'N'},
3634 {"units-exponent", required_argument, 0, 'X'},
3635 {"units-length", required_argument, 0, 'L'},
3636 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3637 {"step", required_argument, 0, 'S'},
3638 {"tabwidth", required_argument, 0, 'T'},
3639 {"font-render-mode", required_argument, 0, 'R'},
3640 {"font-smoothing-threshold", required_argument, 0, 'B'},
3641 {"watermark", required_argument, 0, 'W'},
3642 {"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 */
3643 {0, 0, 0, 0}
3644 };
3645 int option_index = 0;
3646 int opt;
3647 int col_start, col_end;
3649 opt = getopt_long(argc, argv,
3650 "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:",
3651 long_options, &option_index);
3653 if (opt == EOF)
3654 break;
3656 switch (opt) {
3657 case 'I':
3658 im->extra_flags |= NOMINOR;
3659 break;
3660 case 'Y':
3661 im->extra_flags |= ALTYGRID;
3662 break;
3663 case 'A':
3664 im->extra_flags |= ALTAUTOSCALE;
3665 break;
3666 case 'J':
3667 im->extra_flags |= ALTAUTOSCALE_MIN;
3668 break;
3669 case 'M':
3670 im->extra_flags |= ALTAUTOSCALE_MAX;
3671 break;
3672 case 'j':
3673 im->extra_flags |= ONLY_GRAPH;
3674 break;
3675 case 'g':
3676 im->extra_flags |= NOLEGEND;
3677 break;
3678 case 'F':
3679 im->extra_flags |= FORCE_RULES_LEGEND;
3680 break;
3681 case LONGOPT_UNITS_SI:
3682 if (im->extra_flags & FORCE_UNITS) {
3683 rrd_set_error("--units can only be used once!");
3684 return;
3685 }
3686 if (strcmp(optarg, "si") == 0)
3687 im->extra_flags |= FORCE_UNITS_SI;
3688 else {
3689 rrd_set_error("invalid argument for --units: %s", optarg);
3690 return;
3691 }
3692 break;
3693 case 'X':
3694 im->unitsexponent = atoi(optarg);
3695 break;
3696 case 'L':
3697 im->unitslength = atoi(optarg);
3698 im->forceleftspace = 1;
3699 break;
3700 case 'T':
3701 im->tabwidth = atof(optarg);
3702 break;
3703 case 'S':
3704 im->step = atoi(optarg);
3705 break;
3706 case 'N':
3707 im->gridfit = 0;
3708 break;
3709 case 's':
3710 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3711 rrd_set_error("start time: %s", parsetime_error);
3712 return;
3713 }
3714 break;
3715 case 'e':
3716 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3717 rrd_set_error("end time: %s", parsetime_error);
3718 return;
3719 }
3720 break;
3721 case 'x':
3722 if (strcmp(optarg, "none") == 0) {
3723 im->draw_x_grid = 0;
3724 break;
3725 };
3727 if (sscanf(optarg,
3728 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3729 scan_gtm,
3730 &im->xlab_user.gridst,
3731 scan_mtm,
3732 &im->xlab_user.mgridst,
3733 scan_ltm,
3734 &im->xlab_user.labst,
3735 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3736 strncpy(im->xlab_form, optarg + stroff,
3737 sizeof(im->xlab_form) - 1);
3738 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3739 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3740 rrd_set_error("unknown keyword %s", scan_gtm);
3741 return;
3742 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3743 == -1) {
3744 rrd_set_error("unknown keyword %s", scan_mtm);
3745 return;
3746 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3747 -1) {
3748 rrd_set_error("unknown keyword %s", scan_ltm);
3749 return;
3750 }
3751 im->xlab_user.minsec = 1;
3752 im->xlab_user.stst = im->xlab_form;
3753 } else {
3754 rrd_set_error("invalid x-grid format");
3755 return;
3756 }
3757 break;
3758 case 'y':
3760 if (strcmp(optarg, "none") == 0) {
3761 im->draw_y_grid = 0;
3762 break;
3763 };
3765 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3766 if (im->ygridstep <= 0) {
3767 rrd_set_error("grid step must be > 0");
3768 return;
3769 } else if (im->ylabfact < 1) {
3770 rrd_set_error("label factor must be > 0");
3771 return;
3772 }
3773 } else {
3774 rrd_set_error("invalid y-grid format");
3775 return;
3776 }
3777 break;
3778 case 'v':
3779 strncpy(im->ylegend, optarg, 150);
3780 im->ylegend[150] = '\0';
3781 break;
3782 case 'u':
3783 im->maxval = atof(optarg);
3784 break;
3785 case 'l':
3786 im->minval = atof(optarg);
3787 break;
3788 case 'b':
3789 im->base = atol(optarg);
3790 if (im->base != 1024 && im->base != 1000) {
3791 rrd_set_error
3792 ("the only sensible value for base apart from 1000 is 1024");
3793 return;
3794 }
3795 break;
3796 case 'w':
3797 long_tmp = atol(optarg);
3798 if (long_tmp < 10) {
3799 rrd_set_error("width below 10 pixels");
3800 return;
3801 }
3802 im->xsize = long_tmp;
3803 break;
3804 case 'h':
3805 long_tmp = atol(optarg);
3806 if (long_tmp < 10) {
3807 rrd_set_error("height below 10 pixels");
3808 return;
3809 }
3810 im->ysize = long_tmp;
3811 break;
3812 case 'D':
3813 im->extra_flags |= FULL_SIZE_MODE;
3814 break;
3815 case 'i':
3816 im->canvas->interlaced = 1;
3817 break;
3818 case 'r':
3819 im->rigid = 1;
3820 break;
3821 case 'f':
3822 im->imginfo = optarg;
3823 break;
3824 case 'a':
3825 if ((int) (im->canvas->imgformat = if_conv(optarg)) == -1) {
3826 rrd_set_error("unsupported graphics format '%s'", optarg);
3827 return;
3828 }
3829 break;
3830 case 'z':
3831 im->lazy = 1;
3832 break;
3833 case 'E':
3834 im->slopemode = 1;
3835 break;
3837 case 'o':
3838 im->logarithmic = 1;
3839 break;
3840 case 'c':
3841 if (sscanf(optarg,
3842 "%10[A-Z]#%n%8lx%n",
3843 col_nam, &col_start, &color, &col_end) == 2) {
3844 int ci;
3845 int col_len = col_end - col_start;
3847 switch (col_len) {
3848 case 3:
3849 color = (((color & 0xF00) * 0x110000) |
3850 ((color & 0x0F0) * 0x011000) |
3851 ((color & 0x00F) * 0x001100) | 0x000000FF);
3852 break;
3853 case 4:
3854 color = (((color & 0xF000) * 0x11000) |
3855 ((color & 0x0F00) * 0x01100) |
3856 ((color & 0x00F0) * 0x00110) |
3857 ((color & 0x000F) * 0x00011)
3858 );
3859 break;
3860 case 6:
3861 color = (color << 8) + 0xff /* shift left by 8 */ ;
3862 break;
3863 case 8:
3864 break;
3865 default:
3866 rrd_set_error("the color format is #RRGGBB[AA]");
3867 return;
3868 }
3869 if ((ci = grc_conv(col_nam)) != -1) {
3870 im->graph_col[ci] = color;
3871 } else {
3872 rrd_set_error("invalid color name '%s'", col_nam);
3873 return;
3874 }
3875 } else {
3876 rrd_set_error("invalid color def format");
3877 return;
3878 }
3879 break;
3880 case 'n':{
3881 char prop[15];
3882 double size = 1;
3883 char font[1024] = "";
3885 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3886 int sindex, propidx;
3888 if ((sindex = text_prop_conv(prop)) != -1) {
3889 for (propidx = sindex; propidx < TEXT_PROP_LAST;
3890 propidx++) {
3891 if (size > 0) {
3892 im->text_prop[propidx].size = size;
3893 }
3894 if (strlen(font) > 0) {
3895 strcpy(im->text_prop[propidx].font, font);
3896 }
3897 if (propidx == sindex && sindex != 0)
3898 break;
3899 }
3900 } else {
3901 rrd_set_error("invalid fonttag '%s'", prop);
3902 return;
3903 }
3904 } else {
3905 rrd_set_error("invalid text property format");
3906 return;
3907 }
3908 break;
3909 }
3910 case 'm':
3911 im->canvas->zoom = atof(optarg);
3912 if (im->canvas->zoom <= 0.0) {
3913 rrd_set_error("zoom factor must be > 0");
3914 return;
3915 }
3916 break;
3917 case 't':
3918 strncpy(im->title, optarg, 150);
3919 im->title[150] = '\0';
3920 break;
3922 case 'R':
3923 if (strcmp(optarg, "normal") == 0)
3924 im->canvas->aa_type = AA_NORMAL;
3925 else if (strcmp(optarg, "light") == 0)
3926 im->canvas->aa_type = AA_LIGHT;
3927 else if (strcmp(optarg, "mono") == 0)
3928 im->canvas->aa_type = AA_NONE;
3929 else {
3930 rrd_set_error("unknown font-render-mode '%s'", optarg);
3931 return;
3932 }
3933 break;
3935 case 'B':
3936 im->canvas->font_aa_threshold = atof(optarg);
3937 break;
3939 case 'W':
3940 strncpy(im->watermark, optarg, 100);
3941 im->watermark[99] = '\0';
3942 break;
3944 case '?':
3945 if (optopt != 0)
3946 rrd_set_error("unknown option '%c'", optopt);
3947 else
3948 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3949 return;
3950 }
3951 }
3953 if (optind >= argc) {
3954 rrd_set_error("missing filename");
3955 return;
3956 }
3958 if (im->logarithmic == 1 && im->minval <= 0) {
3959 rrd_set_error
3960 ("for a logarithmic yaxis you must specify a lower-limit > 0");
3961 return;
3962 }
3964 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3965 /* error string is set in parsetime.c */
3966 return;
3967 }
3969 if (start_tmp < 3600 * 24 * 365 * 10) {
3970 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3971 start_tmp);
3972 return;
3973 }
3975 if (end_tmp < start_tmp) {
3976 rrd_set_error("start (%ld) should be less than end (%ld)",
3977 start_tmp, end_tmp);
3978 return;
3979 }
3981 im->start = start_tmp;
3982 im->end = end_tmp;
3983 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3984 }
3986 int rrd_graph_color(
3987 image_desc_t *im,
3988 char *var,
3989 char *err,
3990 int optional)
3991 {
3992 char *color;
3993 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
3995 color = strstr(var, "#");
3996 if (color == NULL) {
3997 if (optional == 0) {
3998 rrd_set_error("Found no color in %s", err);
3999 return 0;
4000 }
4001 return 0;
4002 } else {
4003 int n = 0;
4004 char *rest;
4005 gfx_color_t col;
4007 rest = strstr(color, ":");
4008 if (rest != NULL)
4009 n = rest - color;
4010 else
4011 n = strlen(color);
4013 switch (n) {
4014 case 7:
4015 sscanf(color, "#%6lx%n", &col, &n);
4016 col = (col << 8) + 0xff /* shift left by 8 */ ;
4017 if (n != 7)
4018 rrd_set_error("Color problem in %s", err);
4019 break;
4020 case 9:
4021 sscanf(color, "#%8lx%n", &col, &n);
4022 if (n == 9)
4023 break;
4024 default:
4025 rrd_set_error("Color problem in %s", err);
4026 }
4027 if (rrd_test_error())
4028 return 0;
4029 gdp->col = col;
4030 return n;
4031 }
4032 }
4035 int bad_format(
4036 char *fmt)
4037 {
4038 char *ptr;
4039 int n = 0;
4041 ptr = fmt;
4042 while (*ptr != '\0')
4043 if (*ptr++ == '%') {
4045 /* line cannot end with percent char */
4046 if (*ptr == '\0')
4047 return 1;
4049 /* '%s', '%S' and '%%' are allowed */
4050 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4051 ptr++;
4053 /* %c is allowed (but use only with vdef!) */
4054 else if (*ptr == 'c') {
4055 ptr++;
4056 n = 1;
4057 }
4059 /* or else '% 6.2lf' and such are allowed */
4060 else {
4061 /* optional padding character */
4062 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4063 ptr++;
4065 /* This should take care of 'm.n' with all three optional */
4066 while (*ptr >= '0' && *ptr <= '9')
4067 ptr++;
4068 if (*ptr == '.')
4069 ptr++;
4070 while (*ptr >= '0' && *ptr <= '9')
4071 ptr++;
4073 /* Either 'le', 'lf' or 'lg' must follow here */
4074 if (*ptr++ != 'l')
4075 return 1;
4076 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4077 ptr++;
4078 else
4079 return 1;
4080 n++;
4081 }
4082 }
4084 return (n != 1);
4085 }
4088 int vdef_parse(
4089 gdes,
4090 str)
4091 struct graph_desc_t *gdes;
4092 const char *const str;
4093 {
4094 /* A VDEF currently is either "func" or "param,func"
4095 * so the parsing is rather simple. Change if needed.
4096 */
4097 double param;
4098 char func[30];
4099 int n;
4101 n = 0;
4102 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4103 if (n == (int) strlen(str)) { /* matched */
4104 ;
4105 } else {
4106 n = 0;
4107 sscanf(str, "%29[A-Z]%n", func, &n);
4108 if (n == (int) strlen(str)) { /* matched */
4109 param = DNAN;
4110 } else {
4111 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4112 gdes->vname);
4113 return -1;
4114 }
4115 }
4116 if (!strcmp("PERCENT", func))
4117 gdes->vf.op = VDEF_PERCENT;
4118 else if (!strcmp("MAXIMUM", func))
4119 gdes->vf.op = VDEF_MAXIMUM;
4120 else if (!strcmp("AVERAGE", func))
4121 gdes->vf.op = VDEF_AVERAGE;
4122 else if (!strcmp("MINIMUM", func))
4123 gdes->vf.op = VDEF_MINIMUM;
4124 else if (!strcmp("TOTAL", func))
4125 gdes->vf.op = VDEF_TOTAL;
4126 else if (!strcmp("FIRST", func))
4127 gdes->vf.op = VDEF_FIRST;
4128 else if (!strcmp("LAST", func))
4129 gdes->vf.op = VDEF_LAST;
4130 else if (!strcmp("LSLSLOPE", func))
4131 gdes->vf.op = VDEF_LSLSLOPE;
4132 else if (!strcmp("LSLINT", func))
4133 gdes->vf.op = VDEF_LSLINT;
4134 else if (!strcmp("LSLCORREL", func))
4135 gdes->vf.op = VDEF_LSLCORREL;
4136 else {
4137 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4138 gdes->vname);
4139 return -1;
4140 };
4142 switch (gdes->vf.op) {
4143 case VDEF_PERCENT:
4144 if (isnan(param)) { /* no parameter given */
4145 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4146 func, gdes->vname);
4147 return -1;
4148 };
4149 if (param >= 0.0 && param <= 100.0) {
4150 gdes->vf.param = param;
4151 gdes->vf.val = DNAN; /* undefined */
4152 gdes->vf.when = 0; /* undefined */
4153 } else {
4154 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4155 gdes->vname);
4156 return -1;
4157 };
4158 break;
4159 case VDEF_MAXIMUM:
4160 case VDEF_AVERAGE:
4161 case VDEF_MINIMUM:
4162 case VDEF_TOTAL:
4163 case VDEF_FIRST:
4164 case VDEF_LAST:
4165 case VDEF_LSLSLOPE:
4166 case VDEF_LSLINT:
4167 case VDEF_LSLCORREL:
4168 if (isnan(param)) {
4169 gdes->vf.param = DNAN;
4170 gdes->vf.val = DNAN;
4171 gdes->vf.when = 0;
4172 } else {
4173 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4174 func, gdes->vname);
4175 return -1;
4176 };
4177 break;
4178 };
4179 return 0;
4180 }
4183 int vdef_calc(
4184 im,
4185 gdi)
4186 image_desc_t *im;
4187 int gdi;
4188 {
4189 graph_desc_t *src, *dst;
4190 rrd_value_t *data;
4191 long step, steps;
4193 dst = &im->gdes[gdi];
4194 src = &im->gdes[dst->vidx];
4195 data = src->data + src->ds;
4196 steps = (src->end - src->start) / src->step;
4198 #if 0
4199 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4200 src->end, steps);
4201 #endif
4203 switch (dst->vf.op) {
4204 case VDEF_PERCENT:{
4205 rrd_value_t *array;
4206 int field;
4209 if ((array = malloc(steps * sizeof(double))) == NULL) {
4210 rrd_set_error("malloc VDEV_PERCENT");
4211 return -1;
4212 }
4213 for (step = 0; step < steps; step++) {
4214 array[step] = data[step * src->ds_cnt];
4215 }
4216 qsort(array, step, sizeof(double), vdef_percent_compar);
4218 field = (steps - 1) * dst->vf.param / 100;
4219 dst->vf.val = array[field];
4220 dst->vf.when = 0; /* no time component */
4221 free(array);
4222 #if 0
4223 for (step = 0; step < steps; step++)
4224 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4225 step == field ? '*' : ' ');
4226 #endif
4227 }
4228 break;
4229 case VDEF_MAXIMUM:
4230 step = 0;
4231 while (step != steps && isnan(data[step * src->ds_cnt]))
4232 step++;
4233 if (step == steps) {
4234 dst->vf.val = DNAN;
4235 dst->vf.when = 0;
4236 } else {
4237 dst->vf.val = data[step * src->ds_cnt];
4238 dst->vf.when = src->start + (step + 1) * src->step;
4239 }
4240 while (step != steps) {
4241 if (finite(data[step * src->ds_cnt])) {
4242 if (data[step * src->ds_cnt] > dst->vf.val) {
4243 dst->vf.val = data[step * src->ds_cnt];
4244 dst->vf.when = src->start + (step + 1) * src->step;
4245 }
4246 }
4247 step++;
4248 }
4249 break;
4250 case VDEF_TOTAL:
4251 case VDEF_AVERAGE:{
4252 int cnt = 0;
4253 double sum = 0.0;
4255 for (step = 0; step < steps; step++) {
4256 if (finite(data[step * src->ds_cnt])) {
4257 sum += data[step * src->ds_cnt];
4258 cnt++;
4259 };
4260 }
4261 if (cnt) {
4262 if (dst->vf.op == VDEF_TOTAL) {
4263 dst->vf.val = sum * src->step;
4264 dst->vf.when = 0; /* no time component */
4265 } else {
4266 dst->vf.val = sum / cnt;
4267 dst->vf.when = 0; /* no time component */
4268 };
4269 } else {
4270 dst->vf.val = DNAN;
4271 dst->vf.when = 0;
4272 }
4273 }
4274 break;
4275 case VDEF_MINIMUM:
4276 step = 0;
4277 while (step != steps && isnan(data[step * src->ds_cnt]))
4278 step++;
4279 if (step == steps) {
4280 dst->vf.val = DNAN;
4281 dst->vf.when = 0;
4282 } else {
4283 dst->vf.val = data[step * src->ds_cnt];
4284 dst->vf.when = src->start + (step + 1) * src->step;
4285 }
4286 while (step != steps) {
4287 if (finite(data[step * src->ds_cnt])) {
4288 if (data[step * src->ds_cnt] < dst->vf.val) {
4289 dst->vf.val = data[step * src->ds_cnt];
4290 dst->vf.when = src->start + (step + 1) * src->step;
4291 }
4292 }
4293 step++;
4294 }
4295 break;
4296 case VDEF_FIRST:
4297 /* The time value returned here is one step before the
4298 * actual time value. This is the start of the first
4299 * non-NaN interval.
4300 */
4301 step = 0;
4302 while (step != steps && isnan(data[step * src->ds_cnt]))
4303 step++;
4304 if (step == steps) { /* all entries were NaN */
4305 dst->vf.val = DNAN;
4306 dst->vf.when = 0;
4307 } else {
4308 dst->vf.val = data[step * src->ds_cnt];
4309 dst->vf.when = src->start + step * src->step;
4310 }
4311 break;
4312 case VDEF_LAST:
4313 /* The time value returned here is the
4314 * actual time value. This is the end of the last
4315 * non-NaN interval.
4316 */
4317 step = steps - 1;
4318 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4319 step--;
4320 if (step < 0) { /* all entries were NaN */
4321 dst->vf.val = DNAN;
4322 dst->vf.when = 0;
4323 } else {
4324 dst->vf.val = data[step * src->ds_cnt];
4325 dst->vf.when = src->start + (step + 1) * src->step;
4326 }
4327 break;
4328 case VDEF_LSLSLOPE:
4329 case VDEF_LSLINT:
4330 case VDEF_LSLCORREL:{
4331 /* Bestfit line by linear least squares method */
4333 int cnt = 0;
4334 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4336 SUMx = 0;
4337 SUMy = 0;
4338 SUMxy = 0;
4339 SUMxx = 0;
4340 SUMyy = 0;
4342 for (step = 0; step < steps; step++) {
4343 if (finite(data[step * src->ds_cnt])) {
4344 cnt++;
4345 SUMx += step;
4346 SUMxx += step * step;
4347 SUMxy += step * data[step * src->ds_cnt];
4348 SUMy += data[step * src->ds_cnt];
4349 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4350 };
4351 }
4353 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4354 y_intercept = (SUMy - slope * SUMx) / cnt;
4355 correl =
4356 (SUMxy -
4357 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4358 (SUMx * SUMx) / cnt) * (SUMyy -
4359 (SUMy *
4360 SUMy) /
4361 cnt));
4363 if (cnt) {
4364 if (dst->vf.op == VDEF_LSLSLOPE) {
4365 dst->vf.val = slope;
4366 dst->vf.when = 0;
4367 } else if (dst->vf.op == VDEF_LSLINT) {
4368 dst->vf.val = y_intercept;
4369 dst->vf.when = 0;
4370 } else if (dst->vf.op == VDEF_LSLCORREL) {
4371 dst->vf.val = correl;
4372 dst->vf.when = 0;
4373 };
4375 } else {
4376 dst->vf.val = DNAN;
4377 dst->vf.when = 0;
4378 }
4379 }
4380 break;
4381 }
4382 return 0;
4383 }
4385 /* NaN < -INF < finite_values < INF */
4386 int vdef_percent_compar(
4387 a,
4388 b)
4389 const void *a, *b;
4390 {
4391 /* Equality is not returned; this doesn't hurt except
4392 * (maybe) for a little performance.
4393 */
4395 /* First catch NaN values. They are smallest */
4396 if (isnan(*(double *) a))
4397 return -1;
4398 if (isnan(*(double *) b))
4399 return 1;
4401 /* NaN doesn't reach this part so INF and -INF are extremes.
4402 * The sign from isinf() is compatible with the sign we return
4403 */
4404 if (isinf(*(double *) a))
4405 return isinf(*(double *) a);
4406 if (isinf(*(double *) b))
4407 return isinf(*(double *) b);
4409 /* If we reach this, both values must be finite */
4410 if (*(double *) a < *(double *) b)
4411 return -1;
4412 else
4413 return 1;
4414 }