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 {
3350 cairo_save(im->cr);
3351 if (im->gdes[i].dash) {
3352 cairo_set_dash(im->cr, im->gdes[i].p_dashes,
3353 im->gdes[i].ndash, im->gdes[i].offset);
3354 }
3355 gfx_line(im,
3356 im->xorigin, ytr(im, im->gdes[i].yrule),
3357 im->xorigin + im->xsize, ytr(im,
3358 im->gdes[i].yrule),
3359 1.0, im->gdes[i].col);
3360 cairo_stroke(im->cr);
3361 cairo_restore(im->cr);
3362 }
3363 break;
3364 case GF_VRULE:
3365 if (im->gdes[i].xrule >= im->start
3366 && im->gdes[i].xrule <= im->end)
3367 {
3368 cairo_save(im->cr);
3369 if (im->gdes[i].dash) {
3370 cairo_set_dash(im->cr, im->gdes[i].p_dashes,
3371 im->gdes[i].ndash, im->gdes[i].offset);
3372 }
3373 gfx_line(im,
3374 xtr(im, im->gdes[i].xrule), im->yorigin,
3375 xtr(im, im->gdes[i].xrule),
3376 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3377 cairo_stroke(im->cr);
3378 cairo_restore(im->cr);
3379 }
3380 break;
3381 default:
3382 break;
3383 }
3384 }
3387 switch (im->imgformat) {
3388 case IF_PNG:
3389 {
3390 cairo_status_t status;
3392 if (strlen(im->graphfile) == 0) {
3393 status =
3394 cairo_surface_write_to_png_stream(im->surface,
3395 &cairo_copy_to_buffer, im);
3396 } else if (strcmp(im->graphfile, "-") == 0) {
3397 status =
3398 cairo_surface_write_to_png_stream(im->surface,
3399 &cairo_write_func_filehandle,
3400 (void *) stdout);
3401 } else {
3402 status = cairo_surface_write_to_png(im->surface, im->graphfile);
3403 }
3405 if (status != CAIRO_STATUS_SUCCESS) {
3406 rrd_set_error("Could not save png to '%s'", im->graphfile);
3407 return 1;
3408 }
3409 }
3410 break;
3411 default:
3412 if (strlen(im->graphfile)) {
3413 cairo_show_page(im->cr);
3414 } else {
3415 cairo_surface_finish(im->surface);
3416 }
3417 break;
3418 }
3419 return 0;
3420 }
3423 /*****************************************************
3424 * graph stuff
3425 *****************************************************/
3427 int gdes_alloc(
3428 image_desc_t *im)
3429 {
3431 im->gdes_c++;
3432 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3433 * sizeof(graph_desc_t))) ==
3434 NULL) {
3435 rrd_set_error("realloc graph_descs");
3436 return -1;
3437 }
3440 im->gdes[im->gdes_c - 1].step = im->step;
3441 im->gdes[im->gdes_c - 1].step_orig = im->step;
3442 im->gdes[im->gdes_c - 1].stack = 0;
3443 im->gdes[im->gdes_c - 1].linewidth = 0;
3444 im->gdes[im->gdes_c - 1].debug = 0;
3445 im->gdes[im->gdes_c - 1].start = im->start;
3446 im->gdes[im->gdes_c - 1].start_orig = im->start;
3447 im->gdes[im->gdes_c - 1].end = im->end;
3448 im->gdes[im->gdes_c - 1].end_orig = im->end;
3449 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3450 im->gdes[im->gdes_c - 1].data = NULL;
3451 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3452 im->gdes[im->gdes_c - 1].data_first = 0;
3453 im->gdes[im->gdes_c - 1].p_data = NULL;
3454 im->gdes[im->gdes_c - 1].rpnp = NULL;
3455 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3456 im->gdes[im->gdes_c - 1].shift = 0.0;
3457 im->gdes[im->gdes_c - 1].dash = 0;
3458 im->gdes[im->gdes_c - 1].ndash = 0;
3459 im->gdes[im->gdes_c - 1].offset = 0;
3460 im->gdes[im->gdes_c - 1].col.red = 0.0;
3461 im->gdes[im->gdes_c - 1].col.green = 0.0;
3462 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3463 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3464 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3465 im->gdes[im->gdes_c - 1].format[0] = '\0';
3466 im->gdes[im->gdes_c - 1].strftm = 0;
3467 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3468 im->gdes[im->gdes_c - 1].ds = -1;
3469 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3470 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3471 im->gdes[im->gdes_c - 1].yrule = DNAN;
3472 im->gdes[im->gdes_c - 1].xrule = 0;
3473 return 0;
3474 }
3476 /* copies input untill the first unescaped colon is found
3477 or until input ends. backslashes have to be escaped as well */
3478 int scan_for_col(
3479 const char *const input,
3480 int len,
3481 char *const output)
3482 {
3483 int inp, outp = 0;
3485 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3486 if (input[inp] == '\\' &&
3487 input[inp + 1] != '\0' &&
3488 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3489 output[outp++] = input[++inp];
3490 } else {
3491 output[outp++] = input[inp];
3492 }
3493 }
3494 output[outp] = '\0';
3495 return inp;
3496 }
3498 /* Some surgery done on this function, it became ridiculously big.
3499 ** Things moved:
3500 ** - initializing now in rrd_graph_init()
3501 ** - options parsing now in rrd_graph_options()
3502 ** - script parsing now in rrd_graph_script()
3503 */
3504 int rrd_graph(
3505 int argc,
3506 char **argv,
3507 char ***prdata,
3508 int *xsize,
3509 int *ysize,
3510 FILE * stream,
3511 double *ymin,
3512 double *ymax)
3513 {
3514 image_desc_t im;
3516 rrd_graph_init(&im);
3518 /* a dummy surface so that we can measure text sizes for placements */
3519 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3520 im.cr = cairo_create(im.surface);
3521 im.graphhandle = stream;
3523 rrd_graph_options(argc, argv, &im);
3524 if (rrd_test_error()) {
3525 im_free(&im);
3526 return -1;
3527 }
3529 if (optind >= argc) {
3530 rrd_set_error("missing filename");
3531 return -1;
3532 }
3534 if (strlen(argv[optind]) >= MAXPATH) {
3535 rrd_set_error("filename (including path) too long");
3536 im_free(&im);
3537 return -1;
3538 }
3540 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3541 im.graphfile[MAXPATH - 1] = '\0';
3543 rrd_graph_script(argc, argv, &im, 1);
3544 if (rrd_test_error()) {
3545 im_free(&im);
3546 return -1;
3547 }
3549 /* Everything is now read and the actual work can start */
3551 (*prdata) = NULL;
3552 if (graph_paint(&im, prdata) == -1) {
3553 im_free(&im);
3554 return -1;
3555 }
3557 /* The image is generated and needs to be output.
3558 ** Also, if needed, print a line with information about the image.
3559 */
3561 *xsize = im.ximg;
3562 *ysize = im.yimg;
3563 *ymin = im.minval;
3564 *ymax = im.maxval;
3565 if (im.imginfo) {
3566 char *filename;
3568 if (!(*prdata)) {
3569 /* maybe prdata is not allocated yet ... lets do it now */
3570 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3571 rrd_set_error("malloc imginfo");
3572 return -1;
3573 };
3574 }
3575 if (((*prdata)[0] =
3576 malloc((strlen(im.imginfo) + 200 +
3577 strlen(im.graphfile)) * sizeof(char)))
3578 == NULL) {
3579 rrd_set_error("malloc imginfo");
3580 return -1;
3581 }
3582 filename = im.graphfile + strlen(im.graphfile);
3583 while (filename > im.graphfile) {
3584 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3585 break;
3586 filename--;
3587 }
3589 sprintf((*prdata)[0], im.imginfo, filename,
3590 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3591 }
3592 im_free(&im);
3593 return 0;
3594 }
3596 /* a simplified version of the above that just creates the graph in memory
3597 and returns a pointer to it. */
3599 unsigned char *rrd_graph_in_memory(
3600 int argc,
3601 char **argv,
3602 char ***prdata,
3603 int *xsize,
3604 int *ysize,
3605 double *ymin,
3606 double *ymax,
3607 size_t * img_size)
3608 {
3609 image_desc_t im;
3611 rrd_graph_init(&im);
3613 /* a dummy surface so that we can measure text sizes for placements */
3614 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3615 im.cr = cairo_create(im.surface);
3617 rrd_graph_options(argc, argv, &im);
3618 if (rrd_test_error()) {
3619 im_free(&im);
3620 return NULL;
3621 }
3623 rrd_graph_script(argc, argv, &im, 1);
3624 if (rrd_test_error()) {
3625 im_free(&im);
3626 return NULL;
3627 }
3629 /* Everything is now read and the actual work can start */
3631 /* by not assigning a name to im.graphfile data will be written to
3632 newly allocated memory on im.rendered_image ... */
3634 (*prdata) = NULL;
3635 if (graph_paint(&im, prdata) == -1) {
3636 im_free(&im);
3637 return NULL;
3638 }
3640 *xsize = im.ximg;
3641 *ysize = im.yimg;
3642 *ymin = im.minval;
3643 *ymax = im.maxval;
3644 *img_size = im.rendered_image_size;
3645 im_free(&im);
3647 return im.rendered_image;
3648 }
3650 void rrd_graph_init(
3651 image_desc_t *im)
3652 {
3653 unsigned int i;
3655 #ifdef HAVE_TZSET
3656 tzset();
3657 #endif
3658 #ifdef HAVE_SETLOCALE
3659 setlocale(LC_TIME, "");
3660 #ifdef HAVE_MBSTOWCS
3661 setlocale(LC_CTYPE, "");
3662 #endif
3663 #endif
3664 im->yorigin = 0;
3665 im->xorigin = 0;
3666 im->minval = 0;
3667 im->xlab_user.minsec = -1;
3668 im->ximg = 0;
3669 im->yimg = 0;
3670 im->xsize = 400;
3671 im->ysize = 100;
3672 im->rendered_image_size = 0;
3673 im->rendered_image = NULL;
3674 im->step = 0;
3675 im->ylegend[0] = '\0';
3676 im->title[0] = '\0';
3677 im->watermark[0] = '\0';
3678 im->minval = DNAN;
3679 im->maxval = DNAN;
3680 im->unitsexponent = 9999;
3681 im->unitslength = 6;
3682 im->forceleftspace = 0;
3683 im->symbol = ' ';
3684 im->viewfactor = 1.0;
3685 im->imgformat = IF_PNG;
3686 im->graphfile[0] = '\0';
3687 im->cr = NULL;
3688 im->surface = NULL;
3689 im->extra_flags = 0;
3690 im->rigid = 0;
3691 im->gridfit = 1;
3692 im->imginfo = NULL;
3693 im->lazy = 0;
3694 im->slopemode = 0;
3695 im->logarithmic = 0;
3696 im->ygridstep = DNAN;
3697 im->draw_x_grid = 1;
3698 im->draw_y_grid = 1;
3699 im->base = 1000;
3700 im->prt_c = 0;
3701 im->gdes_c = 0;
3702 im->gdes = NULL;
3703 im->grid_dash_on = 1;
3704 im->grid_dash_off = 1;
3705 im->tabwidth = 40.0;
3706 im->zoom = 1;
3707 im->font_options = cairo_font_options_create();
3708 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3710 cairo_font_options_set_hint_style(im->font_options,
3711 CAIRO_HINT_STYLE_FULL);
3712 cairo_font_options_set_hint_metrics(im->font_options,
3713 CAIRO_HINT_METRICS_ON);
3714 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3717 for (i = 0; i < DIM(graph_col); i++)
3718 im->graph_col[i] = graph_col[i];
3720 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3721 {
3722 char *windir;
3723 char rrd_win_default_font[1000];
3725 windir = getenv("windir");
3726 /* %windir% is something like D:\windows or C:\winnt */
3727 if (windir != NULL) {
3728 strncpy(rrd_win_default_font, windir, 500);
3729 rrd_win_default_font[500] = '\0';
3730 strcat(rrd_win_default_font, "\\fonts\\");
3731 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3732 for (i = 0; i < DIM(text_prop); i++) {
3733 strncpy(text_prop[i].font, rrd_win_default_font,
3734 sizeof(text_prop[i].font) - 1);
3735 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3736 }
3737 }
3738 }
3739 #endif
3740 {
3741 char *deffont;
3743 deffont = getenv("RRD_DEFAULT_FONT");
3744 if (deffont != NULL) {
3745 for (i = 0; i < DIM(text_prop); i++) {
3746 strncpy(text_prop[i].font, deffont,
3747 sizeof(text_prop[i].font) - 1);
3748 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3749 }
3750 }
3751 }
3752 for (i = 0; i < DIM(text_prop); i++) {
3753 im->text_prop[i].size = text_prop[i].size;
3754 strcpy(im->text_prop[i].font, text_prop[i].font);
3755 }
3756 }
3758 void rrd_graph_options(
3759 int argc,
3760 char *argv[],
3761 image_desc_t *im)
3762 {
3763 int stroff;
3764 char *parsetime_error = NULL;
3765 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3766 time_t start_tmp = 0, end_tmp = 0;
3767 long long_tmp;
3768 struct rrd_time_value start_tv, end_tv;
3769 long unsigned int color;
3770 char *old_locale = "";
3772 /* defines for long options without a short equivalent. should be bytes,
3773 and may not collide with (the ASCII value of) short options */
3774 #define LONGOPT_UNITS_SI 255
3775 struct option long_options[] = {
3776 {"start", required_argument, 0, 's'},
3777 {"end", required_argument, 0, 'e'},
3778 {"x-grid", required_argument, 0, 'x'},
3779 {"y-grid", required_argument, 0, 'y'},
3780 {"vertical-label", required_argument, 0, 'v'},
3781 {"width", required_argument, 0, 'w'},
3782 {"height", required_argument, 0, 'h'},
3783 {"full-size-mode", no_argument, 0, 'D'},
3784 {"interlaced", no_argument, 0, 'i'},
3785 {"upper-limit", required_argument, 0, 'u'},
3786 {"lower-limit", required_argument, 0, 'l'},
3787 {"rigid", no_argument, 0, 'r'},
3788 {"base", required_argument, 0, 'b'},
3789 {"logarithmic", no_argument, 0, 'o'},
3790 {"color", required_argument, 0, 'c'},
3791 {"font", required_argument, 0, 'n'},
3792 {"title", required_argument, 0, 't'},
3793 {"imginfo", required_argument, 0, 'f'},
3794 {"imgformat", required_argument, 0, 'a'},
3795 {"lazy", no_argument, 0, 'z'},
3796 {"zoom", required_argument, 0, 'm'},
3797 {"no-legend", no_argument, 0, 'g'},
3798 {"force-rules-legend", no_argument, 0, 'F'},
3799 {"only-graph", no_argument, 0, 'j'},
3800 {"alt-y-grid", no_argument, 0, 'Y'},
3801 {"no-minor", no_argument, 0, 'I'},
3802 {"slope-mode", no_argument, 0, 'E'},
3803 {"alt-autoscale", no_argument, 0, 'A'},
3804 {"alt-autoscale-min", no_argument, 0, 'J'},
3805 {"alt-autoscale-max", no_argument, 0, 'M'},
3806 {"no-gridfit", no_argument, 0, 'N'},
3807 {"units-exponent", required_argument, 0, 'X'},
3808 {"units-length", required_argument, 0, 'L'},
3809 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3810 {"step", required_argument, 0, 'S'},
3811 {"tabwidth", required_argument, 0, 'T'},
3812 {"font-render-mode", required_argument, 0, 'R'},
3813 {"graph-render-mode", required_argument, 0, 'G'},
3814 {"font-smoothing-threshold", required_argument, 0, 'B'},
3815 {"watermark", required_argument, 0, 'W'},
3816 {"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 */
3817 {0, 0, 0, 0}
3818 };
3820 optind = 0;
3821 opterr = 0; /* initialize getopt */
3823 parsetime("end-24h", &start_tv);
3824 parsetime("now", &end_tv);
3826 while (1) {
3827 int option_index = 0;
3828 int opt;
3829 int col_start, col_end;
3831 opt = getopt_long(argc, argv,
3832 "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:",
3833 long_options, &option_index);
3835 if (opt == EOF)
3836 break;
3838 switch (opt) {
3839 case 'I':
3840 im->extra_flags |= NOMINOR;
3841 break;
3842 case 'Y':
3843 im->extra_flags |= ALTYGRID;
3844 break;
3845 case 'A':
3846 im->extra_flags |= ALTAUTOSCALE;
3847 break;
3848 case 'J':
3849 im->extra_flags |= ALTAUTOSCALE_MIN;
3850 break;
3851 case 'M':
3852 im->extra_flags |= ALTAUTOSCALE_MAX;
3853 break;
3854 case 'j':
3855 im->extra_flags |= ONLY_GRAPH;
3856 break;
3857 case 'g':
3858 im->extra_flags |= NOLEGEND;
3859 break;
3860 case 'F':
3861 im->extra_flags |= FORCE_RULES_LEGEND;
3862 break;
3863 case LONGOPT_UNITS_SI:
3864 if (im->extra_flags & FORCE_UNITS) {
3865 rrd_set_error("--units can only be used once!");
3866 setlocale(LC_NUMERIC, old_locale);
3867 return;
3868 }
3869 if (strcmp(optarg, "si") == 0)
3870 im->extra_flags |= FORCE_UNITS_SI;
3871 else {
3872 rrd_set_error("invalid argument for --units: %s", optarg);
3873 return;
3874 }
3875 break;
3876 case 'X':
3877 im->unitsexponent = atoi(optarg);
3878 break;
3879 case 'L':
3880 im->unitslength = atoi(optarg);
3881 im->forceleftspace = 1;
3882 break;
3883 case 'T':
3884 old_locale = setlocale(LC_NUMERIC, "C");
3885 im->tabwidth = atof(optarg);
3886 setlocale(LC_NUMERIC, old_locale);
3887 break;
3888 case 'S':
3889 old_locale = setlocale(LC_NUMERIC, "C");
3890 im->step = atoi(optarg);
3891 setlocale(LC_NUMERIC, old_locale);
3892 break;
3893 case 'N':
3894 im->gridfit = 0;
3895 break;
3896 case 's':
3897 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3898 rrd_set_error("start time: %s", parsetime_error);
3899 return;
3900 }
3901 break;
3902 case 'e':
3903 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3904 rrd_set_error("end time: %s", parsetime_error);
3905 return;
3906 }
3907 break;
3908 case 'x':
3909 if (strcmp(optarg, "none") == 0) {
3910 im->draw_x_grid = 0;
3911 break;
3912 };
3914 if (sscanf(optarg,
3915 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3916 scan_gtm,
3917 &im->xlab_user.gridst,
3918 scan_mtm,
3919 &im->xlab_user.mgridst,
3920 scan_ltm,
3921 &im->xlab_user.labst,
3922 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3923 strncpy(im->xlab_form, optarg + stroff,
3924 sizeof(im->xlab_form) - 1);
3925 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3926 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3927 rrd_set_error("unknown keyword %s", scan_gtm);
3928 return;
3929 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3930 == -1) {
3931 rrd_set_error("unknown keyword %s", scan_mtm);
3932 return;
3933 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3934 -1) {
3935 rrd_set_error("unknown keyword %s", scan_ltm);
3936 return;
3937 }
3938 im->xlab_user.minsec = 1;
3939 im->xlab_user.stst = im->xlab_form;
3940 } else {
3941 rrd_set_error("invalid x-grid format");
3942 return;
3943 }
3944 break;
3945 case 'y':
3947 if (strcmp(optarg, "none") == 0) {
3948 im->draw_y_grid = 0;
3949 break;
3950 };
3951 old_locale = setlocale(LC_NUMERIC, "C");
3952 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3953 setlocale(LC_NUMERIC, old_locale);
3954 if (im->ygridstep <= 0) {
3955 rrd_set_error("grid step must be > 0");
3956 return;
3957 } else if (im->ylabfact < 1) {
3958 rrd_set_error("label factor must be > 0");
3959 return;
3960 }
3961 } else {
3962 setlocale(LC_NUMERIC, old_locale);
3963 rrd_set_error("invalid y-grid format");
3964 return;
3965 }
3966 break;
3967 case 'v':
3968 strncpy(im->ylegend, optarg, 150);
3969 im->ylegend[150] = '\0';
3970 break;
3971 case 'u':
3972 old_locale = setlocale(LC_NUMERIC, "C");
3973 im->maxval = atof(optarg);
3974 setlocale(LC_NUMERIC, old_locale);
3975 break;
3976 case 'l':
3977 old_locale = setlocale(LC_NUMERIC, "C");
3978 im->minval = atof(optarg);
3979 setlocale(LC_NUMERIC, old_locale);
3980 break;
3981 case 'b':
3982 im->base = atol(optarg);
3983 if (im->base != 1024 && im->base != 1000) {
3984 rrd_set_error
3985 ("the only sensible value for base apart from 1000 is 1024");
3986 return;
3987 }
3988 break;
3989 case 'w':
3990 long_tmp = atol(optarg);
3991 if (long_tmp < 10) {
3992 rrd_set_error("width below 10 pixels");
3993 return;
3994 }
3995 im->xsize = long_tmp;
3996 break;
3997 case 'h':
3998 long_tmp = atol(optarg);
3999 if (long_tmp < 10) {
4000 rrd_set_error("height below 10 pixels");
4001 return;
4002 }
4003 im->ysize = long_tmp;
4004 break;
4005 case 'D':
4006 im->extra_flags |= FULL_SIZE_MODE;
4007 break;
4008 case 'i':
4009 /* interlaced png not supported at the moment */
4010 break;
4011 case 'r':
4012 im->rigid = 1;
4013 break;
4014 case 'f':
4015 im->imginfo = optarg;
4016 break;
4017 case 'a':
4018 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
4019 rrd_set_error("unsupported graphics format '%s'", optarg);
4020 return;
4021 }
4022 break;
4023 case 'z':
4024 im->lazy = 1;
4025 break;
4026 case 'E':
4027 im->slopemode = 1;
4028 break;
4030 case 'o':
4031 im->logarithmic = 1;
4032 break;
4033 case 'c':
4034 if (sscanf(optarg,
4035 "%10[A-Z]#%n%8lx%n",
4036 col_nam, &col_start, &color, &col_end) == 2) {
4037 int ci;
4038 int col_len = col_end - col_start;
4040 switch (col_len) {
4041 case 3:
4042 color = (((color & 0xF00) * 0x110000) |
4043 ((color & 0x0F0) * 0x011000) |
4044 ((color & 0x00F) * 0x001100) | 0x000000FF);
4045 break;
4046 case 4:
4047 color = (((color & 0xF000) * 0x11000) |
4048 ((color & 0x0F00) * 0x01100) |
4049 ((color & 0x00F0) * 0x00110) |
4050 ((color & 0x000F) * 0x00011)
4051 );
4052 break;
4053 case 6:
4054 color = (color << 8) + 0xff /* shift left by 8 */ ;
4055 break;
4056 case 8:
4057 break;
4058 default:
4059 rrd_set_error("the color format is #RRGGBB[AA]");
4060 return;
4061 }
4062 if ((ci = grc_conv(col_nam)) != -1) {
4063 im->graph_col[ci] = gfx_hex_to_col(color);
4064 } else {
4065 rrd_set_error("invalid color name '%s'", col_nam);
4066 return;
4067 }
4068 } else {
4069 rrd_set_error("invalid color def format");
4070 return;
4071 }
4072 break;
4073 case 'n':{
4074 char prop[15];
4075 double size = 1;
4076 int end;
4078 old_locale = setlocale(LC_NUMERIC, "C");
4079 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4080 int sindex, propidx;
4082 setlocale(LC_NUMERIC, old_locale);
4083 if ((sindex = text_prop_conv(prop)) != -1) {
4084 for (propidx = sindex; propidx < TEXT_PROP_LAST;
4085 propidx++) {
4086 if (size > 0) {
4087 im->text_prop[propidx].size = size;
4088 }
4089 if (strlen(prop) > end) {
4090 if (prop[end] == ':') {
4091 strncpy(im->text_prop[propidx].font,
4092 prop + end + 1, 255);
4093 im->text_prop[propidx].font[255] = '\0';
4094 } else {
4095 rrd_set_error
4096 ("expected after font size in '%s'",
4097 prop);
4098 return;
4099 }
4100 }
4101 if (propidx == sindex && sindex != 0)
4102 break;
4103 }
4104 } else {
4105 rrd_set_error("invalid fonttag '%s'", prop);
4106 return;
4107 }
4108 } else {
4109 setlocale(LC_NUMERIC, old_locale);
4110 rrd_set_error("invalid text property format");
4111 return;
4112 }
4113 break;
4114 }
4115 case 'm':
4116 old_locale = setlocale(LC_NUMERIC, "C");
4117 im->zoom = atof(optarg);
4118 setlocale(LC_NUMERIC, old_locale);
4119 if (im->zoom <= 0.0) {
4120 rrd_set_error("zoom factor must be > 0");
4121 return;
4122 }
4123 break;
4124 case 't':
4125 strncpy(im->title, optarg, 150);
4126 im->title[150] = '\0';
4127 break;
4129 case 'R':
4130 if (strcmp(optarg, "normal") == 0) {
4131 cairo_font_options_set_antialias(im->font_options,
4132 CAIRO_ANTIALIAS_GRAY);
4133 cairo_font_options_set_hint_style(im->font_options,
4134 CAIRO_HINT_STYLE_FULL);
4135 } else if (strcmp(optarg, "light") == 0) {
4136 cairo_font_options_set_antialias(im->font_options,
4137 CAIRO_ANTIALIAS_GRAY);
4138 cairo_font_options_set_hint_style(im->font_options,
4139 CAIRO_HINT_STYLE_SLIGHT);
4140 } else if (strcmp(optarg, "mono") == 0) {
4141 cairo_font_options_set_antialias(im->font_options,
4142 CAIRO_ANTIALIAS_NONE);
4143 cairo_font_options_set_hint_style(im->font_options,
4144 CAIRO_HINT_STYLE_FULL);
4145 } else {
4146 rrd_set_error("unknown font-render-mode '%s'", optarg);
4147 return;
4148 }
4149 break;
4150 case 'G':
4151 if (strcmp(optarg, "normal") == 0)
4152 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4153 else if (strcmp(optarg, "mono") == 0)
4154 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4155 else {
4156 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4157 return;
4158 }
4159 break;
4160 case 'B':
4161 /* not supported curently */
4162 break;
4164 case 'W':
4165 strncpy(im->watermark, optarg, 100);
4166 im->watermark[99] = '\0';
4167 break;
4169 case '?':
4170 if (optopt != 0)
4171 rrd_set_error("unknown option '%c'", optopt);
4172 else
4173 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4174 return;
4175 }
4176 }
4178 if (im->logarithmic == 1 && im->minval <= 0) {
4179 rrd_set_error
4180 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4181 return;
4182 }
4184 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4185 /* error string is set in parsetime.c */
4186 return;
4187 }
4189 if (start_tmp < 3600 * 24 * 365 * 10) {
4190 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4191 start_tmp);
4192 return;
4193 }
4195 if (end_tmp < start_tmp) {
4196 rrd_set_error("start (%ld) should be less than end (%ld)",
4197 start_tmp, end_tmp);
4198 return;
4199 }
4201 im->start = start_tmp;
4202 im->end = end_tmp;
4203 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4204 }
4206 int rrd_graph_color(
4207 image_desc_t *im,
4208 char *var,
4209 char *err,
4210 int optional)
4211 {
4212 char *color;
4213 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4215 color = strstr(var, "#");
4216 if (color == NULL) {
4217 if (optional == 0) {
4218 rrd_set_error("Found no color in %s", err);
4219 return 0;
4220 }
4221 return 0;
4222 } else {
4223 int n = 0;
4224 char *rest;
4225 long unsigned int col;
4227 rest = strstr(color, ":");
4228 if (rest != NULL)
4229 n = rest - color;
4230 else
4231 n = strlen(color);
4233 switch (n) {
4234 case 7:
4235 sscanf(color, "#%6lx%n", &col, &n);
4236 col = (col << 8) + 0xff /* shift left by 8 */ ;
4237 if (n != 7)
4238 rrd_set_error("Color problem in %s", err);
4239 break;
4240 case 9:
4241 sscanf(color, "#%8lx%n", &col, &n);
4242 if (n == 9)
4243 break;
4244 default:
4245 rrd_set_error("Color problem in %s", err);
4246 }
4247 if (rrd_test_error())
4248 return 0;
4249 gdp->col = gfx_hex_to_col(col);
4250 return n;
4251 }
4252 }
4255 int bad_format(
4256 char *fmt)
4257 {
4258 char *ptr;
4259 int n = 0;
4261 ptr = fmt;
4262 while (*ptr != '\0')
4263 if (*ptr++ == '%') {
4265 /* line cannot end with percent char */
4266 if (*ptr == '\0')
4267 return 1;
4269 /* '%s', '%S' and '%%' are allowed */
4270 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4271 ptr++;
4273 /* %c is allowed (but use only with vdef!) */
4274 else if (*ptr == 'c') {
4275 ptr++;
4276 n = 1;
4277 }
4279 /* or else '% 6.2lf' and such are allowed */
4280 else {
4281 /* optional padding character */
4282 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4283 ptr++;
4285 /* This should take care of 'm.n' with all three optional */
4286 while (*ptr >= '0' && *ptr <= '9')
4287 ptr++;
4288 if (*ptr == '.')
4289 ptr++;
4290 while (*ptr >= '0' && *ptr <= '9')
4291 ptr++;
4293 /* Either 'le', 'lf' or 'lg' must follow here */
4294 if (*ptr++ != 'l')
4295 return 1;
4296 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4297 ptr++;
4298 else
4299 return 1;
4300 n++;
4301 }
4302 }
4304 return (n != 1);
4305 }
4308 int vdef_parse(
4309 struct graph_desc_t *gdes,
4310 const char *const str)
4311 {
4312 /* A VDEF currently is either "func" or "param,func"
4313 * so the parsing is rather simple. Change if needed.
4314 */
4315 double param;
4316 char func[30];
4317 int n;
4318 char *old_locale;
4320 n = 0;
4321 old_locale = setlocale(LC_NUMERIC, "C");
4322 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4323 setlocale(LC_NUMERIC, old_locale);
4324 if (n == (int) strlen(str)) { /* matched */
4325 ;
4326 } else {
4327 n = 0;
4328 sscanf(str, "%29[A-Z]%n", func, &n);
4329 if (n == (int) strlen(str)) { /* matched */
4330 param = DNAN;
4331 } else {
4332 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4333 gdes->vname);
4334 return -1;
4335 }
4336 }
4337 if (!strcmp("PERCENT", func))
4338 gdes->vf.op = VDEF_PERCENT;
4339 else if (!strcmp("MAXIMUM", func))
4340 gdes->vf.op = VDEF_MAXIMUM;
4341 else if (!strcmp("AVERAGE", func))
4342 gdes->vf.op = VDEF_AVERAGE;
4343 else if (!strcmp("STDEV", func))
4344 gdes->vf.op = VDEF_STDEV;
4345 else if (!strcmp("MINIMUM", func))
4346 gdes->vf.op = VDEF_MINIMUM;
4347 else if (!strcmp("TOTAL", func))
4348 gdes->vf.op = VDEF_TOTAL;
4349 else if (!strcmp("FIRST", func))
4350 gdes->vf.op = VDEF_FIRST;
4351 else if (!strcmp("LAST", func))
4352 gdes->vf.op = VDEF_LAST;
4353 else if (!strcmp("LSLSLOPE", func))
4354 gdes->vf.op = VDEF_LSLSLOPE;
4355 else if (!strcmp("LSLINT", func))
4356 gdes->vf.op = VDEF_LSLINT;
4357 else if (!strcmp("LSLCORREL", func))
4358 gdes->vf.op = VDEF_LSLCORREL;
4359 else {
4360 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4361 gdes->vname);
4362 return -1;
4363 };
4365 switch (gdes->vf.op) {
4366 case VDEF_PERCENT:
4367 if (isnan(param)) { /* no parameter given */
4368 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4369 func, gdes->vname);
4370 return -1;
4371 };
4372 if (param >= 0.0 && param <= 100.0) {
4373 gdes->vf.param = param;
4374 gdes->vf.val = DNAN; /* undefined */
4375 gdes->vf.when = 0; /* undefined */
4376 } else {
4377 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4378 gdes->vname);
4379 return -1;
4380 };
4381 break;
4382 case VDEF_MAXIMUM:
4383 case VDEF_AVERAGE:
4384 case VDEF_STDEV:
4385 case VDEF_MINIMUM:
4386 case VDEF_TOTAL:
4387 case VDEF_FIRST:
4388 case VDEF_LAST:
4389 case VDEF_LSLSLOPE:
4390 case VDEF_LSLINT:
4391 case VDEF_LSLCORREL:
4392 if (isnan(param)) {
4393 gdes->vf.param = DNAN;
4394 gdes->vf.val = DNAN;
4395 gdes->vf.when = 0;
4396 } else {
4397 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4398 func, gdes->vname);
4399 return -1;
4400 };
4401 break;
4402 };
4403 return 0;
4404 }
4407 int vdef_calc(
4408 image_desc_t *im,
4409 int gdi)
4410 {
4411 graph_desc_t *src, *dst;
4412 rrd_value_t *data;
4413 long step, steps;
4415 dst = &im->gdes[gdi];
4416 src = &im->gdes[dst->vidx];
4417 data = src->data + src->ds;
4418 steps = (src->end - src->start) / src->step;
4420 #if 0
4421 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4422 src->end, steps);
4423 #endif
4425 switch (dst->vf.op) {
4426 case VDEF_PERCENT:{
4427 rrd_value_t *array;
4428 int field;
4431 if ((array = malloc(steps * sizeof(double))) == NULL) {
4432 rrd_set_error("malloc VDEV_PERCENT");
4433 return -1;
4434 }
4435 for (step = 0; step < steps; step++) {
4436 array[step] = data[step * src->ds_cnt];
4437 }
4438 qsort(array, step, sizeof(double), vdef_percent_compar);
4440 field = (steps - 1) * dst->vf.param / 100;
4441 dst->vf.val = array[field];
4442 dst->vf.when = 0; /* no time component */
4443 free(array);
4444 #if 0
4445 for (step = 0; step < steps; step++)
4446 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4447 step == field ? '*' : ' ');
4448 #endif
4449 }
4450 break;
4451 case VDEF_MAXIMUM:
4452 step = 0;
4453 while (step != steps && isnan(data[step * src->ds_cnt]))
4454 step++;
4455 if (step == steps) {
4456 dst->vf.val = DNAN;
4457 dst->vf.when = 0;
4458 } else {
4459 dst->vf.val = data[step * src->ds_cnt];
4460 dst->vf.when = src->start + (step + 1) * src->step;
4461 }
4462 while (step != steps) {
4463 if (finite(data[step * src->ds_cnt])) {
4464 if (data[step * src->ds_cnt] > dst->vf.val) {
4465 dst->vf.val = data[step * src->ds_cnt];
4466 dst->vf.when = src->start + (step + 1) * src->step;
4467 }
4468 }
4469 step++;
4470 }
4471 break;
4472 case VDEF_TOTAL:
4473 case VDEF_STDEV:
4474 case VDEF_AVERAGE:{
4475 int cnt = 0;
4476 double sum = 0.0;
4477 double average = 0.0;
4479 for (step = 0; step < steps; step++) {
4480 if (finite(data[step * src->ds_cnt])) {
4481 sum += data[step * src->ds_cnt];
4482 cnt++;
4483 };
4484 }
4485 if (cnt) {
4486 if (dst->vf.op == VDEF_TOTAL) {
4487 dst->vf.val = sum * src->step;
4488 dst->vf.when = 0; /* no time component */
4489 } else if (dst->vf.op == VDEF_AVERAGE) {
4490 dst->vf.val = sum / cnt;
4491 dst->vf.when = 0; /* no time component */
4492 } else {
4493 average = sum / cnt;
4494 sum = 0.0;
4495 for (step = 0; step < steps; step++) {
4496 if (finite(data[step * src->ds_cnt])) {
4497 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4498 };
4499 }
4500 dst->vf.val = pow(sum / cnt, 0.5);
4501 dst->vf.when = 0; /* no time component */
4502 };
4503 } else {
4504 dst->vf.val = DNAN;
4505 dst->vf.when = 0;
4506 }
4507 }
4508 break;
4509 case VDEF_MINIMUM:
4510 step = 0;
4511 while (step != steps && isnan(data[step * src->ds_cnt]))
4512 step++;
4513 if (step == steps) {
4514 dst->vf.val = DNAN;
4515 dst->vf.when = 0;
4516 } else {
4517 dst->vf.val = data[step * src->ds_cnt];
4518 dst->vf.when = src->start + (step + 1) * src->step;
4519 }
4520 while (step != steps) {
4521 if (finite(data[step * src->ds_cnt])) {
4522 if (data[step * src->ds_cnt] < dst->vf.val) {
4523 dst->vf.val = data[step * src->ds_cnt];
4524 dst->vf.when = src->start + (step + 1) * src->step;
4525 }
4526 }
4527 step++;
4528 }
4529 break;
4530 case VDEF_FIRST:
4531 /* The time value returned here is one step before the
4532 * actual time value. This is the start of the first
4533 * non-NaN interval.
4534 */
4535 step = 0;
4536 while (step != steps && isnan(data[step * src->ds_cnt]))
4537 step++;
4538 if (step == steps) { /* all entries were NaN */
4539 dst->vf.val = DNAN;
4540 dst->vf.when = 0;
4541 } else {
4542 dst->vf.val = data[step * src->ds_cnt];
4543 dst->vf.when = src->start + step * src->step;
4544 }
4545 break;
4546 case VDEF_LAST:
4547 /* The time value returned here is the
4548 * actual time value. This is the end of the last
4549 * non-NaN interval.
4550 */
4551 step = steps - 1;
4552 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4553 step--;
4554 if (step < 0) { /* all entries were NaN */
4555 dst->vf.val = DNAN;
4556 dst->vf.when = 0;
4557 } else {
4558 dst->vf.val = data[step * src->ds_cnt];
4559 dst->vf.when = src->start + (step + 1) * src->step;
4560 }
4561 break;
4562 case VDEF_LSLSLOPE:
4563 case VDEF_LSLINT:
4564 case VDEF_LSLCORREL:{
4565 /* Bestfit line by linear least squares method */
4567 int cnt = 0;
4568 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4570 SUMx = 0;
4571 SUMy = 0;
4572 SUMxy = 0;
4573 SUMxx = 0;
4574 SUMyy = 0;
4576 for (step = 0; step < steps; step++) {
4577 if (finite(data[step * src->ds_cnt])) {
4578 cnt++;
4579 SUMx += step;
4580 SUMxx += step * step;
4581 SUMxy += step * data[step * src->ds_cnt];
4582 SUMy += data[step * src->ds_cnt];
4583 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4584 };
4585 }
4587 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4588 y_intercept = (SUMy - slope * SUMx) / cnt;
4589 correl =
4590 (SUMxy -
4591 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4592 (SUMx * SUMx) / cnt) * (SUMyy -
4593 (SUMy *
4594 SUMy) /
4595 cnt));
4597 if (cnt) {
4598 if (dst->vf.op == VDEF_LSLSLOPE) {
4599 dst->vf.val = slope;
4600 dst->vf.when = 0;
4601 } else if (dst->vf.op == VDEF_LSLINT) {
4602 dst->vf.val = y_intercept;
4603 dst->vf.when = 0;
4604 } else if (dst->vf.op == VDEF_LSLCORREL) {
4605 dst->vf.val = correl;
4606 dst->vf.when = 0;
4607 };
4609 } else {
4610 dst->vf.val = DNAN;
4611 dst->vf.when = 0;
4612 }
4613 }
4614 break;
4615 }
4616 return 0;
4617 }
4619 /* NaN < -INF < finite_values < INF */
4620 int vdef_percent_compar(
4621 const void *a,
4622 const void *b)
4623 {
4624 /* Equality is not returned; this doesn't hurt except
4625 * (maybe) for a little performance.
4626 */
4628 /* First catch NaN values. They are smallest */
4629 if (isnan(*(double *) a))
4630 return -1;
4631 if (isnan(*(double *) b))
4632 return 1;
4634 /* NaN doesn't reach this part so INF and -INF are extremes.
4635 * The sign from isinf() is compatible with the sign we return
4636 */
4637 if (isinf(*(double *) a))
4638 return isinf(*(double *) a);
4639 if (isinf(*(double *) b))
4640 return isinf(*(double *) b);
4642 /* If we reach this, both values must be finite */
4643 if (*(double *) a < *(double *) b)
4644 return -1;
4645 else
4646 return 1;
4647 }