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