1 /****************************************************************************
2 * RRDtool 1.2.99907080300 Copyright by Tobi Oetiker, 1997-2007
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
37 #endif
39 text_prop_t text_prop[] = {
40 {8.0, RRD_DEFAULT_FONT}
41 , /* default */
42 {9.0, RRD_DEFAULT_FONT}
43 , /* title */
44 {7.0, RRD_DEFAULT_FONT}
45 , /* axis */
46 {8.0, RRD_DEFAULT_FONT}
47 , /* unit */
48 {8.0, RRD_DEFAULT_FONT} /* legend */
49 };
51 xlab_t xlab[] = {
52 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
53 ,
54 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
55 ,
56 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
57 ,
58 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
59 ,
60 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
61 ,
62 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
63 ,
64 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 4, 0, "%a %H:%M"}
65 ,
66 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
67 ,
68 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
69 ,
70 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
71 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
72 ,
73 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
74 ,
75 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
76 ,
77 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
78 ,
79 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
80 ,
81 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
82 "Week %V"}
83 ,
84 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
85 "%b"}
86 ,
87 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
88 "%b"}
89 ,
90 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
91 ,
92 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
93 365 * 24 * 3600, "%y"}
94 ,
95 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
96 };
98 /* sensible y label intervals ...*/
100 ylab_t ylab[] = {
101 {0.1, {1, 2, 5, 10}
102 }
103 ,
104 {0.2, {1, 5, 10, 20}
105 }
106 ,
107 {0.5, {1, 2, 4, 10}
108 }
109 ,
110 {1.0, {1, 2, 5, 10}
111 }
112 ,
113 {2.0, {1, 5, 10, 20}
114 }
115 ,
116 {5.0, {1, 2, 4, 10}
117 }
118 ,
119 {10.0, {1, 2, 5, 10}
120 }
121 ,
122 {20.0, {1, 5, 10, 20}
123 }
124 ,
125 {50.0, {1, 2, 4, 10}
126 }
127 ,
128 {100.0, {1, 2, 5, 10}
129 }
130 ,
131 {200.0, {1, 5, 10, 20}
132 }
133 ,
134 {500.0, {1, 2, 4, 10}
135 }
136 ,
137 {0.0, {0, 0, 0, 0}
138 }
139 };
142 gfx_color_t graph_col[] = /* default colors */
143 {
144 {1.00, 1.00, 1.00, 1.00}, /* canvas */
145 {0.95, 0.95, 0.95, 1.00}, /* background */
146 {0.81, 0.81, 0.81, 1.00}, /* shade A */
147 {0.62, 0.62, 0.62, 1.00}, /* shade B */
148 {0.56, 0.56, 0.56, 0.75}, /* grid */
149 {0.87, 0.31, 0.31, 0.60}, /* major grid */
150 {0.00, 0.00, 0.00, 1.00}, /* font */
151 {0.50, 0.12, 0.12, 1.00}, /* arrow */
152 {0.12, 0.12, 0.12, 1.00}, /* axis */
153 {0.00, 0.00, 0.00, 1.00} /* frame */
154 };
157 /* #define DEBUG */
159 #ifdef DEBUG
160 # define DPRINT(x) (void)(printf x, printf("\n"))
161 #else
162 # define DPRINT(x)
163 #endif
166 /* initialize with xtr(im,0); */
167 int xtr(
168 image_desc_t *im,
169 time_t mytime)
170 {
171 static double pixie;
173 if (mytime == 0) {
174 pixie = (double) im->xsize / (double) (im->end - im->start);
175 return im->xorigin;
176 }
177 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
178 }
180 /* translate data values into y coordinates */
181 double ytr(
182 image_desc_t *im,
183 double value)
184 {
185 static double pixie;
186 double yval;
188 if (isnan(value)) {
189 if (!im->logarithmic)
190 pixie = (double) im->ysize / (im->maxval - im->minval);
191 else
192 pixie =
193 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
194 yval = im->yorigin;
195 } else if (!im->logarithmic) {
196 yval = im->yorigin - pixie * (value - im->minval);
197 } else {
198 if (value < im->minval) {
199 yval = im->yorigin;
200 } else {
201 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
202 }
203 }
204 /* make sure we don't return anything too unreasonable. GD lib can
205 get terribly slow when drawing lines outside its scope. This is
206 especially problematic in connection with the rigid option */
207 if (!im->rigid) {
208 /* keep yval as-is */
209 } else if (yval > im->yorigin) {
210 yval = im->yorigin + 0.00001;
211 } else if (yval < im->yorigin - im->ysize) {
212 yval = im->yorigin - im->ysize - 0.00001;
213 }
214 return yval;
215 }
219 /* conversion function for symbolic entry names */
222 #define conv_if(VV,VVV) \
223 if (strcmp(#VV, string) == 0) return VVV ;
225 enum gf_en gf_conv(
226 char *string)
227 {
229 conv_if(PRINT, GF_PRINT);
230 conv_if(GPRINT, GF_GPRINT);
231 conv_if(COMMENT, GF_COMMENT);
232 conv_if(HRULE, GF_HRULE);
233 conv_if(VRULE, GF_VRULE);
234 conv_if(LINE, GF_LINE);
235 conv_if(AREA, GF_AREA);
236 conv_if(STACK, GF_STACK);
237 conv_if(TICK, GF_TICK);
238 conv_if(TEXTALIGN, GF_TEXTALIGN);
239 conv_if(DEF, GF_DEF);
240 conv_if(CDEF, GF_CDEF);
241 conv_if(VDEF, GF_VDEF);
242 conv_if(XPORT, GF_XPORT);
243 conv_if(SHIFT, GF_SHIFT);
245 return (-1);
246 }
248 enum gfx_if_en if_conv(
249 char *string)
250 {
252 conv_if(PNG, IF_PNG);
253 conv_if(SVG, IF_SVG);
254 conv_if(EPS, IF_EPS);
255 conv_if(PDF, IF_PDF);
257 return (-1);
258 }
260 enum tmt_en tmt_conv(
261 char *string)
262 {
264 conv_if(SECOND, TMT_SECOND);
265 conv_if(MINUTE, TMT_MINUTE);
266 conv_if(HOUR, TMT_HOUR);
267 conv_if(DAY, TMT_DAY);
268 conv_if(WEEK, TMT_WEEK);
269 conv_if(MONTH, TMT_MONTH);
270 conv_if(YEAR, TMT_YEAR);
271 return (-1);
272 }
274 enum grc_en grc_conv(
275 char *string)
276 {
278 conv_if(BACK, GRC_BACK);
279 conv_if(CANVAS, GRC_CANVAS);
280 conv_if(SHADEA, GRC_SHADEA);
281 conv_if(SHADEB, GRC_SHADEB);
282 conv_if(GRID, GRC_GRID);
283 conv_if(MGRID, GRC_MGRID);
284 conv_if(FONT, GRC_FONT);
285 conv_if(ARROW, GRC_ARROW);
286 conv_if(AXIS, GRC_AXIS);
287 conv_if(FRAME, GRC_FRAME);
289 return -1;
290 }
292 enum text_prop_en text_prop_conv(
293 char *string)
294 {
296 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
297 conv_if(TITLE, TEXT_PROP_TITLE);
298 conv_if(AXIS, TEXT_PROP_AXIS);
299 conv_if(UNIT, TEXT_PROP_UNIT);
300 conv_if(LEGEND, TEXT_PROP_LEGEND);
301 return -1;
302 }
305 #undef conv_if
307 int im_free(
308 image_desc_t *im)
309 {
310 unsigned long i, ii;
311 cairo_status_t status = 0;
313 if (im == NULL)
314 return 0;
315 for (i = 0; i < (unsigned) im->gdes_c; i++) {
316 if (im->gdes[i].data_first) {
317 /* careful here, because a single pointer can occur several times */
318 free(im->gdes[i].data);
319 if (im->gdes[i].ds_namv) {
320 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
321 free(im->gdes[i].ds_namv[ii]);
322 free(im->gdes[i].ds_namv);
323 }
324 }
325 /* free 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 issuesm 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 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3035 cairo_set_antialias(im->cr, im->graph_antialias);
3036 cairo_scale(im->cr, im->zoom, im->zoom);
3038 gfx_new_area(im,
3039 0, 0,
3040 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3042 gfx_add_point(im, im->ximg, 0);
3043 gfx_close_path(im);
3045 gfx_new_area(im,
3046 im->xorigin, im->yorigin,
3047 im->xorigin + im->xsize, im->yorigin,
3048 im->xorigin + im->xsize, im->yorigin - im->ysize,
3049 im->graph_col[GRC_CANVAS]);
3051 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3052 gfx_close_path(im);
3054 if (im->minval > 0.0)
3055 areazero = im->minval;
3056 if (im->maxval < 0.0)
3057 areazero = im->maxval;
3059 for (i = 0; i < im->gdes_c; i++) {
3060 switch (im->gdes[i].gf) {
3061 case GF_CDEF:
3062 case GF_VDEF:
3063 case GF_DEF:
3064 case GF_PRINT:
3065 case GF_GPRINT:
3066 case GF_COMMENT:
3067 case GF_TEXTALIGN:
3068 case GF_HRULE:
3069 case GF_VRULE:
3070 case GF_XPORT:
3071 case GF_SHIFT:
3072 break;
3073 case GF_TICK:
3074 for (ii = 0; ii < im->xsize; ii++) {
3075 if (!isnan(im->gdes[i].p_data[ii]) &&
3076 im->gdes[i].p_data[ii] != 0.0) {
3077 if (im->gdes[i].yrule > 0) {
3078 gfx_line(im,
3079 im->xorigin + ii, im->yorigin,
3080 im->xorigin + ii,
3081 im->yorigin -
3082 im->gdes[i].yrule * im->ysize, 1.0,
3083 im->gdes[i].col);
3084 } else if (im->gdes[i].yrule < 0) {
3085 gfx_line(im,
3086 im->xorigin + ii,
3087 im->yorigin - im->ysize,
3088 im->xorigin + ii,
3089 im->yorigin - (1 -
3090 im->gdes[i].yrule) *
3091 im->ysize, 1.0, im->gdes[i].col);
3093 }
3094 }
3095 }
3096 break;
3097 case GF_LINE:
3098 case GF_AREA:
3099 /* fix data points at oo and -oo */
3100 for (ii = 0; ii < im->xsize; ii++) {
3101 if (isinf(im->gdes[i].p_data[ii])) {
3102 if (im->gdes[i].p_data[ii] > 0) {
3103 im->gdes[i].p_data[ii] = im->maxval;
3104 } else {
3105 im->gdes[i].p_data[ii] = im->minval;
3106 }
3108 }
3109 } /* for */
3111 /* *******************************************************
3112 a ___. (a,t)
3113 | | ___
3114 ____| | | |
3115 | |___|
3116 -------|--t-1--t--------------------------------
3118 if we know the value at time t was a then
3119 we draw a square from t-1 to t with the value a.
3121 ********************************************************* */
3122 if (im->gdes[i].col.alpha != 0.0) {
3123 /* GF_LINE and friend */
3124 if (im->gdes[i].gf == GF_LINE) {
3125 double last_y = 0.0;
3126 int draw_on = 0;
3128 cairo_save(im->cr);
3129 cairo_new_path(im->cr);
3131 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3133 if (im->gdes[i].dash) {
3134 cairo_set_dash(im->cr, im->gdes[i].p_dashes,
3135 im->gdes[i].ndash, im->gdes[i].offset);
3136 }
3138 for (ii = 1; ii < im->xsize; ii++) {
3139 if (isnan(im->gdes[i].p_data[ii])
3140 || (im->slopemode == 1
3141 && isnan(im->gdes[i].p_data[ii - 1]))) {
3142 draw_on = 0;
3143 continue;
3144 }
3145 if (draw_on == 0) {
3146 last_y = ytr(im, im->gdes[i].p_data[ii]);
3147 if (im->slopemode == 0) {
3148 double x = ii - 1 + im->xorigin;
3149 double y = last_y;
3151 gfx_line_fit(im, &x, &y);
3152 cairo_move_to(im->cr, x, y);
3153 x = ii + im->xorigin;
3154 y = last_y;
3155 gfx_line_fit(im, &x, &y);
3156 cairo_line_to(im->cr, x, y);
3157 } else {
3158 double x = ii - 1 + im->xorigin;
3159 double y = ytr(im,
3160 im->gdes[i].p_data[ii - 1]);
3162 gfx_line_fit(im, &x, &y);
3163 cairo_move_to(im->cr, x, y);
3164 x = ii + im->xorigin;
3165 y = last_y;
3166 gfx_line_fit(im, &x, &y);
3167 cairo_line_to(im->cr, x, y);
3168 }
3169 draw_on = 1;
3170 } else {
3171 double x1 = ii + im->xorigin;
3172 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3174 if (im->slopemode == 0
3175 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3176 double x = ii - 1 + im->xorigin;
3177 double y = y1;
3179 gfx_line_fit(im, &x, &y);
3180 cairo_line_to(im->cr, x, y);
3181 };
3182 last_y = y1;
3183 gfx_line_fit(im, &x1, &y1);
3184 cairo_line_to(im->cr, x1, y1);
3185 };
3187 }
3188 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3189 im->gdes[i].col.green,
3190 im->gdes[i].col.blue,
3191 im->gdes[i].col.alpha);
3192 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3193 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3194 cairo_stroke(im->cr);
3195 cairo_restore(im->cr);
3196 } else {
3197 int idxI = -1;
3198 double *foreY =
3199 (double *) malloc(sizeof(double) * im->xsize * 2);
3200 double *foreX =
3201 (double *) malloc(sizeof(double) * im->xsize * 2);
3202 double *backY =
3203 (double *) malloc(sizeof(double) * im->xsize * 2);
3204 double *backX =
3205 (double *) malloc(sizeof(double) * im->xsize * 2);
3206 int drawem = 0;
3208 for (ii = 0; ii <= im->xsize; ii++) {
3209 double ybase, ytop;
3211 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3212 int cntI = 1;
3213 int lastI = 0;
3215 while (cntI < idxI
3216 && AlmostEqual2sComplement(foreY[lastI],
3217 foreY[cntI], 4)
3218 && AlmostEqual2sComplement(foreY[lastI],
3219 foreY[cntI + 1],
3220 4)) {
3221 cntI++;
3222 }
3223 gfx_new_area(im,
3224 backX[0], backY[0],
3225 foreX[0], foreY[0],
3226 foreX[cntI], foreY[cntI],
3227 im->gdes[i].col);
3228 while (cntI < idxI) {
3229 lastI = cntI;
3230 cntI++;
3231 while (cntI < idxI
3232 &&
3233 AlmostEqual2sComplement(foreY[lastI],
3234 foreY[cntI], 4)
3235 &&
3236 AlmostEqual2sComplement(foreY[lastI],
3237 foreY[cntI +
3238 1], 4)) {
3239 cntI++;
3240 }
3241 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3242 }
3243 gfx_add_point(im, backX[idxI], backY[idxI]);
3244 while (idxI > 1) {
3245 lastI = idxI;
3246 idxI--;
3247 while (idxI > 1
3248 &&
3249 AlmostEqual2sComplement(backY[lastI],
3250 backY[idxI], 4)
3251 &&
3252 AlmostEqual2sComplement(backY[lastI],
3253 backY[idxI -
3254 1], 4)) {
3255 idxI--;
3256 }
3257 gfx_add_point(im, backX[idxI], backY[idxI]);
3258 }
3259 idxI = -1;
3260 drawem = 0;
3261 gfx_close_path(im);
3262 }
3263 if (drawem != 0) {
3264 drawem = 0;
3265 idxI = -1;
3266 }
3267 if (ii == im->xsize)
3268 break;
3270 if (im->slopemode == 0 && ii == 0) {
3271 continue;
3272 }
3273 if (isnan(im->gdes[i].p_data[ii])) {
3274 drawem = 1;
3275 continue;
3276 }
3277 ytop = ytr(im, im->gdes[i].p_data[ii]);
3278 if (lastgdes && im->gdes[i].stack) {
3279 ybase = ytr(im, lastgdes->p_data[ii]);
3280 } else {
3281 ybase = ytr(im, areazero);
3282 }
3283 if (ybase == ytop) {
3284 drawem = 1;
3285 continue;
3286 }
3288 if (ybase > ytop) {
3289 double extra = ytop;
3291 ytop = ybase;
3292 ybase = extra;
3293 }
3294 if (im->slopemode == 0) {
3295 backY[++idxI] = ybase - 0.2;
3296 backX[idxI] = ii + im->xorigin - 1;
3297 foreY[idxI] = ytop + 0.2;
3298 foreX[idxI] = ii + im->xorigin - 1;
3299 }
3300 backY[++idxI] = ybase - 0.2;
3301 backX[idxI] = ii + im->xorigin;
3302 foreY[idxI] = ytop + 0.2;
3303 foreX[idxI] = ii + im->xorigin;
3304 }
3305 /* close up any remaining area */
3306 free(foreY);
3307 free(foreX);
3308 free(backY);
3309 free(backX);
3310 } /* else GF_LINE */
3311 }
3312 /* if color != 0x0 */
3313 /* make sure we do not run into trouble when stacking on NaN */
3314 for (ii = 0; ii < im->xsize; ii++) {
3315 if (isnan(im->gdes[i].p_data[ii])) {
3316 if (lastgdes && (im->gdes[i].stack)) {
3317 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3318 } else {
3319 im->gdes[i].p_data[ii] = areazero;
3320 }
3321 }
3322 }
3323 lastgdes = &(im->gdes[i]);
3324 break;
3325 case GF_STACK:
3326 rrd_set_error
3327 ("STACK should already be turned into LINE or AREA here");
3328 return -1;
3329 break;
3331 } /* switch */
3332 }
3334 /* grid_paint also does the text */
3335 if (!(im->extra_flags & ONLY_GRAPH))
3336 grid_paint(im);
3339 if (!(im->extra_flags & ONLY_GRAPH))
3340 axis_paint(im);
3342 /* the RULES are the last thing to paint ... */
3343 for (i = 0; i < im->gdes_c; i++) {
3345 switch (im->gdes[i].gf) {
3346 case GF_HRULE:
3347 if (im->gdes[i].yrule >= im->minval
3348 && im->gdes[i].yrule <= im->maxval)
3349 cairo_save(im->cr);
3350 if (im->gdes[i].dash) {
3351 cairo_set_dash(im->cr, im->gdes[i].p_dashes,
3352 im->gdes[i].ndash, im->gdes[i].offset);
3353 }
3354 gfx_line(im,
3355 im->xorigin, ytr(im, im->gdes[i].yrule),
3356 im->xorigin + im->xsize, ytr(im,
3357 im->gdes[i].yrule),
3358 1.0, im->gdes[i].col);
3359 cairo_stroke(im->cr);
3360 cairo_restore(im->cr);
3361 break;
3362 case GF_VRULE:
3363 if (im->gdes[i].xrule >= im->start
3364 && im->gdes[i].xrule <= im->end)
3365 cairo_save(im->cr);
3366 if (im->gdes[i].dash) {
3367 cairo_set_dash(im->cr, im->gdes[i].p_dashes,
3368 im->gdes[i].ndash, im->gdes[i].offset);
3369 }
3370 gfx_line(im,
3371 xtr(im, im->gdes[i].xrule), im->yorigin,
3372 xtr(im, im->gdes[i].xrule),
3373 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3374 cairo_stroke(im->cr);
3375 cairo_restore(im->cr);
3376 break;
3377 default:
3378 break;
3379 }
3380 }
3383 switch (im->imgformat) {
3384 case IF_PNG:
3385 {
3386 cairo_status_t status;
3388 if (strlen(im->graphfile) == 0) {
3389 status =
3390 cairo_surface_write_to_png_stream(im->surface,
3391 &cairo_copy_to_buffer, im);
3392 } else if (strcmp(im->graphfile, "-") == 0) {
3393 status =
3394 cairo_surface_write_to_png_stream(im->surface,
3395 &cairo_write_func_filehandle,
3396 (void *) stdout);
3397 } else {
3398 status = cairo_surface_write_to_png(im->surface, im->graphfile);
3399 }
3401 if (status != CAIRO_STATUS_SUCCESS) {
3402 rrd_set_error("Could not save png to '%s'", im->graphfile);
3403 return 1;
3404 }
3405 }
3406 break;
3407 default:
3408 if (strlen(im->graphfile)) {
3409 cairo_show_page(im->cr);
3410 } else {
3411 cairo_surface_finish(im->surface);
3412 }
3413 break;
3414 }
3415 return 0;
3416 }
3419 /*****************************************************
3420 * graph stuff
3421 *****************************************************/
3423 int gdes_alloc(
3424 image_desc_t *im)
3425 {
3427 im->gdes_c++;
3428 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3429 * sizeof(graph_desc_t))) ==
3430 NULL) {
3431 rrd_set_error("realloc graph_descs");
3432 return -1;
3433 }
3436 im->gdes[im->gdes_c - 1].step = im->step;
3437 im->gdes[im->gdes_c - 1].step_orig = im->step;
3438 im->gdes[im->gdes_c - 1].stack = 0;
3439 im->gdes[im->gdes_c - 1].linewidth = 0;
3440 im->gdes[im->gdes_c - 1].debug = 0;
3441 im->gdes[im->gdes_c - 1].start = im->start;
3442 im->gdes[im->gdes_c - 1].start_orig = im->start;
3443 im->gdes[im->gdes_c - 1].end = im->end;
3444 im->gdes[im->gdes_c - 1].end_orig = im->end;
3445 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3446 im->gdes[im->gdes_c - 1].data = NULL;
3447 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3448 im->gdes[im->gdes_c - 1].data_first = 0;
3449 im->gdes[im->gdes_c - 1].p_data = NULL;
3450 im->gdes[im->gdes_c - 1].rpnp = NULL;
3451 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3452 im->gdes[im->gdes_c - 1].shift = 0.0;
3453 im->gdes[im->gdes_c - 1].col.red = 0.0;
3454 im->gdes[im->gdes_c - 1].col.green = 0.0;
3455 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3456 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3457 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3458 im->gdes[im->gdes_c - 1].format[0] = '\0';
3459 im->gdes[im->gdes_c - 1].strftm = 0;
3460 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3461 im->gdes[im->gdes_c - 1].ds = -1;
3462 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3463 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3464 im->gdes[im->gdes_c - 1].yrule = DNAN;
3465 im->gdes[im->gdes_c - 1].xrule = 0;
3466 return 0;
3467 }
3469 /* copies input untill the first unescaped colon is found
3470 or until input ends. backslashes have to be escaped as well */
3471 int scan_for_col(
3472 const char *const input,
3473 int len,
3474 char *const output)
3475 {
3476 int inp, outp = 0;
3478 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3479 if (input[inp] == '\\' &&
3480 input[inp + 1] != '\0' &&
3481 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3482 output[outp++] = input[++inp];
3483 } else {
3484 output[outp++] = input[inp];
3485 }
3486 }
3487 output[outp] = '\0';
3488 return inp;
3489 }
3491 /* Some surgery done on this function, it became ridiculously big.
3492 ** Things moved:
3493 ** - initializing now in rrd_graph_init()
3494 ** - options parsing now in rrd_graph_options()
3495 ** - script parsing now in rrd_graph_script()
3496 */
3497 int rrd_graph(
3498 int argc,
3499 char **argv,
3500 char ***prdata,
3501 int *xsize,
3502 int *ysize,
3503 FILE * stream,
3504 double *ymin,
3505 double *ymax)
3506 {
3507 image_desc_t im;
3509 rrd_graph_init(&im);
3511 /* a dummy surface so that we can measure text sizes for placements */
3512 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3513 im.cr = cairo_create(im.surface);
3514 im.graphhandle = stream;
3516 rrd_graph_options(argc, argv, &im);
3517 if (rrd_test_error()) {
3518 im_free(&im);
3519 return -1;
3520 }
3522 if (optind >= argc) {
3523 rrd_set_error("missing filename");
3524 return -1;
3525 }
3527 if (strlen(argv[optind]) >= MAXPATH) {
3528 rrd_set_error("filename (including path) too long");
3529 im_free(&im);
3530 return -1;
3531 }
3533 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3534 im.graphfile[MAXPATH - 1] = '\0';
3536 rrd_graph_script(argc, argv, &im, 1);
3537 if (rrd_test_error()) {
3538 im_free(&im);
3539 return -1;
3540 }
3542 /* Everything is now read and the actual work can start */
3544 (*prdata) = NULL;
3545 if (graph_paint(&im, prdata) == -1) {
3546 im_free(&im);
3547 return -1;
3548 }
3550 /* The image is generated and needs to be output.
3551 ** Also, if needed, print a line with information about the image.
3552 */
3554 *xsize = im.ximg;
3555 *ysize = im.yimg;
3556 *ymin = im.minval;
3557 *ymax = im.maxval;
3558 if (im.imginfo) {
3559 char *filename;
3561 if (!(*prdata)) {
3562 /* maybe prdata is not allocated yet ... lets do it now */
3563 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3564 rrd_set_error("malloc imginfo");
3565 return -1;
3566 };
3567 }
3568 if (((*prdata)[0] =
3569 malloc((strlen(im.imginfo) + 200 +
3570 strlen(im.graphfile)) * sizeof(char)))
3571 == NULL) {
3572 rrd_set_error("malloc imginfo");
3573 return -1;
3574 }
3575 filename = im.graphfile + strlen(im.graphfile);
3576 while (filename > im.graphfile) {
3577 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3578 break;
3579 filename--;
3580 }
3582 sprintf((*prdata)[0], im.imginfo, filename,
3583 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3584 }
3585 im_free(&im);
3586 return 0;
3587 }
3589 /* a simplified version of the above that just creates the graph in memory
3590 and returns a pointer to it. */
3592 unsigned char *rrd_graph_in_memory(
3593 int argc,
3594 char **argv,
3595 char ***prdata,
3596 int *xsize,
3597 int *ysize,
3598 double *ymin,
3599 double *ymax,
3600 size_t * img_size)
3601 {
3602 image_desc_t im;
3604 rrd_graph_init(&im);
3606 /* a dummy surface so that we can measure text sizes for placements */
3607 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3608 im.cr = cairo_create(im.surface);
3610 rrd_graph_options(argc, argv, &im);
3611 if (rrd_test_error()) {
3612 im_free(&im);
3613 return NULL;
3614 }
3616 rrd_graph_script(argc, argv, &im, 1);
3617 if (rrd_test_error()) {
3618 im_free(&im);
3619 return NULL;
3620 }
3622 /* Everything is now read and the actual work can start */
3624 /* by not assigning a name to im.graphfile data will be written to
3625 newly allocated memory on im.rendered_image ... */
3627 (*prdata) = NULL;
3628 if (graph_paint(&im, prdata) == -1) {
3629 im_free(&im);
3630 return NULL;
3631 }
3633 *xsize = im.ximg;
3634 *ysize = im.yimg;
3635 *ymin = im.minval;
3636 *ymax = im.maxval;
3637 *img_size = im.rendered_image_size;
3638 im_free(&im);
3640 return im.rendered_image;
3641 }
3643 void rrd_graph_init(
3644 image_desc_t *im)
3645 {
3646 unsigned int i;
3648 #ifdef HAVE_TZSET
3649 tzset();
3650 #endif
3651 #ifdef HAVE_SETLOCALE
3652 setlocale(LC_TIME, "");
3653 #ifdef HAVE_MBSTOWCS
3654 setlocale(LC_CTYPE, "");
3655 #endif
3656 #endif
3657 im->yorigin = 0;
3658 im->xorigin = 0;
3659 im->minval = 0;
3660 im->xlab_user.minsec = -1;
3661 im->ximg = 0;
3662 im->yimg = 0;
3663 im->xsize = 400;
3664 im->ysize = 100;
3665 im->rendered_image_size = 0;
3666 im->rendered_image = NULL;
3667 im->step = 0;
3668 im->ylegend[0] = '\0';
3669 im->title[0] = '\0';
3670 im->watermark[0] = '\0';
3671 im->minval = DNAN;
3672 im->maxval = DNAN;
3673 im->unitsexponent = 9999;
3674 im->unitslength = 6;
3675 im->forceleftspace = 0;
3676 im->symbol = ' ';
3677 im->viewfactor = 1.0;
3678 im->imgformat = IF_PNG;
3679 im->graphfile[0] = '\0';
3680 im->cr = NULL;
3681 im->surface = NULL;
3682 im->extra_flags = 0;
3683 im->rigid = 0;
3684 im->gridfit = 1;
3685 im->imginfo = NULL;
3686 im->lazy = 0;
3687 im->slopemode = 0;
3688 im->logarithmic = 0;
3689 im->ygridstep = DNAN;
3690 im->draw_x_grid = 1;
3691 im->draw_y_grid = 1;
3692 im->base = 1000;
3693 im->prt_c = 0;
3694 im->gdes_c = 0;
3695 im->gdes = NULL;
3696 im->grid_dash_on = 1;
3697 im->grid_dash_off = 1;
3698 im->tabwidth = 40.0;
3699 im->zoom = 1;
3700 im->font_options = cairo_font_options_create();
3701 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3703 cairo_font_options_set_hint_style(im->font_options,
3704 CAIRO_HINT_STYLE_FULL);
3705 cairo_font_options_set_hint_metrics(im->font_options,
3706 CAIRO_HINT_METRICS_ON);
3707 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3710 for (i = 0; i < DIM(graph_col); i++)
3711 im->graph_col[i] = graph_col[i];
3713 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3714 {
3715 char *windir;
3716 char rrd_win_default_font[1000];
3718 windir = getenv("windir");
3719 /* %windir% is something like D:\windows or C:\winnt */
3720 if (windir != NULL) {
3721 strncpy(rrd_win_default_font, windir, 500);
3722 rrd_win_default_font[500] = '\0';
3723 strcat(rrd_win_default_font, "\\fonts\\");
3724 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3725 for (i = 0; i < DIM(text_prop); i++) {
3726 strncpy(text_prop[i].font, rrd_win_default_font,
3727 sizeof(text_prop[i].font) - 1);
3728 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3729 }
3730 }
3731 }
3732 #endif
3733 {
3734 char *deffont;
3736 deffont = getenv("RRD_DEFAULT_FONT");
3737 if (deffont != NULL) {
3738 for (i = 0; i < DIM(text_prop); i++) {
3739 strncpy(text_prop[i].font, deffont,
3740 sizeof(text_prop[i].font) - 1);
3741 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3742 }
3743 }
3744 }
3745 for (i = 0; i < DIM(text_prop); i++) {
3746 im->text_prop[i].size = text_prop[i].size;
3747 strcpy(im->text_prop[i].font, text_prop[i].font);
3748 }
3749 }
3751 void rrd_graph_options(
3752 int argc,
3753 char *argv[],
3754 image_desc_t *im)
3755 {
3756 int stroff;
3757 char *parsetime_error = NULL;
3758 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3759 time_t start_tmp = 0, end_tmp = 0;
3760 long long_tmp;
3761 struct rrd_time_value start_tv, end_tv;
3762 long unsigned int color;
3763 char *old_locale = "";
3765 /* defines for long options without a short equivalent. should be bytes,
3766 and may not collide with (the ASCII value of) short options */
3767 #define LONGOPT_UNITS_SI 255
3768 struct option long_options[] = {
3769 {"start", required_argument, 0, 's'},
3770 {"end", required_argument, 0, 'e'},
3771 {"x-grid", required_argument, 0, 'x'},
3772 {"y-grid", required_argument, 0, 'y'},
3773 {"vertical-label", required_argument, 0, 'v'},
3774 {"width", required_argument, 0, 'w'},
3775 {"height", required_argument, 0, 'h'},
3776 {"full-size-mode", no_argument, 0, 'D'},
3777 {"interlaced", no_argument, 0, 'i'},
3778 {"upper-limit", required_argument, 0, 'u'},
3779 {"lower-limit", required_argument, 0, 'l'},
3780 {"rigid", no_argument, 0, 'r'},
3781 {"base", required_argument, 0, 'b'},
3782 {"logarithmic", no_argument, 0, 'o'},
3783 {"color", required_argument, 0, 'c'},
3784 {"font", required_argument, 0, 'n'},
3785 {"title", required_argument, 0, 't'},
3786 {"imginfo", required_argument, 0, 'f'},
3787 {"imgformat", required_argument, 0, 'a'},
3788 {"lazy", no_argument, 0, 'z'},
3789 {"zoom", required_argument, 0, 'm'},
3790 {"no-legend", no_argument, 0, 'g'},
3791 {"force-rules-legend", no_argument, 0, 'F'},
3792 {"only-graph", no_argument, 0, 'j'},
3793 {"alt-y-grid", no_argument, 0, 'Y'},
3794 {"no-minor", no_argument, 0, 'I'},
3795 {"slope-mode", no_argument, 0, 'E'},
3796 {"alt-autoscale", no_argument, 0, 'A'},
3797 {"alt-autoscale-min", no_argument, 0, 'J'},
3798 {"alt-autoscale-max", no_argument, 0, 'M'},
3799 {"no-gridfit", no_argument, 0, 'N'},
3800 {"units-exponent", required_argument, 0, 'X'},
3801 {"units-length", required_argument, 0, 'L'},
3802 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3803 {"step", required_argument, 0, 'S'},
3804 {"tabwidth", required_argument, 0, 'T'},
3805 {"font-render-mode", required_argument, 0, 'R'},
3806 {"graph-render-mode", required_argument, 0, 'G'},
3807 {"font-smoothing-threshold", required_argument, 0, 'B'},
3808 {"watermark", required_argument, 0, 'W'},
3809 {"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 */
3810 {0, 0, 0, 0}
3811 };
3813 optind = 0;
3814 opterr = 0; /* initialize getopt */
3816 parsetime("end-24h", &start_tv);
3817 parsetime("now", &end_tv);
3819 while (1) {
3820 int option_index = 0;
3821 int opt;
3822 int col_start, col_end;
3824 opt = getopt_long(argc, argv,
3825 "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:",
3826 long_options, &option_index);
3828 if (opt == EOF)
3829 break;
3831 switch (opt) {
3832 case 'I':
3833 im->extra_flags |= NOMINOR;
3834 break;
3835 case 'Y':
3836 im->extra_flags |= ALTYGRID;
3837 break;
3838 case 'A':
3839 im->extra_flags |= ALTAUTOSCALE;
3840 break;
3841 case 'J':
3842 im->extra_flags |= ALTAUTOSCALE_MIN;
3843 break;
3844 case 'M':
3845 im->extra_flags |= ALTAUTOSCALE_MAX;
3846 break;
3847 case 'j':
3848 im->extra_flags |= ONLY_GRAPH;
3849 break;
3850 case 'g':
3851 im->extra_flags |= NOLEGEND;
3852 break;
3853 case 'F':
3854 im->extra_flags |= FORCE_RULES_LEGEND;
3855 break;
3856 case LONGOPT_UNITS_SI:
3857 if (im->extra_flags & FORCE_UNITS) {
3858 rrd_set_error("--units can only be used once!");
3859 setlocale(LC_NUMERIC, old_locale);
3860 return;
3861 }
3862 if (strcmp(optarg, "si") == 0)
3863 im->extra_flags |= FORCE_UNITS_SI;
3864 else {
3865 rrd_set_error("invalid argument for --units: %s", optarg);
3866 return;
3867 }
3868 break;
3869 case 'X':
3870 im->unitsexponent = atoi(optarg);
3871 break;
3872 case 'L':
3873 im->unitslength = atoi(optarg);
3874 im->forceleftspace = 1;
3875 break;
3876 case 'T':
3877 old_locale = setlocale(LC_NUMERIC, "C");
3878 im->tabwidth = atof(optarg);
3879 setlocale(LC_NUMERIC, old_locale);
3880 break;
3881 case 'S':
3882 old_locale = setlocale(LC_NUMERIC, "C");
3883 im->step = atoi(optarg);
3884 setlocale(LC_NUMERIC, old_locale);
3885 break;
3886 case 'N':
3887 im->gridfit = 0;
3888 break;
3889 case 's':
3890 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3891 rrd_set_error("start time: %s", parsetime_error);
3892 return;
3893 }
3894 break;
3895 case 'e':
3896 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3897 rrd_set_error("end time: %s", parsetime_error);
3898 return;
3899 }
3900 break;
3901 case 'x':
3902 if (strcmp(optarg, "none") == 0) {
3903 im->draw_x_grid = 0;
3904 break;
3905 };
3907 if (sscanf(optarg,
3908 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3909 scan_gtm,
3910 &im->xlab_user.gridst,
3911 scan_mtm,
3912 &im->xlab_user.mgridst,
3913 scan_ltm,
3914 &im->xlab_user.labst,
3915 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3916 strncpy(im->xlab_form, optarg + stroff,
3917 sizeof(im->xlab_form) - 1);
3918 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3919 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3920 rrd_set_error("unknown keyword %s", scan_gtm);
3921 return;
3922 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3923 == -1) {
3924 rrd_set_error("unknown keyword %s", scan_mtm);
3925 return;
3926 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3927 -1) {
3928 rrd_set_error("unknown keyword %s", scan_ltm);
3929 return;
3930 }
3931 im->xlab_user.minsec = 1;
3932 im->xlab_user.stst = im->xlab_form;
3933 } else {
3934 rrd_set_error("invalid x-grid format");
3935 return;
3936 }
3937 break;
3938 case 'y':
3940 if (strcmp(optarg, "none") == 0) {
3941 im->draw_y_grid = 0;
3942 break;
3943 };
3944 old_locale = setlocale(LC_NUMERIC, "C");
3945 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3946 setlocale(LC_NUMERIC, old_locale);
3947 if (im->ygridstep <= 0) {
3948 rrd_set_error("grid step must be > 0");
3949 return;
3950 } else if (im->ylabfact < 1) {
3951 rrd_set_error("label factor must be > 0");
3952 return;
3953 }
3954 } else {
3955 setlocale(LC_NUMERIC, old_locale);
3956 rrd_set_error("invalid y-grid format");
3957 return;
3958 }
3959 break;
3960 case 'v':
3961 strncpy(im->ylegend, optarg, 150);
3962 im->ylegend[150] = '\0';
3963 break;
3964 case 'u':
3965 old_locale = setlocale(LC_NUMERIC, "C");
3966 im->maxval = atof(optarg);
3967 setlocale(LC_NUMERIC, old_locale);
3968 break;
3969 case 'l':
3970 old_locale = setlocale(LC_NUMERIC, "C");
3971 im->minval = atof(optarg);
3972 setlocale(LC_NUMERIC, old_locale);
3973 break;
3974 case 'b':
3975 im->base = atol(optarg);
3976 if (im->base != 1024 && im->base != 1000) {
3977 rrd_set_error
3978 ("the only sensible value for base apart from 1000 is 1024");
3979 return;
3980 }
3981 break;
3982 case 'w':
3983 long_tmp = atol(optarg);
3984 if (long_tmp < 10) {
3985 rrd_set_error("width below 10 pixels");
3986 return;
3987 }
3988 im->xsize = long_tmp;
3989 break;
3990 case 'h':
3991 long_tmp = atol(optarg);
3992 if (long_tmp < 10) {
3993 rrd_set_error("height below 10 pixels");
3994 return;
3995 }
3996 im->ysize = long_tmp;
3997 break;
3998 case 'D':
3999 im->extra_flags |= FULL_SIZE_MODE;
4000 break;
4001 case 'i':
4002 /* interlaced png not supported at the moment */
4003 break;
4004 case 'r':
4005 im->rigid = 1;
4006 break;
4007 case 'f':
4008 im->imginfo = optarg;
4009 break;
4010 case 'a':
4011 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
4012 rrd_set_error("unsupported graphics format '%s'", optarg);
4013 return;
4014 }
4015 break;
4016 case 'z':
4017 im->lazy = 1;
4018 break;
4019 case 'E':
4020 im->slopemode = 1;
4021 break;
4023 case 'o':
4024 im->logarithmic = 1;
4025 break;
4026 case 'c':
4027 if (sscanf(optarg,
4028 "%10[A-Z]#%n%8lx%n",
4029 col_nam, &col_start, &color, &col_end) == 2) {
4030 int ci;
4031 int col_len = col_end - col_start;
4033 switch (col_len) {
4034 case 3:
4035 color = (((color & 0xF00) * 0x110000) |
4036 ((color & 0x0F0) * 0x011000) |
4037 ((color & 0x00F) * 0x001100) | 0x000000FF);
4038 break;
4039 case 4:
4040 color = (((color & 0xF000) * 0x11000) |
4041 ((color & 0x0F00) * 0x01100) |
4042 ((color & 0x00F0) * 0x00110) |
4043 ((color & 0x000F) * 0x00011)
4044 );
4045 break;
4046 case 6:
4047 color = (color << 8) + 0xff /* shift left by 8 */ ;
4048 break;
4049 case 8:
4050 break;
4051 default:
4052 rrd_set_error("the color format is #RRGGBB[AA]");
4053 return;
4054 }
4055 if ((ci = grc_conv(col_nam)) != -1) {
4056 im->graph_col[ci] = gfx_hex_to_col(color);
4057 } else {
4058 rrd_set_error("invalid color name '%s'", col_nam);
4059 return;
4060 }
4061 } else {
4062 rrd_set_error("invalid color def format");
4063 return;
4064 }
4065 break;
4066 case 'n':{
4067 char prop[15];
4068 double size = 1;
4069 char font[1024] = "";
4071 old_locale = setlocale(LC_NUMERIC, "C");
4072 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
4073 int sindex, propidx;
4075 setlocale(LC_NUMERIC, old_locale);
4076 if ((sindex = text_prop_conv(prop)) != -1) {
4077 for (propidx = sindex; propidx < TEXT_PROP_LAST;
4078 propidx++) {
4079 if (size > 0) {
4080 im->text_prop[propidx].size = size;
4081 }
4082 if (strlen(font) > 0) {
4083 strcpy(im->text_prop[propidx].font, font);
4084 }
4085 if (propidx == sindex && sindex != 0)
4086 break;
4087 }
4088 } else {
4089 rrd_set_error("invalid fonttag '%s'", prop);
4090 return;
4091 }
4092 } else {
4093 setlocale(LC_NUMERIC, old_locale);
4094 rrd_set_error("invalid text property format");
4095 return;
4096 }
4097 break;
4098 }
4099 case 'm':
4100 old_locale = setlocale(LC_NUMERIC, "C");
4101 im->zoom = atof(optarg);
4102 setlocale(LC_NUMERIC, old_locale);
4103 if (im->zoom <= 0.0) {
4104 rrd_set_error("zoom factor must be > 0");
4105 return;
4106 }
4107 break;
4108 case 't':
4109 strncpy(im->title, optarg, 150);
4110 im->title[150] = '\0';
4111 break;
4113 case 'R':
4114 if (strcmp(optarg, "normal") == 0) {
4115 cairo_font_options_set_antialias(im->font_options,
4116 CAIRO_ANTIALIAS_GRAY);
4117 cairo_font_options_set_hint_style(im->font_options,
4118 CAIRO_HINT_STYLE_FULL);
4119 } else if (strcmp(optarg, "light") == 0) {
4120 cairo_font_options_set_antialias(im->font_options,
4121 CAIRO_ANTIALIAS_GRAY);
4122 cairo_font_options_set_hint_style(im->font_options,
4123 CAIRO_HINT_STYLE_SLIGHT);
4124 } else if (strcmp(optarg, "mono") == 0) {
4125 cairo_font_options_set_antialias(im->font_options,
4126 CAIRO_ANTIALIAS_NONE);
4127 cairo_font_options_set_hint_style(im->font_options,
4128 CAIRO_HINT_STYLE_FULL);
4129 } else {
4130 rrd_set_error("unknown font-render-mode '%s'", optarg);
4131 return;
4132 }
4133 break;
4134 case 'G':
4135 if (strcmp(optarg, "normal") == 0)
4136 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4137 else if (strcmp(optarg, "mono") == 0)
4138 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4139 else {
4140 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4141 return;
4142 }
4143 break;
4144 case 'B':
4145 /* not supported curently */
4146 break;
4148 case 'W':
4149 strncpy(im->watermark, optarg, 100);
4150 im->watermark[99] = '\0';
4151 break;
4153 case '?':
4154 if (optopt != 0)
4155 rrd_set_error("unknown option '%c'", optopt);
4156 else
4157 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4158 return;
4159 }
4160 }
4162 if (im->logarithmic == 1 && im->minval <= 0) {
4163 rrd_set_error
4164 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4165 return;
4166 }
4168 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4169 /* error string is set in parsetime.c */
4170 return;
4171 }
4173 if (start_tmp < 3600 * 24 * 365 * 10) {
4174 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4175 start_tmp);
4176 return;
4177 }
4179 if (end_tmp < start_tmp) {
4180 rrd_set_error("start (%ld) should be less than end (%ld)",
4181 start_tmp, end_tmp);
4182 return;
4183 }
4185 im->start = start_tmp;
4186 im->end = end_tmp;
4187 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4188 }
4190 int rrd_graph_color(
4191 image_desc_t *im,
4192 char *var,
4193 char *err,
4194 int optional)
4195 {
4196 char *color;
4197 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4199 color = strstr(var, "#");
4200 if (color == NULL) {
4201 if (optional == 0) {
4202 rrd_set_error("Found no color in %s", err);
4203 return 0;
4204 }
4205 return 0;
4206 } else {
4207 int n = 0;
4208 char *rest;
4209 long unsigned int col;
4211 rest = strstr(color, ":");
4212 if (rest != NULL)
4213 n = rest - color;
4214 else
4215 n = strlen(color);
4217 switch (n) {
4218 case 7:
4219 sscanf(color, "#%6lx%n", &col, &n);
4220 col = (col << 8) + 0xff /* shift left by 8 */ ;
4221 if (n != 7)
4222 rrd_set_error("Color problem in %s", err);
4223 break;
4224 case 9:
4225 sscanf(color, "#%8lx%n", &col, &n);
4226 if (n == 9)
4227 break;
4228 default:
4229 rrd_set_error("Color problem in %s", err);
4230 }
4231 if (rrd_test_error())
4232 return 0;
4233 gdp->col = gfx_hex_to_col(col);
4234 return n;
4235 }
4236 }
4239 int bad_format(
4240 char *fmt)
4241 {
4242 char *ptr;
4243 int n = 0;
4245 ptr = fmt;
4246 while (*ptr != '\0')
4247 if (*ptr++ == '%') {
4249 /* line cannot end with percent char */
4250 if (*ptr == '\0')
4251 return 1;
4253 /* '%s', '%S' and '%%' are allowed */
4254 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4255 ptr++;
4257 /* %c is allowed (but use only with vdef!) */
4258 else if (*ptr == 'c') {
4259 ptr++;
4260 n = 1;
4261 }
4263 /* or else '% 6.2lf' and such are allowed */
4264 else {
4265 /* optional padding character */
4266 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4267 ptr++;
4269 /* This should take care of 'm.n' with all three optional */
4270 while (*ptr >= '0' && *ptr <= '9')
4271 ptr++;
4272 if (*ptr == '.')
4273 ptr++;
4274 while (*ptr >= '0' && *ptr <= '9')
4275 ptr++;
4277 /* Either 'le', 'lf' or 'lg' must follow here */
4278 if (*ptr++ != 'l')
4279 return 1;
4280 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4281 ptr++;
4282 else
4283 return 1;
4284 n++;
4285 }
4286 }
4288 return (n != 1);
4289 }
4292 int vdef_parse(
4293 struct graph_desc_t *gdes,
4294 const char *const str)
4295 {
4296 /* A VDEF currently is either "func" or "param,func"
4297 * so the parsing is rather simple. Change if needed.
4298 */
4299 double param;
4300 char func[30];
4301 int n;
4302 char *old_locale;
4304 n = 0;
4305 old_locale = setlocale(LC_NUMERIC, "C");
4306 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4307 setlocale(LC_NUMERIC, old_locale);
4308 if (n == (int) strlen(str)) { /* matched */
4309 ;
4310 } else {
4311 n = 0;
4312 sscanf(str, "%29[A-Z]%n", func, &n);
4313 if (n == (int) strlen(str)) { /* matched */
4314 param = DNAN;
4315 } else {
4316 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4317 gdes->vname);
4318 return -1;
4319 }
4320 }
4321 if (!strcmp("PERCENT", func))
4322 gdes->vf.op = VDEF_PERCENT;
4323 else if (!strcmp("MAXIMUM", func))
4324 gdes->vf.op = VDEF_MAXIMUM;
4325 else if (!strcmp("AVERAGE", func))
4326 gdes->vf.op = VDEF_AVERAGE;
4327 else if (!strcmp("STDEV", func))
4328 gdes->vf.op = VDEF_STDEV;
4329 else if (!strcmp("MINIMUM", func))
4330 gdes->vf.op = VDEF_MINIMUM;
4331 else if (!strcmp("TOTAL", func))
4332 gdes->vf.op = VDEF_TOTAL;
4333 else if (!strcmp("FIRST", func))
4334 gdes->vf.op = VDEF_FIRST;
4335 else if (!strcmp("LAST", func))
4336 gdes->vf.op = VDEF_LAST;
4337 else if (!strcmp("LSLSLOPE", func))
4338 gdes->vf.op = VDEF_LSLSLOPE;
4339 else if (!strcmp("LSLINT", func))
4340 gdes->vf.op = VDEF_LSLINT;
4341 else if (!strcmp("LSLCORREL", func))
4342 gdes->vf.op = VDEF_LSLCORREL;
4343 else {
4344 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4345 gdes->vname);
4346 return -1;
4347 };
4349 switch (gdes->vf.op) {
4350 case VDEF_PERCENT:
4351 if (isnan(param)) { /* no parameter given */
4352 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4353 func, gdes->vname);
4354 return -1;
4355 };
4356 if (param >= 0.0 && param <= 100.0) {
4357 gdes->vf.param = param;
4358 gdes->vf.val = DNAN; /* undefined */
4359 gdes->vf.when = 0; /* undefined */
4360 } else {
4361 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4362 gdes->vname);
4363 return -1;
4364 };
4365 break;
4366 case VDEF_MAXIMUM:
4367 case VDEF_AVERAGE:
4368 case VDEF_STDEV:
4369 case VDEF_MINIMUM:
4370 case VDEF_TOTAL:
4371 case VDEF_FIRST:
4372 case VDEF_LAST:
4373 case VDEF_LSLSLOPE:
4374 case VDEF_LSLINT:
4375 case VDEF_LSLCORREL:
4376 if (isnan(param)) {
4377 gdes->vf.param = DNAN;
4378 gdes->vf.val = DNAN;
4379 gdes->vf.when = 0;
4380 } else {
4381 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4382 func, gdes->vname);
4383 return -1;
4384 };
4385 break;
4386 };
4387 return 0;
4388 }
4391 int vdef_calc(
4392 image_desc_t *im,
4393 int gdi)
4394 {
4395 graph_desc_t *src, *dst;
4396 rrd_value_t *data;
4397 long step, steps;
4399 dst = &im->gdes[gdi];
4400 src = &im->gdes[dst->vidx];
4401 data = src->data + src->ds;
4402 steps = (src->end - src->start) / src->step;
4404 #if 0
4405 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4406 src->end, steps);
4407 #endif
4409 switch (dst->vf.op) {
4410 case VDEF_PERCENT:{
4411 rrd_value_t *array;
4412 int field;
4415 if ((array = malloc(steps * sizeof(double))) == NULL) {
4416 rrd_set_error("malloc VDEV_PERCENT");
4417 return -1;
4418 }
4419 for (step = 0; step < steps; step++) {
4420 array[step] = data[step * src->ds_cnt];
4421 }
4422 qsort(array, step, sizeof(double), vdef_percent_compar);
4424 field = (steps - 1) * dst->vf.param / 100;
4425 dst->vf.val = array[field];
4426 dst->vf.when = 0; /* no time component */
4427 free(array);
4428 #if 0
4429 for (step = 0; step < steps; step++)
4430 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4431 step == field ? '*' : ' ');
4432 #endif
4433 }
4434 break;
4435 case VDEF_MAXIMUM:
4436 step = 0;
4437 while (step != steps && isnan(data[step * src->ds_cnt]))
4438 step++;
4439 if (step == steps) {
4440 dst->vf.val = DNAN;
4441 dst->vf.when = 0;
4442 } else {
4443 dst->vf.val = data[step * src->ds_cnt];
4444 dst->vf.when = src->start + (step + 1) * src->step;
4445 }
4446 while (step != steps) {
4447 if (finite(data[step * src->ds_cnt])) {
4448 if (data[step * src->ds_cnt] > dst->vf.val) {
4449 dst->vf.val = data[step * src->ds_cnt];
4450 dst->vf.when = src->start + (step + 1) * src->step;
4451 }
4452 }
4453 step++;
4454 }
4455 break;
4456 case VDEF_TOTAL:
4457 case VDEF_STDEV:
4458 case VDEF_AVERAGE:{
4459 int cnt = 0;
4460 double sum = 0.0;
4461 double average = 0.0;
4463 for (step = 0; step < steps; step++) {
4464 if (finite(data[step * src->ds_cnt])) {
4465 sum += data[step * src->ds_cnt];
4466 cnt++;
4467 };
4468 }
4469 if (cnt) {
4470 if (dst->vf.op == VDEF_TOTAL) {
4471 dst->vf.val = sum * src->step;
4472 dst->vf.when = 0; /* no time component */
4473 } else if (dst->vf.op == VDEF_AVERAGE) {
4474 dst->vf.val = sum / cnt;
4475 dst->vf.when = 0; /* no time component */
4476 } else {
4477 average = sum / cnt;
4478 sum = 0.0;
4479 for (step = 0; step < steps; step++) {
4480 if (finite(data[step * src->ds_cnt])) {
4481 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4482 };
4483 }
4484 dst->vf.val = pow(sum / cnt, 0.5);
4485 dst->vf.when = 0; /* no time component */
4486 };
4487 } else {
4488 dst->vf.val = DNAN;
4489 dst->vf.when = 0;
4490 }
4491 }
4492 break;
4493 case VDEF_MINIMUM:
4494 step = 0;
4495 while (step != steps && isnan(data[step * src->ds_cnt]))
4496 step++;
4497 if (step == steps) {
4498 dst->vf.val = DNAN;
4499 dst->vf.when = 0;
4500 } else {
4501 dst->vf.val = data[step * src->ds_cnt];
4502 dst->vf.when = src->start + (step + 1) * src->step;
4503 }
4504 while (step != steps) {
4505 if (finite(data[step * src->ds_cnt])) {
4506 if (data[step * src->ds_cnt] < dst->vf.val) {
4507 dst->vf.val = data[step * src->ds_cnt];
4508 dst->vf.when = src->start + (step + 1) * src->step;
4509 }
4510 }
4511 step++;
4512 }
4513 break;
4514 case VDEF_FIRST:
4515 /* The time value returned here is one step before the
4516 * actual time value. This is the start of the first
4517 * non-NaN interval.
4518 */
4519 step = 0;
4520 while (step != steps && isnan(data[step * src->ds_cnt]))
4521 step++;
4522 if (step == steps) { /* all entries were NaN */
4523 dst->vf.val = DNAN;
4524 dst->vf.when = 0;
4525 } else {
4526 dst->vf.val = data[step * src->ds_cnt];
4527 dst->vf.when = src->start + step * src->step;
4528 }
4529 break;
4530 case VDEF_LAST:
4531 /* The time value returned here is the
4532 * actual time value. This is the end of the last
4533 * non-NaN interval.
4534 */
4535 step = steps - 1;
4536 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4537 step--;
4538 if (step < 0) { /* 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 + 1) * src->step;
4544 }
4545 break;
4546 case VDEF_LSLSLOPE:
4547 case VDEF_LSLINT:
4548 case VDEF_LSLCORREL:{
4549 /* Bestfit line by linear least squares method */
4551 int cnt = 0;
4552 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4554 SUMx = 0;
4555 SUMy = 0;
4556 SUMxy = 0;
4557 SUMxx = 0;
4558 SUMyy = 0;
4560 for (step = 0; step < steps; step++) {
4561 if (finite(data[step * src->ds_cnt])) {
4562 cnt++;
4563 SUMx += step;
4564 SUMxx += step * step;
4565 SUMxy += step * data[step * src->ds_cnt];
4566 SUMy += data[step * src->ds_cnt];
4567 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4568 };
4569 }
4571 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4572 y_intercept = (SUMy - slope * SUMx) / cnt;
4573 correl =
4574 (SUMxy -
4575 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4576 (SUMx * SUMx) / cnt) * (SUMyy -
4577 (SUMy *
4578 SUMy) /
4579 cnt));
4581 if (cnt) {
4582 if (dst->vf.op == VDEF_LSLSLOPE) {
4583 dst->vf.val = slope;
4584 dst->vf.when = 0;
4585 } else if (dst->vf.op == VDEF_LSLINT) {
4586 dst->vf.val = y_intercept;
4587 dst->vf.when = 0;
4588 } else if (dst->vf.op == VDEF_LSLCORREL) {
4589 dst->vf.val = correl;
4590 dst->vf.when = 0;
4591 };
4593 } else {
4594 dst->vf.val = DNAN;
4595 dst->vf.when = 0;
4596 }
4597 }
4598 break;
4599 }
4600 return 0;
4601 }
4603 /* NaN < -INF < finite_values < INF */
4604 int vdef_percent_compar(
4605 const void *a,
4606 const void *b)
4607 {
4608 /* Equality is not returned; this doesn't hurt except
4609 * (maybe) for a little performance.
4610 */
4612 /* First catch NaN values. They are smallest */
4613 if (isnan(*(double *) a))
4614 return -1;
4615 if (isnan(*(double *) b))
4616 return 1;
4618 /* NaN doesn't reach this part so INF and -INF are extremes.
4619 * The sign from isinf() is compatible with the sign we return
4620 */
4621 if (isinf(*(double *) a))
4622 return isinf(*(double *) a);
4623 if (isinf(*(double *) b))
4624 return isinf(*(double *) b);
4626 /* If we reach this, both values must be finite */
4627 if (*(double *) a < *(double *) b)
4628 return -1;
4629 else
4630 return 1;
4631 }