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