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