fb3c7e9c1381d29f519f90f6c6e3f567a25624f3
1 /****************************************************************************
2 * RRDtool 1.2.99907080300 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 = 0;
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 if (im->cr) {
333 status = cairo_status(im->cr);
334 cairo_destroy(im->cr);
335 }
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_MHWPREDICT:
721 case CF_DEVSEASONAL:
722 case CF_DEVPREDICT:
723 case CF_SEASONAL:
724 case CF_AVERAGE:
725 newval += srcptr[i * (*ds_cnt) + col];
726 break;
727 case CF_MINIMUM:
728 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
729 break;
730 case CF_FAILURES:
731 /* an interval contains a failure if any subintervals contained a failure */
732 case CF_MAXIMUM:
733 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
734 break;
735 case CF_LAST:
736 newval = srcptr[i * (*ds_cnt) + col];
737 break;
738 }
739 }
740 }
741 if (validval == 0) {
742 newval = DNAN;
743 } else {
744 switch (cf) {
745 case CF_HWPREDICT:
746 case CF_MHWPREDICT:
747 case CF_DEVSEASONAL:
748 case CF_DEVPREDICT:
749 case CF_SEASONAL:
750 case CF_AVERAGE:
751 newval /= validval;
752 break;
753 case CF_MINIMUM:
754 case CF_FAILURES:
755 case CF_MAXIMUM:
756 case CF_LAST:
757 break;
758 }
759 }
760 *dstptr++ = newval;
761 }
762 srcptr += (*ds_cnt) * reduce_factor;
763 row_cnt -= reduce_factor;
764 }
765 /* If we had to alter the endtime, we didn't have enough
766 ** source rows to fill the last row. Fill it with NaN.
767 */
768 if (end_offset)
769 for (col = 0; col < (*ds_cnt); col++)
770 *dstptr++ = DNAN;
771 #ifdef DEBUG_REDUCE
772 row_cnt = ((*end) - (*start)) / *step;
773 srcptr = *data;
774 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
775 row_cnt, *start, *end, *step);
776 for (col = 0; col < row_cnt; col++) {
777 printf("time %10lu: ", *start + (col + 1) * (*step));
778 for (i = 0; i < *ds_cnt; i++)
779 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
780 printf("\n");
781 }
782 #endif
783 }
786 /* get the data required for the graphs from the
787 relevant rrds ... */
789 int data_fetch(
790 image_desc_t *im)
791 {
792 int i, ii;
793 int skip;
795 /* pull the data from the rrd files ... */
796 for (i = 0; i < (int) im->gdes_c; i++) {
797 /* only GF_DEF elements fetch data */
798 if (im->gdes[i].gf != GF_DEF)
799 continue;
801 skip = 0;
802 /* do we have it already ? */
803 for (ii = 0; ii < i; ii++) {
804 if (im->gdes[ii].gf != GF_DEF)
805 continue;
806 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
807 && (im->gdes[i].cf == im->gdes[ii].cf)
808 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
809 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
810 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
811 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
812 /* OK, the data is already there.
813 ** Just copy the header portion
814 */
815 im->gdes[i].start = im->gdes[ii].start;
816 im->gdes[i].end = im->gdes[ii].end;
817 im->gdes[i].step = im->gdes[ii].step;
818 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
819 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
820 im->gdes[i].data = im->gdes[ii].data;
821 im->gdes[i].data_first = 0;
822 skip = 1;
823 }
824 if (skip)
825 break;
826 }
827 if (!skip) {
828 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
830 if ((rrd_fetch_fn(im->gdes[i].rrd,
831 im->gdes[i].cf,
832 &im->gdes[i].start,
833 &im->gdes[i].end,
834 &ft_step,
835 &im->gdes[i].ds_cnt,
836 &im->gdes[i].ds_namv,
837 &im->gdes[i].data)) == -1) {
838 return -1;
839 }
840 im->gdes[i].data_first = 1;
842 if (ft_step < im->gdes[i].step) {
843 reduce_data(im->gdes[i].cf_reduce,
844 ft_step,
845 &im->gdes[i].start,
846 &im->gdes[i].end,
847 &im->gdes[i].step,
848 &im->gdes[i].ds_cnt, &im->gdes[i].data);
849 } else {
850 im->gdes[i].step = ft_step;
851 }
852 }
854 /* lets see if the required data source is really there */
855 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
856 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
857 im->gdes[i].ds = ii;
858 }
859 }
860 if (im->gdes[i].ds == -1) {
861 rrd_set_error("No DS called '%s' in '%s'",
862 im->gdes[i].ds_nam, im->gdes[i].rrd);
863 return -1;
864 }
866 }
867 return 0;
868 }
870 /* evaluate the expressions in the CDEF functions */
872 /*************************************************************
873 * CDEF stuff
874 *************************************************************/
876 long find_var_wrapper(
877 void *arg1,
878 char *key)
879 {
880 return find_var((image_desc_t *) arg1, key);
881 }
883 /* find gdes containing var*/
884 long find_var(
885 image_desc_t *im,
886 char *key)
887 {
888 long ii;
890 for (ii = 0; ii < im->gdes_c - 1; ii++) {
891 if ((im->gdes[ii].gf == GF_DEF
892 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
893 && (strcmp(im->gdes[ii].vname, key) == 0)) {
894 return ii;
895 }
896 }
897 return -1;
898 }
900 /* find the largest common denominator for all the numbers
901 in the 0 terminated num array */
902 long lcd(
903 long *num)
904 {
905 long rest;
906 int i;
908 for (i = 0; num[i + 1] != 0; i++) {
909 do {
910 rest = num[i] % num[i + 1];
911 num[i] = num[i + 1];
912 num[i + 1] = rest;
913 } while (rest != 0);
914 num[i + 1] = num[i];
915 }
916 /* return i==0?num[i]:num[i-1]; */
917 return num[i];
918 }
920 /* run the rpn calculator on all the VDEF and CDEF arguments */
921 int data_calc(
922 image_desc_t *im)
923 {
925 int gdi;
926 int dataidx;
927 long *steparray, rpi;
928 int stepcnt;
929 time_t now;
930 rpnstack_t rpnstack;
932 rpnstack_init(&rpnstack);
934 for (gdi = 0; gdi < im->gdes_c; gdi++) {
935 /* Look for GF_VDEF and GF_CDEF in the same loop,
936 * so CDEFs can use VDEFs and vice versa
937 */
938 switch (im->gdes[gdi].gf) {
939 case GF_XPORT:
940 break;
941 case GF_SHIFT:{
942 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
944 /* remove current shift */
945 vdp->start -= vdp->shift;
946 vdp->end -= vdp->shift;
948 /* vdef */
949 if (im->gdes[gdi].shidx >= 0)
950 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
951 /* constant */
952 else
953 vdp->shift = im->gdes[gdi].shval;
955 /* normalize shift to multiple of consolidated step */
956 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
958 /* apply shift */
959 vdp->start += vdp->shift;
960 vdp->end += vdp->shift;
961 break;
962 }
963 case GF_VDEF:
964 /* A VDEF has no DS. This also signals other parts
965 * of rrdtool that this is a VDEF value, not a CDEF.
966 */
967 im->gdes[gdi].ds_cnt = 0;
968 if (vdef_calc(im, gdi)) {
969 rrd_set_error("Error processing VDEF '%s'",
970 im->gdes[gdi].vname);
971 rpnstack_free(&rpnstack);
972 return -1;
973 }
974 break;
975 case GF_CDEF:
976 im->gdes[gdi].ds_cnt = 1;
977 im->gdes[gdi].ds = 0;
978 im->gdes[gdi].data_first = 1;
979 im->gdes[gdi].start = 0;
980 im->gdes[gdi].end = 0;
981 steparray = NULL;
982 stepcnt = 0;
983 dataidx = -1;
985 /* Find the variables in the expression.
986 * - VDEF variables are substituted by their values
987 * and the opcode is changed into OP_NUMBER.
988 * - CDEF variables are analized for their step size,
989 * the lowest common denominator of all the step
990 * sizes of the data sources involved is calculated
991 * and the resulting number is the step size for the
992 * resulting data source.
993 */
994 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
995 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
996 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
997 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
999 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1000 #if 0
1001 printf
1002 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1003 im->gdes[gdi].vname, im->gdes[ptr].vname);
1004 printf("DEBUG: value from vdef is %f\n",
1005 im->gdes[ptr].vf.val);
1006 #endif
1007 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1008 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1009 } else { /* normal variables and PREF(variables) */
1011 /* add one entry to the array that keeps track of the step sizes of the
1012 * data sources going into the CDEF. */
1013 if ((steparray =
1014 rrd_realloc(steparray,
1015 (++stepcnt +
1016 1) * sizeof(*steparray))) == NULL) {
1017 rrd_set_error("realloc steparray");
1018 rpnstack_free(&rpnstack);
1019 return -1;
1020 };
1022 steparray[stepcnt - 1] = im->gdes[ptr].step;
1024 /* adjust start and end of cdef (gdi) so
1025 * that it runs from the latest start point
1026 * to the earliest endpoint of any of the
1027 * rras involved (ptr)
1028 */
1030 if (im->gdes[gdi].start < im->gdes[ptr].start)
1031 im->gdes[gdi].start = im->gdes[ptr].start;
1033 if (im->gdes[gdi].end == 0 ||
1034 im->gdes[gdi].end > im->gdes[ptr].end)
1035 im->gdes[gdi].end = im->gdes[ptr].end;
1037 /* store pointer to the first element of
1038 * the rra providing data for variable,
1039 * further save step size and data source
1040 * count of this rra
1041 */
1042 im->gdes[gdi].rpnp[rpi].data =
1043 im->gdes[ptr].data + im->gdes[ptr].ds;
1044 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1045 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1047 /* backoff the *.data ptr; this is done so
1048 * rpncalc() function doesn't have to treat
1049 * the first case differently
1050 */
1051 } /* if ds_cnt != 0 */
1052 } /* if OP_VARIABLE */
1053 } /* loop through all rpi */
1055 /* move the data pointers to the correct period */
1056 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1057 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1058 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1059 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1060 long diff =
1061 im->gdes[gdi].start - im->gdes[ptr].start;
1063 if (diff > 0)
1064 im->gdes[gdi].rpnp[rpi].data +=
1065 (diff / im->gdes[ptr].step) *
1066 im->gdes[ptr].ds_cnt;
1067 }
1068 }
1070 if (steparray == NULL) {
1071 rrd_set_error("rpn expressions without DEF"
1072 " or CDEF variables are not supported");
1073 rpnstack_free(&rpnstack);
1074 return -1;
1075 }
1076 steparray[stepcnt] = 0;
1077 /* Now find the resulting step. All steps in all
1078 * used RRAs have to be visited
1079 */
1080 im->gdes[gdi].step = lcd(steparray);
1081 free(steparray);
1082 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1083 im->gdes[gdi].start)
1084 / im->gdes[gdi].step)
1085 * sizeof(double))) == NULL) {
1086 rrd_set_error("malloc im->gdes[gdi].data");
1087 rpnstack_free(&rpnstack);
1088 return -1;
1089 }
1091 /* Step through the new cdef results array and
1092 * calculate the values
1093 */
1094 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1095 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1096 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1098 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1099 * in this case we are advancing by timesteps;
1100 * we use the fact that time_t is a synonym for long
1101 */
1102 if (rpn_calc(rpnp, &rpnstack, (long) now,
1103 im->gdes[gdi].data, ++dataidx) == -1) {
1104 /* rpn_calc sets the error string */
1105 rpnstack_free(&rpnstack);
1106 return -1;
1107 }
1108 } /* enumerate over time steps within a CDEF */
1109 break;
1110 default:
1111 continue;
1112 }
1113 } /* enumerate over CDEFs */
1114 rpnstack_free(&rpnstack);
1115 return 0;
1116 }
1118 static int AlmostEqual2sComplement(
1119 float A,
1120 float B,
1121 int maxUlps)
1122 {
1124 int aInt = *(int *) &A;
1125 int bInt = *(int *) &B;
1126 int intDiff;
1128 /* Make sure maxUlps is non-negative and small enough that the
1129 default NAN won't compare as equal to anything. */
1131 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1133 /* Make aInt lexicographically ordered as a twos-complement int */
1135 if (aInt < 0)
1136 aInt = 0x80000000l - aInt;
1138 /* Make bInt lexicographically ordered as a twos-complement int */
1140 if (bInt < 0)
1141 bInt = 0x80000000l - bInt;
1143 intDiff = abs(aInt - bInt);
1145 if (intDiff <= maxUlps)
1146 return 1;
1148 return 0;
1149 }
1151 /* massage data so, that we get one value for each x coordinate in the graph */
1152 int data_proc(
1153 image_desc_t *im)
1154 {
1155 long i, ii;
1156 double pixstep = (double) (im->end - im->start)
1157 / (double) im->xsize; /* how much time
1158 passes in one pixel */
1159 double paintval;
1160 double minval = DNAN, maxval = DNAN;
1162 unsigned long gr_time;
1164 /* memory for the processed data */
1165 for (i = 0; i < im->gdes_c; i++) {
1166 if ((im->gdes[i].gf == GF_LINE) ||
1167 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1168 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1169 * sizeof(rrd_value_t))) == NULL) {
1170 rrd_set_error("malloc data_proc");
1171 return -1;
1172 }
1173 }
1174 }
1176 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1177 long vidx;
1179 gr_time = im->start + pixstep * i; /* time of the current step */
1180 paintval = 0.0;
1182 for (ii = 0; ii < im->gdes_c; ii++) {
1183 double value;
1185 switch (im->gdes[ii].gf) {
1186 case GF_LINE:
1187 case GF_AREA:
1188 case GF_TICK:
1189 if (!im->gdes[ii].stack)
1190 paintval = 0.0;
1191 value = im->gdes[ii].yrule;
1192 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1193 /* The time of the data doesn't necessarily match
1194 ** the time of the graph. Beware.
1195 */
1196 vidx = im->gdes[ii].vidx;
1197 if (im->gdes[vidx].gf == GF_VDEF) {
1198 value = im->gdes[vidx].vf.val;
1199 } else
1200 if (((long int) gr_time >=
1201 (long int) im->gdes[vidx].start)
1202 && ((long int) gr_time <=
1203 (long int) im->gdes[vidx].end)) {
1204 value = im->gdes[vidx].data[(unsigned long)
1205 floor((double)
1206 (gr_time -
1207 im->gdes[vidx].
1208 start)
1209 /
1210 im->gdes[vidx].step)
1211 * im->gdes[vidx].ds_cnt +
1212 im->gdes[vidx].ds];
1213 } else {
1214 value = DNAN;
1215 }
1216 };
1218 if (!isnan(value)) {
1219 paintval += value;
1220 im->gdes[ii].p_data[i] = paintval;
1221 /* GF_TICK: the data values are not
1222 ** relevant for min and max
1223 */
1224 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1225 if ((isnan(minval) || paintval < minval) &&
1226 !(im->logarithmic && paintval <= 0.0))
1227 minval = paintval;
1228 if (isnan(maxval) || paintval > maxval)
1229 maxval = paintval;
1230 }
1231 } else {
1232 im->gdes[ii].p_data[i] = DNAN;
1233 }
1234 break;
1235 case GF_STACK:
1236 rrd_set_error
1237 ("STACK should already be turned into LINE or AREA here");
1238 return -1;
1239 break;
1240 default:
1241 break;
1242 }
1243 }
1244 }
1246 /* if min or max have not been asigned a value this is because
1247 there was no data in the graph ... this is not good ...
1248 lets set these to dummy values then ... */
1250 if (im->logarithmic) {
1251 if (isnan(minval))
1252 minval = 0.2;
1253 if (isnan(maxval))
1254 maxval = 5.1;
1255 } else {
1256 if (isnan(minval))
1257 minval = 0.0;
1258 if (isnan(maxval))
1259 maxval = 1.0;
1260 }
1262 /* adjust min and max values */
1263 /* for logscale we add something on top */
1264 if (isnan(im->minval)
1265 || ((!im->rigid) && im->minval > minval)
1266 ) {
1267 if (im->logarithmic)
1268 im->minval = minval * 0.5;
1269 else
1270 im->minval = minval;
1271 }
1272 if (isnan(im->maxval)
1273 || (!im->rigid && im->maxval < maxval)
1274 ) {
1275 if (im->logarithmic)
1276 im->maxval = maxval * 2.0;
1277 else
1278 im->maxval = maxval;
1279 }
1281 /* make sure min is smaller than max */
1282 if (im->minval > im->maxval) {
1283 if (im->minval > 0)
1284 im->minval = 0.99 * im->maxval;
1285 else
1286 im->minval = 1.01 * im->maxval;
1287 }
1289 /* make sure min and max are not equal */
1290 if (AlmostEqual2sComplement(im->minval,im->maxval,4)) {
1291 if (im->maxval > 0)
1292 im->maxval *= 1.01;
1293 else
1294 im->maxval *= 0.99;
1296 /* make sure min and max are not both zero */
1297 if (AlmostEqual2sComplement(im->maxval,0,4)) {
1298 im->maxval = 1.0;
1299 }
1300 }
1301 return 0;
1302 }
1306 /* identify the point where the first gridline, label ... gets placed */
1308 time_t find_first_time(
1309 time_t start, /* what is the initial time */
1310 enum tmt_en baseint, /* what is the basic interval */
1311 long basestep /* how many if these do we jump a time */
1312 )
1313 {
1314 struct tm tm;
1316 localtime_r(&start, &tm);
1318 switch (baseint) {
1319 case TMT_SECOND:
1320 tm. tm_sec -= tm.tm_sec % basestep;
1322 break;
1323 case TMT_MINUTE:
1324 tm. tm_sec = 0;
1325 tm. tm_min -= tm.tm_min % basestep;
1327 break;
1328 case TMT_HOUR:
1329 tm. tm_sec = 0;
1330 tm. tm_min = 0;
1331 tm. tm_hour -= tm.tm_hour % basestep;
1333 break;
1334 case TMT_DAY:
1335 /* we do NOT look at the basestep for this ... */
1336 tm. tm_sec = 0;
1337 tm. tm_min = 0;
1338 tm. tm_hour = 0;
1340 break;
1341 case TMT_WEEK:
1342 /* we do NOT look at the basestep for this ... */
1343 tm. tm_sec = 0;
1344 tm. tm_min = 0;
1345 tm. tm_hour = 0;
1346 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1348 if (tm.tm_wday == 0)
1349 tm. tm_mday -= 7; /* we want the *previous* monday */
1351 break;
1352 case TMT_MONTH:
1353 tm. tm_sec = 0;
1354 tm. tm_min = 0;
1355 tm. tm_hour = 0;
1356 tm. tm_mday = 1;
1357 tm. tm_mon -= tm.tm_mon % basestep;
1359 break;
1361 case TMT_YEAR:
1362 tm. tm_sec = 0;
1363 tm. tm_min = 0;
1364 tm. tm_hour = 0;
1365 tm. tm_mday = 1;
1366 tm. tm_mon = 0;
1367 tm. tm_year -= (
1368 tm.tm_year + 1900) %basestep;
1370 }
1371 return mktime(&tm);
1372 }
1374 /* identify the point where the next gridline, label ... gets placed */
1375 time_t find_next_time(
1376 time_t current, /* what is the initial time */
1377 enum tmt_en baseint, /* what is the basic interval */
1378 long basestep /* how many if these do we jump a time */
1379 )
1380 {
1381 struct tm tm;
1382 time_t madetime;
1384 localtime_r(¤t, &tm);
1386 do {
1387 switch (baseint) {
1388 case TMT_SECOND:
1389 tm. tm_sec += basestep;
1391 break;
1392 case TMT_MINUTE:
1393 tm. tm_min += basestep;
1395 break;
1396 case TMT_HOUR:
1397 tm. tm_hour += basestep;
1399 break;
1400 case TMT_DAY:
1401 tm. tm_mday += basestep;
1403 break;
1404 case TMT_WEEK:
1405 tm. tm_mday += 7 * basestep;
1407 break;
1408 case TMT_MONTH:
1409 tm. tm_mon += basestep;
1411 break;
1412 case TMT_YEAR:
1413 tm. tm_year += basestep;
1414 }
1415 madetime = mktime(&tm);
1416 } while (madetime == -1); /* this is necessary to skip impssible times
1417 like the daylight saving time skips */
1418 return madetime;
1420 }
1423 /* calculate values required for PRINT and GPRINT functions */
1425 int print_calc(
1426 image_desc_t *im,
1427 char ***prdata)
1428 {
1429 long i, ii, validsteps;
1430 double printval;
1431 struct tm tmvdef;
1432 int graphelement = 0;
1433 long vidx;
1434 int max_ii;
1435 double magfact = -1;
1436 char *si_symb = "";
1437 char *percent_s;
1438 int prlines = 1;
1440 /* wow initializing tmvdef is quite a task :-) */
1441 time_t now = time(NULL);
1443 localtime_r(&now, &tmvdef);
1444 if (im->imginfo)
1445 prlines++;
1446 for (i = 0; i < im->gdes_c; i++) {
1447 vidx = im->gdes[i].vidx;
1448 switch (im->gdes[i].gf) {
1449 case GF_PRINT:
1450 prlines++;
1451 if (((*prdata) =
1452 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1453 rrd_set_error("realloc prdata");
1454 return 0;
1455 }
1456 case GF_GPRINT:
1457 /* PRINT and GPRINT can now print VDEF generated values.
1458 * There's no need to do any calculations on them as these
1459 * calculations were already made.
1460 */
1461 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1462 printval = im->gdes[vidx].vf.val;
1463 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1464 } else { /* need to calculate max,min,avg etcetera */
1465 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1466 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1467 printval = DNAN;
1468 validsteps = 0;
1469 for (ii = im->gdes[vidx].ds;
1470 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1471 if (!finite(im->gdes[vidx].data[ii]))
1472 continue;
1473 if (isnan(printval)) {
1474 printval = im->gdes[vidx].data[ii];
1475 validsteps++;
1476 continue;
1477 }
1479 switch (im->gdes[i].cf) {
1480 case CF_HWPREDICT:
1481 case CF_MHWPREDICT:
1482 case CF_DEVPREDICT:
1483 case CF_DEVSEASONAL:
1484 case CF_SEASONAL:
1485 case CF_AVERAGE:
1486 validsteps++;
1487 printval += im->gdes[vidx].data[ii];
1488 break;
1489 case CF_MINIMUM:
1490 printval = min(printval, im->gdes[vidx].data[ii]);
1491 break;
1492 case CF_FAILURES:
1493 case CF_MAXIMUM:
1494 printval = max(printval, im->gdes[vidx].data[ii]);
1495 break;
1496 case CF_LAST:
1497 printval = im->gdes[vidx].data[ii];
1498 }
1499 }
1500 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1501 if (validsteps > 1) {
1502 printval = (printval / validsteps);
1503 }
1504 }
1505 } /* prepare printval */
1507 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1508 /* Magfact is set to -1 upon entry to print_calc. If it
1509 * is still less than 0, then we need to run auto_scale.
1510 * Otherwise, put the value into the correct units. If
1511 * the value is 0, then do not set the symbol or magnification
1512 * so next the calculation will be performed again. */
1513 if (magfact < 0.0) {
1514 auto_scale(im, &printval, &si_symb, &magfact);
1515 if (printval == 0.0)
1516 magfact = -1.0;
1517 } else {
1518 printval /= magfact;
1519 }
1520 *(++percent_s) = 's';
1521 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1522 auto_scale(im, &printval, &si_symb, &magfact);
1523 }
1525 if (im->gdes[i].gf == GF_PRINT) {
1526 (*prdata)[prlines - 2] =
1527 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1528 (*prdata)[prlines - 1] = NULL;
1529 if (im->gdes[i].strftm) {
1530 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1531 im->gdes[i].format, &tmvdef);
1532 } else {
1533 if (bad_format(im->gdes[i].format)) {
1534 rrd_set_error("bad format for PRINT in '%s'",
1535 im->gdes[i].format);
1536 return -1;
1537 }
1538 #ifdef HAVE_SNPRINTF
1539 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1540 im->gdes[i].format, printval, si_symb);
1541 #else
1542 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1543 printval, si_symb);
1544 #endif
1545 }
1546 } else {
1547 /* GF_GPRINT */
1549 if (im->gdes[i].strftm) {
1550 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1551 im->gdes[i].format, &tmvdef);
1552 } else {
1553 if (bad_format(im->gdes[i].format)) {
1554 rrd_set_error("bad format for GPRINT in '%s'",
1555 im->gdes[i].format);
1556 return -1;
1557 }
1558 #ifdef HAVE_SNPRINTF
1559 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1560 im->gdes[i].format, printval, si_symb);
1561 #else
1562 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1563 si_symb);
1564 #endif
1565 }
1566 graphelement = 1;
1567 }
1568 break;
1569 case GF_LINE:
1570 case GF_AREA:
1571 case GF_TICK:
1572 graphelement = 1;
1573 break;
1574 case GF_HRULE:
1575 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1576 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1577 };
1578 graphelement = 1;
1579 break;
1580 case GF_VRULE:
1581 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1582 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1583 };
1584 graphelement = 1;
1585 break;
1586 case GF_COMMENT:
1587 case GF_TEXTALIGN:
1588 case GF_DEF:
1589 case GF_CDEF:
1590 case GF_VDEF:
1591 #ifdef WITH_PIECHART
1592 case GF_PART:
1593 #endif
1594 case GF_SHIFT:
1595 case GF_XPORT:
1596 break;
1597 case GF_STACK:
1598 rrd_set_error
1599 ("STACK should already be turned into LINE or AREA here");
1600 return -1;
1601 break;
1602 }
1603 }
1604 return graphelement;
1605 }
1608 /* place legends with color spots */
1609 int leg_place(
1610 image_desc_t *im,
1611 int *gY)
1612 {
1613 /* graph labels */
1614 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1615 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1616 int fill = 0, fill_last;
1617 int leg_c = 0;
1618 int leg_x = border, leg_y = im->yimg;
1619 int leg_y_prev = im->yimg;
1620 int leg_cc;
1621 int glue = 0;
1622 int i, ii, mark = 0;
1623 char prt_fctn; /*special printfunctions */
1624 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1625 int *legspace;
1627 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1628 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1629 rrd_set_error("malloc for legspace");
1630 return -1;
1631 }
1633 if (im->extra_flags & FULL_SIZE_MODE)
1634 leg_y = leg_y_prev =
1635 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1637 for (i = 0; i < im->gdes_c; i++) {
1638 fill_last = fill;
1640 /* hide legends for rules which are not displayed */
1642 if (im->gdes[i].gf == GF_TEXTALIGN) {
1643 default_txtalign = im->gdes[i].txtalign;
1644 }
1646 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1647 if (im->gdes[i].gf == GF_HRULE &&
1648 (im->gdes[i].yrule < im->minval
1649 || im->gdes[i].yrule > im->maxval))
1650 im->gdes[i].legend[0] = '\0';
1652 if (im->gdes[i].gf == GF_VRULE &&
1653 (im->gdes[i].xrule < im->start
1654 || im->gdes[i].xrule > im->end))
1655 im->gdes[i].legend[0] = '\0';
1656 }
1658 leg_cc = strlen(im->gdes[i].legend);
1660 /* is there a controle code ant the end of the legend string ? */
1661 /* and it is not a tab \\t */
1662 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1663 && im->gdes[i].legend[leg_cc - 1] != 't') {
1664 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1665 leg_cc -= 2;
1666 im->gdes[i].legend[leg_cc] = '\0';
1667 } else {
1668 prt_fctn = '\0';
1669 }
1670 /* only valid control codes */
1671 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1672 prt_fctn != 'r' &&
1673 prt_fctn != 'j' &&
1674 prt_fctn != 'c' &&
1675 prt_fctn != 's' &&
1676 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1677 free(legspace);
1678 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1679 im->gdes[i].legend, prt_fctn);
1680 return -1;
1682 }
1683 /* \n -> \l */
1684 if (prt_fctn == 'n') {
1685 prt_fctn = 'l';
1686 }
1688 /* remove exess space from the end of the legend for \g */
1689 while (prt_fctn == 'g' &&
1690 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1691 leg_cc--;
1692 im->gdes[i].legend[leg_cc] = '\0';
1693 }
1695 if (leg_cc != 0) {
1697 /* no interleg space if string ends in \g */
1698 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1700 if (fill > 0) {
1701 fill += legspace[i];
1702 }
1703 fill += gfx_get_text_width(im, fill + border,
1704 im->text_prop[TEXT_PROP_LEGEND].
1705 font,
1706 im->text_prop[TEXT_PROP_LEGEND].
1707 size, im->tabwidth,
1708 im->gdes[i].legend);
1709 leg_c++;
1710 } else {
1711 legspace[i] = 0;
1712 }
1713 /* who said there was a special tag ... ? */
1714 if (prt_fctn == 'g') {
1715 prt_fctn = '\0';
1716 }
1718 if (prt_fctn == '\0') {
1719 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1720 /* just one legend item is left right or center */
1721 switch (default_txtalign) {
1722 case TXA_RIGHT:
1723 prt_fctn = 'r';
1724 break;
1725 case TXA_CENTER:
1726 prt_fctn = 'c';
1727 break;
1728 case TXA_JUSTIFIED:
1729 prt_fctn = 'j';
1730 break;
1731 default:
1732 prt_fctn = 'l';
1733 break;
1734 }
1735 }
1736 /* is it time to place the legends ? */
1737 if (fill > im->ximg - 2 * border) {
1738 if (leg_c > 1) {
1739 /* go back one */
1740 i--;
1741 fill = fill_last;
1742 leg_c--;
1743 }
1744 }
1745 if (leg_c == 1 && prt_fctn == 'j') {
1746 prt_fctn = 'l';
1747 }
1748 }
1751 if (prt_fctn != '\0') {
1752 leg_x = border;
1753 if (leg_c >= 2 && prt_fctn == 'j') {
1754 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1755 } else {
1756 glue = 0;
1757 }
1758 if (prt_fctn == 'c')
1759 leg_x = (im->ximg - fill) / 2.0;
1760 if (prt_fctn == 'r')
1761 leg_x = im->ximg - fill - border;
1763 for (ii = mark; ii <= i; ii++) {
1764 if (im->gdes[ii].legend[0] == '\0')
1765 continue; /* skip empty legends */
1766 im->gdes[ii].leg_x = leg_x;
1767 im->gdes[ii].leg_y = leg_y;
1768 leg_x +=
1769 gfx_get_text_width(im, leg_x,
1770 im->text_prop[TEXT_PROP_LEGEND].
1771 font,
1772 im->text_prop[TEXT_PROP_LEGEND].
1773 size, im->tabwidth,
1774 im->gdes[ii].legend)
1775 + legspace[ii]
1776 + glue;
1777 }
1778 leg_y_prev = leg_y;
1779 if (im->extra_flags & FULL_SIZE_MODE) {
1780 /* only add y space if there was text on the line */
1781 if (leg_x > border || prt_fctn == 's')
1782 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1783 if (prt_fctn == 's')
1784 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1785 } else {
1786 if (leg_x > border || prt_fctn == 's')
1787 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1788 if (prt_fctn == 's')
1789 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1790 }
1791 fill = 0;
1792 leg_c = 0;
1793 mark = ii;
1794 }
1795 }
1797 if (im->extra_flags & FULL_SIZE_MODE) {
1798 if (leg_y != leg_y_prev) {
1799 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1800 im->yorigin =
1801 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1802 }
1803 } else {
1804 im->yimg = leg_y_prev;
1805 /* if we did place some legends we have to add vertical space */
1806 if (leg_y != im->yimg)
1807 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1808 }
1809 free(legspace);
1810 }
1811 return 0;
1812 }
1814 /* create a grid on the graph. it determines what to do
1815 from the values of xsize, start and end */
1817 /* the xaxis labels are determined from the number of seconds per pixel
1818 in the requested graph */
1822 int calc_horizontal_grid(
1823 image_desc_t *im)
1824 {
1825 double range;
1826 double scaledrange;
1827 int pixel, i;
1828 int gridind = 0;
1829 int decimals, fractionals;
1831 im->ygrid_scale.labfact = 2;
1832 range = im->maxval - im->minval;
1833 scaledrange = range / im->magfact;
1835 /* does the scale of this graph make it impossible to put lines
1836 on it? If so, give up. */
1837 if (isnan(scaledrange)) {
1838 return 0;
1839 }
1841 /* find grid spaceing */
1842 pixel = 1;
1843 if (isnan(im->ygridstep)) {
1844 if (im->extra_flags & ALTYGRID) {
1845 /* find the value with max number of digits. Get number of digits */
1846 decimals =
1847 ceil(log10
1848 (max(fabs(im->maxval), fabs(im->minval)) *
1849 im->viewfactor / im->magfact));
1850 if (decimals <= 0) /* everything is small. make place for zero */
1851 decimals = 1;
1853 im->ygrid_scale.gridstep =
1854 pow((double) 10,
1855 floor(log10(range * im->viewfactor / im->magfact))) /
1856 im->viewfactor * im->magfact;
1858 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1859 im->ygrid_scale.gridstep = 0.1;
1860 /* should have at least 5 lines but no more then 15 */
1861 if (range / im->ygrid_scale.gridstep < 5)
1862 im->ygrid_scale.gridstep /= 10;
1863 if (range / im->ygrid_scale.gridstep > 15)
1864 im->ygrid_scale.gridstep *= 10;
1865 if (range / im->ygrid_scale.gridstep > 5) {
1866 im->ygrid_scale.labfact = 1;
1867 if (range / im->ygrid_scale.gridstep > 8)
1868 im->ygrid_scale.labfact = 2;
1869 } else {
1870 im->ygrid_scale.gridstep /= 5;
1871 im->ygrid_scale.labfact = 5;
1872 }
1873 fractionals =
1874 floor(log10
1875 (im->ygrid_scale.gridstep *
1876 (double) im->ygrid_scale.labfact * im->viewfactor /
1877 im->magfact));
1878 if (fractionals < 0) { /* small amplitude. */
1879 int len = decimals - fractionals + 1;
1881 if (im->unitslength < len + 2)
1882 im->unitslength = len + 2;
1883 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1884 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1885 } else {
1886 int len = decimals + 1;
1888 if (im->unitslength < len + 2)
1889 im->unitslength = len + 2;
1890 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1891 (im->symbol != ' ' ? " %c" : ""));
1892 }
1893 } else {
1894 for (i = 0; ylab[i].grid > 0; i++) {
1895 pixel = im->ysize / (scaledrange / ylab[i].grid);
1896 gridind = i;
1897 if (pixel > 7)
1898 break;
1899 }
1901 for (i = 0; i < 4; i++) {
1902 if (pixel * ylab[gridind].lfac[i] >=
1903 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1904 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1905 break;
1906 }
1907 }
1909 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1910 }
1911 } else {
1912 im->ygrid_scale.gridstep = im->ygridstep;
1913 im->ygrid_scale.labfact = im->ylabfact;
1914 }
1915 return 1;
1916 }
1918 int draw_horizontal_grid(
1919 image_desc_t *im)
1920 {
1921 int i;
1922 double scaledstep;
1923 char graph_label[100];
1924 int nlabels = 0;
1925 double X0 = im->xorigin;
1926 double X1 = im->xorigin + im->xsize;
1928 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1929 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1930 double MaxY;
1932 scaledstep =
1933 im->ygrid_scale.gridstep / (double) im->magfact *
1934 (double) im->viewfactor;
1935 MaxY = scaledstep * (double) egrid;
1936 for (i = sgrid; i <= egrid; i++) {
1937 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1938 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1940 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1941 && floor(Y0 + 0.5) <= im->yorigin) {
1942 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1943 with the chosen settings. Add a label if required by settings, or if
1944 there is only one label so far and the next grid line is out of bounds. */
1945 if (i % im->ygrid_scale.labfact == 0
1946 || (nlabels == 1
1947 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1948 if (im->symbol == ' ') {
1949 if (im->extra_flags & ALTYGRID) {
1950 sprintf(graph_label, im->ygrid_scale.labfmt,
1951 scaledstep * (double) i);
1952 } else {
1953 if (MaxY < 10) {
1954 sprintf(graph_label, "%4.1f",
1955 scaledstep * (double) i);
1956 } else {
1957 sprintf(graph_label, "%4.0f",
1958 scaledstep * (double) i);
1959 }
1960 }
1961 } else {
1962 char sisym = (i == 0 ? ' ' : im->symbol);
1964 if (im->extra_flags & ALTYGRID) {
1965 sprintf(graph_label, im->ygrid_scale.labfmt,
1966 scaledstep * (double) i, sisym);
1967 } else {
1968 if (MaxY < 10) {
1969 sprintf(graph_label, "%4.1f %c",
1970 scaledstep * (double) i, sisym);
1971 } else {
1972 sprintf(graph_label, "%4.0f %c",
1973 scaledstep * (double) i, sisym);
1974 }
1975 }
1976 }
1977 nlabels++;
1979 gfx_text(im,
1980 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1981 im->graph_col[GRC_FONT],
1982 im->text_prop[TEXT_PROP_AXIS].font,
1983 im->text_prop[TEXT_PROP_AXIS].size,
1984 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1985 graph_label);
1986 gfx_line(im,
1987 X0 - 2, Y0,
1988 X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1989 gfx_line(im,
1990 X1, Y0,
1991 X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1992 gfx_dashed_line(im,
1993 X0 - 2, Y0,
1994 X1 + 2, Y0,
1995 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1996 im->grid_dash_on, im->grid_dash_off);
1998 } else if (!(im->extra_flags & NOMINOR)) {
1999 gfx_line(im,
2000 X0 - 2, Y0,
2001 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2002 gfx_line(im,
2003 X1, Y0,
2004 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2005 gfx_dashed_line(im,
2006 X0 - 1, Y0,
2007 X1 + 1, Y0,
2008 GRIDWIDTH, im->graph_col[GRC_GRID],
2009 im->grid_dash_on, im->grid_dash_off);
2011 }
2012 }
2013 }
2014 return 1;
2015 }
2017 /* this is frexp for base 10 */
2018 double frexp10(
2019 double,
2020 double *);
2021 double frexp10(
2022 double x,
2023 double *e)
2024 {
2025 double mnt;
2026 int iexp;
2028 iexp = floor(log(fabs(x)) / log(10));
2029 mnt = x / pow(10.0, iexp);
2030 if (mnt >= 10.0) {
2031 iexp++;
2032 mnt = x / pow(10.0, iexp);
2033 }
2034 *e = iexp;
2035 return mnt;
2036 }
2038 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2039 /* yes we are loosing precision by doing tos with floats instead of doubles
2040 but it seems more stable this way. */
2043 /* logaritmic horizontal grid */
2044 int horizontal_log_grid(
2045 image_desc_t *im)
2046 {
2047 double yloglab[][10] = {
2048 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2049 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2050 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2051 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2052 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2053 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2054 };
2056 int i, j, val_exp, min_exp;
2057 double nex; /* number of decades in data */
2058 double logscale; /* scale in logarithmic space */
2059 int exfrac = 1; /* decade spacing */
2060 int mid = -1; /* row in yloglab for major grid */
2061 double mspac; /* smallest major grid spacing (pixels) */
2062 int flab; /* first value in yloglab to use */
2063 double value, tmp, pre_value;
2064 double X0, X1, Y0;
2065 char graph_label[100];
2067 nex = log10(im->maxval / im->minval);
2068 logscale = im->ysize / nex;
2070 /* major spacing for data with high dynamic range */
2071 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2072 if (exfrac == 1)
2073 exfrac = 3;
2074 else
2075 exfrac += 3;
2076 }
2078 /* major spacing for less dynamic data */
2079 do {
2080 /* search best row in yloglab */
2081 mid++;
2082 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2083 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2084 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2085 && yloglab[mid][0] > 0);
2086 if (mid)
2087 mid--;
2089 /* find first value in yloglab */
2090 for (flab = 0;
2091 yloglab[mid][flab] < 10
2092 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2093 if (yloglab[mid][flab] == 10.0) {
2094 tmp += 1.0;
2095 flab = 0;
2096 }
2097 val_exp = tmp;
2098 if (val_exp % exfrac)
2099 val_exp += abs(-val_exp % exfrac);
2101 X0 = im->xorigin;
2102 X1 = im->xorigin + im->xsize;
2104 /* draw grid */
2105 pre_value = DNAN;
2106 while (1) {
2108 value = yloglab[mid][flab] * pow(10.0, val_exp);
2109 if (AlmostEqual2sComplement(value, pre_value, 4))
2110 break; /* it seems we are not converging */
2112 pre_value = value;
2114 Y0 = ytr(im, value);
2115 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2116 break;
2118 /* major grid line */
2120 gfx_line(im,
2121 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2122 gfx_line(im,
2123 X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2126 gfx_dashed_line(im,
2127 X0 - 2, Y0,
2128 X1 + 2, Y0,
2129 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2130 im->grid_dash_on, im->grid_dash_off);
2132 /* label */
2133 if (im->extra_flags & FORCE_UNITS_SI) {
2134 int scale;
2135 double pvalue;
2136 char symbol;
2138 scale = floor(val_exp / 3.0);
2139 if (value >= 1.0)
2140 pvalue = pow(10.0, val_exp % 3);
2141 else
2142 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2143 pvalue *= yloglab[mid][flab];
2145 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2146 ((scale + si_symbcenter) >= 0))
2147 symbol = si_symbol[scale + si_symbcenter];
2148 else
2149 symbol = '?';
2151 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2152 } else
2153 sprintf(graph_label, "%3.0e", value);
2154 gfx_text(im,
2155 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2156 im->graph_col[GRC_FONT],
2157 im->text_prop[TEXT_PROP_AXIS].font,
2158 im->text_prop[TEXT_PROP_AXIS].size,
2159 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2161 /* minor grid */
2162 if (mid < 4 && exfrac == 1) {
2163 /* find first and last minor line behind current major line
2164 * i is the first line and j tha last */
2165 if (flab == 0) {
2166 min_exp = val_exp - 1;
2167 for (i = 1; yloglab[mid][i] < 10.0; i++);
2168 i = yloglab[mid][i - 1] + 1;
2169 j = 10;
2170 } else {
2171 min_exp = val_exp;
2172 i = yloglab[mid][flab - 1] + 1;
2173 j = yloglab[mid][flab];
2174 }
2176 /* draw minor lines below current major line */
2177 for (; i < j; i++) {
2179 value = i * pow(10.0, min_exp);
2180 if (value < im->minval)
2181 continue;
2183 Y0 = ytr(im, value);
2184 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2185 break;
2187 /* draw lines */
2188 gfx_line(im,
2189 X0 - 2, Y0,
2190 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2191 gfx_line(im,
2192 X1, Y0,
2193 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2194 gfx_dashed_line(im,
2195 X0 - 1, Y0,
2196 X1 + 1, Y0,
2197 GRIDWIDTH, im->graph_col[GRC_GRID],
2198 im->grid_dash_on, im->grid_dash_off);
2199 }
2200 } else if (exfrac > 1) {
2201 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2202 value = pow(10.0, i);
2203 if (value < im->minval)
2204 continue;
2206 Y0 = ytr(im, value);
2207 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2208 break;
2210 /* draw lines */
2211 gfx_line(im,
2212 X0 - 2, Y0,
2213 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2214 gfx_line(im,
2215 X1, Y0,
2216 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2217 gfx_dashed_line(im,
2218 X0 - 1, Y0,
2219 X1 + 1, Y0,
2220 GRIDWIDTH, im->graph_col[GRC_GRID],
2221 im->grid_dash_on, im->grid_dash_off);
2222 }
2223 }
2225 /* next decade */
2226 if (yloglab[mid][++flab] == 10.0) {
2227 flab = 0;
2228 val_exp += exfrac;
2229 }
2230 }
2232 /* draw minor lines after highest major line */
2233 if (mid < 4 && exfrac == 1) {
2234 /* find first and last minor line below current major line
2235 * i is the first line and j tha last */
2236 if (flab == 0) {
2237 min_exp = val_exp - 1;
2238 for (i = 1; yloglab[mid][i] < 10.0; i++);
2239 i = yloglab[mid][i - 1] + 1;
2240 j = 10;
2241 } else {
2242 min_exp = val_exp;
2243 i = yloglab[mid][flab - 1] + 1;
2244 j = yloglab[mid][flab];
2245 }
2247 /* draw minor lines below current major line */
2248 for (; i < j; i++) {
2250 value = i * pow(10.0, min_exp);
2251 if (value < im->minval)
2252 continue;
2254 Y0 = ytr(im, value);
2255 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2256 break;
2258 /* draw lines */
2259 gfx_line(im,
2260 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2261 gfx_line(im,
2262 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2263 gfx_dashed_line(im,
2264 X0 - 1, Y0,
2265 X1 + 1, Y0,
2266 GRIDWIDTH, im->graph_col[GRC_GRID],
2267 im->grid_dash_on, im->grid_dash_off);
2268 }
2269 }
2270 /* fancy minor gridlines */
2271 else if (exfrac > 1) {
2272 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2273 value = pow(10.0, i);
2274 if (value < im->minval)
2275 continue;
2277 Y0 = ytr(im, value);
2278 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2279 break;
2281 /* draw lines */
2282 gfx_line(im,
2283 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2284 gfx_line(im,
2285 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2286 gfx_dashed_line(im,
2287 X0 - 1, Y0,
2288 X1 + 1, Y0,
2289 GRIDWIDTH, im->graph_col[GRC_GRID],
2290 im->grid_dash_on, im->grid_dash_off);
2291 }
2292 }
2294 return 1;
2295 }
2298 void vertical_grid(
2299 image_desc_t *im)
2300 {
2301 int xlab_sel; /* which sort of label and grid ? */
2302 time_t ti, tilab, timajor;
2303 long factor;
2304 char graph_label[100];
2305 double X0, Y0, Y1; /* points for filled graph and more */
2306 struct tm tm;
2308 /* the type of time grid is determined by finding
2309 the number of seconds per pixel in the graph */
2312 if (im->xlab_user.minsec == -1) {
2313 factor = (im->end - im->start) / im->xsize;
2314 xlab_sel = 0;
2315 while (xlab[xlab_sel + 1].minsec != -1
2316 && xlab[xlab_sel + 1].minsec <= factor) {
2317 xlab_sel++;
2318 } /* pick the last one */
2319 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2320 && xlab[xlab_sel].length > (im->end - im->start)) {
2321 xlab_sel--;
2322 } /* go back to the smallest size */
2323 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2324 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2325 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2326 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2327 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2328 im->xlab_user.labst = xlab[xlab_sel].labst;
2329 im->xlab_user.precis = xlab[xlab_sel].precis;
2330 im->xlab_user.stst = xlab[xlab_sel].stst;
2331 }
2333 /* y coords are the same for every line ... */
2334 Y0 = im->yorigin;
2335 Y1 = im->yorigin - im->ysize;
2338 /* paint the minor grid */
2339 if (!(im->extra_flags & NOMINOR)) {
2340 for (ti = find_first_time(im->start,
2341 im->xlab_user.gridtm,
2342 im->xlab_user.gridst),
2343 timajor = find_first_time(im->start,
2344 im->xlab_user.mgridtm,
2345 im->xlab_user.mgridst);
2346 ti < im->end;
2347 ti =
2348 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2349 ) {
2350 /* are we inside the graph ? */
2351 if (ti < im->start || ti > im->end)
2352 continue;
2353 while (timajor < ti) {
2354 timajor = find_next_time(timajor,
2355 im->xlab_user.mgridtm,
2356 im->xlab_user.mgridst);
2357 }
2358 if (ti == timajor)
2359 continue; /* skip as falls on major grid line */
2360 X0 = xtr(im, ti);
2361 gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2362 im->graph_col[GRC_GRID]);
2363 gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2364 im->graph_col[GRC_GRID]);
2365 gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2366 im->graph_col[GRC_GRID],
2367 im->grid_dash_on, im->grid_dash_off);
2369 }
2370 }
2372 /* paint the major grid */
2373 for (ti = find_first_time(im->start,
2374 im->xlab_user.mgridtm,
2375 im->xlab_user.mgridst);
2376 ti < im->end;
2377 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2378 ) {
2379 /* are we inside the graph ? */
2380 if (ti < im->start || ti > im->end)
2381 continue;
2382 X0 = xtr(im, ti);
2383 gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2384 im->graph_col[GRC_MGRID]);
2385 gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2386 im->graph_col[GRC_MGRID]);
2387 gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2388 im->graph_col[GRC_MGRID],
2389 im->grid_dash_on, im->grid_dash_off);
2391 }
2392 /* paint the labels below the graph */
2393 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2394 im->xlab_user.labtm,
2395 im->xlab_user.labst);
2396 ti <= im->end - im->xlab_user.precis / 2;
2397 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2398 ) {
2399 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2400 /* are we inside the graph ? */
2401 if (tilab < im->start || tilab > im->end)
2402 continue;
2404 #if HAVE_STRFTIME
2405 localtime_r(&tilab, &tm);
2406 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2407 #else
2408 # error "your libc has no strftime I guess we'll abort the exercise here."
2409 #endif
2410 gfx_text(im,
2411 xtr(im, tilab),
2412 Y0 + 3,
2413 im->graph_col[GRC_FONT],
2414 im->text_prop[TEXT_PROP_AXIS].font,
2415 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2416 GFX_H_CENTER, GFX_V_TOP, graph_label);
2418 }
2420 }
2423 void axis_paint(
2424 image_desc_t *im)
2425 {
2426 /* draw x and y axis */
2427 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2428 im->xorigin+im->xsize,im->yorigin-im->ysize,
2429 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2431 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2432 im->xorigin+im->xsize,im->yorigin-im->ysize,
2433 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2435 gfx_line(im, im->xorigin - 4, im->yorigin,
2436 im->xorigin + im->xsize + 4, im->yorigin,
2437 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2439 gfx_line(im, im->xorigin, im->yorigin + 4,
2440 im->xorigin, im->yorigin - im->ysize - 4,
2441 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2444 /* arrow for X and Y axis direction */
2446 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 */
2447 im->graph_col[GRC_ARROW]);
2448 gfx_close_path(im);
2450 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 */
2451 im->graph_col[GRC_ARROW]);
2452 gfx_close_path(im);
2455 }
2457 void grid_paint(
2458 image_desc_t *im)
2459 {
2460 long i;
2461 int res = 0;
2462 double X0, Y0; /* points for filled graph and more */
2463 struct gfx_color_t water_color;
2465 /* draw 3d border */
2466 gfx_new_area(im, 0, im->yimg,
2467 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2468 gfx_add_point(im, im->ximg - 2, 2);
2469 gfx_add_point(im, im->ximg, 0);
2470 gfx_add_point(im, 0, 0);
2471 gfx_close_path(im);
2473 gfx_new_area(im, 2, im->yimg - 2,
2474 im->ximg - 2, im->yimg - 2,
2475 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2476 gfx_add_point(im, im->ximg, 0);
2477 gfx_add_point(im, im->ximg, im->yimg);
2478 gfx_add_point(im, 0, im->yimg);
2479 gfx_close_path(im);
2482 if (im->draw_x_grid == 1)
2483 vertical_grid(im);
2485 if (im->draw_y_grid == 1) {
2486 if (im->logarithmic) {
2487 res = horizontal_log_grid(im);
2488 } else {
2489 res = draw_horizontal_grid(im);
2490 }
2492 /* dont draw horizontal grid if there is no min and max val */
2493 if (!res) {
2494 char *nodata = "No Data found";
2496 gfx_text(im, im->ximg / 2,
2497 (2 * im->yorigin - im->ysize) / 2,
2498 im->graph_col[GRC_FONT],
2499 im->text_prop[TEXT_PROP_AXIS].font,
2500 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2501 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2502 }
2503 }
2505 /* yaxis unit description */
2506 gfx_text(im,
2507 10, (im->yorigin - im->ysize / 2),
2508 im->graph_col[GRC_FONT],
2509 im->text_prop[TEXT_PROP_UNIT].font,
2510 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2511 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2513 /* graph title */
2514 gfx_text(im,
2515 im->ximg / 2, 6,
2516 im->graph_col[GRC_FONT],
2517 im->text_prop[TEXT_PROP_TITLE].font,
2518 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2519 GFX_H_CENTER, GFX_V_TOP, im->title);
2520 /* rrdtool 'logo' */
2521 water_color = im->graph_col[GRC_FONT];
2522 water_color.alpha = 0.3;
2523 gfx_text(im,
2524 im->ximg - 4, 5,
2525 water_color,
2526 im->text_prop[TEXT_PROP_AXIS].font,
2527 5.5, im->tabwidth, -90,
2528 GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2530 /* graph watermark */
2531 if (im->watermark[0] != '\0') {
2532 gfx_text(im,
2533 im->ximg / 2, im->yimg - 6,
2534 water_color,
2535 im->text_prop[TEXT_PROP_AXIS].font,
2536 5.5, im->tabwidth, 0,
2537 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2538 }
2540 /* graph labels */
2541 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2542 for (i = 0; i < im->gdes_c; i++) {
2543 if (im->gdes[i].legend[0] == '\0')
2544 continue;
2546 /* im->gdes[i].leg_y is the bottom of the legend */
2547 X0 = im->gdes[i].leg_x;
2548 Y0 = im->gdes[i].leg_y;
2549 gfx_text(im, X0, Y0,
2550 im->graph_col[GRC_FONT],
2551 im->text_prop[TEXT_PROP_LEGEND].font,
2552 im->text_prop[TEXT_PROP_LEGEND].size,
2553 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2554 im->gdes[i].legend);
2555 /* The legend for GRAPH items starts with "M " to have
2556 enough space for the box */
2557 if (im->gdes[i].gf != GF_PRINT &&
2558 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2559 double boxH, boxV;
2560 double X1, Y1;
2563 boxH = gfx_get_text_width(im, 0,
2564 im->text_prop[TEXT_PROP_LEGEND].
2565 font,
2566 im->text_prop[TEXT_PROP_LEGEND].
2567 size, im->tabwidth, "o") * 1.2;
2568 boxV = boxH;
2570 /* shift the box up a bit */
2571 Y0 -= boxV * 0.4;
2573 /* make sure transparent colors show up the same way as in the graph */
2575 gfx_new_area(im,
2576 X0, Y0 - boxV,
2577 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2578 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2579 gfx_close_path(im);
2581 gfx_new_area(im,
2582 X0, Y0 - boxV,
2583 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2584 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2585 gfx_close_path(im);
2587 cairo_save(im->cr);
2588 cairo_new_path(im->cr);
2589 cairo_set_line_width(im->cr, 1.0);
2590 X1 = X0 + boxH;
2591 Y1 = Y0 - boxV;
2592 gfx_line_fit(im, &X0, &Y0);
2593 gfx_line_fit(im, &X1, &Y1);
2594 cairo_move_to(im->cr, X0, Y0);
2595 cairo_line_to(im->cr, X1, Y0);
2596 cairo_line_to(im->cr, X1, Y1);
2597 cairo_line_to(im->cr, X0, Y1);
2598 cairo_close_path(im->cr);
2599 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2600 im->graph_col[GRC_FRAME].green,
2601 im->graph_col[GRC_FRAME].blue,
2602 im->graph_col[GRC_FRAME].alpha);
2603 cairo_stroke(im->cr);
2604 cairo_restore(im->cr);
2605 }
2606 }
2607 }
2608 }
2611 /*****************************************************
2612 * lazy check make sure we rely need to create this graph
2613 *****************************************************/
2615 int lazy_check(
2616 image_desc_t *im)
2617 {
2618 FILE *fd = NULL;
2619 int size = 1;
2620 struct stat imgstat;
2622 if (im->lazy == 0)
2623 return 0; /* no lazy option */
2624 if (stat(im->graphfile, &imgstat) != 0)
2625 return 0; /* can't stat */
2626 /* one pixel in the existing graph is more then what we would
2627 change here ... */
2628 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2629 return 0;
2630 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2631 return 0; /* the file does not exist */
2632 switch (im->imgformat) {
2633 case IF_PNG:
2634 size = PngSize(fd, &(im->ximg), &(im->yimg));
2635 break;
2636 default:
2637 size = 1;
2638 }
2639 fclose(fd);
2640 return size;
2641 }
2644 int graph_size_location(
2645 image_desc_t *im,
2646 int elements)
2647 {
2648 /* The actual size of the image to draw is determined from
2649 ** several sources. The size given on the command line is
2650 ** the graph area but we need more as we have to draw labels
2651 ** and other things outside the graph area
2652 */
2654 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2655 Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2657 if (im->extra_flags & ONLY_GRAPH) {
2658 im->xorigin = 0;
2659 im->ximg = im->xsize;
2660 im->yimg = im->ysize;
2661 im->yorigin = im->ysize;
2662 ytr(im, DNAN);
2663 return 0;
2664 }
2666 /** +---+--------------------------------------------+
2667 ** | y |...............graph title..................|
2668 ** | +---+-------------------------------+--------+
2669 ** | a | y | | |
2670 ** | x | | | |
2671 ** | i | a | | pie |
2672 ** | s | x | main graph area | chart |
2673 ** | | i | | area |
2674 ** | t | s | | |
2675 ** | i | | | |
2676 ** | t | l | | |
2677 ** | l | b +-------------------------------+--------+
2678 ** | e | l | x axis labels | |
2679 ** +---+---+-------------------------------+--------+
2680 ** |....................legends.....................|
2681 ** +------------------------------------------------+
2682 ** | watermark |
2683 ** +------------------------------------------------+
2684 */
2686 if (im->ylegend[0] != '\0') {
2687 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2688 }
2690 if (im->title[0] != '\0') {
2691 /* The title is placed "inbetween" two text lines so it
2692 ** automatically has some vertical spacing. The horizontal
2693 ** spacing is added here, on each side.
2694 */
2695 /* if necessary, reduce the font size of the title until it fits the image width */
2696 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2697 }
2699 if (elements) {
2700 if (im->draw_x_grid) {
2701 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2702 }
2703 if (im->draw_y_grid || im->forceleftspace) {
2704 Xylabel = gfx_get_text_width(im, 0,
2705 im->text_prop[TEXT_PROP_AXIS].font,
2706 im->text_prop[TEXT_PROP_AXIS].size,
2707 im->tabwidth, "0") * im->unitslength;
2708 }
2709 }
2711 if (im->extra_flags & FULL_SIZE_MODE) {
2712 /* The actual size of the image to draw has been determined by the user.
2713 ** The graph area is the space remaining after accounting for the legend,
2714 ** the watermark, the pie chart, the axis labels, and the title.
2715 */
2716 im->xorigin = 0;
2717 im->ximg = im->xsize;
2718 im->yimg = im->ysize;
2719 im->yorigin = im->ysize;
2720 Xmain = im->ximg;
2721 Ymain = im->yimg;
2723 im->yorigin += Ytitle;
2725 /* Now calculate the total size. Insert some spacing where
2726 desired. im->xorigin and im->yorigin need to correspond
2727 with the lower left corner of the main graph area or, if
2728 this one is not set, the imaginary box surrounding the
2729 pie chart area. */
2731 /* Initial size calculation for the main graph area */
2732 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2733 if (Xmain)
2734 Xmain -= Xspacing; /* put space between main graph area and right edge */
2736 im->xorigin = Xspacing + Xylabel;
2738 /* the length of the title should not influence with width of the graph
2739 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2741 if (Xvertical) { /* unit description */
2742 Xmain -= Xvertical;
2743 im->xorigin += Xvertical;
2744 }
2745 im->xsize = Xmain;
2746 xtr(im, 0);
2748 /* The vertical size of the image is known in advance. The main graph area
2749 ** (Ymain) and im->yorigin must be set according to the space requirements
2750 ** of the legend and the axis labels.
2751 */
2753 if (im->extra_flags & NOLEGEND) {
2754 /* set dimensions correctly if using full size mode with no legend */
2755 im->yorigin =
2756 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2757 Yspacing;
2758 Ymain = im->yorigin;
2759 } else {
2760 /* Determine where to place the legends onto the image.
2761 ** Set Ymain and adjust im->yorigin to match the space requirements.
2762 */
2763 if (leg_place(im, &Ymain) == -1)
2764 return -1;
2765 }
2768 /* remove title space *or* some padding above the graph from the main graph area */
2769 if (Ytitle) {
2770 Ymain -= Ytitle;
2771 } else {
2772 Ymain -= 1.5 * Yspacing;
2773 }
2775 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2776 if (im->watermark[0] != '\0') {
2777 Ymain -= Ywatermark;
2778 }
2780 im->ysize = Ymain;
2782 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2784 /* The actual size of the image to draw is determined from
2785 ** several sources. The size given on the command line is
2786 ** the graph area but we need more as we have to draw labels
2787 ** and other things outside the graph area.
2788 */
2790 if (im->ylegend[0] != '\0') {
2791 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2792 }
2795 if (im->title[0] != '\0') {
2796 /* The title is placed "inbetween" two text lines so it
2797 ** automatically has some vertical spacing. The horizontal
2798 ** spacing is added here, on each side.
2799 */
2800 /* don't care for the with of the title
2801 Xtitle = gfx_get_text_width(im->canvas, 0,
2802 im->text_prop[TEXT_PROP_TITLE].font,
2803 im->text_prop[TEXT_PROP_TITLE].size,
2804 im->tabwidth,
2805 im->title, 0) + 2*Xspacing; */
2806 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2807 }
2809 if (elements) {
2810 Xmain = im->xsize;
2811 Ymain = im->ysize;
2812 }
2813 /* Now calculate the total size. Insert some spacing where
2814 desired. im->xorigin and im->yorigin need to correspond
2815 with the lower left corner of the main graph area or, if
2816 this one is not set, the imaginary box surrounding the
2817 pie chart area. */
2819 /* The legend width cannot yet be determined, as a result we
2820 ** have problems adjusting the image to it. For now, we just
2821 ** forget about it at all; the legend will have to fit in the
2822 ** size already allocated.
2823 */
2824 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2826 if (Xmain)
2827 im->ximg += Xspacing;
2829 im->xorigin = Xspacing + Xylabel;
2831 /* the length of the title should not influence with width of the graph
2832 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2834 if (Xvertical) { /* unit description */
2835 im->ximg += Xvertical;
2836 im->xorigin += Xvertical;
2837 }
2838 xtr(im, 0);
2840 /* The vertical size is interesting... we need to compare
2841 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2842 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2843 ** in order to start even thinking about Ylegend or Ywatermark.
2844 **
2845 ** Do it in three portions: First calculate the inner part,
2846 ** then do the legend, then adjust the total height of the img,
2847 ** adding space for a watermark if one exists;
2848 */
2850 /* reserve space for main and/or pie */
2852 im->yimg = Ymain + Yxlabel;
2855 im->yorigin = im->yimg - Yxlabel;
2857 /* reserve space for the title *or* some padding above the graph */
2858 if (Ytitle) {
2859 im->yimg += Ytitle;
2860 im->yorigin += Ytitle;
2861 } else {
2862 im->yimg += 1.5 * Yspacing;
2863 im->yorigin += 1.5 * Yspacing;
2864 }
2865 /* reserve space for padding below the graph */
2866 im->yimg += Yspacing;
2868 /* Determine where to place the legends onto the image.
2869 ** Adjust im->yimg to match the space requirements.
2870 */
2871 if (leg_place(im, 0) == -1)
2872 return -1;
2874 if (im->watermark[0] != '\0') {
2875 im->yimg += Ywatermark;
2876 }
2877 }
2879 ytr(im, DNAN);
2880 return 0;
2881 }
2885 static cairo_status_t cairo_write_func_filehandle(
2886 void *closure,
2887 const unsigned char *data,
2888 unsigned int length)
2889 {
2890 if (fwrite(data, length, 1, closure) != 1)
2891 return CAIRO_STATUS_WRITE_ERROR;
2892 return CAIRO_STATUS_SUCCESS;
2893 }
2895 static cairo_status_t cairo_copy_to_buffer(
2896 void *closure,
2897 const unsigned char *data,
2898 unsigned int length)
2899 {
2900 image_desc_t *im = closure;
2902 im->rendered_image =
2903 realloc(im->rendered_image, im->rendered_image_size + length);
2904 if (im->rendered_image == NULL) {
2905 return CAIRO_STATUS_WRITE_ERROR;
2906 }
2908 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2910 im->rendered_image_size += length;
2912 return CAIRO_STATUS_SUCCESS;
2913 }
2915 /* draw that picture thing ... */
2916 int graph_paint(
2917 image_desc_t *im,
2918 char ***calcpr)
2919 {
2920 int i, ii;
2921 int lazy = lazy_check(im);
2923 double areazero = 0.0;
2924 graph_desc_t *lastgdes = NULL;
2926 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2929 /* if we are lazy and there is nothing to PRINT ... quit now */
2930 if (lazy && im->prt_c == 0)
2931 return 0;
2933 /* pull the data from the rrd files ... */
2935 if (data_fetch(im) == -1)
2936 return -1;
2938 /* evaluate VDEF and CDEF operations ... */
2939 if (data_calc(im) == -1)
2940 return -1;
2943 /* calculate and PRINT and GPRINT definitions. We have to do it at
2944 * this point because it will affect the length of the legends
2945 * if there are no graph elements we stop here ...
2946 * if we are lazy, try to quit ...
2947 */
2948 i = print_calc(im, calcpr);
2949 if (i < 0)
2950 return -1;
2951 if ((i == 0) || lazy)
2952 return 0;
2954 /**************************************************************
2955 *** Calculating sizes and locations became a bit confusing ***
2956 *** so I moved this into a separate function. ***
2957 **************************************************************/
2958 if (graph_size_location(im, i) == -1)
2959 return -1;
2961 /* get actual drawing data and find min and max values */
2962 if (data_proc(im) == -1)
2963 return -1;
2965 if (!im->logarithmic) {
2966 si_unit(im);
2967 }
2968 /* identify si magnitude Kilo, Mega Giga ? */
2969 if (!im->rigid && !im->logarithmic)
2970 expand_range(im); /* make sure the upper and lower limit are
2971 sensible values */
2973 if (!calc_horizontal_grid(im))
2974 return -1;
2976 /* reset precalc */
2977 ytr(im, DNAN);
2979 /* if (im->gridfit)
2980 apply_gridfit(im); */
2983 /* the actual graph is created by going through the individual
2984 graph elements and then drawing them */
2985 cairo_surface_destroy(im->surface);
2987 switch (im->imgformat) {
2988 case IF_PNG:
2989 im->surface =
2990 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2991 im->ximg * im->zoom,
2992 im->yimg * im->zoom);
2993 break;
2994 case IF_PDF:
2995 im->gridfit = 0;
2996 im->surface = strlen(im->graphfile)
2997 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2998 im->yimg * im->zoom)
2999 : cairo_pdf_surface_create_for_stream(&cairo_copy_to_buffer, im,
3000 im->ximg * im->zoom,
3001 im->yimg * im->zoom);
3002 break;
3003 case IF_EPS:
3004 im->gridfit = 0;
3005 im->surface = strlen(im->graphfile)
3006 ? cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3007 im->yimg * im->zoom)
3008 : cairo_ps_surface_create_for_stream(&cairo_copy_to_buffer, im,
3009 im->ximg * im->zoom,
3010 im->yimg * im->zoom);
3011 break;
3012 case IF_SVG:
3013 im->gridfit = 0;
3014 im->surface = strlen(im->graphfile)
3015 ? cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
3016 im->yimg * im->zoom)
3017 : cairo_svg_surface_create_for_stream(&cairo_copy_to_buffer, im,
3018 im->ximg * im->zoom,
3019 im->yimg * im->zoom);
3020 cairo_svg_surface_restrict_to_version(im->surface,
3021 CAIRO_SVG_VERSION_1_1);
3022 break;
3023 };
3024 im->cr = cairo_create(im->surface);
3025 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3026 cairo_set_antialias(im->cr, im->graph_antialias);
3027 cairo_scale(im->cr, im->zoom, im->zoom);
3029 gfx_new_area(im,
3030 0, 0,
3031 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3033 gfx_add_point(im, im->ximg, 0);
3034 gfx_close_path(im);
3036 gfx_new_area(im,
3037 im->xorigin, im->yorigin,
3038 im->xorigin + im->xsize, im->yorigin,
3039 im->xorigin + im->xsize, im->yorigin - im->ysize,
3040 im->graph_col[GRC_CANVAS]);
3042 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3043 gfx_close_path(im);
3045 if (im->minval > 0.0)
3046 areazero = im->minval;
3047 if (im->maxval < 0.0)
3048 areazero = im->maxval;
3050 for (i = 0; i < im->gdes_c; i++) {
3051 switch (im->gdes[i].gf) {
3052 case GF_CDEF:
3053 case GF_VDEF:
3054 case GF_DEF:
3055 case GF_PRINT:
3056 case GF_GPRINT:
3057 case GF_COMMENT:
3058 case GF_TEXTALIGN:
3059 case GF_HRULE:
3060 case GF_VRULE:
3061 case GF_XPORT:
3062 case GF_SHIFT:
3063 break;
3064 case GF_TICK:
3065 for (ii = 0; ii < im->xsize; ii++) {
3066 if (!isnan(im->gdes[i].p_data[ii]) &&
3067 im->gdes[i].p_data[ii] != 0.0) {
3068 if (im->gdes[i].yrule > 0) {
3069 gfx_line(im,
3070 im->xorigin + ii, im->yorigin,
3071 im->xorigin + ii,
3072 im->yorigin -
3073 im->gdes[i].yrule * im->ysize, 1.0,
3074 im->gdes[i].col);
3075 } else if (im->gdes[i].yrule < 0) {
3076 gfx_line(im,
3077 im->xorigin + ii,
3078 im->yorigin - im->ysize,
3079 im->xorigin + ii,
3080 im->yorigin - (1 -
3081 im->gdes[i].yrule) *
3082 im->ysize, 1.0, im->gdes[i].col);
3084 }
3085 }
3086 }
3087 break;
3088 case GF_LINE:
3089 case GF_AREA:
3090 /* fix data points at oo and -oo */
3091 for (ii = 0; ii < im->xsize; ii++) {
3092 if (isinf(im->gdes[i].p_data[ii])) {
3093 if (im->gdes[i].p_data[ii] > 0) {
3094 im->gdes[i].p_data[ii] = im->maxval;
3095 } else {
3096 im->gdes[i].p_data[ii] = im->minval;
3097 }
3099 }
3100 } /* for */
3102 /* *******************************************************
3103 a ___. (a,t)
3104 | | ___
3105 ____| | | |
3106 | |___|
3107 -------|--t-1--t--------------------------------
3109 if we know the value at time t was a then
3110 we draw a square from t-1 to t with the value a.
3112 ********************************************************* */
3113 if (im->gdes[i].col.alpha != 0.0) {
3114 /* GF_LINE and friend */
3115 if (im->gdes[i].gf == GF_LINE) {
3116 double last_y = 0.0;
3117 int draw_on = 0;
3119 cairo_save(im->cr);
3120 cairo_new_path(im->cr);
3122 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3123 for (ii = 1; ii < im->xsize; ii++) {
3124 if (isnan(im->gdes[i].p_data[ii])
3125 || (im->slopemode == 1
3126 && isnan(im->gdes[i].p_data[ii - 1]))) {
3127 draw_on = 0;
3128 continue;
3129 }
3130 if (draw_on == 0) {
3131 last_y = ytr(im, im->gdes[i].p_data[ii]);
3132 if (im->slopemode == 0) {
3133 double x = ii - 1 + im->xorigin;
3134 double y = last_y;
3136 gfx_line_fit(im, &x, &y);
3137 cairo_move_to(im->cr, x, y);
3138 x = ii + im->xorigin;
3139 y = last_y;
3140 gfx_line_fit(im, &x, &y);
3141 cairo_line_to(im->cr, x, y);
3142 } else {
3143 double x = ii - 1 + im->xorigin;
3144 double y = ytr(im,
3145 im->gdes[i].p_data[ii - 1]);
3147 gfx_line_fit(im, &x, &y);
3148 cairo_move_to(im->cr, x, y);
3149 x = ii + im->xorigin;
3150 y = last_y;
3151 gfx_line_fit(im, &x, &y);
3152 cairo_line_to(im->cr, x, y);
3153 }
3154 draw_on = 1;
3155 } else {
3156 double x1 = ii + im->xorigin;
3157 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3159 if (im->slopemode == 0
3160 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3161 double x = ii - 1 + im->xorigin;
3162 double y = y1;
3164 gfx_line_fit(im, &x, &y);
3165 cairo_line_to(im->cr, x, y);
3166 };
3167 last_y = y1;
3168 gfx_line_fit(im, &x1, &y1);
3169 cairo_line_to(im->cr, x1, y1);
3170 };
3172 }
3173 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3174 im->gdes[i].col.green,
3175 im->gdes[i].col.blue,
3176 im->gdes[i].col.alpha);
3177 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3178 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3179 cairo_stroke(im->cr);
3180 cairo_restore(im->cr);
3181 } else {
3182 int idxI = -1;
3183 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3184 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3185 double *backY = malloc(sizeof(double) * im->xsize * 2);
3186 double *backX = malloc(sizeof(double) * im->xsize * 2);
3187 int drawem = 0;
3189 for (ii = 0; ii <= im->xsize; ii++) {
3190 double ybase, ytop;
3192 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3193 int cntI = 1;
3194 int lastI = 0;
3196 while (cntI < idxI
3197 && AlmostEqual2sComplement(foreY[lastI],
3198 foreY[cntI], 4)
3199 && AlmostEqual2sComplement(foreY[lastI],
3200 foreY[cntI + 1],
3201 4)) {
3202 cntI++;
3203 }
3204 gfx_new_area(im,
3205 backX[0], backY[0],
3206 foreX[0], foreY[0],
3207 foreX[cntI], foreY[cntI],
3208 im->gdes[i].col);
3209 while (cntI < idxI) {
3210 lastI = cntI;
3211 cntI++;
3212 while (cntI < idxI
3213 &&
3214 AlmostEqual2sComplement(foreY[lastI],
3215 foreY[cntI], 4)
3216 &&
3217 AlmostEqual2sComplement(foreY[lastI],
3218 foreY[cntI +
3219 1], 4)) {
3220 cntI++;
3221 }
3222 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3223 }
3224 gfx_add_point(im, backX[idxI], backY[idxI]);
3225 while (idxI > 1) {
3226 lastI = idxI;
3227 idxI--;
3228 while (idxI > 1
3229 &&
3230 AlmostEqual2sComplement(backY[lastI],
3231 backY[idxI], 4)
3232 &&
3233 AlmostEqual2sComplement(backY[lastI],
3234 backY[idxI -
3235 1], 4)) {
3236 idxI--;
3237 }
3238 gfx_add_point(im, backX[idxI], backY[idxI]);
3239 }
3240 idxI = -1;
3241 drawem = 0;
3242 gfx_close_path(im);
3243 }
3244 if (drawem != 0) {
3245 drawem = 0;
3246 idxI = -1;
3247 }
3248 if (ii == im->xsize)
3249 break;
3251 if (im->slopemode == 0 && ii == 0) {
3252 continue;
3253 }
3254 if (isnan(im->gdes[i].p_data[ii])) {
3255 drawem = 1;
3256 continue;
3257 }
3258 ytop = ytr(im, im->gdes[i].p_data[ii]);
3259 if (lastgdes && im->gdes[i].stack) {
3260 ybase = ytr(im, lastgdes->p_data[ii]);
3261 } else {
3262 ybase = ytr(im, areazero);
3263 }
3264 if (ybase == ytop) {
3265 drawem = 1;
3266 continue;
3267 }
3269 if (ybase > ytop) {
3270 double extra = ytop;
3272 ytop = ybase;
3273 ybase = extra;
3274 }
3275 if (im->slopemode == 0) {
3276 backY[++idxI] = ybase - 0.2;
3277 backX[idxI] = ii + im->xorigin - 1;
3278 foreY[idxI] = ytop + 0.2;
3279 foreX[idxI] = ii + im->xorigin - 1;
3280 }
3281 backY[++idxI] = ybase - 0.2;
3282 backX[idxI] = ii + im->xorigin;
3283 foreY[idxI] = ytop + 0.2;
3284 foreX[idxI] = ii + im->xorigin;
3285 }
3286 /* close up any remaining area */
3287 free(foreY);
3288 free(foreX);
3289 free(backY);
3290 free(backX);
3291 } /* else GF_LINE */
3292 }
3293 /* if color != 0x0 */
3294 /* make sure we do not run into trouble when stacking on NaN */
3295 for (ii = 0; ii < im->xsize; ii++) {
3296 if (isnan(im->gdes[i].p_data[ii])) {
3297 if (lastgdes && (im->gdes[i].stack)) {
3298 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3299 } else {
3300 im->gdes[i].p_data[ii] = areazero;
3301 }
3302 }
3303 }
3304 lastgdes = &(im->gdes[i]);
3305 break;
3306 case GF_STACK:
3307 rrd_set_error
3308 ("STACK should already be turned into LINE or AREA here");
3309 return -1;
3310 break;
3312 } /* switch */
3313 }
3315 /* grid_paint also does the text */
3316 if (!(im->extra_flags & ONLY_GRAPH))
3317 grid_paint(im);
3320 if (!(im->extra_flags & ONLY_GRAPH))
3321 axis_paint(im);
3323 /* the RULES are the last thing to paint ... */
3324 for (i = 0; i < im->gdes_c; i++) {
3326 switch (im->gdes[i].gf) {
3327 case GF_HRULE:
3328 if (im->gdes[i].yrule >= im->minval
3329 && im->gdes[i].yrule <= im->maxval)
3330 gfx_line(im,
3331 im->xorigin, ytr(im, im->gdes[i].yrule),
3332 im->xorigin + im->xsize, ytr(im,
3333 im->gdes[i].yrule),
3334 1.0, im->gdes[i].col);
3335 break;
3336 case GF_VRULE:
3337 if (im->gdes[i].xrule >= im->start
3338 && im->gdes[i].xrule <= im->end)
3339 gfx_line(im,
3340 xtr(im, im->gdes[i].xrule), im->yorigin,
3341 xtr(im, im->gdes[i].xrule),
3342 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3343 break;
3344 default:
3345 break;
3346 }
3347 }
3350 switch (im->imgformat) {
3351 case IF_PNG:
3352 {
3353 cairo_status_t status;
3355 if (strlen(im->graphfile) == 0) {
3356 status =
3357 cairo_surface_write_to_png_stream(im->surface,
3358 &cairo_copy_to_buffer, im);
3359 } else if (strcmp(im->graphfile, "-") == 0) {
3360 status =
3361 cairo_surface_write_to_png_stream(im->surface,
3362 &cairo_write_func_filehandle,
3363 (void *) stdout);
3364 } else {
3365 status = cairo_surface_write_to_png(im->surface, im->graphfile);
3366 }
3368 if (status != CAIRO_STATUS_SUCCESS) {
3369 rrd_set_error("Could not save png to '%s'", im->graphfile);
3370 return 1;
3371 }
3372 }
3373 break;
3374 default:
3375 if (strlen(im->graphfile)) {
3376 cairo_show_page(im->cr);
3377 } else {
3378 cairo_surface_finish(im->surface);
3379 }
3380 break;
3381 }
3382 return 0;
3383 }
3386 /*****************************************************
3387 * graph stuff
3388 *****************************************************/
3390 int gdes_alloc(
3391 image_desc_t *im)
3392 {
3394 im->gdes_c++;
3395 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3396 * sizeof(graph_desc_t))) ==
3397 NULL) {
3398 rrd_set_error("realloc graph_descs");
3399 return -1;
3400 }
3403 im->gdes[im->gdes_c - 1].step = im->step;
3404 im->gdes[im->gdes_c - 1].step_orig = im->step;
3405 im->gdes[im->gdes_c - 1].stack = 0;
3406 im->gdes[im->gdes_c - 1].linewidth = 0;
3407 im->gdes[im->gdes_c - 1].debug = 0;
3408 im->gdes[im->gdes_c - 1].start = im->start;
3409 im->gdes[im->gdes_c - 1].start_orig = im->start;
3410 im->gdes[im->gdes_c - 1].end = im->end;
3411 im->gdes[im->gdes_c - 1].end_orig = im->end;
3412 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3413 im->gdes[im->gdes_c - 1].data = NULL;
3414 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3415 im->gdes[im->gdes_c - 1].data_first = 0;
3416 im->gdes[im->gdes_c - 1].p_data = NULL;
3417 im->gdes[im->gdes_c - 1].rpnp = NULL;
3418 im->gdes[im->gdes_c - 1].shift = 0.0;
3419 im->gdes[im->gdes_c - 1].col.red = 0.0;
3420 im->gdes[im->gdes_c - 1].col.green = 0.0;
3421 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3422 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3423 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3424 im->gdes[im->gdes_c - 1].format[0] = '\0';
3425 im->gdes[im->gdes_c - 1].strftm = 0;
3426 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3427 im->gdes[im->gdes_c - 1].ds = -1;
3428 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3429 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3430 im->gdes[im->gdes_c - 1].p_data = NULL;
3431 im->gdes[im->gdes_c - 1].yrule = DNAN;
3432 im->gdes[im->gdes_c - 1].xrule = 0;
3433 return 0;
3434 }
3436 /* copies input untill the first unescaped colon is found
3437 or until input ends. backslashes have to be escaped as well */
3438 int scan_for_col(
3439 const char *const input,
3440 int len,
3441 char *const output)
3442 {
3443 int inp, outp = 0;
3445 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3446 if (input[inp] == '\\' &&
3447 input[inp + 1] != '\0' &&
3448 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3449 output[outp++] = input[++inp];
3450 } else {
3451 output[outp++] = input[inp];
3452 }
3453 }
3454 output[outp] = '\0';
3455 return inp;
3456 }
3458 /* Some surgery done on this function, it became ridiculously big.
3459 ** Things moved:
3460 ** - initializing now in rrd_graph_init()
3461 ** - options parsing now in rrd_graph_options()
3462 ** - script parsing now in rrd_graph_script()
3463 */
3464 int rrd_graph(
3465 int argc,
3466 char **argv,
3467 char ***prdata,
3468 int *xsize,
3469 int *ysize,
3470 FILE * stream,
3471 double *ymin,
3472 double *ymax)
3473 {
3474 image_desc_t im;
3476 rrd_graph_init(&im);
3478 /* a dummy surface so that we can measure text sizes for placements */
3479 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3480 im.cr = cairo_create(im.surface);
3481 im.graphhandle = stream;
3483 rrd_graph_options(argc, argv, &im);
3484 if (rrd_test_error()) {
3485 im_free(&im);
3486 return -1;
3487 }
3489 if (optind >= argc) {
3490 rrd_set_error("missing filename");
3491 return -1;
3492 }
3494 if (strlen(argv[optind]) >= MAXPATH) {
3495 rrd_set_error("filename (including path) too long");
3496 im_free(&im);
3497 return -1;
3498 }
3500 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3501 im.graphfile[MAXPATH - 1] = '\0';
3503 rrd_graph_script(argc, argv, &im, 1);
3504 if (rrd_test_error()) {
3505 im_free(&im);
3506 return -1;
3507 }
3509 /* Everything is now read and the actual work can start */
3511 (*prdata) = NULL;
3512 if (graph_paint(&im, prdata) == -1) {
3513 im_free(&im);
3514 return -1;
3515 }
3517 /* The image is generated and needs to be output.
3518 ** Also, if needed, print a line with information about the image.
3519 */
3521 *xsize = im.ximg;
3522 *ysize = im.yimg;
3523 *ymin = im.minval;
3524 *ymax = im.maxval;
3525 if (im.imginfo) {
3526 char *filename;
3528 if (!(*prdata)) {
3529 /* maybe prdata is not allocated yet ... lets do it now */
3530 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3531 rrd_set_error("malloc imginfo");
3532 return -1;
3533 };
3534 }
3535 if (((*prdata)[0] =
3536 malloc((strlen(im.imginfo) + 200 +
3537 strlen(im.graphfile)) * sizeof(char)))
3538 == NULL) {
3539 rrd_set_error("malloc imginfo");
3540 return -1;
3541 }
3542 filename = im.graphfile + strlen(im.graphfile);
3543 while (filename > im.graphfile) {
3544 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3545 break;
3546 filename--;
3547 }
3549 sprintf((*prdata)[0], im.imginfo, filename,
3550 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3551 }
3552 im_free(&im);
3553 return 0;
3554 }
3556 /* a simplified version of the above that just creates the graph in memory
3557 and returns a pointer to it. */
3559 unsigned char *rrd_graph_in_memory(
3560 int argc,
3561 char **argv,
3562 char ***prdata,
3563 int *xsize,
3564 int *ysize,
3565 double *ymin,
3566 double *ymax,
3567 size_t * img_size)
3568 {
3569 image_desc_t im;
3571 rrd_graph_init(&im);
3573 /* a dummy surface so that we can measure text sizes for placements */
3574 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3575 im.cr = cairo_create(im.surface);
3577 rrd_graph_options(argc, argv, &im);
3578 if (rrd_test_error()) {
3579 im_free(&im);
3580 return NULL;
3581 }
3583 rrd_graph_script(argc, argv, &im, 1);
3584 if (rrd_test_error()) {
3585 im_free(&im);
3586 return NULL;
3587 }
3589 /* Everything is now read and the actual work can start */
3591 /* by not assigning a name to im.graphfile data will be written to
3592 newly allocated memory on im.rendered_image ... */
3594 (*prdata) = NULL;
3595 if (graph_paint(&im, prdata) == -1) {
3596 im_free(&im);
3597 return NULL;
3598 }
3600 *xsize = im.ximg;
3601 *ysize = im.yimg;
3602 *ymin = im.minval;
3603 *ymax = im.maxval;
3604 *img_size = im.rendered_image_size;
3605 im_free(&im);
3607 return im.rendered_image;
3608 }
3610 void rrd_graph_init(
3611 image_desc_t *im)
3612 {
3613 unsigned int i;
3615 #ifdef HAVE_TZSET
3616 tzset();
3617 #endif
3618 #ifdef HAVE_SETLOCALE
3619 setlocale(LC_TIME, "");
3620 #ifdef HAVE_MBSTOWCS
3621 setlocale(LC_CTYPE, "");
3622 #endif
3623 #endif
3624 im->yorigin = 0;
3625 im->xorigin = 0;
3626 im->minval = 0;
3627 im->xlab_user.minsec = -1;
3628 im->ximg = 0;
3629 im->yimg = 0;
3630 im->xsize = 400;
3631 im->ysize = 100;
3632 im->rendered_image_size = 0;
3633 im->rendered_image = NULL;
3634 im->step = 0;
3635 im->ylegend[0] = '\0';
3636 im->title[0] = '\0';
3637 im->watermark[0] = '\0';
3638 im->minval = DNAN;
3639 im->maxval = DNAN;
3640 im->unitsexponent = 9999;
3641 im->unitslength = 6;
3642 im->forceleftspace = 0;
3643 im->symbol = ' ';
3644 im->viewfactor = 1.0;
3645 im->imgformat = IF_PNG;
3646 im->graphfile[0] = '\0';
3647 im->cr = NULL;
3648 im->surface = NULL;
3649 im->extra_flags = 0;
3650 im->rigid = 0;
3651 im->gridfit = 1;
3652 im->imginfo = NULL;
3653 im->lazy = 0;
3654 im->slopemode = 0;
3655 im->logarithmic = 0;
3656 im->ygridstep = DNAN;
3657 im->draw_x_grid = 1;
3658 im->draw_y_grid = 1;
3659 im->base = 1000;
3660 im->prt_c = 0;
3661 im->gdes_c = 0;
3662 im->gdes = NULL;
3663 im->grid_dash_on = 1;
3664 im->grid_dash_off = 1;
3665 im->tabwidth = 40.0;
3666 im->zoom = 1;
3667 im->font_options = cairo_font_options_create();
3668 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3670 cairo_font_options_set_hint_style(im->font_options,
3671 CAIRO_HINT_STYLE_FULL);
3672 cairo_font_options_set_hint_metrics(im->font_options,
3673 CAIRO_HINT_METRICS_ON);
3674 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3677 for (i = 0; i < DIM(graph_col); i++)
3678 im->graph_col[i] = graph_col[i];
3680 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3681 {
3682 char *windir;
3683 char rrd_win_default_font[1000];
3685 windir = getenv("windir");
3686 /* %windir% is something like D:\windows or C:\winnt */
3687 if (windir != NULL) {
3688 strncpy(rrd_win_default_font, windir, 500);
3689 rrd_win_default_font[500] = '\0';
3690 strcat(rrd_win_default_font, "\\fonts\\");
3691 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3692 for (i = 0; i < DIM(text_prop); i++) {
3693 strncpy(text_prop[i].font, rrd_win_default_font,
3694 sizeof(text_prop[i].font) - 1);
3695 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3696 }
3697 }
3698 }
3699 #endif
3700 {
3701 char *deffont;
3703 deffont = getenv("RRD_DEFAULT_FONT");
3704 if (deffont != NULL) {
3705 for (i = 0; i < DIM(text_prop); i++) {
3706 strncpy(text_prop[i].font, deffont,
3707 sizeof(text_prop[i].font) - 1);
3708 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3709 }
3710 }
3711 }
3712 for (i = 0; i < DIM(text_prop); i++) {
3713 im->text_prop[i].size = text_prop[i].size;
3714 strcpy(im->text_prop[i].font, text_prop[i].font);
3715 }
3716 }
3718 void rrd_graph_options(
3719 int argc,
3720 char *argv[],
3721 image_desc_t *im)
3722 {
3723 int stroff;
3724 char *parsetime_error = NULL;
3725 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3726 time_t start_tmp = 0, end_tmp = 0;
3727 long long_tmp;
3728 struct rrd_time_value start_tv, end_tv;
3729 long unsigned int color;
3730 char *old_locale = "";
3732 /* defines for long options without a short equivalent. should be bytes,
3733 and may not collide with (the ASCII value of) short options */
3734 #define LONGOPT_UNITS_SI 255
3735 struct option long_options[] = {
3736 {"start", required_argument, 0, 's'},
3737 {"end", required_argument, 0, 'e'},
3738 {"x-grid", required_argument, 0, 'x'},
3739 {"y-grid", required_argument, 0, 'y'},
3740 {"vertical-label", required_argument, 0, 'v'},
3741 {"width", required_argument, 0, 'w'},
3742 {"height", required_argument, 0, 'h'},
3743 {"full-size-mode", no_argument, 0, 'D'},
3744 {"interlaced", no_argument, 0, 'i'},
3745 {"upper-limit", required_argument, 0, 'u'},
3746 {"lower-limit", required_argument, 0, 'l'},
3747 {"rigid", no_argument, 0, 'r'},
3748 {"base", required_argument, 0, 'b'},
3749 {"logarithmic", no_argument, 0, 'o'},
3750 {"color", required_argument, 0, 'c'},
3751 {"font", required_argument, 0, 'n'},
3752 {"title", required_argument, 0, 't'},
3753 {"imginfo", required_argument, 0, 'f'},
3754 {"imgformat", required_argument, 0, 'a'},
3755 {"lazy", no_argument, 0, 'z'},
3756 {"zoom", required_argument, 0, 'm'},
3757 {"no-legend", no_argument, 0, 'g'},
3758 {"force-rules-legend", no_argument, 0, 'F'},
3759 {"only-graph", no_argument, 0, 'j'},
3760 {"alt-y-grid", no_argument, 0, 'Y'},
3761 {"no-minor", no_argument, 0, 'I'},
3762 {"slope-mode", no_argument, 0, 'E'},
3763 {"alt-autoscale", no_argument, 0, 'A'},
3764 {"alt-autoscale-min", no_argument, 0, 'J'},
3765 {"alt-autoscale-max", no_argument, 0, 'M'},
3766 {"no-gridfit", no_argument, 0, 'N'},
3767 {"units-exponent", required_argument, 0, 'X'},
3768 {"units-length", required_argument, 0, 'L'},
3769 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3770 {"step", required_argument, 0, 'S'},
3771 {"tabwidth", required_argument, 0, 'T'},
3772 {"font-render-mode", required_argument, 0, 'R'},
3773 {"graph-render-mode", required_argument, 0, 'G'},
3774 {"font-smoothing-threshold", required_argument, 0, 'B'},
3775 {"watermark", required_argument, 0, 'W'},
3776 {"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 */
3777 {0, 0, 0, 0}
3778 };
3780 optind = 0;
3781 opterr = 0; /* initialize getopt */
3783 parsetime("end-24h", &start_tv);
3784 parsetime("now", &end_tv);
3786 while (1) {
3787 int option_index = 0;
3788 int opt;
3789 int col_start, col_end;
3791 opt = getopt_long(argc, argv,
3792 "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:",
3793 long_options, &option_index);
3795 if (opt == EOF)
3796 break;
3798 switch (opt) {
3799 case 'I':
3800 im->extra_flags |= NOMINOR;
3801 break;
3802 case 'Y':
3803 im->extra_flags |= ALTYGRID;
3804 break;
3805 case 'A':
3806 im->extra_flags |= ALTAUTOSCALE;
3807 break;
3808 case 'J':
3809 im->extra_flags |= ALTAUTOSCALE_MIN;
3810 break;
3811 case 'M':
3812 im->extra_flags |= ALTAUTOSCALE_MAX;
3813 break;
3814 case 'j':
3815 im->extra_flags |= ONLY_GRAPH;
3816 break;
3817 case 'g':
3818 im->extra_flags |= NOLEGEND;
3819 break;
3820 case 'F':
3821 im->extra_flags |= FORCE_RULES_LEGEND;
3822 break;
3823 case LONGOPT_UNITS_SI:
3824 if (im->extra_flags & FORCE_UNITS) {
3825 rrd_set_error("--units can only be used once!");
3826 setlocale(LC_NUMERIC, old_locale);
3827 return;
3828 }
3829 if (strcmp(optarg, "si") == 0)
3830 im->extra_flags |= FORCE_UNITS_SI;
3831 else {
3832 rrd_set_error("invalid argument for --units: %s", optarg);
3833 return;
3834 }
3835 break;
3836 case 'X':
3837 im->unitsexponent = atoi(optarg);
3838 break;
3839 case 'L':
3840 im->unitslength = atoi(optarg);
3841 im->forceleftspace = 1;
3842 break;
3843 case 'T':
3844 old_locale = setlocale(LC_NUMERIC, "C");
3845 im->tabwidth = atof(optarg);
3846 setlocale(LC_NUMERIC, old_locale);
3847 break;
3848 case 'S':
3849 old_locale = setlocale(LC_NUMERIC, "C");
3850 im->step = atoi(optarg);
3851 setlocale(LC_NUMERIC, old_locale);
3852 break;
3853 case 'N':
3854 im->gridfit = 0;
3855 break;
3856 case 's':
3857 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3858 rrd_set_error("start time: %s", parsetime_error);
3859 return;
3860 }
3861 break;
3862 case 'e':
3863 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3864 rrd_set_error("end time: %s", parsetime_error);
3865 return;
3866 }
3867 break;
3868 case 'x':
3869 if (strcmp(optarg, "none") == 0) {
3870 im->draw_x_grid = 0;
3871 break;
3872 };
3874 if (sscanf(optarg,
3875 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3876 scan_gtm,
3877 &im->xlab_user.gridst,
3878 scan_mtm,
3879 &im->xlab_user.mgridst,
3880 scan_ltm,
3881 &im->xlab_user.labst,
3882 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3883 strncpy(im->xlab_form, optarg + stroff,
3884 sizeof(im->xlab_form) - 1);
3885 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3886 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3887 rrd_set_error("unknown keyword %s", scan_gtm);
3888 return;
3889 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3890 == -1) {
3891 rrd_set_error("unknown keyword %s", scan_mtm);
3892 return;
3893 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3894 -1) {
3895 rrd_set_error("unknown keyword %s", scan_ltm);
3896 return;
3897 }
3898 im->xlab_user.minsec = 1;
3899 im->xlab_user.stst = im->xlab_form;
3900 } else {
3901 rrd_set_error("invalid x-grid format");
3902 return;
3903 }
3904 break;
3905 case 'y':
3907 if (strcmp(optarg, "none") == 0) {
3908 im->draw_y_grid = 0;
3909 break;
3910 };
3911 old_locale = setlocale(LC_NUMERIC, "C");
3912 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3913 setlocale(LC_NUMERIC, old_locale);
3914 if (im->ygridstep <= 0) {
3915 rrd_set_error("grid step must be > 0");
3916 return;
3917 } else if (im->ylabfact < 1) {
3918 rrd_set_error("label factor must be > 0");
3919 return;
3920 }
3921 } else {
3922 setlocale(LC_NUMERIC, old_locale);
3923 rrd_set_error("invalid y-grid format");
3924 return;
3925 }
3926 break;
3927 case 'v':
3928 strncpy(im->ylegend, optarg, 150);
3929 im->ylegend[150] = '\0';
3930 break;
3931 case 'u':
3932 old_locale = setlocale(LC_NUMERIC, "C");
3933 im->maxval = atof(optarg);
3934 setlocale(LC_NUMERIC, old_locale);
3935 break;
3936 case 'l':
3937 old_locale = setlocale(LC_NUMERIC, "C");
3938 im->minval = atof(optarg);
3939 setlocale(LC_NUMERIC, old_locale);
3940 break;
3941 case 'b':
3942 im->base = atol(optarg);
3943 if (im->base != 1024 && im->base != 1000) {
3944 rrd_set_error
3945 ("the only sensible value for base apart from 1000 is 1024");
3946 return;
3947 }
3948 break;
3949 case 'w':
3950 long_tmp = atol(optarg);
3951 if (long_tmp < 10) {
3952 rrd_set_error("width below 10 pixels");
3953 return;
3954 }
3955 im->xsize = long_tmp;
3956 break;
3957 case 'h':
3958 long_tmp = atol(optarg);
3959 if (long_tmp < 10) {
3960 rrd_set_error("height below 10 pixels");
3961 return;
3962 }
3963 im->ysize = long_tmp;
3964 break;
3965 case 'D':
3966 im->extra_flags |= FULL_SIZE_MODE;
3967 break;
3968 case 'i':
3969 /* interlaced png not supported at the moment */
3970 break;
3971 case 'r':
3972 im->rigid = 1;
3973 break;
3974 case 'f':
3975 im->imginfo = optarg;
3976 break;
3977 case 'a':
3978 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3979 rrd_set_error("unsupported graphics format '%s'", optarg);
3980 return;
3981 }
3982 break;
3983 case 'z':
3984 im->lazy = 1;
3985 break;
3986 case 'E':
3987 im->slopemode = 1;
3988 break;
3990 case 'o':
3991 im->logarithmic = 1;
3992 break;
3993 case 'c':
3994 if (sscanf(optarg,
3995 "%10[A-Z]#%n%8lx%n",
3996 col_nam, &col_start, &color, &col_end) == 2) {
3997 int ci;
3998 int col_len = col_end - col_start;
4000 switch (col_len) {
4001 case 3:
4002 color = (((color & 0xF00) * 0x110000) |
4003 ((color & 0x0F0) * 0x011000) |
4004 ((color & 0x00F) * 0x001100) | 0x000000FF);
4005 break;
4006 case 4:
4007 color = (((color & 0xF000) * 0x11000) |
4008 ((color & 0x0F00) * 0x01100) |
4009 ((color & 0x00F0) * 0x00110) |
4010 ((color & 0x000F) * 0x00011)
4011 );
4012 break;
4013 case 6:
4014 color = (color << 8) + 0xff /* shift left by 8 */ ;
4015 break;
4016 case 8:
4017 break;
4018 default:
4019 rrd_set_error("the color format is #RRGGBB[AA]");
4020 return;
4021 }
4022 if ((ci = grc_conv(col_nam)) != -1) {
4023 im->graph_col[ci] = gfx_hex_to_col(color);
4024 } else {
4025 rrd_set_error("invalid color name '%s'", col_nam);
4026 return;
4027 }
4028 } else {
4029 rrd_set_error("invalid color def format");
4030 return;
4031 }
4032 break;
4033 case 'n':{
4034 char prop[15];
4035 double size = 1;
4036 char font[1024] = "";
4038 old_locale = setlocale(LC_NUMERIC, "C");
4039 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
4040 int sindex, propidx;
4042 setlocale(LC_NUMERIC, old_locale);
4043 if ((sindex = text_prop_conv(prop)) != -1) {
4044 for (propidx = sindex; propidx < TEXT_PROP_LAST;
4045 propidx++) {
4046 if (size > 0) {
4047 im->text_prop[propidx].size = size;
4048 }
4049 if (strlen(font) > 0) {
4050 strcpy(im->text_prop[propidx].font, font);
4051 }
4052 if (propidx == sindex && sindex != 0)
4053 break;
4054 }
4055 } else {
4056 rrd_set_error("invalid fonttag '%s'", prop);
4057 return;
4058 }
4059 } else {
4060 setlocale(LC_NUMERIC, old_locale);
4061 rrd_set_error("invalid text property format");
4062 return;
4063 }
4064 break;
4065 }
4066 case 'm':
4067 old_locale = setlocale(LC_NUMERIC, "C");
4068 im->zoom = atof(optarg);
4069 setlocale(LC_NUMERIC, old_locale);
4070 if (im->zoom <= 0.0) {
4071 rrd_set_error("zoom factor must be > 0");
4072 return;
4073 }
4074 break;
4075 case 't':
4076 strncpy(im->title, optarg, 150);
4077 im->title[150] = '\0';
4078 break;
4080 case 'R':
4081 if (strcmp(optarg, "normal") == 0) {
4082 cairo_font_options_set_antialias(im->font_options,
4083 CAIRO_ANTIALIAS_GRAY);
4084 cairo_font_options_set_hint_style(im->font_options,
4085 CAIRO_HINT_STYLE_FULL);
4086 } else if (strcmp(optarg, "light") == 0) {
4087 cairo_font_options_set_antialias(im->font_options,
4088 CAIRO_ANTIALIAS_GRAY);
4089 cairo_font_options_set_hint_style(im->font_options,
4090 CAIRO_HINT_STYLE_SLIGHT);
4091 } else if (strcmp(optarg, "mono") == 0) {
4092 cairo_font_options_set_antialias(im->font_options,
4093 CAIRO_ANTIALIAS_NONE);
4094 cairo_font_options_set_hint_style(im->font_options,
4095 CAIRO_HINT_STYLE_FULL);
4096 } else {
4097 rrd_set_error("unknown font-render-mode '%s'", optarg);
4098 return;
4099 }
4100 break;
4101 case 'G':
4102 if (strcmp(optarg, "normal") == 0)
4103 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4104 else if (strcmp(optarg, "mono") == 0)
4105 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4106 else {
4107 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4108 return;
4109 }
4110 break;
4111 case 'B':
4112 /* not supported curently */
4113 break;
4115 case 'W':
4116 strncpy(im->watermark, optarg, 100);
4117 im->watermark[99] = '\0';
4118 break;
4120 case '?':
4121 if (optopt != 0)
4122 rrd_set_error("unknown option '%c'", optopt);
4123 else
4124 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4125 return;
4126 }
4127 }
4129 if (im->logarithmic == 1 && im->minval <= 0) {
4130 rrd_set_error
4131 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4132 return;
4133 }
4135 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4136 /* error string is set in parsetime.c */
4137 return;
4138 }
4140 if (start_tmp < 3600 * 24 * 365 * 10) {
4141 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4142 start_tmp);
4143 return;
4144 }
4146 if (end_tmp < start_tmp) {
4147 rrd_set_error("start (%ld) should be less than end (%ld)",
4148 start_tmp, end_tmp);
4149 return;
4150 }
4152 im->start = start_tmp;
4153 im->end = end_tmp;
4154 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4155 }
4157 int rrd_graph_color(
4158 image_desc_t *im,
4159 char *var,
4160 char *err,
4161 int optional)
4162 {
4163 char *color;
4164 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4166 color = strstr(var, "#");
4167 if (color == NULL) {
4168 if (optional == 0) {
4169 rrd_set_error("Found no color in %s", err);
4170 return 0;
4171 }
4172 return 0;
4173 } else {
4174 int n = 0;
4175 char *rest;
4176 long unsigned int col;
4178 rest = strstr(color, ":");
4179 if (rest != NULL)
4180 n = rest - color;
4181 else
4182 n = strlen(color);
4184 switch (n) {
4185 case 7:
4186 sscanf(color, "#%6lx%n", &col, &n);
4187 col = (col << 8) + 0xff /* shift left by 8 */ ;
4188 if (n != 7)
4189 rrd_set_error("Color problem in %s", err);
4190 break;
4191 case 9:
4192 sscanf(color, "#%8lx%n", &col, &n);
4193 if (n == 9)
4194 break;
4195 default:
4196 rrd_set_error("Color problem in %s", err);
4197 }
4198 if (rrd_test_error())
4199 return 0;
4200 gdp->col = gfx_hex_to_col(col);
4201 return n;
4202 }
4203 }
4206 int bad_format(
4207 char *fmt)
4208 {
4209 char *ptr;
4210 int n = 0;
4212 ptr = fmt;
4213 while (*ptr != '\0')
4214 if (*ptr++ == '%') {
4216 /* line cannot end with percent char */
4217 if (*ptr == '\0')
4218 return 1;
4220 /* '%s', '%S' and '%%' are allowed */
4221 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4222 ptr++;
4224 /* %c is allowed (but use only with vdef!) */
4225 else if (*ptr == 'c') {
4226 ptr++;
4227 n = 1;
4228 }
4230 /* or else '% 6.2lf' and such are allowed */
4231 else {
4232 /* optional padding character */
4233 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4234 ptr++;
4236 /* This should take care of 'm.n' with all three optional */
4237 while (*ptr >= '0' && *ptr <= '9')
4238 ptr++;
4239 if (*ptr == '.')
4240 ptr++;
4241 while (*ptr >= '0' && *ptr <= '9')
4242 ptr++;
4244 /* Either 'le', 'lf' or 'lg' must follow here */
4245 if (*ptr++ != 'l')
4246 return 1;
4247 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4248 ptr++;
4249 else
4250 return 1;
4251 n++;
4252 }
4253 }
4255 return (n != 1);
4256 }
4259 int vdef_parse(
4260 struct graph_desc_t *gdes,
4261 const char *const str)
4262 {
4263 /* A VDEF currently is either "func" or "param,func"
4264 * so the parsing is rather simple. Change if needed.
4265 */
4266 double param;
4267 char func[30];
4268 int n;
4269 char *old_locale;
4271 n = 0;
4272 old_locale = setlocale(LC_NUMERIC, "C");
4273 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4274 setlocale(LC_NUMERIC, old_locale);
4275 if (n == (int) strlen(str)) { /* matched */
4276 ;
4277 } else {
4278 n = 0;
4279 sscanf(str, "%29[A-Z]%n", func, &n);
4280 if (n == (int) strlen(str)) { /* matched */
4281 param = DNAN;
4282 } else {
4283 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4284 gdes->vname);
4285 return -1;
4286 }
4287 }
4288 if (!strcmp("PERCENT", func))
4289 gdes->vf.op = VDEF_PERCENT;
4290 else if (!strcmp("MAXIMUM", func))
4291 gdes->vf.op = VDEF_MAXIMUM;
4292 else if (!strcmp("AVERAGE", func))
4293 gdes->vf.op = VDEF_AVERAGE;
4294 else if (!strcmp("STDEV", func))
4295 gdes->vf.op = VDEF_STDEV;
4296 else if (!strcmp("MINIMUM", func))
4297 gdes->vf.op = VDEF_MINIMUM;
4298 else if (!strcmp("TOTAL", func))
4299 gdes->vf.op = VDEF_TOTAL;
4300 else if (!strcmp("FIRST", func))
4301 gdes->vf.op = VDEF_FIRST;
4302 else if (!strcmp("LAST", func))
4303 gdes->vf.op = VDEF_LAST;
4304 else if (!strcmp("LSLSLOPE", func))
4305 gdes->vf.op = VDEF_LSLSLOPE;
4306 else if (!strcmp("LSLINT", func))
4307 gdes->vf.op = VDEF_LSLINT;
4308 else if (!strcmp("LSLCORREL", func))
4309 gdes->vf.op = VDEF_LSLCORREL;
4310 else {
4311 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4312 gdes->vname);
4313 return -1;
4314 };
4316 switch (gdes->vf.op) {
4317 case VDEF_PERCENT:
4318 if (isnan(param)) { /* no parameter given */
4319 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4320 func, gdes->vname);
4321 return -1;
4322 };
4323 if (param >= 0.0 && param <= 100.0) {
4324 gdes->vf.param = param;
4325 gdes->vf.val = DNAN; /* undefined */
4326 gdes->vf.when = 0; /* undefined */
4327 } else {
4328 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4329 gdes->vname);
4330 return -1;
4331 };
4332 break;
4333 case VDEF_MAXIMUM:
4334 case VDEF_AVERAGE:
4335 case VDEF_STDEV:
4336 case VDEF_MINIMUM:
4337 case VDEF_TOTAL:
4338 case VDEF_FIRST:
4339 case VDEF_LAST:
4340 case VDEF_LSLSLOPE:
4341 case VDEF_LSLINT:
4342 case VDEF_LSLCORREL:
4343 if (isnan(param)) {
4344 gdes->vf.param = DNAN;
4345 gdes->vf.val = DNAN;
4346 gdes->vf.when = 0;
4347 } else {
4348 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4349 func, gdes->vname);
4350 return -1;
4351 };
4352 break;
4353 };
4354 return 0;
4355 }
4358 int vdef_calc(
4359 image_desc_t *im,
4360 int gdi)
4361 {
4362 graph_desc_t *src, *dst;
4363 rrd_value_t *data;
4364 long step, steps;
4366 dst = &im->gdes[gdi];
4367 src = &im->gdes[dst->vidx];
4368 data = src->data + src->ds;
4369 steps = (src->end - src->start) / src->step;
4371 #if 0
4372 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4373 src->end, steps);
4374 #endif
4376 switch (dst->vf.op) {
4377 case VDEF_PERCENT:{
4378 rrd_value_t *array;
4379 int field;
4382 if ((array = malloc(steps * sizeof(double))) == NULL) {
4383 rrd_set_error("malloc VDEV_PERCENT");
4384 return -1;
4385 }
4386 for (step = 0; step < steps; step++) {
4387 array[step] = data[step * src->ds_cnt];
4388 }
4389 qsort(array, step, sizeof(double), vdef_percent_compar);
4391 field = (steps - 1) * dst->vf.param / 100;
4392 dst->vf.val = array[field];
4393 dst->vf.when = 0; /* no time component */
4394 free(array);
4395 #if 0
4396 for (step = 0; step < steps; step++)
4397 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4398 step == field ? '*' : ' ');
4399 #endif
4400 }
4401 break;
4402 case VDEF_MAXIMUM:
4403 step = 0;
4404 while (step != steps && isnan(data[step * src->ds_cnt]))
4405 step++;
4406 if (step == steps) {
4407 dst->vf.val = DNAN;
4408 dst->vf.when = 0;
4409 } else {
4410 dst->vf.val = data[step * src->ds_cnt];
4411 dst->vf.when = src->start + (step + 1) * src->step;
4412 }
4413 while (step != steps) {
4414 if (finite(data[step * src->ds_cnt])) {
4415 if (data[step * src->ds_cnt] > dst->vf.val) {
4416 dst->vf.val = data[step * src->ds_cnt];
4417 dst->vf.when = src->start + (step + 1) * src->step;
4418 }
4419 }
4420 step++;
4421 }
4422 break;
4423 case VDEF_TOTAL:
4424 case VDEF_STDEV:
4425 case VDEF_AVERAGE:{
4426 int cnt = 0;
4427 double sum = 0.0;
4428 double average = 0.0;
4430 for (step = 0; step < steps; step++) {
4431 if (finite(data[step * src->ds_cnt])) {
4432 sum += data[step * src->ds_cnt];
4433 cnt++;
4434 };
4435 }
4436 if (cnt) {
4437 if (dst->vf.op == VDEF_TOTAL) {
4438 dst->vf.val = sum * src->step;
4439 dst->vf.when = 0; /* no time component */
4440 } else if (dst->vf.op == VDEF_AVERAGE) {
4441 dst->vf.val = sum / cnt;
4442 dst->vf.when = 0; /* no time component */
4443 } else {
4444 average = sum / cnt;
4445 sum = 0.0;
4446 for (step = 0; step < steps; step++) {
4447 if (finite(data[step * src->ds_cnt])) {
4448 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4449 };
4450 }
4451 dst->vf.val = pow(sum / cnt, 0.5);
4452 dst->vf.when = 0; /* no time component */
4453 };
4454 } else {
4455 dst->vf.val = DNAN;
4456 dst->vf.when = 0;
4457 }
4458 }
4459 break;
4460 case VDEF_MINIMUM:
4461 step = 0;
4462 while (step != steps && isnan(data[step * src->ds_cnt]))
4463 step++;
4464 if (step == steps) {
4465 dst->vf.val = DNAN;
4466 dst->vf.when = 0;
4467 } else {
4468 dst->vf.val = data[step * src->ds_cnt];
4469 dst->vf.when = src->start + (step + 1) * src->step;
4470 }
4471 while (step != steps) {
4472 if (finite(data[step * src->ds_cnt])) {
4473 if (data[step * src->ds_cnt] < dst->vf.val) {
4474 dst->vf.val = data[step * src->ds_cnt];
4475 dst->vf.when = src->start + (step + 1) * src->step;
4476 }
4477 }
4478 step++;
4479 }
4480 break;
4481 case VDEF_FIRST:
4482 /* The time value returned here is one step before the
4483 * actual time value. This is the start of the first
4484 * non-NaN interval.
4485 */
4486 step = 0;
4487 while (step != steps && isnan(data[step * src->ds_cnt]))
4488 step++;
4489 if (step == steps) { /* all entries were NaN */
4490 dst->vf.val = DNAN;
4491 dst->vf.when = 0;
4492 } else {
4493 dst->vf.val = data[step * src->ds_cnt];
4494 dst->vf.when = src->start + step * src->step;
4495 }
4496 break;
4497 case VDEF_LAST:
4498 /* The time value returned here is the
4499 * actual time value. This is the end of the last
4500 * non-NaN interval.
4501 */
4502 step = steps - 1;
4503 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4504 step--;
4505 if (step < 0) { /* all entries were NaN */
4506 dst->vf.val = DNAN;
4507 dst->vf.when = 0;
4508 } else {
4509 dst->vf.val = data[step * src->ds_cnt];
4510 dst->vf.when = src->start + (step + 1) * src->step;
4511 }
4512 break;
4513 case VDEF_LSLSLOPE:
4514 case VDEF_LSLINT:
4515 case VDEF_LSLCORREL:{
4516 /* Bestfit line by linear least squares method */
4518 int cnt = 0;
4519 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4521 SUMx = 0;
4522 SUMy = 0;
4523 SUMxy = 0;
4524 SUMxx = 0;
4525 SUMyy = 0;
4527 for (step = 0; step < steps; step++) {
4528 if (finite(data[step * src->ds_cnt])) {
4529 cnt++;
4530 SUMx += step;
4531 SUMxx += step * step;
4532 SUMxy += step * data[step * src->ds_cnt];
4533 SUMy += data[step * src->ds_cnt];
4534 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4535 };
4536 }
4538 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4539 y_intercept = (SUMy - slope * SUMx) / cnt;
4540 correl =
4541 (SUMxy -
4542 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4543 (SUMx * SUMx) / cnt) * (SUMyy -
4544 (SUMy *
4545 SUMy) /
4546 cnt));
4548 if (cnt) {
4549 if (dst->vf.op == VDEF_LSLSLOPE) {
4550 dst->vf.val = slope;
4551 dst->vf.when = 0;
4552 } else if (dst->vf.op == VDEF_LSLINT) {
4553 dst->vf.val = y_intercept;
4554 dst->vf.when = 0;
4555 } else if (dst->vf.op == VDEF_LSLCORREL) {
4556 dst->vf.val = correl;
4557 dst->vf.when = 0;
4558 };
4560 } else {
4561 dst->vf.val = DNAN;
4562 dst->vf.when = 0;
4563 }
4564 }
4565 break;
4566 }
4567 return 0;
4568 }
4570 /* NaN < -INF < finite_values < INF */
4571 int vdef_percent_compar(
4572 const void *a,
4573 const void *b)
4574 {
4575 /* Equality is not returned; this doesn't hurt except
4576 * (maybe) for a little performance.
4577 */
4579 /* First catch NaN values. They are smallest */
4580 if (isnan(*(double *) a))
4581 return -1;
4582 if (isnan(*(double *) b))
4583 return 1;
4585 /* NaN doesn't reach this part so INF and -INF are extremes.
4586 * The sign from isinf() is compatible with the sign we return
4587 */
4588 if (isinf(*(double *) a))
4589 return isinf(*(double *) a);
4590 if (isinf(*(double *) b))
4591 return isinf(*(double *) b);
4593 /* If we reach this, both values must be finite */
4594 if (*(double *) a < *(double *) b)
4595 return -1;
4596 else
4597 return 1;
4598 }