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