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