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 =
1579 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1581 for (i = 0; i < im->gdes_c; i++) {
1582 fill_last = fill;
1584 /* hide legends for rules which are not displayed */
1586 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1587 if (im->gdes[i].gf == GF_HRULE &&
1588 (im->gdes[i].yrule < im->minval
1589 || im->gdes[i].yrule > im->maxval))
1590 im->gdes[i].legend[0] = '\0';
1592 if (im->gdes[i].gf == GF_VRULE &&
1593 (im->gdes[i].xrule < im->start
1594 || im->gdes[i].xrule > im->end))
1595 im->gdes[i].legend[0] = '\0';
1596 }
1598 leg_cc = strlen(im->gdes[i].legend);
1600 /* is there a controle code ant the end of the legend string ? */
1601 /* and it is not a tab \\t */
1602 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1603 && im->gdes[i].legend[leg_cc - 1] != 't') {
1604 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1605 leg_cc -= 2;
1606 im->gdes[i].legend[leg_cc] = '\0';
1607 } else {
1608 prt_fctn = '\0';
1609 }
1610 /* only valid control codes */
1611 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1612 prt_fctn != 'r' &&
1613 prt_fctn != 'j' &&
1614 prt_fctn != 'c' &&
1615 prt_fctn != 's' &&
1616 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1617 free(legspace);
1618 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1619 im->gdes[i].legend, prt_fctn);
1620 return -1;
1622 }
1624 /* remove exess space */
1625 if (prt_fctn == 'n') {
1626 prt_fctn = 'l';
1627 }
1629 while (prt_fctn == 'g' &&
1630 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1631 leg_cc--;
1632 im->gdes[i].legend[leg_cc] = '\0';
1633 }
1634 if (leg_cc != 0) {
1635 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1637 if (fill > 0) {
1638 /* no interleg space if string ends in \g */
1639 fill += legspace[i];
1640 }
1641 fill += gfx_get_text_width(im->canvas, fill + border,
1642 im->text_prop[TEXT_PROP_LEGEND].
1643 font,
1644 im->text_prop[TEXT_PROP_LEGEND].
1645 size, im->tabwidth,
1646 im->gdes[i].legend, 0);
1647 leg_c++;
1648 } else {
1649 legspace[i] = 0;
1650 }
1651 /* who said there was a special tag ... ? */
1652 if (prt_fctn == 'g') {
1653 prt_fctn = '\0';
1654 }
1655 if (prt_fctn == '\0') {
1656 if (i == im->gdes_c - 1)
1657 prt_fctn = 'l';
1659 /* is it time to place the legends ? */
1660 if (fill > im->ximg - 2 * border) {
1661 if (leg_c > 1) {
1662 /* go back one */
1663 i--;
1664 fill = fill_last;
1665 leg_c--;
1666 prt_fctn = 'j';
1667 } else {
1668 prt_fctn = 'l';
1669 }
1671 }
1672 }
1675 if (prt_fctn != '\0') {
1676 leg_x = border;
1677 if (leg_c >= 2 && prt_fctn == 'j') {
1678 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1679 } else {
1680 glue = 0;
1681 }
1682 if (prt_fctn == 'c')
1683 leg_x = (im->ximg - fill) / 2.0;
1684 if (prt_fctn == 'r')
1685 leg_x = im->ximg - fill - border;
1687 for (ii = mark; ii <= i; ii++) {
1688 if (im->gdes[ii].legend[0] == '\0')
1689 continue; /* skip empty legends */
1690 im->gdes[ii].leg_x = leg_x;
1691 im->gdes[ii].leg_y = leg_y;
1692 leg_x +=
1693 gfx_get_text_width(im->canvas, leg_x,
1694 im->text_prop[TEXT_PROP_LEGEND].
1695 font,
1696 im->text_prop[TEXT_PROP_LEGEND].
1697 size, im->tabwidth,
1698 im->gdes[ii].legend, 0)
1699 + legspace[ii]
1700 + glue;
1701 }
1702 leg_y_prev = leg_y;
1703 if (im->extra_flags & FULL_SIZE_MODE) {
1704 /* only add y space if there was text on the line */
1705 if (leg_x > border || prt_fctn == 's')
1706 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1707 if (prt_fctn == 's')
1708 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1709 } else {
1710 if (leg_x > border || prt_fctn == 's')
1711 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1712 if (prt_fctn == 's')
1713 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1714 }
1715 fill = 0;
1716 leg_c = 0;
1717 mark = ii;
1718 }
1719 }
1721 if (im->extra_flags & FULL_SIZE_MODE) {
1722 if (leg_y != leg_y_prev) {
1723 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1724 im->yorigin =
1725 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1726 }
1727 } else {
1728 im->yimg = leg_y_prev;
1729 /* if we did place some legends we have to add vertical space */
1730 if (leg_y != im->yimg)
1731 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1732 }
1733 free(legspace);
1734 }
1735 return 0;
1736 }
1738 /* create a grid on the graph. it determines what to do
1739 from the values of xsize, start and end */
1741 /* the xaxis labels are determined from the number of seconds per pixel
1742 in the requested graph */
1746 int calc_horizontal_grid(
1747 image_desc_t *im)
1748 {
1749 double range;
1750 double scaledrange;
1751 int pixel, i;
1752 int gridind = 0;
1753 int decimals, fractionals;
1755 im->ygrid_scale.labfact = 2;
1756 range = im->maxval - im->minval;
1757 scaledrange = range / im->magfact;
1759 /* does the scale of this graph make it impossible to put lines
1760 on it? If so, give up. */
1761 if (isnan(scaledrange)) {
1762 return 0;
1763 }
1765 /* find grid spaceing */
1766 pixel = 1;
1767 if (isnan(im->ygridstep)) {
1768 if (im->extra_flags & ALTYGRID) {
1769 /* find the value with max number of digits. Get number of digits */
1770 decimals =
1771 ceil(log10
1772 (max(fabs(im->maxval), fabs(im->minval)) *
1773 im->viewfactor / im->magfact));
1774 if (decimals <= 0) /* everything is small. make place for zero */
1775 decimals = 1;
1777 im->ygrid_scale.gridstep =
1778 pow((double) 10,
1779 floor(log10(range * im->viewfactor / im->magfact))) /
1780 im->viewfactor * im->magfact;
1782 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1783 im->ygrid_scale.gridstep = 0.1;
1784 /* should have at least 5 lines but no more then 15 */
1785 if (range / im->ygrid_scale.gridstep < 5)
1786 im->ygrid_scale.gridstep /= 10;
1787 if (range / im->ygrid_scale.gridstep > 15)
1788 im->ygrid_scale.gridstep *= 10;
1789 if (range / im->ygrid_scale.gridstep > 5) {
1790 im->ygrid_scale.labfact = 1;
1791 if (range / im->ygrid_scale.gridstep > 8)
1792 im->ygrid_scale.labfact = 2;
1793 } else {
1794 im->ygrid_scale.gridstep /= 5;
1795 im->ygrid_scale.labfact = 5;
1796 }
1797 fractionals =
1798 floor(log10
1799 (im->ygrid_scale.gridstep *
1800 (double) im->ygrid_scale.labfact * im->viewfactor /
1801 im->magfact));
1802 if (fractionals < 0) { /* small amplitude. */
1803 int len = decimals - fractionals + 1;
1805 if (im->unitslength < len + 2)
1806 im->unitslength = len + 2;
1807 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1808 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1809 } else {
1810 int len = decimals + 1;
1812 if (im->unitslength < len + 2)
1813 im->unitslength = len + 2;
1814 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1815 (im->symbol != ' ' ? " %c" : ""));
1816 }
1817 } else {
1818 for (i = 0; ylab[i].grid > 0; i++) {
1819 pixel = im->ysize / (scaledrange / ylab[i].grid);
1820 gridind = i;
1821 if (pixel > 7)
1822 break;
1823 }
1825 for (i = 0; i < 4; i++) {
1826 if (pixel * ylab[gridind].lfac[i] >=
1827 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1828 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1829 break;
1830 }
1831 }
1833 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1834 }
1835 } else {
1836 im->ygrid_scale.gridstep = im->ygridstep;
1837 im->ygrid_scale.labfact = im->ylabfact;
1838 }
1839 return 1;
1840 }
1842 int draw_horizontal_grid(
1843 image_desc_t *im)
1844 {
1845 int i;
1846 double scaledstep;
1847 char graph_label[100];
1848 int nlabels = 0;
1849 double X0 = im->xorigin;
1850 double X1 = im->xorigin + im->xsize;
1852 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1853 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1854 double MaxY;
1856 scaledstep =
1857 im->ygrid_scale.gridstep / (double) im->magfact *
1858 (double) im->viewfactor;
1859 MaxY = scaledstep * (double) egrid;
1860 for (i = sgrid; i <= egrid; i++) {
1861 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1862 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1864 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1865 && floor(Y0 + 0.5) <= im->yorigin) {
1866 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1867 with the chosen settings. Add a label if required by settings, or if
1868 there is only one label so far and the next grid line is out of bounds. */
1869 if (i % im->ygrid_scale.labfact == 0
1870 || (nlabels == 1
1871 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1872 if (im->symbol == ' ') {
1873 if (im->extra_flags & ALTYGRID) {
1874 sprintf(graph_label, im->ygrid_scale.labfmt,
1875 scaledstep * (double) i);
1876 } else {
1877 if (MaxY < 10) {
1878 sprintf(graph_label, "%4.1f",
1879 scaledstep * (double) i);
1880 } else {
1881 sprintf(graph_label, "%4.0f",
1882 scaledstep * (double) i);
1883 }
1884 }
1885 } else {
1886 char sisym = (i == 0 ? ' ' : im->symbol);
1888 if (im->extra_flags & ALTYGRID) {
1889 sprintf(graph_label, im->ygrid_scale.labfmt,
1890 scaledstep * (double) i, sisym);
1891 } else {
1892 if (MaxY < 10) {
1893 sprintf(graph_label, "%4.1f %c",
1894 scaledstep * (double) i, sisym);
1895 } else {
1896 sprintf(graph_label, "%4.0f %c",
1897 scaledstep * (double) i, sisym);
1898 }
1899 }
1900 }
1901 nlabels++;
1903 gfx_new_text(im->canvas,
1904 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1905 im->graph_col[GRC_FONT],
1906 im->text_prop[TEXT_PROP_AXIS].font,
1907 im->text_prop[TEXT_PROP_AXIS].size,
1908 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1909 graph_label);
1910 gfx_new_dashed_line(im->canvas,
1911 X0 - 2, Y0,
1912 X1 + 2, Y0,
1913 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1914 im->grid_dash_on, im->grid_dash_off);
1916 } else if (!(im->extra_flags & NOMINOR)) {
1917 gfx_new_dashed_line(im->canvas,
1918 X0 - 1, Y0,
1919 X1 + 1, Y0,
1920 GRIDWIDTH, im->graph_col[GRC_GRID],
1921 im->grid_dash_on, im->grid_dash_off);
1923 }
1924 }
1925 }
1926 return 1;
1927 }
1929 /* this is frexp for base 10 */
1930 double frexp10(
1931 double,
1932 double *);
1933 double frexp10(
1934 double x,
1935 double *e)
1936 {
1937 double mnt;
1938 int iexp;
1940 iexp = floor(log(fabs(x)) / log(10));
1941 mnt = x / pow(10.0, iexp);
1942 if (mnt >= 10.0) {
1943 iexp++;
1944 mnt = x / pow(10.0, iexp);
1945 }
1946 *e = iexp;
1947 return mnt;
1948 }
1950 static int AlmostEqual2sComplement(
1951 float A,
1952 float B,
1953 int maxUlps)
1954 {
1956 int aInt = *(int *) &A;
1957 int bInt = *(int *) &B;
1958 int intDiff;
1960 /* Make sure maxUlps is non-negative and small enough that the
1961 default NAN won't compare as equal to anything. */
1963 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1965 /* Make aInt lexicographically ordered as a twos-complement int */
1967 if (aInt < 0)
1968 aInt = 0x80000000l - aInt;
1970 /* Make bInt lexicographically ordered as a twos-complement int */
1972 if (bInt < 0)
1973 bInt = 0x80000000l - bInt;
1975 intDiff = abs(aInt - bInt);
1977 if (intDiff <= maxUlps)
1978 return 1;
1980 return 0;
1981 }
1983 /* logaritmic horizontal grid */
1984 int horizontal_log_grid(
1985 image_desc_t *im)
1986 {
1987 double yloglab[][10] = {
1988 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1989 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1990 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1991 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1992 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1993 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
1994 };
1996 int i, j, val_exp, min_exp;
1997 double nex; /* number of decades in data */
1998 double logscale; /* scale in logarithmic space */
1999 int exfrac = 1; /* decade spacing */
2000 int mid = -1; /* row in yloglab for major grid */
2001 double mspac; /* smallest major grid spacing (pixels) */
2002 int flab; /* first value in yloglab to use */
2003 double value, tmp, pre_value;
2004 double X0, X1, Y0;
2005 char graph_label[100];
2007 nex = log10(im->maxval / im->minval);
2008 logscale = im->ysize / nex;
2010 /* major spacing for data with high dynamic range */
2011 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2012 if (exfrac == 1)
2013 exfrac = 3;
2014 else
2015 exfrac += 3;
2016 }
2018 /* major spacing for less dynamic data */
2019 do {
2020 /* search best row in yloglab */
2021 mid++;
2022 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2023 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2024 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2025 && yloglab[mid][0] > 0);
2026 if (mid)
2027 mid--;
2029 /* find first value in yloglab */
2030 for (flab = 0;
2031 yloglab[mid][flab] < 10
2032 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2033 if (yloglab[mid][flab] == 10.0) {
2034 tmp += 1.0;
2035 flab = 0;
2036 }
2037 val_exp = tmp;
2038 if (val_exp % exfrac)
2039 val_exp += abs(-val_exp % exfrac);
2041 X0 = im->xorigin;
2042 X1 = im->xorigin + im->xsize;
2044 /* draw grid */
2045 pre_value = DNAN;
2046 while (1) {
2048 value = yloglab[mid][flab] * pow(10.0, val_exp);
2049 if (AlmostEqual2sComplement(value, pre_value, 4))
2050 break; /* it seems we are not converging */
2052 pre_value = value;
2054 Y0 = ytr(im, value);
2055 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2056 break;
2058 /* major grid line */
2059 gfx_new_dashed_line(im->canvas,
2060 X0 - 2, Y0,
2061 X1 + 2, Y0,
2062 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2063 im->grid_dash_on, im->grid_dash_off);
2065 /* label */
2066 if (im->extra_flags & FORCE_UNITS_SI) {
2067 int scale;
2068 double pvalue;
2069 char symbol;
2071 scale = floor(val_exp / 3.0);
2072 if (value >= 1.0)
2073 pvalue = pow(10.0, val_exp % 3);
2074 else
2075 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2076 pvalue *= yloglab[mid][flab];
2078 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2079 ((scale + si_symbcenter) >= 0))
2080 symbol = si_symbol[scale + si_symbcenter];
2081 else
2082 symbol = '?';
2084 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2085 } else
2086 sprintf(graph_label, "%3.0e", value);
2087 gfx_new_text(im->canvas,
2088 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2089 im->graph_col[GRC_FONT],
2090 im->text_prop[TEXT_PROP_AXIS].font,
2091 im->text_prop[TEXT_PROP_AXIS].size,
2092 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
2093 graph_label);
2095 /* minor grid */
2096 if (mid < 4 && exfrac == 1) {
2097 /* find first and last minor line behind current major line
2098 * i is the first line and j tha last */
2099 if (flab == 0) {
2100 min_exp = val_exp - 1;
2101 for (i = 1; yloglab[mid][i] < 10.0; i++);
2102 i = yloglab[mid][i - 1] + 1;
2103 j = 10;
2104 } else {
2105 min_exp = val_exp;
2106 i = yloglab[mid][flab - 1] + 1;
2107 j = yloglab[mid][flab];
2108 }
2110 /* draw minor lines below current major line */
2111 for (; i < j; i++) {
2113 value = i * pow(10.0, min_exp);
2114 if (value < im->minval)
2115 continue;
2117 Y0 = ytr(im, value);
2118 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2119 break;
2121 /* draw lines */
2122 gfx_new_dashed_line(im->canvas,
2123 X0 - 1, Y0,
2124 X1 + 1, Y0,
2125 GRIDWIDTH, im->graph_col[GRC_GRID],
2126 im->grid_dash_on, im->grid_dash_off);
2127 }
2128 } else if (exfrac > 1) {
2129 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2130 value = pow(10.0, i);
2131 if (value < im->minval)
2132 continue;
2134 Y0 = ytr(im, value);
2135 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2136 break;
2138 /* draw lines */
2139 gfx_new_dashed_line(im->canvas,
2140 X0 - 1, Y0,
2141 X1 + 1, Y0,
2142 GRIDWIDTH, im->graph_col[GRC_GRID],
2143 im->grid_dash_on, im->grid_dash_off);
2144 }
2145 }
2147 /* next decade */
2148 if (yloglab[mid][++flab] == 10.0) {
2149 flab = 0;
2150 val_exp += exfrac;
2151 }
2152 }
2154 /* draw minor lines after highest major line */
2155 if (mid < 4 && exfrac == 1) {
2156 /* find first and last minor line below current major line
2157 * i is the first line and j tha last */
2158 if (flab == 0) {
2159 min_exp = val_exp - 1;
2160 for (i = 1; yloglab[mid][i] < 10.0; i++);
2161 i = yloglab[mid][i - 1] + 1;
2162 j = 10;
2163 } else {
2164 min_exp = val_exp;
2165 i = yloglab[mid][flab - 1] + 1;
2166 j = yloglab[mid][flab];
2167 }
2169 /* draw minor lines below current major line */
2170 for (; i < j; i++) {
2172 value = i * pow(10.0, min_exp);
2173 if (value < im->minval)
2174 continue;
2176 Y0 = ytr(im, value);
2177 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2178 break;
2180 /* draw lines */
2181 gfx_new_dashed_line(im->canvas,
2182 X0 - 1, Y0,
2183 X1 + 1, Y0,
2184 GRIDWIDTH, im->graph_col[GRC_GRID],
2185 im->grid_dash_on, im->grid_dash_off);
2186 }
2187 }
2188 /* fancy minor gridlines */
2189 else if (exfrac > 1) {
2190 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2191 value = pow(10.0, i);
2192 if (value < im->minval)
2193 continue;
2195 Y0 = ytr(im, value);
2196 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2197 break;
2199 /* draw lines */
2200 gfx_new_dashed_line(im->canvas,
2201 X0 - 1, Y0,
2202 X1 + 1, Y0,
2203 GRIDWIDTH, im->graph_col[GRC_GRID],
2204 im->grid_dash_on, im->grid_dash_off);
2205 }
2206 }
2208 return 1;
2209 }
2212 void vertical_grid(
2213 image_desc_t *im)
2214 {
2215 int xlab_sel; /* which sort of label and grid ? */
2216 time_t ti, tilab, timajor;
2217 long factor;
2218 char graph_label[100];
2219 double X0, Y0, Y1; /* points for filled graph and more */
2220 struct tm tm;
2222 /* the type of time grid is determined by finding
2223 the number of seconds per pixel in the graph */
2226 if (im->xlab_user.minsec == -1) {
2227 factor = (im->end - im->start) / im->xsize;
2228 xlab_sel = 0;
2229 while (xlab[xlab_sel + 1].minsec != -1
2230 && xlab[xlab_sel + 1].minsec <= factor) {
2231 xlab_sel++;
2232 } /* pick the last one */
2233 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2234 && xlab[xlab_sel].length > (im->end - im->start)) {
2235 xlab_sel--;
2236 } /* go back to the smallest size */
2237 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2238 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2239 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2240 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2241 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2242 im->xlab_user.labst = xlab[xlab_sel].labst;
2243 im->xlab_user.precis = xlab[xlab_sel].precis;
2244 im->xlab_user.stst = xlab[xlab_sel].stst;
2245 }
2247 /* y coords are the same for every line ... */
2248 Y0 = im->yorigin;
2249 Y1 = im->yorigin - im->ysize;
2252 /* paint the minor grid */
2253 if (!(im->extra_flags & NOMINOR)) {
2254 for (ti = find_first_time(im->start,
2255 im->xlab_user.gridtm,
2256 im->xlab_user.gridst),
2257 timajor = find_first_time(im->start,
2258 im->xlab_user.mgridtm,
2259 im->xlab_user.mgridst);
2260 ti < im->end;
2261 ti =
2262 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2263 ) {
2264 /* are we inside the graph ? */
2265 if (ti < im->start || ti > im->end)
2266 continue;
2267 while (timajor < ti) {
2268 timajor = find_next_time(timajor,
2269 im->xlab_user.mgridtm,
2270 im->xlab_user.mgridst);
2271 }
2272 if (ti == timajor)
2273 continue; /* skip as falls on major grid line */
2274 X0 = xtr(im, ti);
2275 gfx_new_dashed_line(im->canvas, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2276 im->graph_col[GRC_GRID],
2277 im->grid_dash_on, im->grid_dash_off);
2279 }
2280 }
2282 /* paint the major grid */
2283 for (ti = find_first_time(im->start,
2284 im->xlab_user.mgridtm,
2285 im->xlab_user.mgridst);
2286 ti < im->end;
2287 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2288 ) {
2289 /* are we inside the graph ? */
2290 if (ti < im->start || ti > im->end)
2291 continue;
2292 X0 = xtr(im, ti);
2293 gfx_new_dashed_line(im->canvas, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2294 im->graph_col[GRC_MGRID],
2295 im->grid_dash_on, im->grid_dash_off);
2297 }
2298 /* paint the labels below the graph */
2299 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2300 im->xlab_user.labtm,
2301 im->xlab_user.labst);
2302 ti <= im->end - im->xlab_user.precis / 2;
2303 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2304 ) {
2305 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2306 /* are we inside the graph ? */
2307 if (tilab < im->start || tilab > im->end)
2308 continue;
2310 #if HAVE_STRFTIME
2311 localtime_r(&tilab, &tm);
2312 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2313 #else
2314 # error "your libc has no strftime I guess we'll abort the exercise here."
2315 #endif
2316 gfx_new_text(im->canvas,
2317 xtr(im, tilab),
2318 Y0 + im->text_prop[TEXT_PROP_AXIS].size * 1.4 + 5,
2319 im->graph_col[GRC_FONT],
2320 im->text_prop[TEXT_PROP_AXIS].font,
2321 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2322 GFX_H_CENTER, GFX_V_BOTTOM, graph_label);
2324 }
2326 }
2329 void axis_paint(
2330 image_desc_t *im)
2331 {
2332 /* draw x and y axis */
2333 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2334 im->xorigin+im->xsize,im->yorigin-im->ysize,
2335 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2337 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2338 im->xorigin+im->xsize,im->yorigin-im->ysize,
2339 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2341 gfx_new_line(im->canvas, im->xorigin - 4, im->yorigin,
2342 im->xorigin + im->xsize + 4, im->yorigin,
2343 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2345 gfx_new_line(im->canvas, im->xorigin, im->yorigin + 4,
2346 im->xorigin, im->yorigin - im->ysize - 4,
2347 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2350 /* arrow for X and Y axis direction */
2351 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 */
2352 im->graph_col[GRC_ARROW]);
2354 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 */
2355 im->graph_col[GRC_ARROW]);
2357 }
2359 void grid_paint(
2360 image_desc_t *im)
2361 {
2362 long i;
2363 int res = 0;
2364 double X0, Y0; /* points for filled graph and more */
2365 gfx_node_t *node;
2367 /* draw 3d border */
2368 node = gfx_new_area(im->canvas, 0, im->yimg,
2369 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2370 gfx_add_point(node, im->ximg - 2, 2);
2371 gfx_add_point(node, im->ximg, 0);
2372 gfx_add_point(node, 0, 0);
2373 /* gfx_add_point( node , 0,im->yimg ); */
2375 node = gfx_new_area(im->canvas, 2, im->yimg - 2,
2376 im->ximg - 2, im->yimg - 2,
2377 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2378 gfx_add_point(node, im->ximg, 0);
2379 gfx_add_point(node, im->ximg, im->yimg);
2380 gfx_add_point(node, 0, im->yimg);
2381 /* gfx_add_point( node , 0,im->yimg ); */
2384 if (im->draw_x_grid == 1)
2385 vertical_grid(im);
2387 if (im->draw_y_grid == 1) {
2388 if (im->logarithmic) {
2389 res = horizontal_log_grid(im);
2390 } else {
2391 res = draw_horizontal_grid(im);
2392 }
2394 /* dont draw horizontal grid if there is no min and max val */
2395 if (!res) {
2396 char *nodata = "No Data found";
2398 gfx_new_text(im->canvas, im->ximg / 2,
2399 (2 * im->yorigin - im->ysize) / 2,
2400 im->graph_col[GRC_FONT],
2401 im->text_prop[TEXT_PROP_AXIS].font,
2402 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2403 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2404 }
2405 }
2407 /* yaxis unit description */
2408 gfx_new_text(im->canvas,
2409 10, (im->yorigin - im->ysize / 2),
2410 im->graph_col[GRC_FONT],
2411 im->text_prop[TEXT_PROP_UNIT].font,
2412 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2413 RRDGRAPH_YLEGEND_ANGLE,
2414 GFX_H_LEFT, GFX_V_CENTER, im->ylegend);
2416 /* graph title */
2417 gfx_new_text(im->canvas,
2418 im->ximg / 2, im->text_prop[TEXT_PROP_TITLE].size * 1.3 + 4,
2419 im->graph_col[GRC_FONT],
2420 im->text_prop[TEXT_PROP_TITLE].font,
2421 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2422 GFX_H_CENTER, GFX_V_CENTER, im->title);
2423 /* rrdtool 'logo' */
2424 gfx_new_text(im->canvas,
2425 im->ximg - 7, 7,
2426 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2427 im->text_prop[TEXT_PROP_AXIS].font,
2428 5.5, im->tabwidth, 270,
2429 GFX_H_RIGHT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2431 /* graph watermark */
2432 if (im->watermark[0] != '\0') {
2433 gfx_new_text(im->canvas,
2434 im->ximg / 2, im->yimg - 6,
2435 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2436 im->text_prop[TEXT_PROP_AXIS].font,
2437 5.5, im->tabwidth, 0,
2438 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2439 }
2441 /* graph labels */
2442 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2443 for (i = 0; i < im->gdes_c; i++) {
2444 if (im->gdes[i].legend[0] == '\0')
2445 continue;
2447 /* im->gdes[i].leg_y is the bottom of the legend */
2448 X0 = im->gdes[i].leg_x;
2449 Y0 = im->gdes[i].leg_y;
2450 gfx_new_text(im->canvas, X0, Y0,
2451 im->graph_col[GRC_FONT],
2452 im->text_prop[TEXT_PROP_LEGEND].font,
2453 im->text_prop[TEXT_PROP_LEGEND].size,
2454 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2455 im->gdes[i].legend);
2456 /* The legend for GRAPH items starts with "M " to have
2457 enough space for the box */
2458 if (im->gdes[i].gf != GF_PRINT &&
2459 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2460 int boxH, boxV;
2462 boxH = gfx_get_text_width(im->canvas, 0,
2463 im->text_prop[TEXT_PROP_LEGEND].
2464 font,
2465 im->text_prop[TEXT_PROP_LEGEND].
2466 size, im->tabwidth, "o", 0) * 1.2;
2467 boxV = boxH * 1.1;
2469 /* make sure transparent colors show up the same way as in the graph */
2470 node = gfx_new_area(im->canvas,
2471 X0, Y0 - boxV,
2472 X0, Y0,
2473 X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2474 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2476 node = gfx_new_area(im->canvas,
2477 X0, Y0 - boxV,
2478 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2479 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2480 node = gfx_new_line(im->canvas,
2481 X0, Y0 - boxV,
2482 X0, Y0, 1.0, im->graph_col[GRC_FRAME]);
2483 gfx_add_point(node, X0 + boxH, Y0);
2484 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2485 gfx_close_path(node);
2486 }
2487 }
2488 }
2489 }
2492 /*****************************************************
2493 * lazy check make sure we rely need to create this graph
2494 *****************************************************/
2496 int lazy_check(
2497 image_desc_t *im)
2498 {
2499 FILE *fd = NULL;
2500 int size = 1;
2501 struct stat imgstat;
2503 if (im->lazy == 0)
2504 return 0; /* no lazy option */
2505 if (stat(im->graphfile, &imgstat) != 0)
2506 return 0; /* can't stat */
2507 /* one pixel in the existing graph is more then what we would
2508 change here ... */
2509 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2510 return 0;
2511 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2512 return 0; /* the file does not exist */
2513 switch (im->canvas->imgformat) {
2514 case IF_PNG:
2515 size = PngSize(fd, &(im->ximg), &(im->yimg));
2516 break;
2517 default:
2518 size = 1;
2519 }
2520 fclose(fd);
2521 return size;
2522 }
2524 #ifdef WITH_PIECHART
2525 void pie_part(
2526 image_desc_t *im,
2527 gfx_color_t color,
2528 double PieCenterX,
2529 double PieCenterY,
2530 double Radius,
2531 double startangle,
2532 double endangle)
2533 {
2534 gfx_node_t *node;
2535 double angle;
2536 double step = M_PI / 50; /* Number of iterations for the circle;
2537 ** 10 is definitely too low, more than
2538 ** 50 seems to be overkill
2539 */
2541 /* Strange but true: we have to work clockwise or else
2542 ** anti aliasing nor transparency don't work.
2543 **
2544 ** This test is here to make sure we do it right, also
2545 ** this makes the for...next loop more easy to implement.
2546 ** The return will occur if the user enters a negative number
2547 ** (which shouldn't be done according to the specs) or if the
2548 ** programmers do something wrong (which, as we all know, never
2549 ** happens anyway :)
2550 */
2551 if (endangle < startangle)
2552 return;
2554 /* Hidden feature: Radius decreases each full circle */
2555 angle = startangle;
2556 while (angle >= 2 * M_PI) {
2557 angle -= 2 * M_PI;
2558 Radius *= 0.8;
2559 }
2561 node = gfx_new_area(im->canvas,
2562 PieCenterX + sin(startangle) * Radius,
2563 PieCenterY - cos(startangle) * Radius,
2564 PieCenterX,
2565 PieCenterY,
2566 PieCenterX + sin(endangle) * Radius,
2567 PieCenterY - cos(endangle) * Radius, color);
2568 for (angle = endangle; angle - startangle >= step; angle -= step) {
2569 gfx_add_point(node,
2570 PieCenterX + sin(angle) * Radius,
2571 PieCenterY - cos(angle) * Radius);
2572 }
2573 }
2575 #endif
2577 int graph_size_location(
2578 image_desc_t *im,
2579 int elements
2580 #ifdef WITH_PIECHART
2581 ,
2582 int piechart
2583 #endif
2584 )
2585 {
2586 /* The actual size of the image to draw is determined from
2587 ** several sources. The size given on the command line is
2588 ** the graph area but we need more as we have to draw labels
2589 ** and other things outside the graph area
2590 */
2592 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2593 #ifdef WITH_PIECHART
2594 Xpie = 0, Ypie = 0,
2595 #endif
2596 Yxlabel = 0,
2597 #if 0
2598 Xlegend = 0, Ylegend = 0,
2599 #endif
2600 Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2602 if (im->extra_flags & ONLY_GRAPH) {
2603 im->xorigin = 0;
2604 im->ximg = im->xsize;
2605 im->yimg = im->ysize;
2606 im->yorigin = im->ysize;
2607 ytr(im, DNAN);
2608 return 0;
2609 }
2611 /** +---+--------------------------------------------+
2612 ** | y |...............graph title..................|
2613 ** | +---+-------------------------------+--------+
2614 ** | a | y | | |
2615 ** | x | | | |
2616 ** | i | a | | pie |
2617 ** | s | x | main graph area | chart |
2618 ** | | i | | area |
2619 ** | t | s | | |
2620 ** | i | | | |
2621 ** | t | l | | |
2622 ** | l | b +-------------------------------+--------+
2623 ** | e | l | x axis labels | |
2624 ** +---+---+-------------------------------+--------+
2625 ** |....................legends.....................|
2626 ** +------------------------------------------------+
2627 ** | watermark |
2628 ** +------------------------------------------------+
2629 */
2631 if (im->ylegend[0] != '\0') {
2632 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2633 }
2635 if (im->title[0] != '\0') {
2636 /* The title is placed "inbetween" two text lines so it
2637 ** automatically has some vertical spacing. The horizontal
2638 ** spacing is added here, on each side.
2639 */
2640 /* if necessary, reduce the font size of the title until it fits the image width */
2641 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2642 }
2644 if (elements) {
2645 if (im->draw_x_grid) {
2646 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2647 }
2648 if (im->draw_y_grid || im->forceleftspace) {
2649 Xylabel = gfx_get_text_width(im->canvas, 0,
2650 im->text_prop[TEXT_PROP_AXIS].font,
2651 im->text_prop[TEXT_PROP_AXIS].size,
2652 im->tabwidth,
2653 "0", 0) * im->unitslength;
2654 }
2655 }
2657 if (im->extra_flags & FULL_SIZE_MODE) {
2658 /* The actual size of the image to draw has been determined by the user.
2659 ** The graph area is the space remaining after accounting for the legend,
2660 ** the watermark, the pie chart, the axis labels, and the title.
2661 */
2662 im->xorigin = 0;
2663 im->ximg = im->xsize;
2664 im->yimg = im->ysize;
2665 im->yorigin = im->ysize;
2666 Xmain = im->ximg;
2667 Ymain = im->yimg;
2669 im->yorigin += Ytitle;
2671 #ifdef WITH_PIECHART
2672 if (piechart) {
2673 im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2674 Xpie = im->piesize;
2675 Ypie = im->piesize;
2676 }
2677 #endif
2679 /* Now calculate the total size. Insert some spacing where
2680 desired. im->xorigin and im->yorigin need to correspond
2681 with the lower left corner of the main graph area or, if
2682 this one is not set, the imaginary box surrounding the
2683 pie chart area. */
2685 /* Initial size calculation for the main graph area */
2686 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2687 if (Xmain)
2688 Xmain -= Xspacing; /* put space between main graph area and right edge */
2690 #ifdef WITH_PIECHART
2691 Xmain -= Xpie; /* remove pie width from main graph area */
2692 if (Xpie)
2693 Xmain -= Xspacing; /* put space between pie and main graph area */
2694 #endif
2696 im->xorigin = Xspacing + Xylabel;
2698 /* the length of the title should not influence with width of the graph
2699 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2701 if (Xvertical) { /* unit description */
2702 Xmain -= Xvertical;
2703 im->xorigin += Xvertical;
2704 }
2705 im->xsize = Xmain;
2706 xtr(im, 0);
2708 /* The vertical size of the image is known in advance. The main graph area
2709 ** (Ymain) and im->yorigin must be set according to the space requirements
2710 ** of the legend and the axis labels.
2711 */
2713 if (im->extra_flags & NOLEGEND)
2714 {
2715 /* set dimensions correctly if using full size mode with no legend */
2716 im->yorigin = im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2717 Ymain = im->yorigin;
2718 } else
2719 {
2720 /* Determine where to place the legends onto the image.
2721 ** Set Ymain and adjust im->yorigin to match the space requirements.
2722 */
2723 if (leg_place(im, &Ymain) == -1)
2724 return -1;
2725 }
2727 #ifdef WITH_PIECHART
2728 /* if (im->yimg < Ypie) im->yimg = Ypie; * not sure what do about this */
2729 #endif
2731 /* remove title space *or* some padding above the graph from the main graph area */
2732 if (Ytitle) {
2733 Ymain -= Ytitle;
2734 } else {
2735 Ymain -= 1.5 * Yspacing;
2736 }
2738 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2739 if (im->watermark[0] != '\0') {
2740 Ymain -= Ywatermark;
2741 }
2743 im->ysize = Ymain;
2745 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2747 /* The actual size of the image to draw is determined from
2748 ** several sources. The size given on the command line is
2749 ** the graph area but we need more as we have to draw labels
2750 ** and other things outside the graph area.
2751 */
2753 if (im->ylegend[0] != '\0') {
2754 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2755 }
2758 if (im->title[0] != '\0') {
2759 /* The title is placed "inbetween" two text lines so it
2760 ** automatically has some vertical spacing. The horizontal
2761 ** spacing is added here, on each side.
2762 */
2763 /* don't care for the with of the title
2764 Xtitle = gfx_get_text_width(im->canvas, 0,
2765 im->text_prop[TEXT_PROP_TITLE].font,
2766 im->text_prop[TEXT_PROP_TITLE].size,
2767 im->tabwidth,
2768 im->title, 0) + 2*Xspacing; */
2769 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2770 }
2772 if (elements) {
2773 Xmain = im->xsize;
2774 Ymain = im->ysize;
2775 }
2776 #ifdef WITH_PIECHART
2777 if (piechart) {
2778 im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2779 Xpie = im->piesize;
2780 Ypie = im->piesize;
2781 }
2782 #endif
2784 /* Now calculate the total size. Insert some spacing where
2785 desired. im->xorigin and im->yorigin need to correspond
2786 with the lower left corner of the main graph area or, if
2787 this one is not set, the imaginary box surrounding the
2788 pie chart area. */
2790 /* The legend width cannot yet be determined, as a result we
2791 ** have problems adjusting the image to it. For now, we just
2792 ** forget about it at all; the legend will have to fit in the
2793 ** size already allocated.
2794 */
2795 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2797 #ifdef WITH_PIECHART
2798 im->ximg += Xpie;
2799 #endif
2801 if (Xmain)
2802 im->ximg += Xspacing;
2803 #ifdef WITH_PIECHART
2804 if (Xpie)
2805 im->ximg += Xspacing;
2806 #endif
2808 im->xorigin = Xspacing + Xylabel;
2810 /* the length of the title should not influence with width of the graph
2811 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2813 if (Xvertical) { /* unit description */
2814 im->ximg += Xvertical;
2815 im->xorigin += Xvertical;
2816 }
2817 xtr(im, 0);
2819 /* The vertical size is interesting... we need to compare
2820 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2821 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2822 ** in order to start even thinking about Ylegend or Ywatermark.
2823 **
2824 ** Do it in three portions: First calculate the inner part,
2825 ** then do the legend, then adjust the total height of the img,
2826 ** adding space for a watermark if one exists;
2827 */
2829 /* reserve space for main and/or pie */
2831 im->yimg = Ymain + Yxlabel;
2833 #ifdef WITH_PIECHART
2834 if (im->yimg < Ypie)
2835 im->yimg = Ypie;
2836 #endif
2838 im->yorigin = im->yimg - Yxlabel;
2840 /* reserve space for the title *or* some padding above the graph */
2841 if (Ytitle) {
2842 im->yimg += Ytitle;
2843 im->yorigin += Ytitle;
2844 } else {
2845 im->yimg += 1.5 * Yspacing;
2846 im->yorigin += 1.5 * Yspacing;
2847 }
2848 /* reserve space for padding below the graph */
2849 im->yimg += Yspacing;
2851 /* Determine where to place the legends onto the image.
2852 ** Adjust im->yimg to match the space requirements.
2853 */
2854 if (leg_place(im, 0) == -1)
2855 return -1;
2857 if (im->watermark[0] != '\0') {
2858 im->yimg += Ywatermark;
2859 }
2860 }
2862 #if 0
2863 if (Xlegend > im->ximg) {
2864 im->ximg = Xlegend;
2865 /* reposition Pie */
2866 }
2867 #endif
2869 #ifdef WITH_PIECHART
2870 /* The pie is placed in the upper right hand corner,
2871 ** just below the title (if any) and with sufficient
2872 ** padding.
2873 */
2874 if (elements) {
2875 im->pie_x = im->ximg - Xspacing - Xpie / 2;
2876 im->pie_y = im->yorigin - Ymain + Ypie / 2;
2877 } else {
2878 im->pie_x = im->ximg / 2;
2879 im->pie_y = im->yorigin - Ypie / 2;
2880 }
2881 #endif
2883 ytr(im, DNAN);
2884 return 0;
2885 }
2887 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2888 /* yes we are loosing precision by doing tos with floats instead of doubles
2889 but it seems more stable this way. */
2892 /* draw that picture thing ... */
2893 int graph_paint(
2894 image_desc_t *im,
2895 char ***calcpr)
2896 {
2897 int i, ii;
2898 int lazy = lazy_check(im);
2900 #ifdef WITH_PIECHART
2901 int piechart = 0;
2902 double PieStart = 0.0;
2903 #endif
2904 FILE *fo;
2905 gfx_node_t *node;
2907 double areazero = 0.0;
2908 graph_desc_t *lastgdes = NULL;
2910 /* if we are lazy and there is nothing to PRINT ... quit now */
2911 if (lazy && im->prt_c == 0)
2912 return 0;
2914 /* pull the data from the rrd files ... */
2916 if (data_fetch(im) == -1)
2917 return -1;
2919 /* evaluate VDEF and CDEF operations ... */
2920 if (data_calc(im) == -1)
2921 return -1;
2923 #ifdef WITH_PIECHART
2924 /* check if we need to draw a piechart */
2925 for (i = 0; i < im->gdes_c; i++) {
2926 if (im->gdes[i].gf == GF_PART) {
2927 piechart = 1;
2928 break;
2929 }
2930 }
2931 #endif
2933 /* calculate and PRINT and GPRINT definitions. We have to do it at
2934 * this point because it will affect the length of the legends
2935 * if there are no graph elements we stop here ...
2936 * if we are lazy, try to quit ...
2937 */
2938 i = print_calc(im, calcpr);
2939 if (i < 0)
2940 return -1;
2941 if (((i == 0)
2942 #ifdef WITH_PIECHART
2943 && (piechart == 0)
2944 #endif
2945 ) || lazy)
2946 return 0;
2948 #ifdef WITH_PIECHART
2949 /* If there's only the pie chart to draw, signal this */
2950 if (i == 0)
2951 piechart = 2;
2952 #endif
2954 /**************************************************************
2955 *** Calculating sizes and locations became a bit confusing ***
2956 *** so I moved this into a separate function. ***
2957 **************************************************************/
2958 if (graph_size_location(im, i
2959 #ifdef WITH_PIECHART
2960 , piechart
2961 #endif
2962 ) == -1)
2963 return -1;
2965 /* get actual drawing data and find min and max values */
2966 if (data_proc(im) == -1)
2967 return -1;
2969 if (!im->logarithmic) {
2970 si_unit(im);
2971 }
2972 /* identify si magnitude Kilo, Mega Giga ? */
2973 if (!im->rigid && !im->logarithmic)
2974 expand_range(im); /* make sure the upper and lower limit are
2975 sensible values */
2977 if (!calc_horizontal_grid(im))
2978 return -1;
2980 if (im->gridfit)
2981 apply_gridfit(im);
2983 /* the actual graph is created by going through the individual
2984 graph elements and then drawing them */
2986 node = gfx_new_area(im->canvas,
2987 0, 0,
2988 0, im->yimg,
2989 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2991 gfx_add_point(node, im->ximg, 0);
2993 #ifdef WITH_PIECHART
2994 if (piechart != 2) {
2995 #endif
2996 node = gfx_new_area(im->canvas,
2997 im->xorigin, im->yorigin,
2998 im->xorigin + im->xsize, im->yorigin,
2999 im->xorigin + im->xsize, im->yorigin - im->ysize,
3000 im->graph_col[GRC_CANVAS]);
3002 gfx_add_point(node, im->xorigin, im->yorigin - im->ysize);
3004 if (im->minval > 0.0)
3005 areazero = im->minval;
3006 if (im->maxval < 0.0)
3007 areazero = im->maxval;
3008 #ifdef WITH_PIECHART
3009 }
3010 #endif
3012 #ifdef WITH_PIECHART
3013 if (piechart) {
3014 pie_part(im, im->graph_col[GRC_CANVAS], im->pie_x, im->pie_y,
3015 im->piesize * 0.5, 0, 2 * M_PI);
3016 }
3017 #endif
3019 for (i = 0; i < im->gdes_c; i++) {
3020 switch (im->gdes[i].gf) {
3021 case GF_CDEF:
3022 case GF_VDEF:
3023 case GF_DEF:
3024 case GF_PRINT:
3025 case GF_GPRINT:
3026 case GF_COMMENT:
3027 case GF_HRULE:
3028 case GF_VRULE:
3029 case GF_XPORT:
3030 case GF_SHIFT:
3031 break;
3032 case GF_TICK:
3033 for (ii = 0; ii < im->xsize; ii++) {
3034 if (!isnan(im->gdes[i].p_data[ii]) &&
3035 im->gdes[i].p_data[ii] != 0.0) {
3036 if (im->gdes[i].yrule > 0) {
3037 gfx_new_line(im->canvas,
3038 im->xorigin + ii, im->yorigin,
3039 im->xorigin + ii,
3040 im->yorigin -
3041 im->gdes[i].yrule * im->ysize, 1.0,
3042 im->gdes[i].col);
3043 } else if (im->gdes[i].yrule < 0) {
3044 gfx_new_line(im->canvas,
3045 im->xorigin + ii,
3046 im->yorigin - im->ysize,
3047 im->xorigin + ii,
3048 im->yorigin - (1 -
3049 im->gdes[i].yrule) *
3050 im->ysize, 1.0, im->gdes[i].col);
3052 }
3053 }
3054 }
3055 break;
3056 case GF_LINE:
3057 case GF_AREA:
3058 /* fix data points at oo and -oo */
3059 for (ii = 0; ii < im->xsize; ii++) {
3060 if (isinf(im->gdes[i].p_data[ii])) {
3061 if (im->gdes[i].p_data[ii] > 0) {
3062 im->gdes[i].p_data[ii] = im->maxval;
3063 } else {
3064 im->gdes[i].p_data[ii] = im->minval;
3065 }
3067 }
3068 } /* for */
3070 /* *******************************************************
3071 a ___. (a,t)
3072 | | ___
3073 ____| | | |
3074 | |___|
3075 -------|--t-1--t--------------------------------
3077 if we know the value at time t was a then
3078 we draw a square from t-1 to t with the value a.
3080 ********************************************************* */
3081 if (im->gdes[i].col != 0x0) {
3082 /* GF_LINE and friend */
3083 if (im->gdes[i].gf == GF_LINE) {
3084 double last_y = 0.0;
3086 node = NULL;
3087 for (ii = 1; ii < im->xsize; ii++) {
3088 if (isnan(im->gdes[i].p_data[ii])
3089 || (im->slopemode == 1
3090 && isnan(im->gdes[i].p_data[ii - 1]))) {
3091 node = NULL;
3092 continue;
3093 }
3094 if (node == NULL) {
3095 last_y = ytr(im, im->gdes[i].p_data[ii]);
3096 if (im->slopemode == 0) {
3097 node = gfx_new_line(im->canvas,
3098 ii - 1 + im->xorigin,
3099 last_y, ii + im->xorigin,
3100 last_y,
3101 im->gdes[i].linewidth,
3102 im->gdes[i].col);
3103 } else {
3104 node = gfx_new_line(im->canvas,
3105 ii - 1 + im->xorigin,
3106 ytr(im,
3107 im->gdes[i].
3108 p_data[ii - 1]),
3109 ii + im->xorigin, last_y,
3110 im->gdes[i].linewidth,
3111 im->gdes[i].col);
3112 }
3113 } else {
3114 double new_y = ytr(im, im->gdes[i].p_data[ii]);
3116 if (im->slopemode == 0
3117 && !AlmostEqual2sComplement(new_y, last_y,
3118 4)) {
3119 gfx_add_point(node, ii - 1 + im->xorigin,
3120 new_y);
3121 };
3122 last_y = new_y;
3123 gfx_add_point(node, ii + im->xorigin, new_y);
3124 };
3126 }
3127 } else {
3128 int idxI = -1;
3129 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3130 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3131 double *backY = malloc(sizeof(double) * im->xsize * 2);
3132 double *backX = malloc(sizeof(double) * im->xsize * 2);
3133 int drawem = 0;
3135 for (ii = 0; ii <= im->xsize; ii++) {
3136 double ybase, ytop;
3138 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3139 int cntI = 1;
3140 int lastI = 0;
3142 while (cntI < idxI
3143 && AlmostEqual2sComplement(foreY[lastI],
3144 foreY[cntI], 4)
3145 && AlmostEqual2sComplement(foreY[lastI],
3146 foreY[cntI + 1],
3147 4)) {
3148 cntI++;
3149 }
3150 node = gfx_new_area(im->canvas,
3151 backX[0], backY[0],
3152 foreX[0], foreY[0],
3153 foreX[cntI], foreY[cntI],
3154 im->gdes[i].col);
3155 while (cntI < idxI) {
3156 lastI = cntI;
3157 cntI++;
3158 while (cntI < idxI
3159 &&
3160 AlmostEqual2sComplement(foreY[lastI],
3161 foreY[cntI], 4)
3162 &&
3163 AlmostEqual2sComplement(foreY[lastI],
3164 foreY[cntI +
3165 1], 4)) {
3166 cntI++;
3167 }
3168 gfx_add_point(node, foreX[cntI], foreY[cntI]);
3169 }
3170 gfx_add_point(node, backX[idxI], backY[idxI]);
3171 while (idxI > 1) {
3172 lastI = idxI;
3173 idxI--;
3174 while (idxI > 1
3175 &&
3176 AlmostEqual2sComplement(backY[lastI],
3177 backY[idxI], 4)
3178 &&
3179 AlmostEqual2sComplement(backY[lastI],
3180 backY[idxI -
3181 1], 4)) {
3182 idxI--;
3183 }
3184 gfx_add_point(node, backX[idxI], backY[idxI]);
3185 }
3186 idxI = -1;
3187 drawem = 0;
3188 }
3189 if (drawem != 0) {
3190 drawem = 0;
3191 idxI = -1;
3192 }
3193 if (ii == im->xsize)
3194 break;
3196 /* keep things simple for now, just draw these bars
3197 do not try to build a big and complex area */
3200 if (im->slopemode == 0 && ii == 0) {
3201 continue;
3202 }
3203 if (isnan(im->gdes[i].p_data[ii])) {
3204 drawem = 1;
3205 continue;
3206 }
3207 ytop = ytr(im, im->gdes[i].p_data[ii]);
3208 if (lastgdes && im->gdes[i].stack) {
3209 ybase = ytr(im, lastgdes->p_data[ii]);
3210 } else {
3211 ybase = ytr(im, areazero);
3212 }
3213 if (ybase == ytop) {
3214 drawem = 1;
3215 continue;
3216 }
3217 /* every area has to be wound clock-wise,
3218 so we have to make sur base remains base */
3219 if (ybase > ytop) {
3220 double extra = ytop;
3222 ytop = ybase;
3223 ybase = extra;
3224 }
3225 if (im->slopemode == 0) {
3226 backY[++idxI] = ybase - 0.2;
3227 backX[idxI] = ii + im->xorigin - 1;
3228 foreY[idxI] = ytop + 0.2;
3229 foreX[idxI] = ii + im->xorigin - 1;
3230 }
3231 backY[++idxI] = ybase - 0.2;
3232 backX[idxI] = ii + im->xorigin;
3233 foreY[idxI] = ytop + 0.2;
3234 foreX[idxI] = ii + im->xorigin;
3235 }
3236 /* close up any remaining area */
3237 free(foreY);
3238 free(foreX);
3239 free(backY);
3240 free(backX);
3241 } /* else GF_LINE */
3242 }
3243 /* if color != 0x0 */
3244 /* make sure we do not run into trouble when stacking on NaN */
3245 for (ii = 0; ii < im->xsize; ii++) {
3246 if (isnan(im->gdes[i].p_data[ii])) {
3247 if (lastgdes && (im->gdes[i].stack)) {
3248 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3249 } else {
3250 im->gdes[i].p_data[ii] = areazero;
3251 }
3252 }
3253 }
3254 lastgdes = &(im->gdes[i]);
3255 break;
3256 #ifdef WITH_PIECHART
3257 case GF_PART:
3258 if (isnan(im->gdes[i].yrule)) /* fetch variable */
3259 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
3261 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
3262 pie_part(im, im->gdes[i].col,
3263 im->pie_x, im->pie_y, im->piesize * 0.4,
3264 M_PI * 2.0 * PieStart / 100.0,
3265 M_PI * 2.0 * (PieStart + im->gdes[i].yrule) / 100.0);
3266 PieStart += im->gdes[i].yrule;
3267 }
3268 break;
3269 #endif
3270 case GF_STACK:
3271 rrd_set_error
3272 ("STACK should already be turned into LINE or AREA here");
3273 return -1;
3274 break;
3276 } /* switch */
3277 }
3278 #ifdef WITH_PIECHART
3279 if (piechart == 2) {
3280 im->draw_x_grid = 0;
3281 im->draw_y_grid = 0;
3282 }
3283 #endif
3286 /* grid_paint also does the text */
3287 if (!(im->extra_flags & ONLY_GRAPH))
3288 grid_paint(im);
3291 if (!(im->extra_flags & ONLY_GRAPH))
3292 axis_paint(im);
3294 /* the RULES are the last thing to paint ... */
3295 for (i = 0; i < im->gdes_c; i++) {
3297 switch (im->gdes[i].gf) {
3298 case GF_HRULE:
3299 if (im->gdes[i].yrule >= im->minval
3300 && im->gdes[i].yrule <= im->maxval)
3301 gfx_new_line(im->canvas,
3302 im->xorigin, ytr(im, im->gdes[i].yrule),
3303 im->xorigin + im->xsize, ytr(im,
3304 im->gdes[i].yrule),
3305 1.0, im->gdes[i].col);
3306 break;
3307 case GF_VRULE:
3308 if (im->gdes[i].xrule >= im->start
3309 && im->gdes[i].xrule <= im->end)
3310 gfx_new_line(im->canvas,
3311 xtr(im, im->gdes[i].xrule), im->yorigin,
3312 xtr(im, im->gdes[i].xrule),
3313 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3314 break;
3315 default:
3316 break;
3317 }
3318 }
3321 if (strcmp(im->graphfile, "-") == 0) {
3322 fo = im->graphhandle ? im->graphhandle : stdout;
3323 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3324 /* Change translation mode for stdout to BINARY */
3325 _setmode(_fileno(fo), O_BINARY);
3326 #endif
3327 } else {
3328 if ((fo = fopen(im->graphfile, "wb")) == NULL) {
3329 rrd_set_error("Opening '%s' for write: %s", im->graphfile,
3330 rrd_strerror(errno));
3331 return (-1);
3332 }
3333 }
3334 gfx_render(im->canvas, im->ximg, im->yimg, 0x00000000, fo);
3335 if (strcmp(im->graphfile, "-") != 0)
3336 fclose(fo);
3337 return 0;
3338 }
3341 /*****************************************************
3342 * graph stuff
3343 *****************************************************/
3345 int gdes_alloc(
3346 image_desc_t *im)
3347 {
3349 im->gdes_c++;
3350 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3351 * sizeof(graph_desc_t))) ==
3352 NULL) {
3353 rrd_set_error("realloc graph_descs");
3354 return -1;
3355 }
3358 im->gdes[im->gdes_c - 1].step = im->step;
3359 im->gdes[im->gdes_c - 1].step_orig = im->step;
3360 im->gdes[im->gdes_c - 1].stack = 0;
3361 im->gdes[im->gdes_c - 1].linewidth = 0;
3362 im->gdes[im->gdes_c - 1].debug = 0;
3363 im->gdes[im->gdes_c - 1].start = im->start;
3364 im->gdes[im->gdes_c - 1].start_orig = im->start;
3365 im->gdes[im->gdes_c - 1].end = im->end;
3366 im->gdes[im->gdes_c - 1].end_orig = im->end;
3367 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3368 im->gdes[im->gdes_c - 1].data = NULL;
3369 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3370 im->gdes[im->gdes_c - 1].data_first = 0;
3371 im->gdes[im->gdes_c - 1].p_data = NULL;
3372 im->gdes[im->gdes_c - 1].rpnp = NULL;
3373 im->gdes[im->gdes_c - 1].shift = 0;
3374 im->gdes[im->gdes_c - 1].col = 0x0;
3375 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3376 im->gdes[im->gdes_c - 1].format[0] = '\0';
3377 im->gdes[im->gdes_c - 1].strftm = 0;
3378 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3379 im->gdes[im->gdes_c - 1].ds = -1;
3380 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3381 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3382 im->gdes[im->gdes_c - 1].p_data = NULL;
3383 im->gdes[im->gdes_c - 1].yrule = DNAN;
3384 im->gdes[im->gdes_c - 1].xrule = 0;
3385 return 0;
3386 }
3388 /* copies input untill the first unescaped colon is found
3389 or until input ends. backslashes have to be escaped as well */
3390 int scan_for_col(
3391 const char *const input,
3392 int len,
3393 char *const output)
3394 {
3395 int inp, outp = 0;
3397 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3398 if (input[inp] == '\\' &&
3399 input[inp + 1] != '\0' &&
3400 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3401 output[outp++] = input[++inp];
3402 } else {
3403 output[outp++] = input[inp];
3404 }
3405 }
3406 output[outp] = '\0';
3407 return inp;
3408 }
3410 /* Some surgery done on this function, it became ridiculously big.
3411 ** Things moved:
3412 ** - initializing now in rrd_graph_init()
3413 ** - options parsing now in rrd_graph_options()
3414 ** - script parsing now in rrd_graph_script()
3415 */
3416 int rrd_graph(
3417 int argc,
3418 char **argv,
3419 char ***prdata,
3420 int *xsize,
3421 int *ysize,
3422 FILE * stream,
3423 double *ymin,
3424 double *ymax)
3425 {
3426 image_desc_t im;
3428 rrd_graph_init(&im);
3429 im.graphhandle = stream;
3431 rrd_graph_options(argc, argv, &im);
3432 if (rrd_test_error()) {
3433 im_free(&im);
3434 return -1;
3435 }
3437 if (strlen(argv[optind]) >= MAXPATH) {
3438 rrd_set_error("filename (including path) too long");
3439 im_free(&im);
3440 return -1;
3441 }
3442 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3443 im.graphfile[MAXPATH - 1] = '\0';
3445 rrd_graph_script(argc, argv, &im, 1);
3446 if (rrd_test_error()) {
3447 im_free(&im);
3448 return -1;
3449 }
3451 /* Everything is now read and the actual work can start */
3453 (*prdata) = NULL;
3454 if (graph_paint(&im, prdata) == -1) {
3455 im_free(&im);
3456 return -1;
3457 }
3459 /* The image is generated and needs to be output.
3460 ** Also, if needed, print a line with information about the image.
3461 */
3463 *xsize = im.ximg;
3464 *ysize = im.yimg;
3465 *ymin = im.minval;
3466 *ymax = im.maxval;
3467 if (im.imginfo) {
3468 char *filename;
3470 if (!(*prdata)) {
3471 /* maybe prdata is not allocated yet ... lets do it now */
3472 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3473 rrd_set_error("malloc imginfo");
3474 return -1;
3475 };
3476 }
3477 if (((*prdata)[0] =
3478 malloc((strlen(im.imginfo) + 200 +
3479 strlen(im.graphfile)) * sizeof(char)))
3480 == NULL) {
3481 rrd_set_error("malloc imginfo");
3482 return -1;
3483 }
3484 filename = im.graphfile + strlen(im.graphfile);
3485 while (filename > im.graphfile) {
3486 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3487 break;
3488 filename--;
3489 }
3491 sprintf((*prdata)[0], im.imginfo, filename,
3492 (long) (im.canvas->zoom * im.ximg),
3493 (long) (im.canvas->zoom * im.yimg));
3494 }
3495 im_free(&im);
3496 return 0;
3497 }
3499 void rrd_graph_init(
3500 image_desc_t *im)
3501 {
3502 unsigned int i;
3504 #ifdef HAVE_TZSET
3505 tzset();
3506 #endif
3507 #ifdef HAVE_SETLOCALE
3508 setlocale(LC_TIME, "");
3509 #ifdef HAVE_MBSTOWCS
3510 setlocale(LC_CTYPE, "");
3511 #endif
3512 #endif
3513 im->yorigin = 0;
3514 im->xorigin = 0;
3515 im->minval = 0;
3516 im->xlab_user.minsec = -1;
3517 im->ximg = 0;
3518 im->yimg = 0;
3519 im->xsize = 400;
3520 im->ysize = 100;
3521 im->step = 0;
3522 im->ylegend[0] = '\0';
3523 im->title[0] = '\0';
3524 im->watermark[0] = '\0';
3525 im->minval = DNAN;
3526 im->maxval = DNAN;
3527 im->unitsexponent = 9999;
3528 im->unitslength = 6;
3529 im->forceleftspace = 0;
3530 im->symbol = ' ';
3531 im->viewfactor = 1.0;
3532 im->extra_flags = 0;
3533 im->rigid = 0;
3534 im->gridfit = 1;
3535 im->imginfo = NULL;
3536 im->lazy = 0;
3537 im->slopemode = 0;
3538 im->logarithmic = 0;
3539 im->ygridstep = DNAN;
3540 im->draw_x_grid = 1;
3541 im->draw_y_grid = 1;
3542 im->base = 1000;
3543 im->prt_c = 0;
3544 im->gdes_c = 0;
3545 im->gdes = NULL;
3546 im->canvas = gfx_new_canvas();
3547 im->grid_dash_on = 1;
3548 im->grid_dash_off = 1;
3549 im->tabwidth = 40.0;
3551 for (i = 0; i < DIM(graph_col); i++)
3552 im->graph_col[i] = graph_col[i];
3554 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3555 {
3556 char *windir;
3557 char rrd_win_default_font[1000];
3559 windir = getenv("windir");
3560 /* %windir% is something like D:\windows or C:\winnt */
3561 if (windir != NULL) {
3562 strncpy(rrd_win_default_font, windir, 500);
3563 rrd_win_default_font[500] = '\0';
3564 strcat(rrd_win_default_font, "\\fonts\\");
3565 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3566 for (i = 0; i < DIM(text_prop); i++) {
3567 strncpy(text_prop[i].font, rrd_win_default_font,
3568 sizeof(text_prop[i].font) - 1);
3569 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3570 }
3571 }
3572 }
3573 #endif
3574 {
3575 char *deffont;
3577 deffont = getenv("RRD_DEFAULT_FONT");
3578 if (deffont != NULL) {
3579 for (i = 0; i < DIM(text_prop); i++) {
3580 strncpy(text_prop[i].font, deffont,
3581 sizeof(text_prop[i].font) - 1);
3582 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3583 }
3584 }
3585 }
3586 for (i = 0; i < DIM(text_prop); i++) {
3587 im->text_prop[i].size = text_prop[i].size;
3588 strcpy(im->text_prop[i].font, text_prop[i].font);
3589 }
3590 }
3592 void rrd_graph_options(
3593 int argc,
3594 char *argv[],
3595 image_desc_t *im)
3596 {
3597 int stroff;
3598 char *parsetime_error = NULL;
3599 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3600 time_t start_tmp = 0, end_tmp = 0;
3601 long long_tmp;
3602 struct rrd_time_value start_tv, end_tv;
3603 gfx_color_t color;
3605 optind = 0;
3606 opterr = 0; /* initialize getopt */
3608 parsetime("end-24h", &start_tv);
3609 parsetime("now", &end_tv);
3611 /* defines for long options without a short equivalent. should be bytes,
3612 and may not collide with (the ASCII value of) short options */
3613 #define LONGOPT_UNITS_SI 255
3615 while (1) {
3616 static struct option long_options[] = {
3617 {"start", required_argument, 0, 's'},
3618 {"end", required_argument, 0, 'e'},
3619 {"x-grid", required_argument, 0, 'x'},
3620 {"y-grid", required_argument, 0, 'y'},
3621 {"vertical-label", required_argument, 0, 'v'},
3622 {"width", required_argument, 0, 'w'},
3623 {"height", required_argument, 0, 'h'},
3624 {"full-size-mode", no_argument, 0, 'D'},
3625 {"interlaced", no_argument, 0, 'i'},
3626 {"upper-limit", required_argument, 0, 'u'},
3627 {"lower-limit", required_argument, 0, 'l'},
3628 {"rigid", no_argument, 0, 'r'},
3629 {"base", required_argument, 0, 'b'},
3630 {"logarithmic", no_argument, 0, 'o'},
3631 {"color", required_argument, 0, 'c'},
3632 {"font", required_argument, 0, 'n'},
3633 {"title", required_argument, 0, 't'},
3634 {"imginfo", required_argument, 0, 'f'},
3635 {"imgformat", required_argument, 0, 'a'},
3636 {"lazy", no_argument, 0, 'z'},
3637 {"zoom", required_argument, 0, 'm'},
3638 {"no-legend", no_argument, 0, 'g'},
3639 {"force-rules-legend", no_argument, 0, 'F'},
3640 {"only-graph", no_argument, 0, 'j'},
3641 {"alt-y-grid", no_argument, 0, 'Y'},
3642 {"no-minor", no_argument, 0, 'I'},
3643 {"slope-mode", no_argument, 0, 'E'},
3644 {"alt-autoscale", no_argument, 0, 'A'},
3645 {"alt-autoscale-min", no_argument, 0, 'J'},
3646 {"alt-autoscale-max", no_argument, 0, 'M'},
3647 {"no-gridfit", no_argument, 0, 'N'},
3648 {"units-exponent", required_argument, 0, 'X'},
3649 {"units-length", required_argument, 0, 'L'},
3650 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3651 {"step", required_argument, 0, 'S'},
3652 {"tabwidth", required_argument, 0, 'T'},
3653 {"font-render-mode", required_argument, 0, 'R'},
3654 {"font-smoothing-threshold", required_argument, 0, 'B'},
3655 {"watermark", required_argument, 0, 'W'},
3656 {"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 */
3657 {0, 0, 0, 0}
3658 };
3659 int option_index = 0;
3660 int opt;
3661 int col_start, col_end;
3663 opt = getopt_long(argc, argv,
3664 "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:",
3665 long_options, &option_index);
3667 if (opt == EOF)
3668 break;
3670 switch (opt) {
3671 case 'I':
3672 im->extra_flags |= NOMINOR;
3673 break;
3674 case 'Y':
3675 im->extra_flags |= ALTYGRID;
3676 break;
3677 case 'A':
3678 im->extra_flags |= ALTAUTOSCALE;
3679 break;
3680 case 'J':
3681 im->extra_flags |= ALTAUTOSCALE_MIN;
3682 break;
3683 case 'M':
3684 im->extra_flags |= ALTAUTOSCALE_MAX;
3685 break;
3686 case 'j':
3687 im->extra_flags |= ONLY_GRAPH;
3688 break;
3689 case 'g':
3690 im->extra_flags |= NOLEGEND;
3691 break;
3692 case 'F':
3693 im->extra_flags |= FORCE_RULES_LEGEND;
3694 break;
3695 case LONGOPT_UNITS_SI:
3696 if (im->extra_flags & FORCE_UNITS) {
3697 rrd_set_error("--units can only be used once!");
3698 return;
3699 }
3700 if (strcmp(optarg, "si") == 0)
3701 im->extra_flags |= FORCE_UNITS_SI;
3702 else {
3703 rrd_set_error("invalid argument for --units: %s", optarg);
3704 return;
3705 }
3706 break;
3707 case 'X':
3708 im->unitsexponent = atoi(optarg);
3709 break;
3710 case 'L':
3711 im->unitslength = atoi(optarg);
3712 im->forceleftspace = 1;
3713 break;
3714 case 'T':
3715 im->tabwidth = atof(optarg);
3716 break;
3717 case 'S':
3718 im->step = atoi(optarg);
3719 break;
3720 case 'N':
3721 im->gridfit = 0;
3722 break;
3723 case 's':
3724 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3725 rrd_set_error("start time: %s", parsetime_error);
3726 return;
3727 }
3728 break;
3729 case 'e':
3730 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3731 rrd_set_error("end time: %s", parsetime_error);
3732 return;
3733 }
3734 break;
3735 case 'x':
3736 if (strcmp(optarg, "none") == 0) {
3737 im->draw_x_grid = 0;
3738 break;
3739 };
3741 if (sscanf(optarg,
3742 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3743 scan_gtm,
3744 &im->xlab_user.gridst,
3745 scan_mtm,
3746 &im->xlab_user.mgridst,
3747 scan_ltm,
3748 &im->xlab_user.labst,
3749 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3750 strncpy(im->xlab_form, optarg + stroff,
3751 sizeof(im->xlab_form) - 1);
3752 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3753 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3754 rrd_set_error("unknown keyword %s", scan_gtm);
3755 return;
3756 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3757 == -1) {
3758 rrd_set_error("unknown keyword %s", scan_mtm);
3759 return;
3760 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3761 -1) {
3762 rrd_set_error("unknown keyword %s", scan_ltm);
3763 return;
3764 }
3765 im->xlab_user.minsec = 1;
3766 im->xlab_user.stst = im->xlab_form;
3767 } else {
3768 rrd_set_error("invalid x-grid format");
3769 return;
3770 }
3771 break;
3772 case 'y':
3774 if (strcmp(optarg, "none") == 0) {
3775 im->draw_y_grid = 0;
3776 break;
3777 };
3779 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3780 if (im->ygridstep <= 0) {
3781 rrd_set_error("grid step must be > 0");
3782 return;
3783 } else if (im->ylabfact < 1) {
3784 rrd_set_error("label factor must be > 0");
3785 return;
3786 }
3787 } else {
3788 rrd_set_error("invalid y-grid format");
3789 return;
3790 }
3791 break;
3792 case 'v':
3793 strncpy(im->ylegend, optarg, 150);
3794 im->ylegend[150] = '\0';
3795 break;
3796 case 'u':
3797 im->maxval = atof(optarg);
3798 break;
3799 case 'l':
3800 im->minval = atof(optarg);
3801 break;
3802 case 'b':
3803 im->base = atol(optarg);
3804 if (im->base != 1024 && im->base != 1000) {
3805 rrd_set_error
3806 ("the only sensible value for base apart from 1000 is 1024");
3807 return;
3808 }
3809 break;
3810 case 'w':
3811 long_tmp = atol(optarg);
3812 if (long_tmp < 10) {
3813 rrd_set_error("width below 10 pixels");
3814 return;
3815 }
3816 im->xsize = long_tmp;
3817 break;
3818 case 'h':
3819 long_tmp = atol(optarg);
3820 if (long_tmp < 10) {
3821 rrd_set_error("height below 10 pixels");
3822 return;
3823 }
3824 im->ysize = long_tmp;
3825 break;
3826 case 'D':
3827 im->extra_flags |= FULL_SIZE_MODE;
3828 break;
3829 case 'i':
3830 im->canvas->interlaced = 1;
3831 break;
3832 case 'r':
3833 im->rigid = 1;
3834 break;
3835 case 'f':
3836 im->imginfo = optarg;
3837 break;
3838 case 'a':
3839 if ((int) (im->canvas->imgformat = if_conv(optarg)) == -1) {
3840 rrd_set_error("unsupported graphics format '%s'", optarg);
3841 return;
3842 }
3843 break;
3844 case 'z':
3845 im->lazy = 1;
3846 break;
3847 case 'E':
3848 im->slopemode = 1;
3849 break;
3851 case 'o':
3852 im->logarithmic = 1;
3853 break;
3854 case 'c':
3855 if (sscanf(optarg,
3856 "%10[A-Z]#%n%8lx%n",
3857 col_nam, &col_start, &color, &col_end) == 2) {
3858 int ci;
3859 int col_len = col_end - col_start;
3861 switch (col_len) {
3862 case 3:
3863 color = (((color & 0xF00) * 0x110000) |
3864 ((color & 0x0F0) * 0x011000) |
3865 ((color & 0x00F) * 0x001100) | 0x000000FF);
3866 break;
3867 case 4:
3868 color = (((color & 0xF000) * 0x11000) |
3869 ((color & 0x0F00) * 0x01100) |
3870 ((color & 0x00F0) * 0x00110) |
3871 ((color & 0x000F) * 0x00011)
3872 );
3873 break;
3874 case 6:
3875 color = (color << 8) + 0xff /* shift left by 8 */ ;
3876 break;
3877 case 8:
3878 break;
3879 default:
3880 rrd_set_error("the color format is #RRGGBB[AA]");
3881 return;
3882 }
3883 if ((ci = grc_conv(col_nam)) != -1) {
3884 im->graph_col[ci] = color;
3885 } else {
3886 rrd_set_error("invalid color name '%s'", col_nam);
3887 return;
3888 }
3889 } else {
3890 rrd_set_error("invalid color def format");
3891 return;
3892 }
3893 break;
3894 case 'n':{
3895 char prop[15];
3896 double size = 1;
3897 char font[1024] = "";
3899 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3900 int sindex, propidx;
3902 if ((sindex = text_prop_conv(prop)) != -1) {
3903 for (propidx = sindex; propidx < TEXT_PROP_LAST;
3904 propidx++) {
3905 if (size > 0) {
3906 im->text_prop[propidx].size = size;
3907 }
3908 if (strlen(font) > 0) {
3909 strcpy(im->text_prop[propidx].font, font);
3910 }
3911 if (propidx == sindex && sindex != 0)
3912 break;
3913 }
3914 } else {
3915 rrd_set_error("invalid fonttag '%s'", prop);
3916 return;
3917 }
3918 } else {
3919 rrd_set_error("invalid text property format");
3920 return;
3921 }
3922 break;
3923 }
3924 case 'm':
3925 im->canvas->zoom = atof(optarg);
3926 if (im->canvas->zoom <= 0.0) {
3927 rrd_set_error("zoom factor must be > 0");
3928 return;
3929 }
3930 break;
3931 case 't':
3932 strncpy(im->title, optarg, 150);
3933 im->title[150] = '\0';
3934 break;
3936 case 'R':
3937 if (strcmp(optarg, "normal") == 0)
3938 im->canvas->aa_type = AA_NORMAL;
3939 else if (strcmp(optarg, "light") == 0)
3940 im->canvas->aa_type = AA_LIGHT;
3941 else if (strcmp(optarg, "mono") == 0)
3942 im->canvas->aa_type = AA_NONE;
3943 else {
3944 rrd_set_error("unknown font-render-mode '%s'", optarg);
3945 return;
3946 }
3947 break;
3949 case 'B':
3950 im->canvas->font_aa_threshold = atof(optarg);
3951 break;
3953 case 'W':
3954 strncpy(im->watermark, optarg, 100);
3955 im->watermark[99] = '\0';
3956 break;
3958 case '?':
3959 if (optopt != 0)
3960 rrd_set_error("unknown option '%c'", optopt);
3961 else
3962 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3963 return;
3964 }
3965 }
3967 if (optind >= argc) {
3968 rrd_set_error("missing filename");
3969 return;
3970 }
3972 if (im->logarithmic == 1 && im->minval <= 0) {
3973 rrd_set_error
3974 ("for a logarithmic yaxis you must specify a lower-limit > 0");
3975 return;
3976 }
3978 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3979 /* error string is set in parsetime.c */
3980 return;
3981 }
3983 if (start_tmp < 3600 * 24 * 365 * 10) {
3984 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3985 start_tmp);
3986 return;
3987 }
3989 if (end_tmp < start_tmp) {
3990 rrd_set_error("start (%ld) should be less than end (%ld)",
3991 start_tmp, end_tmp);
3992 return;
3993 }
3995 im->start = start_tmp;
3996 im->end = end_tmp;
3997 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3998 }
4000 int rrd_graph_color(
4001 image_desc_t *im,
4002 char *var,
4003 char *err,
4004 int optional)
4005 {
4006 char *color;
4007 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4009 color = strstr(var, "#");
4010 if (color == NULL) {
4011 if (optional == 0) {
4012 rrd_set_error("Found no color in %s", err);
4013 return 0;
4014 }
4015 return 0;
4016 } else {
4017 int n = 0;
4018 char *rest;
4019 gfx_color_t col;
4021 rest = strstr(color, ":");
4022 if (rest != NULL)
4023 n = rest - color;
4024 else
4025 n = strlen(color);
4027 switch (n) {
4028 case 7:
4029 sscanf(color, "#%6lx%n", &col, &n);
4030 col = (col << 8) + 0xff /* shift left by 8 */ ;
4031 if (n != 7)
4032 rrd_set_error("Color problem in %s", err);
4033 break;
4034 case 9:
4035 sscanf(color, "#%8lx%n", &col, &n);
4036 if (n == 9)
4037 break;
4038 default:
4039 rrd_set_error("Color problem in %s", err);
4040 }
4041 if (rrd_test_error())
4042 return 0;
4043 gdp->col = col;
4044 return n;
4045 }
4046 }
4049 int bad_format(
4050 char *fmt)
4051 {
4052 char *ptr;
4053 int n = 0;
4055 ptr = fmt;
4056 while (*ptr != '\0')
4057 if (*ptr++ == '%') {
4059 /* line cannot end with percent char */
4060 if (*ptr == '\0')
4061 return 1;
4063 /* '%s', '%S' and '%%' are allowed */
4064 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4065 ptr++;
4067 /* %c is allowed (but use only with vdef!) */
4068 else if (*ptr == 'c') {
4069 ptr++;
4070 n = 1;
4071 }
4073 /* or else '% 6.2lf' and such are allowed */
4074 else {
4075 /* optional padding character */
4076 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4077 ptr++;
4079 /* This should take care of 'm.n' with all three optional */
4080 while (*ptr >= '0' && *ptr <= '9')
4081 ptr++;
4082 if (*ptr == '.')
4083 ptr++;
4084 while (*ptr >= '0' && *ptr <= '9')
4085 ptr++;
4087 /* Either 'le', 'lf' or 'lg' must follow here */
4088 if (*ptr++ != 'l')
4089 return 1;
4090 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4091 ptr++;
4092 else
4093 return 1;
4094 n++;
4095 }
4096 }
4098 return (n != 1);
4099 }
4102 int vdef_parse(
4103 gdes,
4104 str)
4105 struct graph_desc_t *gdes;
4106 const char *const str;
4107 {
4108 /* A VDEF currently is either "func" or "param,func"
4109 * so the parsing is rather simple. Change if needed.
4110 */
4111 double param;
4112 char func[30];
4113 int n;
4115 n = 0;
4116 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4117 if (n == (int) strlen(str)) { /* matched */
4118 ;
4119 } else {
4120 n = 0;
4121 sscanf(str, "%29[A-Z]%n", func, &n);
4122 if (n == (int) strlen(str)) { /* matched */
4123 param = DNAN;
4124 } else {
4125 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4126 gdes->vname);
4127 return -1;
4128 }
4129 }
4130 if (!strcmp("PERCENT", func))
4131 gdes->vf.op = VDEF_PERCENT;
4132 else if (!strcmp("MAXIMUM", func))
4133 gdes->vf.op = VDEF_MAXIMUM;
4134 else if (!strcmp("AVERAGE", func))
4135 gdes->vf.op = VDEF_AVERAGE;
4136 else if (!strcmp("MINIMUM", func))
4137 gdes->vf.op = VDEF_MINIMUM;
4138 else if (!strcmp("TOTAL", func))
4139 gdes->vf.op = VDEF_TOTAL;
4140 else if (!strcmp("FIRST", func))
4141 gdes->vf.op = VDEF_FIRST;
4142 else if (!strcmp("LAST", func))
4143 gdes->vf.op = VDEF_LAST;
4144 else if (!strcmp("LSLSLOPE", func))
4145 gdes->vf.op = VDEF_LSLSLOPE;
4146 else if (!strcmp("LSLINT", func))
4147 gdes->vf.op = VDEF_LSLINT;
4148 else if (!strcmp("LSLCORREL", func))
4149 gdes->vf.op = VDEF_LSLCORREL;
4150 else {
4151 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4152 gdes->vname);
4153 return -1;
4154 };
4156 switch (gdes->vf.op) {
4157 case VDEF_PERCENT:
4158 if (isnan(param)) { /* no parameter given */
4159 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4160 func, gdes->vname);
4161 return -1;
4162 };
4163 if (param >= 0.0 && param <= 100.0) {
4164 gdes->vf.param = param;
4165 gdes->vf.val = DNAN; /* undefined */
4166 gdes->vf.when = 0; /* undefined */
4167 } else {
4168 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4169 gdes->vname);
4170 return -1;
4171 };
4172 break;
4173 case VDEF_MAXIMUM:
4174 case VDEF_AVERAGE:
4175 case VDEF_MINIMUM:
4176 case VDEF_TOTAL:
4177 case VDEF_FIRST:
4178 case VDEF_LAST:
4179 case VDEF_LSLSLOPE:
4180 case VDEF_LSLINT:
4181 case VDEF_LSLCORREL:
4182 if (isnan(param)) {
4183 gdes->vf.param = DNAN;
4184 gdes->vf.val = DNAN;
4185 gdes->vf.when = 0;
4186 } else {
4187 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4188 func, gdes->vname);
4189 return -1;
4190 };
4191 break;
4192 };
4193 return 0;
4194 }
4197 int vdef_calc(
4198 im,
4199 gdi)
4200 image_desc_t *im;
4201 int gdi;
4202 {
4203 graph_desc_t *src, *dst;
4204 rrd_value_t *data;
4205 long step, steps;
4207 dst = &im->gdes[gdi];
4208 src = &im->gdes[dst->vidx];
4209 data = src->data + src->ds;
4210 steps = (src->end - src->start) / src->step;
4212 #if 0
4213 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4214 src->end, steps);
4215 #endif
4217 switch (dst->vf.op) {
4218 case VDEF_PERCENT:{
4219 rrd_value_t *array;
4220 int field;
4223 if ((array = malloc(steps * sizeof(double))) == NULL) {
4224 rrd_set_error("malloc VDEV_PERCENT");
4225 return -1;
4226 }
4227 for (step = 0; step < steps; step++) {
4228 array[step] = data[step * src->ds_cnt];
4229 }
4230 qsort(array, step, sizeof(double), vdef_percent_compar);
4232 field = (steps - 1) * dst->vf.param / 100;
4233 dst->vf.val = array[field];
4234 dst->vf.when = 0; /* no time component */
4235 free(array);
4236 #if 0
4237 for (step = 0; step < steps; step++)
4238 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4239 step == field ? '*' : ' ');
4240 #endif
4241 }
4242 break;
4243 case VDEF_MAXIMUM:
4244 step = 0;
4245 while (step != steps && isnan(data[step * src->ds_cnt]))
4246 step++;
4247 if (step == steps) {
4248 dst->vf.val = DNAN;
4249 dst->vf.when = 0;
4250 } else {
4251 dst->vf.val = data[step * src->ds_cnt];
4252 dst->vf.when = src->start + (step + 1) * src->step;
4253 }
4254 while (step != steps) {
4255 if (finite(data[step * src->ds_cnt])) {
4256 if (data[step * src->ds_cnt] > dst->vf.val) {
4257 dst->vf.val = data[step * src->ds_cnt];
4258 dst->vf.when = src->start + (step + 1) * src->step;
4259 }
4260 }
4261 step++;
4262 }
4263 break;
4264 case VDEF_TOTAL:
4265 case VDEF_AVERAGE:{
4266 int cnt = 0;
4267 double sum = 0.0;
4269 for (step = 0; step < steps; step++) {
4270 if (finite(data[step * src->ds_cnt])) {
4271 sum += data[step * src->ds_cnt];
4272 cnt++;
4273 };
4274 }
4275 if (cnt) {
4276 if (dst->vf.op == VDEF_TOTAL) {
4277 dst->vf.val = sum * src->step;
4278 dst->vf.when = 0; /* no time component */
4279 } else {
4280 dst->vf.val = sum / cnt;
4281 dst->vf.when = 0; /* no time component */
4282 };
4283 } else {
4284 dst->vf.val = DNAN;
4285 dst->vf.when = 0;
4286 }
4287 }
4288 break;
4289 case VDEF_MINIMUM:
4290 step = 0;
4291 while (step != steps && isnan(data[step * src->ds_cnt]))
4292 step++;
4293 if (step == steps) {
4294 dst->vf.val = DNAN;
4295 dst->vf.when = 0;
4296 } else {
4297 dst->vf.val = data[step * src->ds_cnt];
4298 dst->vf.when = src->start + (step + 1) * src->step;
4299 }
4300 while (step != steps) {
4301 if (finite(data[step * src->ds_cnt])) {
4302 if (data[step * src->ds_cnt] < dst->vf.val) {
4303 dst->vf.val = data[step * src->ds_cnt];
4304 dst->vf.when = src->start + (step + 1) * src->step;
4305 }
4306 }
4307 step++;
4308 }
4309 break;
4310 case VDEF_FIRST:
4311 /* The time value returned here is one step before the
4312 * actual time value. This is the start of the first
4313 * non-NaN interval.
4314 */
4315 step = 0;
4316 while (step != steps && isnan(data[step * src->ds_cnt]))
4317 step++;
4318 if (step == steps) { /* all entries were NaN */
4319 dst->vf.val = DNAN;
4320 dst->vf.when = 0;
4321 } else {
4322 dst->vf.val = data[step * src->ds_cnt];
4323 dst->vf.when = src->start + step * src->step;
4324 }
4325 break;
4326 case VDEF_LAST:
4327 /* The time value returned here is the
4328 * actual time value. This is the end of the last
4329 * non-NaN interval.
4330 */
4331 step = steps - 1;
4332 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4333 step--;
4334 if (step < 0) { /* all entries were NaN */
4335 dst->vf.val = DNAN;
4336 dst->vf.when = 0;
4337 } else {
4338 dst->vf.val = data[step * src->ds_cnt];
4339 dst->vf.when = src->start + (step + 1) * src->step;
4340 }
4341 break;
4342 case VDEF_LSLSLOPE:
4343 case VDEF_LSLINT:
4344 case VDEF_LSLCORREL:{
4345 /* Bestfit line by linear least squares method */
4347 int cnt = 0;
4348 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4350 SUMx = 0;
4351 SUMy = 0;
4352 SUMxy = 0;
4353 SUMxx = 0;
4354 SUMyy = 0;
4356 for (step = 0; step < steps; step++) {
4357 if (finite(data[step * src->ds_cnt])) {
4358 cnt++;
4359 SUMx += step;
4360 SUMxx += step * step;
4361 SUMxy += step * data[step * src->ds_cnt];
4362 SUMy += data[step * src->ds_cnt];
4363 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4364 };
4365 }
4367 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4368 y_intercept = (SUMy - slope * SUMx) / cnt;
4369 correl =
4370 (SUMxy -
4371 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4372 (SUMx * SUMx) / cnt) * (SUMyy -
4373 (SUMy *
4374 SUMy) /
4375 cnt));
4377 if (cnt) {
4378 if (dst->vf.op == VDEF_LSLSLOPE) {
4379 dst->vf.val = slope;
4380 dst->vf.when = 0;
4381 } else if (dst->vf.op == VDEF_LSLINT) {
4382 dst->vf.val = y_intercept;
4383 dst->vf.when = 0;
4384 } else if (dst->vf.op == VDEF_LSLCORREL) {
4385 dst->vf.val = correl;
4386 dst->vf.when = 0;
4387 };
4389 } else {
4390 dst->vf.val = DNAN;
4391 dst->vf.when = 0;
4392 }
4393 }
4394 break;
4395 }
4396 return 0;
4397 }
4399 /* NaN < -INF < finite_values < INF */
4400 int vdef_percent_compar(
4401 a,
4402 b)
4403 const void *a, *b;
4404 {
4405 /* Equality is not returned; this doesn't hurt except
4406 * (maybe) for a little performance.
4407 */
4409 /* First catch NaN values. They are smallest */
4410 if (isnan(*(double *) a))
4411 return -1;
4412 if (isnan(*(double *) b))
4413 return 1;
4415 /* NaN doesn't reach this part so INF and -INF are extremes.
4416 * The sign from isinf() is compatible with the sign we return
4417 */
4418 if (isinf(*(double *) a))
4419 return isinf(*(double *) a);
4420 if (isinf(*(double *) b))
4421 return isinf(*(double *) b);
4423 /* If we reach this, both values must be finite */
4424 if (*(double *) a < *(double *) b)
4425 return -1;
4426 else
4427 return 1;
4428 }