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