1 /****************************************************************************
2 * RRDtool 1.3rc2 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))
1259 minval = 0.2;
1260 if (isnan(maxval))
1261 maxval = 5.1;
1262 } else {
1263 if (isnan(minval))
1264 minval = 0.0;
1265 if (isnan(maxval))
1266 maxval = 1.0;
1267 }
1269 /* adjust min and max values */
1270 /* for logscale we add something on top */
1271 if (isnan(im->minval)
1272 || ((!im->rigid) && im->minval > minval)
1273 ) {
1274 if (im->logarithmic)
1275 im->minval = minval * 0.5;
1276 else
1277 im->minval = minval;
1278 }
1279 if (isnan(im->maxval)
1280 || (!im->rigid && im->maxval < maxval)
1281 ) {
1282 if (im->logarithmic)
1283 im->maxval = maxval * 2.0;
1284 else
1285 im->maxval = maxval;
1286 }
1288 /* make sure min is smaller than max */
1289 if (im->minval > im->maxval) {
1290 if (im->minval > 0)
1291 im->minval = 0.99 * im->maxval;
1292 else
1293 im->minval = 1.01 * im->maxval;
1294 }
1296 /* make sure min and max are not equal */
1297 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1298 if (im->maxval > 0)
1299 im->maxval *= 1.01;
1300 else
1301 im->maxval *= 0.99;
1303 /* make sure min and max are not both zero */
1304 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1305 im->maxval = 1.0;
1306 }
1307 }
1308 return 0;
1309 }
1313 /* identify the point where the first gridline, label ... gets placed */
1315 time_t find_first_time(
1316 time_t start, /* what is the initial time */
1317 enum tmt_en baseint, /* what is the basic interval */
1318 long basestep /* how many if these do we jump a time */
1319 )
1320 {
1321 struct tm tm;
1323 localtime_r(&start, &tm);
1325 switch (baseint) {
1326 case TMT_SECOND:
1327 tm. tm_sec -= tm.tm_sec % basestep;
1329 break;
1330 case TMT_MINUTE:
1331 tm. tm_sec = 0;
1332 tm. tm_min -= tm.tm_min % basestep;
1334 break;
1335 case TMT_HOUR:
1336 tm. tm_sec = 0;
1337 tm. tm_min = 0;
1338 tm. tm_hour -= tm.tm_hour % basestep;
1340 break;
1341 case TMT_DAY:
1342 /* we do NOT look at the basestep for this ... */
1343 tm. tm_sec = 0;
1344 tm. tm_min = 0;
1345 tm. tm_hour = 0;
1347 break;
1348 case TMT_WEEK:
1349 /* we do NOT look at the basestep for this ... */
1350 tm. tm_sec = 0;
1351 tm. tm_min = 0;
1352 tm. tm_hour = 0;
1353 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1355 if (tm.tm_wday == 0)
1356 tm. tm_mday -= 7; /* we want the *previous* monday */
1358 break;
1359 case TMT_MONTH:
1360 tm. tm_sec = 0;
1361 tm. tm_min = 0;
1362 tm. tm_hour = 0;
1363 tm. tm_mday = 1;
1364 tm. tm_mon -= tm.tm_mon % basestep;
1366 break;
1368 case TMT_YEAR:
1369 tm. tm_sec = 0;
1370 tm. tm_min = 0;
1371 tm. tm_hour = 0;
1372 tm. tm_mday = 1;
1373 tm. tm_mon = 0;
1374 tm. tm_year -= (
1375 tm.tm_year + 1900) %basestep;
1377 }
1378 return mktime(&tm);
1379 }
1381 /* identify the point where the next gridline, label ... gets placed */
1382 time_t find_next_time(
1383 time_t current, /* what is the initial time */
1384 enum tmt_en baseint, /* what is the basic interval */
1385 long basestep /* how many if these do we jump a time */
1386 )
1387 {
1388 struct tm tm;
1389 time_t madetime;
1391 localtime_r(¤t, &tm);
1393 do {
1394 switch (baseint) {
1395 case TMT_SECOND:
1396 tm. tm_sec += basestep;
1398 break;
1399 case TMT_MINUTE:
1400 tm. tm_min += basestep;
1402 break;
1403 case TMT_HOUR:
1404 tm. tm_hour += basestep;
1406 break;
1407 case TMT_DAY:
1408 tm. tm_mday += basestep;
1410 break;
1411 case TMT_WEEK:
1412 tm. tm_mday += 7 * basestep;
1414 break;
1415 case TMT_MONTH:
1416 tm. tm_mon += basestep;
1418 break;
1419 case TMT_YEAR:
1420 tm. tm_year += basestep;
1421 }
1422 madetime = mktime(&tm);
1423 } while (madetime == -1); /* this is necessary to skip impssible times
1424 like the daylight saving time skips */
1425 return madetime;
1427 }
1430 /* calculate values required for PRINT and GPRINT functions */
1432 int print_calc(
1433 image_desc_t *im)
1434 {
1435 long i, ii, validsteps;
1436 double printval;
1437 struct tm tmvdef;
1438 int graphelement = 0;
1439 long vidx;
1440 int max_ii;
1441 double magfact = -1;
1442 char *si_symb = "";
1443 char *percent_s;
1444 int prline_cnt = 0;
1446 /* wow initializing tmvdef is quite a task :-) */
1447 time_t now = time(NULL);
1449 localtime_r(&now, &tmvdef);
1450 for (i = 0; i < im->gdes_c; i++) {
1451 vidx = im->gdes[i].vidx;
1452 switch (im->gdes[i].gf) {
1453 case GF_PRINT:
1454 case GF_GPRINT:
1455 /* PRINT and GPRINT can now print VDEF generated values.
1456 * There's no need to do any calculations on them as these
1457 * calculations were already made.
1458 */
1459 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1460 printval = im->gdes[vidx].vf.val;
1461 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1462 } else { /* need to calculate max,min,avg etcetera */
1463 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1464 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1465 printval = DNAN;
1466 validsteps = 0;
1467 for (ii = im->gdes[vidx].ds;
1468 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1469 if (!finite(im->gdes[vidx].data[ii]))
1470 continue;
1471 if (isnan(printval)) {
1472 printval = im->gdes[vidx].data[ii];
1473 validsteps++;
1474 continue;
1475 }
1477 switch (im->gdes[i].cf) {
1478 case CF_HWPREDICT:
1479 case CF_MHWPREDICT:
1480 case CF_DEVPREDICT:
1481 case CF_DEVSEASONAL:
1482 case CF_SEASONAL:
1483 case CF_AVERAGE:
1484 validsteps++;
1485 printval += im->gdes[vidx].data[ii];
1486 break;
1487 case CF_MINIMUM:
1488 printval = min(printval, im->gdes[vidx].data[ii]);
1489 break;
1490 case CF_FAILURES:
1491 case CF_MAXIMUM:
1492 printval = max(printval, im->gdes[vidx].data[ii]);
1493 break;
1494 case CF_LAST:
1495 printval = im->gdes[vidx].data[ii];
1496 }
1497 }
1498 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1499 if (validsteps > 1) {
1500 printval = (printval / validsteps);
1501 }
1502 }
1503 } /* prepare printval */
1505 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1506 /* Magfact is set to -1 upon entry to print_calc. If it
1507 * is still less than 0, then we need to run auto_scale.
1508 * Otherwise, put the value into the correct units. If
1509 * the value is 0, then do not set the symbol or magnification
1510 * so next the calculation will be performed again. */
1511 if (magfact < 0.0) {
1512 auto_scale(im, &printval, &si_symb, &magfact);
1513 if (printval == 0.0)
1514 magfact = -1.0;
1515 } else {
1516 printval /= magfact;
1517 }
1518 *(++percent_s) = 's';
1519 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1520 auto_scale(im, &printval, &si_symb, &magfact);
1521 }
1523 if (im->gdes[i].gf == GF_PRINT) {
1524 infoval prline;
1526 if (im->gdes[i].strftm) {
1527 prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
1528 strftime(prline.u_str,
1529 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1530 } else if (bad_format(im->gdes[i].format)) {
1531 rrd_set_error
1532 ("bad format for PRINT in '%s'", im->gdes[i].format);
1533 return -1;
1534 } else {
1535 prline.u_str =
1536 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1537 }
1538 grinfo_push(im,
1539 sprintf_alloc
1540 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1541 free(prline.u_str);
1542 } else {
1543 /* GF_GPRINT */
1545 if (im->gdes[i].strftm) {
1546 strftime(im->gdes[i].legend,
1547 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1548 } else {
1549 if (bad_format(im->gdes[i].format)) {
1550 rrd_set_error
1551 ("bad format for GPRINT in '%s'",
1552 im->gdes[i].format);
1553 return -1;
1554 }
1555 #ifdef HAVE_SNPRINTF
1556 snprintf(im->gdes[i].legend,
1557 FMT_LEG_LEN - 2,
1558 im->gdes[i].format, printval, si_symb);
1559 #else
1560 sprintf(im->gdes[i].legend,
1561 im->gdes[i].format, printval, si_symb);
1562 #endif
1563 }
1564 graphelement = 1;
1565 }
1566 break;
1567 case GF_LINE:
1568 case GF_AREA:
1569 case GF_TICK:
1570 graphelement = 1;
1571 break;
1572 case GF_HRULE:
1573 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1574 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1575 };
1576 graphelement = 1;
1577 break;
1578 case GF_VRULE:
1579 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1580 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1581 };
1582 graphelement = 1;
1583 break;
1584 case GF_COMMENT:
1585 case GF_TEXTALIGN:
1586 case GF_DEF:
1587 case GF_CDEF:
1588 case GF_VDEF:
1589 #ifdef WITH_PIECHART
1590 case GF_PART:
1591 #endif
1592 case GF_SHIFT:
1593 case GF_XPORT:
1594 break;
1595 case GF_STACK:
1596 rrd_set_error
1597 ("STACK should already be turned into LINE or AREA here");
1598 return -1;
1599 break;
1600 }
1601 }
1602 return graphelement;
1603 }
1606 /* place legends with color spots */
1607 int leg_place(
1608 image_desc_t *im,
1609 int *gY)
1610 {
1611 /* graph labels */
1612 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1613 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1614 int fill = 0, fill_last;
1615 int leg_c = 0;
1616 int leg_x = border, leg_y = im->yimg;
1617 int leg_y_prev = im->yimg;
1618 int leg_cc;
1619 int glue = 0;
1620 int i, ii, mark = 0;
1621 char prt_fctn; /*special printfunctions */
1622 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1623 int *legspace;
1625 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1626 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1627 rrd_set_error("malloc for legspace");
1628 return -1;
1629 }
1631 if (im->extra_flags & FULL_SIZE_MODE)
1632 leg_y = leg_y_prev =
1633 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1634 for (i = 0; i < im->gdes_c; i++) {
1635 fill_last = fill;
1636 /* hide legends for rules which are not displayed */
1637 if (im->gdes[i].gf == GF_TEXTALIGN) {
1638 default_txtalign = im->gdes[i].txtalign;
1639 }
1641 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1642 if (im->gdes[i].gf == GF_HRULE
1643 && (im->gdes[i].yrule <
1644 im->minval || im->gdes[i].yrule > im->maxval))
1645 im->gdes[i].legend[0] = '\0';
1646 if (im->gdes[i].gf == GF_VRULE
1647 && (im->gdes[i].xrule <
1648 im->start || im->gdes[i].xrule > im->end))
1649 im->gdes[i].legend[0] = '\0';
1650 }
1652 leg_cc = strlen(im->gdes[i].legend);
1653 /* is there a controle code ant the end of the legend string ? */
1654 /* and it is not a tab \\t */
1655 if (leg_cc >= 2
1656 && im->gdes[i].legend[leg_cc -
1657 2] == '\\'
1658 && im->gdes[i].legend[leg_cc - 1] != 't') {
1659 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1660 leg_cc -= 2;
1661 im->gdes[i].legend[leg_cc] = '\0';
1662 } else {
1663 prt_fctn = '\0';
1664 }
1665 /* only valid control codes */
1666 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1667 prt_fctn != 'r' &&
1668 prt_fctn != 'j' &&
1669 prt_fctn != 'c' &&
1670 prt_fctn != 's' &&
1671 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1672 free(legspace);
1673 rrd_set_error
1674 ("Unknown control code at the end of '%s\\%c'",
1675 im->gdes[i].legend, prt_fctn);
1676 return -1;
1677 }
1678 /* \n -> \l */
1679 if (prt_fctn == 'n') {
1680 prt_fctn = 'l';
1681 }
1683 /* remove exess space from the end of the legend for \g */
1684 while (prt_fctn == 'g' &&
1685 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1686 leg_cc--;
1687 im->gdes[i].legend[leg_cc] = '\0';
1688 }
1690 if (leg_cc != 0) {
1692 /* no interleg space if string ends in \g */
1693 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1694 if (fill > 0) {
1695 fill += legspace[i];
1696 }
1697 fill +=
1698 gfx_get_text_width(im,
1699 fill + border,
1700 im->
1701 text_prop
1702 [TEXT_PROP_LEGEND].
1703 font,
1704 im->
1705 text_prop
1706 [TEXT_PROP_LEGEND].
1707 size,
1708 im->tabwidth, im->gdes[i].legend);
1709 leg_c++;
1710 } else {
1711 legspace[i] = 0;
1712 }
1713 /* who said there was a special tag ... ? */
1714 if (prt_fctn == 'g') {
1715 prt_fctn = '\0';
1716 }
1718 if (prt_fctn == '\0') {
1719 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1720 /* just one legend item is left right or center */
1721 switch (default_txtalign) {
1722 case TXA_RIGHT:
1723 prt_fctn = 'r';
1724 break;
1725 case TXA_CENTER:
1726 prt_fctn = 'c';
1727 break;
1728 case TXA_JUSTIFIED:
1729 prt_fctn = 'j';
1730 break;
1731 default:
1732 prt_fctn = 'l';
1733 break;
1734 }
1735 }
1736 /* is it time to place the legends ? */
1737 if (fill > im->ximg - 2 * border) {
1738 if (leg_c > 1) {
1739 /* go back one */
1740 i--;
1741 fill = fill_last;
1742 leg_c--;
1743 }
1744 }
1745 if (leg_c == 1 && prt_fctn == 'j') {
1746 prt_fctn = 'l';
1747 }
1748 }
1751 if (prt_fctn != '\0') {
1752 leg_x = border;
1753 if (leg_c >= 2 && prt_fctn == 'j') {
1754 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1755 } else {
1756 glue = 0;
1757 }
1758 if (prt_fctn == 'c')
1759 leg_x = (im->ximg - fill) / 2.0;
1760 if (prt_fctn == 'r')
1761 leg_x = im->ximg - fill - border;
1762 for (ii = mark; ii <= i; ii++) {
1763 if (im->gdes[ii].legend[0] == '\0')
1764 continue; /* skip empty legends */
1765 im->gdes[ii].leg_x = leg_x;
1766 im->gdes[ii].leg_y = leg_y;
1767 leg_x +=
1768 gfx_get_text_width(im, leg_x,
1769 im->
1770 text_prop
1771 [TEXT_PROP_LEGEND].
1772 font,
1773 im->
1774 text_prop
1775 [TEXT_PROP_LEGEND].
1776 size,
1777 im->tabwidth, im->gdes[ii].legend)
1778 + legspace[ii]
1779 + glue;
1780 }
1781 leg_y_prev = leg_y;
1782 if (im->extra_flags & FULL_SIZE_MODE) {
1783 /* only add y space if there was text on the line */
1784 if (leg_x > border || prt_fctn == 's')
1785 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1786 if (prt_fctn == 's')
1787 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1788 } else {
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 }
1794 fill = 0;
1795 leg_c = 0;
1796 mark = ii;
1797 }
1798 }
1800 if (im->extra_flags & FULL_SIZE_MODE) {
1801 if (leg_y != leg_y_prev) {
1802 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1803 im->yorigin =
1804 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1805 }
1806 } else {
1807 im->yimg = leg_y_prev;
1808 /* if we did place some legends we have to add vertical space */
1809 if (leg_y != im->yimg)
1810 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1811 }
1812 free(legspace);
1813 }
1814 return 0;
1815 }
1817 /* create a grid on the graph. it determines what to do
1818 from the values of xsize, start and end */
1820 /* the xaxis labels are determined from the number of seconds per pixel
1821 in the requested graph */
1825 int calc_horizontal_grid(
1826 image_desc_t
1827 *im)
1828 {
1829 double range;
1830 double scaledrange;
1831 int pixel, i;
1832 int gridind = 0;
1833 int decimals, fractionals;
1835 im->ygrid_scale.labfact = 2;
1836 range = im->maxval - im->minval;
1837 scaledrange = range / im->magfact;
1838 /* does the scale of this graph make it impossible to put lines
1839 on it? If so, give up. */
1840 if (isnan(scaledrange)) {
1841 return 0;
1842 }
1844 /* find grid spaceing */
1845 pixel = 1;
1846 if (isnan(im->ygridstep)) {
1847 if (im->extra_flags & ALTYGRID) {
1848 /* find the value with max number of digits. Get number of digits */
1849 decimals =
1850 ceil(log10
1851 (max(fabs(im->maxval), fabs(im->minval)) *
1852 im->viewfactor / im->magfact));
1853 if (decimals <= 0) /* everything is small. make place for zero */
1854 decimals = 1;
1855 im->ygrid_scale.gridstep =
1856 pow((double) 10,
1857 floor(log10(range * im->viewfactor / im->magfact))) /
1858 im->viewfactor * im->magfact;
1859 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1860 im->ygrid_scale.gridstep = 0.1;
1861 /* should have at least 5 lines but no more then 15 */
1862 if (range / im->ygrid_scale.gridstep < 5)
1863 im->ygrid_scale.gridstep /= 10;
1864 if (range / im->ygrid_scale.gridstep > 15)
1865 im->ygrid_scale.gridstep *= 10;
1866 if (range / im->ygrid_scale.gridstep > 5) {
1867 im->ygrid_scale.labfact = 1;
1868 if (range / im->ygrid_scale.gridstep > 8)
1869 im->ygrid_scale.labfact = 2;
1870 } else {
1871 im->ygrid_scale.gridstep /= 5;
1872 im->ygrid_scale.labfact = 5;
1873 }
1874 fractionals =
1875 floor(log10
1876 (im->ygrid_scale.gridstep *
1877 (double) im->ygrid_scale.labfact * im->viewfactor /
1878 im->magfact));
1879 if (fractionals < 0) { /* small amplitude. */
1880 int len = decimals - fractionals + 1;
1882 if (im->unitslength < len + 2)
1883 im->unitslength = len + 2;
1884 sprintf(im->ygrid_scale.labfmt,
1885 "%%%d.%df%s", len,
1886 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1887 } else {
1888 int len = decimals + 1;
1890 if (im->unitslength < len + 2)
1891 im->unitslength = len + 2;
1892 sprintf(im->ygrid_scale.labfmt,
1893 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1894 }
1895 } else {
1896 for (i = 0; ylab[i].grid > 0; i++) {
1897 pixel = im->ysize / (scaledrange / ylab[i].grid);
1898 gridind = i;
1899 if (pixel > 7)
1900 break;
1901 }
1903 for (i = 0; i < 4; i++) {
1904 if (pixel * ylab[gridind].lfac[i] >=
1905 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1906 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1907 break;
1908 }
1909 }
1911 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1912 }
1913 } else {
1914 im->ygrid_scale.gridstep = im->ygridstep;
1915 im->ygrid_scale.labfact = im->ylabfact;
1916 }
1917 return 1;
1918 }
1920 int draw_horizontal_grid(
1921 image_desc_t
1922 *im)
1923 {
1924 int i;
1925 double scaledstep;
1926 char graph_label[100];
1927 int nlabels = 0;
1928 double X0 = im->xorigin;
1929 double X1 = im->xorigin + im->xsize;
1930 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1931 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1932 double MaxY;
1934 scaledstep =
1935 im->ygrid_scale.gridstep /
1936 (double) im->magfact * (double) im->viewfactor;
1937 MaxY = scaledstep * (double) egrid;
1938 for (i = sgrid; i <= egrid; i++) {
1939 double Y0 = ytr(im,
1940 im->ygrid_scale.gridstep * i);
1941 double YN = ytr(im,
1942 im->ygrid_scale.gridstep * (i + 1));
1944 if (floor(Y0 + 0.5) >=
1945 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1946 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1947 with the chosen settings. Add a label if required by settings, or if
1948 there is only one label so far and the next grid line is out of bounds. */
1949 if (i % im->ygrid_scale.labfact == 0
1950 || (nlabels == 1
1951 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1952 if (im->symbol == ' ') {
1953 if (im->extra_flags & ALTYGRID) {
1954 sprintf(graph_label,
1955 im->ygrid_scale.labfmt,
1956 scaledstep * (double) i);
1957 } else {
1958 if (MaxY < 10) {
1959 sprintf(graph_label, "%4.1f",
1960 scaledstep * (double) i);
1961 } else {
1962 sprintf(graph_label, "%4.0f",
1963 scaledstep * (double) i);
1964 }
1965 }
1966 } else {
1967 char sisym = (i == 0 ? ' ' : im->symbol);
1969 if (im->extra_flags & ALTYGRID) {
1970 sprintf(graph_label,
1971 im->ygrid_scale.labfmt,
1972 scaledstep * (double) i, sisym);
1973 } else {
1974 if (MaxY < 10) {
1975 sprintf(graph_label, "%4.1f %c",
1976 scaledstep * (double) i, sisym);
1977 } else {
1978 sprintf(graph_label, "%4.0f %c",
1979 scaledstep * (double) i, sisym);
1980 }
1981 }
1982 }
1983 nlabels++;
1984 gfx_text(im,
1985 X0 -
1986 im->
1987 text_prop[TEXT_PROP_AXIS].
1988 size, Y0,
1989 im->graph_col[GRC_FONT],
1990 im->
1991 text_prop[TEXT_PROP_AXIS].
1992 font,
1993 im->
1994 text_prop[TEXT_PROP_AXIS].
1995 size, im->tabwidth, 0.0,
1996 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
1997 gfx_line(im, X0 - 2, Y0, X0, Y0,
1998 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1999 gfx_line(im, X1, Y0, X1 + 2, Y0,
2000 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2001 gfx_dashed_line(im, X0 - 2, Y0,
2002 X1 + 2, Y0,
2003 MGRIDWIDTH,
2004 im->
2005 graph_col
2006 [GRC_MGRID],
2007 im->grid_dash_on, im->grid_dash_off);
2008 } else if (!(im->extra_flags & NOMINOR)) {
2009 gfx_line(im,
2010 X0 - 2, Y0,
2011 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2012 gfx_line(im, X1, Y0, X1 + 2, Y0,
2013 GRIDWIDTH, im->graph_col[GRC_GRID]);
2014 gfx_dashed_line(im, X0 - 1, Y0,
2015 X1 + 1, Y0,
2016 GRIDWIDTH,
2017 im->
2018 graph_col[GRC_GRID],
2019 im->grid_dash_on, im->grid_dash_off);
2020 }
2021 }
2022 }
2023 return 1;
2024 }
2026 /* this is frexp for base 10 */
2027 double frexp10(
2028 double,
2029 double *);
2030 double frexp10(
2031 double x,
2032 double *e)
2033 {
2034 double mnt;
2035 int iexp;
2037 iexp = floor(log(fabs(x)) / log(10));
2038 mnt = x / pow(10.0, iexp);
2039 if (mnt >= 10.0) {
2040 iexp++;
2041 mnt = x / pow(10.0, iexp);
2042 }
2043 *e = iexp;
2044 return mnt;
2045 }
2047 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2048 /* yes we are loosing precision by doing tos with floats instead of doubles
2049 but it seems more stable this way. */
2052 /* logaritmic horizontal grid */
2053 int horizontal_log_grid(
2054 image_desc_t
2055 *im)
2056 {
2057 double yloglab[][10] = {
2058 {
2059 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2060 0.0, 0.0, 0.0}, {
2061 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2062 0.0, 0.0, 0.0}, {
2063 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2064 0.0, 0.0, 0.0}, {
2065 1.0, 2.0, 4.0,
2066 6.0, 8.0, 10.,
2067 0.0,
2068 0.0, 0.0, 0.0}, {
2069 1.0,
2070 2.0,
2071 3.0,
2072 4.0,
2073 5.0,
2074 6.0,
2075 7.0,
2076 8.0,
2077 9.0,
2078 10.},
2079 {
2080 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2081 };
2082 int i, j, val_exp, min_exp;
2083 double nex; /* number of decades in data */
2084 double logscale; /* scale in logarithmic space */
2085 int exfrac = 1; /* decade spacing */
2086 int mid = -1; /* row in yloglab for major grid */
2087 double mspac; /* smallest major grid spacing (pixels) */
2088 int flab; /* first value in yloglab to use */
2089 double value, tmp, pre_value;
2090 double X0, X1, Y0;
2091 char graph_label[100];
2093 nex = log10(im->maxval / im->minval);
2094 logscale = im->ysize / nex;
2095 /* major spacing for data with high dynamic range */
2096 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2097 if (exfrac == 1)
2098 exfrac = 3;
2099 else
2100 exfrac += 3;
2101 }
2103 /* major spacing for less dynamic data */
2104 do {
2105 /* search best row in yloglab */
2106 mid++;
2107 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2108 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2109 }
2110 while (mspac >
2111 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2112 if (mid)
2113 mid--;
2114 /* find first value in yloglab */
2115 for (flab = 0;
2116 yloglab[mid][flab] < 10
2117 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2118 if (yloglab[mid][flab] == 10.0) {
2119 tmp += 1.0;
2120 flab = 0;
2121 }
2122 val_exp = tmp;
2123 if (val_exp % exfrac)
2124 val_exp += abs(-val_exp % exfrac);
2125 X0 = im->xorigin;
2126 X1 = im->xorigin + im->xsize;
2127 /* draw grid */
2128 pre_value = DNAN;
2129 while (1) {
2131 value = yloglab[mid][flab] * pow(10.0, val_exp);
2132 if (AlmostEqual2sComplement(value, pre_value, 4))
2133 break; /* it seems we are not converging */
2134 pre_value = value;
2135 Y0 = ytr(im, value);
2136 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2137 break;
2138 /* major grid line */
2139 gfx_line(im,
2140 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2141 gfx_line(im, X1, Y0, X1 + 2, Y0,
2142 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2143 gfx_dashed_line(im, X0 - 2, Y0,
2144 X1 + 2, Y0,
2145 MGRIDWIDTH,
2146 im->
2147 graph_col
2148 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2149 /* label */
2150 if (im->extra_flags & FORCE_UNITS_SI) {
2151 int scale;
2152 double pvalue;
2153 char symbol;
2155 scale = floor(val_exp / 3.0);
2156 if (value >= 1.0)
2157 pvalue = pow(10.0, val_exp % 3);
2158 else
2159 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2160 pvalue *= yloglab[mid][flab];
2161 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2162 && ((scale + si_symbcenter) >= 0))
2163 symbol = si_symbol[scale + si_symbcenter];
2164 else
2165 symbol = '?';
2166 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2167 } else
2168 sprintf(graph_label, "%3.0e", value);
2169 gfx_text(im,
2170 X0 -
2171 im->
2172 text_prop[TEXT_PROP_AXIS].
2173 size, Y0,
2174 im->graph_col[GRC_FONT],
2175 im->
2176 text_prop[TEXT_PROP_AXIS].
2177 font,
2178 im->
2179 text_prop[TEXT_PROP_AXIS].
2180 size, im->tabwidth, 0.0,
2181 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2182 /* minor grid */
2183 if (mid < 4 && exfrac == 1) {
2184 /* find first and last minor line behind current major line
2185 * i is the first line and j tha last */
2186 if (flab == 0) {
2187 min_exp = val_exp - 1;
2188 for (i = 1; yloglab[mid][i] < 10.0; i++);
2189 i = yloglab[mid][i - 1] + 1;
2190 j = 10;
2191 } else {
2192 min_exp = val_exp;
2193 i = yloglab[mid][flab - 1] + 1;
2194 j = yloglab[mid][flab];
2195 }
2197 /* draw minor lines below current major line */
2198 for (; i < j; i++) {
2200 value = i * pow(10.0, min_exp);
2201 if (value < im->minval)
2202 continue;
2203 Y0 = ytr(im, value);
2204 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2205 break;
2206 /* draw lines */
2207 gfx_line(im,
2208 X0 - 2, Y0,
2209 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2210 gfx_line(im, X1, Y0, X1 + 2, Y0,
2211 GRIDWIDTH, im->graph_col[GRC_GRID]);
2212 gfx_dashed_line(im, X0 - 1, Y0,
2213 X1 + 1, Y0,
2214 GRIDWIDTH,
2215 im->
2216 graph_col[GRC_GRID],
2217 im->grid_dash_on, im->grid_dash_off);
2218 }
2219 } else if (exfrac > 1) {
2220 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2221 value = pow(10.0, i);
2222 if (value < im->minval)
2223 continue;
2224 Y0 = ytr(im, value);
2225 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2226 break;
2227 /* draw lines */
2228 gfx_line(im,
2229 X0 - 2, Y0,
2230 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2231 gfx_line(im, X1, Y0, X1 + 2, Y0,
2232 GRIDWIDTH, im->graph_col[GRC_GRID]);
2233 gfx_dashed_line(im, X0 - 1, Y0,
2234 X1 + 1, Y0,
2235 GRIDWIDTH,
2236 im->
2237 graph_col[GRC_GRID],
2238 im->grid_dash_on, im->grid_dash_off);
2239 }
2240 }
2242 /* next decade */
2243 if (yloglab[mid][++flab] == 10.0) {
2244 flab = 0;
2245 val_exp += exfrac;
2246 }
2247 }
2249 /* draw minor lines after highest major line */
2250 if (mid < 4 && exfrac == 1) {
2251 /* find first and last minor line below current major line
2252 * i is the first line and j tha last */
2253 if (flab == 0) {
2254 min_exp = val_exp - 1;
2255 for (i = 1; yloglab[mid][i] < 10.0; i++);
2256 i = yloglab[mid][i - 1] + 1;
2257 j = 10;
2258 } else {
2259 min_exp = val_exp;
2260 i = yloglab[mid][flab - 1] + 1;
2261 j = yloglab[mid][flab];
2262 }
2264 /* draw minor lines below current major line */
2265 for (; i < j; i++) {
2267 value = i * pow(10.0, min_exp);
2268 if (value < im->minval)
2269 continue;
2270 Y0 = ytr(im, value);
2271 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2272 break;
2273 /* draw lines */
2274 gfx_line(im,
2275 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2276 gfx_line(im, X1, Y0, X1 + 2, Y0,
2277 GRIDWIDTH, im->graph_col[GRC_GRID]);
2278 gfx_dashed_line(im, X0 - 1, Y0,
2279 X1 + 1, Y0,
2280 GRIDWIDTH,
2281 im->
2282 graph_col[GRC_GRID],
2283 im->grid_dash_on, im->grid_dash_off);
2284 }
2285 }
2286 /* fancy minor gridlines */
2287 else if (exfrac > 1) {
2288 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2289 value = pow(10.0, i);
2290 if (value < im->minval)
2291 continue;
2292 Y0 = ytr(im, value);
2293 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2294 break;
2295 /* draw lines */
2296 gfx_line(im,
2297 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2298 gfx_line(im, X1, Y0, X1 + 2, Y0,
2299 GRIDWIDTH, im->graph_col[GRC_GRID]);
2300 gfx_dashed_line(im, X0 - 1, Y0,
2301 X1 + 1, Y0,
2302 GRIDWIDTH,
2303 im->
2304 graph_col[GRC_GRID],
2305 im->grid_dash_on, im->grid_dash_off);
2306 }
2307 }
2309 return 1;
2310 }
2313 void vertical_grid(
2314 image_desc_t *im)
2315 {
2316 int xlab_sel; /* which sort of label and grid ? */
2317 time_t ti, tilab, timajor;
2318 long factor;
2319 char graph_label[100];
2320 double X0, Y0, Y1; /* points for filled graph and more */
2321 struct tm tm;
2323 /* the type of time grid is determined by finding
2324 the number of seconds per pixel in the graph */
2325 if (im->xlab_user.minsec == -1) {
2326 factor = (im->end - im->start) / im->xsize;
2327 xlab_sel = 0;
2328 while (xlab[xlab_sel + 1].minsec !=
2329 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2330 xlab_sel++;
2331 } /* pick the last one */
2332 while (xlab[xlab_sel - 1].minsec ==
2333 xlab[xlab_sel].minsec
2334 && xlab[xlab_sel].length > (im->end - im->start)) {
2335 xlab_sel--;
2336 } /* go back to the smallest size */
2337 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2338 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2339 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2340 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2341 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2342 im->xlab_user.labst = xlab[xlab_sel].labst;
2343 im->xlab_user.precis = xlab[xlab_sel].precis;
2344 im->xlab_user.stst = xlab[xlab_sel].stst;
2345 }
2347 /* y coords are the same for every line ... */
2348 Y0 = im->yorigin;
2349 Y1 = im->yorigin - im->ysize;
2350 /* paint the minor grid */
2351 if (!(im->extra_flags & NOMINOR)) {
2352 for (ti = find_first_time(im->start,
2353 im->
2354 xlab_user.
2355 gridtm,
2356 im->
2357 xlab_user.
2358 gridst),
2359 timajor =
2360 find_first_time(im->start,
2361 im->xlab_user.
2362 mgridtm,
2363 im->xlab_user.
2364 mgridst);
2365 ti < im->end;
2366 ti =
2367 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2368 ) {
2369 /* are we inside the graph ? */
2370 if (ti < im->start || ti > im->end)
2371 continue;
2372 while (timajor < ti) {
2373 timajor = find_next_time(timajor,
2374 im->
2375 xlab_user.
2376 mgridtm, im->xlab_user.mgridst);
2377 }
2378 if (ti == timajor)
2379 continue; /* skip as falls on major grid line */
2380 X0 = xtr(im, ti);
2381 gfx_line(im, X0, Y1 - 2, X0, Y1,
2382 GRIDWIDTH, im->graph_col[GRC_GRID]);
2383 gfx_line(im, X0, Y0, X0, Y0 + 2,
2384 GRIDWIDTH, im->graph_col[GRC_GRID]);
2385 gfx_dashed_line(im, X0, Y0 + 1, X0,
2386 Y1 - 1, GRIDWIDTH,
2387 im->
2388 graph_col[GRC_GRID],
2389 im->grid_dash_on, im->grid_dash_off);
2390 }
2391 }
2393 /* paint the major grid */
2394 for (ti = find_first_time(im->start,
2395 im->
2396 xlab_user.
2397 mgridtm,
2398 im->
2399 xlab_user.
2400 mgridst);
2401 ti < im->end;
2402 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2403 ) {
2404 /* are we inside the graph ? */
2405 if (ti < im->start || ti > im->end)
2406 continue;
2407 X0 = xtr(im, ti);
2408 gfx_line(im, X0, Y1 - 2, X0, Y1,
2409 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2410 gfx_line(im, X0, Y0, X0, Y0 + 3,
2411 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2412 gfx_dashed_line(im, X0, Y0 + 3, X0,
2413 Y1 - 2, MGRIDWIDTH,
2414 im->
2415 graph_col
2416 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2417 }
2418 /* paint the labels below the graph */
2419 for (ti =
2420 find_first_time(im->start -
2421 im->xlab_user.
2422 precis / 2,
2423 im->xlab_user.
2424 labtm,
2425 im->xlab_user.
2426 labst);
2427 ti <=
2428 im->end -
2429 im->xlab_user.precis / 2;
2430 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2431 ) {
2432 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2433 /* are we inside the graph ? */
2434 if (tilab < im->start || tilab > im->end)
2435 continue;
2436 #if HAVE_STRFTIME
2437 localtime_r(&tilab, &tm);
2438 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2439 #else
2440 # error "your libc has no strftime I guess we'll abort the exercise here."
2441 #endif
2442 gfx_text(im,
2443 xtr(im, tilab),
2444 Y0 + 3,
2445 im->graph_col[GRC_FONT],
2446 im->
2447 text_prop[TEXT_PROP_AXIS].
2448 font,
2449 im->
2450 text_prop[TEXT_PROP_AXIS].
2451 size, im->tabwidth, 0.0,
2452 GFX_H_CENTER, GFX_V_TOP, graph_label);
2453 }
2455 }
2458 void axis_paint(
2459 image_desc_t *im)
2460 {
2461 /* draw x and y axis */
2462 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2463 im->xorigin+im->xsize,im->yorigin-im->ysize,
2464 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2466 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2467 im->xorigin+im->xsize,im->yorigin-im->ysize,
2468 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2470 gfx_line(im, im->xorigin - 4,
2471 im->yorigin,
2472 im->xorigin + im->xsize +
2473 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2474 gfx_line(im, im->xorigin,
2475 im->yorigin + 4,
2476 im->xorigin,
2477 im->yorigin - im->ysize -
2478 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2479 /* arrow for X and Y axis direction */
2480 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 */
2481 im->graph_col[GRC_ARROW]);
2482 gfx_close_path(im);
2483 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 */
2484 im->graph_col[GRC_ARROW]);
2485 gfx_close_path(im);
2486 }
2488 void grid_paint(
2489 image_desc_t *im)
2490 {
2491 long i;
2492 int res = 0;
2493 double X0, Y0; /* points for filled graph and more */
2494 struct gfx_color_t water_color;
2496 /* draw 3d border */
2497 gfx_new_area(im, 0, im->yimg,
2498 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2499 gfx_add_point(im, im->ximg - 2, 2);
2500 gfx_add_point(im, im->ximg, 0);
2501 gfx_add_point(im, 0, 0);
2502 gfx_close_path(im);
2503 gfx_new_area(im, 2, im->yimg - 2,
2504 im->ximg - 2,
2505 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2506 gfx_add_point(im, im->ximg, 0);
2507 gfx_add_point(im, im->ximg, im->yimg);
2508 gfx_add_point(im, 0, im->yimg);
2509 gfx_close_path(im);
2510 if (im->draw_x_grid == 1)
2511 vertical_grid(im);
2512 if (im->draw_y_grid == 1) {
2513 if (im->logarithmic) {
2514 res = horizontal_log_grid(im);
2515 } else {
2516 res = draw_horizontal_grid(im);
2517 }
2519 /* dont draw horizontal grid if there is no min and max val */
2520 if (!res) {
2521 char *nodata = "No Data found";
2523 gfx_text(im, im->ximg / 2,
2524 (2 * im->yorigin -
2525 im->ysize) / 2,
2526 im->graph_col[GRC_FONT],
2527 im->
2528 text_prop[TEXT_PROP_AXIS].
2529 font,
2530 im->
2531 text_prop[TEXT_PROP_AXIS].
2532 size, im->tabwidth, 0.0,
2533 GFX_H_CENTER, GFX_V_CENTER, nodata);
2534 }
2535 }
2537 /* yaxis unit description */
2538 gfx_text(im,
2539 10,
2540 (im->yorigin -
2541 im->ysize / 2),
2542 im->graph_col[GRC_FONT],
2543 im->
2544 text_prop[TEXT_PROP_UNIT].
2545 font,
2546 im->
2547 text_prop[TEXT_PROP_UNIT].
2548 size, im->tabwidth,
2549 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2550 /* graph title */
2551 gfx_text(im,
2552 im->ximg / 2, 6,
2553 im->graph_col[GRC_FONT],
2554 im->
2555 text_prop[TEXT_PROP_TITLE].
2556 font,
2557 im->
2558 text_prop[TEXT_PROP_TITLE].
2559 size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2560 /* rrdtool 'logo' */
2561 water_color = im->graph_col[GRC_FONT];
2562 water_color.alpha = 0.3;
2563 gfx_text(im, im->ximg - 4, 5,
2564 water_color,
2565 im->
2566 text_prop[TEXT_PROP_AXIS].
2567 font, 5.5, im->tabwidth,
2568 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2569 /* graph watermark */
2570 if (im->watermark[0] != '\0') {
2571 gfx_text(im,
2572 im->ximg / 2, im->yimg - 6,
2573 water_color,
2574 im->
2575 text_prop[TEXT_PROP_AXIS].
2576 font, 5.5, im->tabwidth, 0,
2577 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2578 }
2580 /* graph labels */
2581 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2582 for (i = 0; i < im->gdes_c; i++) {
2583 if (im->gdes[i].legend[0] == '\0')
2584 continue;
2585 /* im->gdes[i].leg_y is the bottom of the legend */
2586 X0 = im->gdes[i].leg_x;
2587 Y0 = im->gdes[i].leg_y;
2588 gfx_text(im, X0, Y0,
2589 im->graph_col[GRC_FONT],
2590 im->
2591 text_prop
2592 [TEXT_PROP_LEGEND].font,
2593 im->
2594 text_prop
2595 [TEXT_PROP_LEGEND].size,
2596 im->tabwidth, 0.0,
2597 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2598 /* The legend for GRAPH items starts with "M " to have
2599 enough space for the box */
2600 if (im->gdes[i].gf != GF_PRINT &&
2601 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2602 double boxH, boxV;
2603 double X1, Y1;
2605 boxH = gfx_get_text_width(im, 0,
2606 im->
2607 text_prop
2608 [TEXT_PROP_LEGEND].
2609 font,
2610 im->
2611 text_prop
2612 [TEXT_PROP_LEGEND].
2613 size, im->tabwidth, "o") * 1.2;
2614 boxV = boxH;
2615 /* shift the box up a bit */
2616 Y0 -= boxV * 0.4;
2617 /* make sure transparent colors show up the same way as in the graph */
2618 gfx_new_area(im,
2619 X0, Y0 - boxV,
2620 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2621 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2622 gfx_close_path(im);
2623 gfx_new_area(im, X0, Y0 - boxV, X0,
2624 Y0, X0 + boxH, Y0, im->gdes[i].col);
2625 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2626 gfx_close_path(im);
2627 cairo_save(im->cr);
2628 cairo_new_path(im->cr);
2629 cairo_set_line_width(im->cr, 1.0);
2630 X1 = X0 + boxH;
2631 Y1 = Y0 - boxV;
2632 gfx_line_fit(im, &X0, &Y0);
2633 gfx_line_fit(im, &X1, &Y1);
2634 cairo_move_to(im->cr, X0, Y0);
2635 cairo_line_to(im->cr, X1, Y0);
2636 cairo_line_to(im->cr, X1, Y1);
2637 cairo_line_to(im->cr, X0, Y1);
2638 cairo_close_path(im->cr);
2639 cairo_set_source_rgba(im->cr,
2640 im->
2641 graph_col
2642 [GRC_FRAME].
2643 red,
2644 im->
2645 graph_col
2646 [GRC_FRAME].
2647 green,
2648 im->
2649 graph_col
2650 [GRC_FRAME].
2651 blue, im->graph_col[GRC_FRAME].alpha);
2652 if (im->gdes[i].dash) {
2653 /* make box borders in legend dashed if the graph is dashed */
2654 double dashes[] = {
2655 3.0
2656 };
2657 cairo_set_dash(im->cr, dashes, 1, 0.0);
2658 }
2659 cairo_stroke(im->cr);
2660 cairo_restore(im->cr);
2661 }
2662 }
2663 }
2664 }
2667 /*****************************************************
2668 * lazy check make sure we rely need to create this graph
2669 *****************************************************/
2671 int lazy_check(
2672 image_desc_t *im)
2673 {
2674 FILE *fd = NULL;
2675 int size = 1;
2676 struct stat imgstat;
2678 if (im->lazy == 0)
2679 return 0; /* no lazy option */
2680 if (strlen(im->graphfile) == 0)
2681 return 0; /* inmemory option */
2682 if (stat(im->graphfile, &imgstat) != 0)
2683 return 0; /* can't stat */
2684 /* one pixel in the existing graph is more then what we would
2685 change here ... */
2686 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2687 return 0;
2688 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2689 return 0; /* the file does not exist */
2690 switch (im->imgformat) {
2691 case IF_PNG:
2692 size = PngSize(fd, &(im->ximg), &(im->yimg));
2693 break;
2694 default:
2695 size = 1;
2696 }
2697 fclose(fd);
2698 return size;
2699 }
2702 int graph_size_location(
2703 image_desc_t
2704 *im,
2705 int elements)
2706 {
2707 /* The actual size of the image to draw is determined from
2708 ** several sources. The size given on the command line is
2709 ** the graph area but we need more as we have to draw labels
2710 ** and other things outside the graph area
2711 */
2713 int Xvertical = 0, Ytitle =
2714 0, Xylabel = 0, Xmain = 0, Ymain =
2715 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2717 if (im->extra_flags & ONLY_GRAPH) {
2718 im->xorigin = 0;
2719 im->ximg = im->xsize;
2720 im->yimg = im->ysize;
2721 im->yorigin = im->ysize;
2722 ytr(im, DNAN);
2723 return 0;
2724 }
2726 /** +---+--------------------------------------------+
2727 ** | y |...............graph title..................|
2728 ** | +---+-------------------------------+--------+
2729 ** | a | y | | |
2730 ** | x | | | |
2731 ** | i | a | | pie |
2732 ** | s | x | main graph area | chart |
2733 ** | | i | | area |
2734 ** | t | s | | |
2735 ** | i | | | |
2736 ** | t | l | | |
2737 ** | l | b +-------------------------------+--------+
2738 ** | e | l | x axis labels | |
2739 ** +---+---+-------------------------------+--------+
2740 ** |....................legends.....................|
2741 ** +------------------------------------------------+
2742 ** | watermark |
2743 ** +------------------------------------------------+
2744 */
2746 if (im->ylegend[0] != '\0') {
2747 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2748 }
2750 if (im->title[0] != '\0') {
2751 /* The title is placed "inbetween" two text lines so it
2752 ** automatically has some vertical spacing. The horizontal
2753 ** spacing is added here, on each side.
2754 */
2755 /* if necessary, reduce the font size of the title until it fits the image width */
2756 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2757 }
2759 if (elements) {
2760 if (im->draw_x_grid) {
2761 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2762 }
2763 if (im->draw_y_grid || im->forceleftspace) {
2764 Xylabel =
2765 gfx_get_text_width(im, 0,
2766 im->
2767 text_prop
2768 [TEXT_PROP_AXIS].
2769 font,
2770 im->
2771 text_prop
2772 [TEXT_PROP_AXIS].
2773 size, im->tabwidth, "0") * im->unitslength;
2774 }
2775 }
2777 if (im->extra_flags & FULL_SIZE_MODE) {
2778 /* The actual size of the image to draw has been determined by the user.
2779 ** The graph area is the space remaining after accounting for the legend,
2780 ** the watermark, the pie chart, the axis labels, and the title.
2781 */
2782 im->xorigin = 0;
2783 im->ximg = im->xsize;
2784 im->yimg = im->ysize;
2785 im->yorigin = im->ysize;
2786 Xmain = im->ximg;
2787 Ymain = im->yimg;
2788 im->yorigin += Ytitle;
2789 /* Now calculate the total size. Insert some spacing where
2790 desired. im->xorigin and im->yorigin need to correspond
2791 with the lower left corner of the main graph area or, if
2792 this one is not set, the imaginary box surrounding the
2793 pie chart area. */
2794 /* Initial size calculation for the main graph area */
2795 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2796 if (Xmain)
2797 Xmain -= Xspacing; /* put space between main graph area and right edge */
2798 im->xorigin = Xspacing + Xylabel;
2799 /* the length of the title should not influence with width of the graph
2800 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2801 if (Xvertical) { /* unit description */
2802 Xmain -= Xvertical;
2803 im->xorigin += Xvertical;
2804 }
2805 im->xsize = Xmain;
2806 xtr(im, 0);
2807 /* The vertical size of the image is known in advance. The main graph area
2808 ** (Ymain) and im->yorigin must be set according to the space requirements
2809 ** of the legend and the axis labels.
2810 */
2811 if (im->extra_flags & NOLEGEND) {
2812 /* set dimensions correctly if using full size mode with no legend */
2813 im->yorigin =
2814 im->yimg -
2815 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2816 Ymain = im->yorigin;
2817 } else {
2818 /* Determine where to place the legends onto the image.
2819 ** Set Ymain and adjust im->yorigin to match the space requirements.
2820 */
2821 if (leg_place(im, &Ymain) == -1)
2822 return -1;
2823 }
2826 /* remove title space *or* some padding above the graph from the main graph area */
2827 if (Ytitle) {
2828 Ymain -= Ytitle;
2829 } else {
2830 Ymain -= 1.5 * Yspacing;
2831 }
2833 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2834 if (im->watermark[0] != '\0') {
2835 Ymain -= Ywatermark;
2836 }
2838 im->ysize = Ymain;
2839 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2841 /* The actual size of the image to draw is determined from
2842 ** several sources. The size given on the command line is
2843 ** the graph area but we need more as we have to draw labels
2844 ** and other things outside the graph area.
2845 */
2847 if (im->ylegend[0] != '\0') {
2848 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2849 }
2852 if (im->title[0] != '\0') {
2853 /* The title is placed "inbetween" two text lines so it
2854 ** automatically has some vertical spacing. The horizontal
2855 ** spacing is added here, on each side.
2856 */
2857 /* don't care for the with of the title
2858 Xtitle = gfx_get_text_width(im->canvas, 0,
2859 im->text_prop[TEXT_PROP_TITLE].font,
2860 im->text_prop[TEXT_PROP_TITLE].size,
2861 im->tabwidth,
2862 im->title, 0) + 2*Xspacing; */
2863 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2864 }
2866 if (elements) {
2867 Xmain = im->xsize;
2868 Ymain = im->ysize;
2869 }
2870 /* Now calculate the total size. Insert some spacing where
2871 desired. im->xorigin and im->yorigin need to correspond
2872 with the lower left corner of the main graph area or, if
2873 this one is not set, the imaginary box surrounding the
2874 pie chart area. */
2876 /* The legend width cannot yet be determined, as a result we
2877 ** have problems adjusting the image to it. For now, we just
2878 ** forget about it at all; the legend will have to fit in the
2879 ** size already allocated.
2880 */
2881 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2882 if (Xmain)
2883 im->ximg += Xspacing;
2884 im->xorigin = Xspacing + Xylabel;
2885 /* the length of the title should not influence with width of the graph
2886 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2887 if (Xvertical) { /* unit description */
2888 im->ximg += Xvertical;
2889 im->xorigin += Xvertical;
2890 }
2891 xtr(im, 0);
2892 /* The vertical size is interesting... we need to compare
2893 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2894 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2895 ** in order to start even thinking about Ylegend or Ywatermark.
2896 **
2897 ** Do it in three portions: First calculate the inner part,
2898 ** then do the legend, then adjust the total height of the img,
2899 ** adding space for a watermark if one exists;
2900 */
2901 /* reserve space for main and/or pie */
2902 im->yimg = Ymain + Yxlabel;
2903 im->yorigin = im->yimg - Yxlabel;
2904 /* reserve space for the title *or* some padding above the graph */
2905 if (Ytitle) {
2906 im->yimg += Ytitle;
2907 im->yorigin += Ytitle;
2908 } else {
2909 im->yimg += 1.5 * Yspacing;
2910 im->yorigin += 1.5 * Yspacing;
2911 }
2912 /* reserve space for padding below the graph */
2913 im->yimg += Yspacing;
2914 /* Determine where to place the legends onto the image.
2915 ** Adjust im->yimg to match the space requirements.
2916 */
2917 if (leg_place(im, 0) == -1)
2918 return -1;
2919 if (im->watermark[0] != '\0') {
2920 im->yimg += Ywatermark;
2921 }
2922 }
2924 ytr(im, DNAN);
2925 return 0;
2926 }
2928 static cairo_status_t cairo_output(
2929 void *closure,
2930 const unsigned char
2931 *data,
2932 unsigned int length)
2933 {
2934 image_desc_t *im = closure;
2936 im->rendered_image =
2937 realloc(im->rendered_image, im->rendered_image_size + length);
2938 if (im->rendered_image == NULL)
2939 return CAIRO_STATUS_WRITE_ERROR;
2940 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2941 im->rendered_image_size += length;
2942 return CAIRO_STATUS_SUCCESS;
2943 }
2945 /* draw that picture thing ... */
2946 int graph_paint(
2947 image_desc_t *im)
2948 {
2949 int i, ii;
2950 int lazy = lazy_check(im);
2951 double areazero = 0.0;
2952 graph_desc_t *lastgdes = NULL;
2953 infoval info;
2954 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2956 /* if we are lazy and there is nothing to PRINT ... quit now */
2957 if (lazy && im->prt_c == 0)
2958 return 0;
2959 /* pull the data from the rrd files ... */
2960 if (data_fetch(im) == -1)
2961 return -1;
2962 /* evaluate VDEF and CDEF operations ... */
2963 if (data_calc(im) == -1)
2964 return -1;
2965 /* calculate and PRINT and GPRINT definitions. We have to do it at
2966 * this point because it will affect the length of the legends
2967 * if there are no graph elements we stop here ...
2968 * if we are lazy, try to quit ...
2969 */
2970 i = print_calc(im);
2971 if (i < 0)
2972 return -1;
2973 if ((i == 0) || lazy)
2974 return 0;
2975 /**************************************************************
2976 *** Calculating sizes and locations became a bit confusing ***
2977 *** so I moved this into a separate function. ***
2978 **************************************************************/
2979 if (graph_size_location(im, i) == -1)
2980 return -1;
2982 info.u_cnt = im->xorigin;
2983 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2984 info.u_cnt = im->yorigin - im->ysize;
2985 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2986 info.u_cnt = im->xsize;
2987 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2988 info.u_cnt = im->ysize;
2989 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2990 info.u_cnt = im->ximg;
2991 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2992 info.u_cnt = im->yimg;
2993 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2995 /* get actual drawing data and find min and max values */
2996 if (data_proc(im) == -1)
2997 return -1;
2998 if (!im->logarithmic) {
2999 si_unit(im);
3000 }
3002 /* identify si magnitude Kilo, Mega Giga ? */
3003 if (!im->rigid && !im->logarithmic)
3004 expand_range(im); /* make sure the upper and lower limit are
3005 sensible values */
3007 info.u_val = im->minval;
3008 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3009 info.u_val = im->maxval;
3010 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3012 if (!calc_horizontal_grid(im))
3013 return -1;
3014 /* reset precalc */
3015 ytr(im, DNAN);
3016 /* if (im->gridfit)
3017 apply_gridfit(im); */
3018 /* the actual graph is created by going through the individual
3019 graph elements and then drawing them */
3020 cairo_surface_destroy(im->surface);
3021 switch (im->imgformat) {
3022 case IF_PNG:
3023 im->surface =
3024 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3025 im->ximg * im->zoom,
3026 im->yimg * im->zoom);
3027 break;
3028 case IF_PDF:
3029 im->gridfit = 0;
3030 im->surface = strlen(im->graphfile)
3031 ?
3032 cairo_pdf_surface_create_for_stream
3033 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3034 : cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3035 im->yimg * im->zoom);
3036 break;
3037 case IF_EPS:
3038 im->gridfit = 0;
3039 im->surface = strlen(im->graphfile)
3040 ?
3041 cairo_ps_surface_create_for_stream
3042 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3043 : cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3044 im->yimg * im->zoom);
3045 break;
3046 case IF_SVG:
3047 im->gridfit = 0;
3048 im->surface = strlen(im->graphfile)
3049 ?
3050 cairo_svg_surface_create_for_stream
3051 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3052 : cairo_svg_surface_create(im->
3053 graphfile,
3054 im->
3055 ximg * im->zoom, im->yimg * im->zoom);
3056 cairo_svg_surface_restrict_to_version
3057 (im->surface, CAIRO_SVG_VERSION_1_1);
3058 break;
3059 };
3060 im->cr = cairo_create(im->surface);
3061 cairo_set_antialias(im->cr, im->graph_antialias);
3062 cairo_scale(im->cr, im->zoom, im->zoom);
3063 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3064 gfx_new_area(im, 0, 0, 0, im->yimg,
3065 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3066 gfx_add_point(im, im->ximg, 0);
3067 gfx_close_path(im);
3068 gfx_new_area(im, im->xorigin,
3069 im->yorigin,
3070 im->xorigin +
3071 im->xsize, im->yorigin,
3072 im->xorigin +
3073 im->xsize,
3074 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3075 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3076 gfx_close_path(im);
3077 if (im->minval > 0.0)
3078 areazero = im->minval;
3079 if (im->maxval < 0.0)
3080 areazero = im->maxval;
3081 for (i = 0; i < im->gdes_c; i++) {
3082 switch (im->gdes[i].gf) {
3083 case GF_CDEF:
3084 case GF_VDEF:
3085 case GF_DEF:
3086 case GF_PRINT:
3087 case GF_GPRINT:
3088 case GF_COMMENT:
3089 case GF_TEXTALIGN:
3090 case GF_HRULE:
3091 case GF_VRULE:
3092 case GF_XPORT:
3093 case GF_SHIFT:
3094 break;
3095 case GF_TICK:
3096 for (ii = 0; ii < im->xsize; ii++) {
3097 if (!isnan(im->gdes[i].p_data[ii])
3098 && im->gdes[i].p_data[ii] != 0.0) {
3099 if (im->gdes[i].yrule > 0) {
3100 gfx_line(im,
3101 im->xorigin + ii,
3102 im->yorigin,
3103 im->xorigin + ii,
3104 im->yorigin -
3105 im->gdes[i].yrule *
3106 im->ysize, 1.0, im->gdes[i].col);
3107 } else if (im->gdes[i].yrule < 0) {
3108 gfx_line(im,
3109 im->xorigin + ii,
3110 im->yorigin - im->ysize,
3111 im->xorigin + ii,
3112 im->yorigin - (1 -
3113 im->gdes[i].
3114 yrule) *
3115 im->ysize, 1.0, im->gdes[i].col);
3116 }
3117 }
3118 }
3119 break;
3120 case GF_LINE:
3121 case GF_AREA:
3122 /* fix data points at oo and -oo */
3123 for (ii = 0; ii < im->xsize; ii++) {
3124 if (isinf(im->gdes[i].p_data[ii])) {
3125 if (im->gdes[i].p_data[ii] > 0) {
3126 im->gdes[i].p_data[ii] = im->maxval;
3127 } else {
3128 im->gdes[i].p_data[ii] = im->minval;
3129 }
3131 }
3132 } /* for */
3134 /* *******************************************************
3135 a ___. (a,t)
3136 | | ___
3137 ____| | | |
3138 | |___|
3139 -------|--t-1--t--------------------------------
3141 if we know the value at time t was a then
3142 we draw a square from t-1 to t with the value a.
3144 ********************************************************* */
3145 if (im->gdes[i].col.alpha != 0.0) {
3146 /* GF_LINE and friend */
3147 if (im->gdes[i].gf == GF_LINE) {
3148 double last_y = 0.0;
3149 int draw_on = 0;
3151 cairo_save(im->cr);
3152 cairo_new_path(im->cr);
3153 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3154 if (im->gdes[i].dash) {
3155 cairo_set_dash(im->cr,
3156 im->gdes[i].p_dashes,
3157 im->gdes[i].ndash, im->gdes[i].offset);
3158 }
3160 for (ii = 1; ii < im->xsize; ii++) {
3161 if (isnan(im->gdes[i].p_data[ii])
3162 || (im->slopemode == 1
3163 && isnan(im->gdes[i].p_data[ii - 1]))) {
3164 draw_on = 0;
3165 continue;
3166 }
3167 if (draw_on == 0) {
3168 last_y = ytr(im, im->gdes[i].p_data[ii]);
3169 if (im->slopemode == 0) {
3170 double x = ii - 1 + im->xorigin;
3171 double y = last_y;
3173 gfx_line_fit(im, &x, &y);
3174 cairo_move_to(im->cr, x, y);
3175 x = ii + im->xorigin;
3176 y = last_y;
3177 gfx_line_fit(im, &x, &y);
3178 cairo_line_to(im->cr, x, y);
3179 } else {
3180 double x = ii - 1 + im->xorigin;
3181 double y =
3182 ytr(im, im->gdes[i].p_data[ii - 1]);
3183 gfx_line_fit(im, &x, &y);
3184 cairo_move_to(im->cr, x, y);
3185 x = ii + im->xorigin;
3186 y = last_y;
3187 gfx_line_fit(im, &x, &y);
3188 cairo_line_to(im->cr, x, y);
3189 }
3190 draw_on = 1;
3191 } else {
3192 double x1 = ii + im->xorigin;
3193 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3195 if (im->slopemode == 0
3196 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3197 double x = ii - 1 + im->xorigin;
3198 double y = y1;
3200 gfx_line_fit(im, &x, &y);
3201 cairo_line_to(im->cr, x, y);
3202 };
3203 last_y = y1;
3204 gfx_line_fit(im, &x1, &y1);
3205 cairo_line_to(im->cr, x1, y1);
3206 };
3207 }
3208 cairo_set_source_rgba(im->cr,
3209 im->gdes[i].
3210 col.red,
3211 im->gdes[i].
3212 col.green,
3213 im->gdes[i].
3214 col.blue, im->gdes[i].col.alpha);
3215 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3216 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3217 cairo_stroke(im->cr);
3218 cairo_restore(im->cr);
3219 } else {
3220 int idxI = -1;
3221 double *foreY =
3222 (double *) malloc(sizeof(double) * im->xsize * 2);
3223 double *foreX =
3224 (double *) malloc(sizeof(double) * im->xsize * 2);
3225 double *backY =
3226 (double *) malloc(sizeof(double) * im->xsize * 2);
3227 double *backX =
3228 (double *) malloc(sizeof(double) * im->xsize * 2);
3229 int drawem = 0;
3231 for (ii = 0; ii <= im->xsize; ii++) {
3232 double ybase, ytop;
3234 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3235 int cntI = 1;
3236 int lastI = 0;
3238 while (cntI < idxI
3239 &&
3240 AlmostEqual2sComplement(foreY
3241 [lastI],
3242 foreY[cntI], 4)
3243 &&
3244 AlmostEqual2sComplement(foreY
3245 [lastI],
3246 foreY
3247 [cntI + 1], 4)) {
3248 cntI++;
3249 }
3250 gfx_new_area(im,
3251 backX[0], backY[0],
3252 foreX[0], foreY[0],
3253 foreX[cntI],
3254 foreY[cntI], im->gdes[i].col);
3255 while (cntI < idxI) {
3256 lastI = cntI;
3257 cntI++;
3258 while (cntI < idxI
3259 &&
3260 AlmostEqual2sComplement(foreY
3261 [lastI],
3262 foreY[cntI], 4)
3263 &&
3264 AlmostEqual2sComplement(foreY
3265 [lastI],
3266 foreY
3267 [cntI
3268 + 1], 4)) {
3269 cntI++;
3270 }
3271 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3272 }
3273 gfx_add_point(im, backX[idxI], backY[idxI]);
3274 while (idxI > 1) {
3275 lastI = idxI;
3276 idxI--;
3277 while (idxI > 1
3278 &&
3279 AlmostEqual2sComplement(backY
3280 [lastI],
3281 backY[idxI], 4)
3282 &&
3283 AlmostEqual2sComplement(backY
3284 [lastI],
3285 backY
3286 [idxI
3287 - 1], 4)) {
3288 idxI--;
3289 }
3290 gfx_add_point(im, backX[idxI], backY[idxI]);
3291 }
3292 idxI = -1;
3293 drawem = 0;
3294 gfx_close_path(im);
3295 }
3296 if (drawem != 0) {
3297 drawem = 0;
3298 idxI = -1;
3299 }
3300 if (ii == im->xsize)
3301 break;
3302 if (im->slopemode == 0 && ii == 0) {
3303 continue;
3304 }
3305 if (isnan(im->gdes[i].p_data[ii])) {
3306 drawem = 1;
3307 continue;
3308 }
3309 ytop = ytr(im, im->gdes[i].p_data[ii]);
3310 if (lastgdes && im->gdes[i].stack) {
3311 ybase = ytr(im, lastgdes->p_data[ii]);
3312 } else {
3313 ybase = ytr(im, areazero);
3314 }
3315 if (ybase == ytop) {
3316 drawem = 1;
3317 continue;
3318 }
3320 if (ybase > ytop) {
3321 double extra = ytop;
3323 ytop = ybase;
3324 ybase = extra;
3325 }
3326 if (im->slopemode == 0) {
3327 backY[++idxI] = ybase - 0.2;
3328 backX[idxI] = ii + im->xorigin - 1;
3329 foreY[idxI] = ytop + 0.2;
3330 foreX[idxI] = ii + im->xorigin - 1;
3331 }
3332 backY[++idxI] = ybase - 0.2;
3333 backX[idxI] = ii + im->xorigin;
3334 foreY[idxI] = ytop + 0.2;
3335 foreX[idxI] = ii + im->xorigin;
3336 }
3337 /* close up any remaining area */
3338 free(foreY);
3339 free(foreX);
3340 free(backY);
3341 free(backX);
3342 } /* else GF_LINE */
3343 }
3344 /* if color != 0x0 */
3345 /* make sure we do not run into trouble when stacking on NaN */
3346 for (ii = 0; ii < im->xsize; ii++) {
3347 if (isnan(im->gdes[i].p_data[ii])) {
3348 if (lastgdes && (im->gdes[i].stack)) {
3349 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3350 } else {
3351 im->gdes[i].p_data[ii] = areazero;
3352 }
3353 }
3354 }
3355 lastgdes = &(im->gdes[i]);
3356 break;
3357 case GF_STACK:
3358 rrd_set_error
3359 ("STACK should already be turned into LINE or AREA here");
3360 return -1;
3361 break;
3362 } /* switch */
3363 }
3365 /* grid_paint also does the text */
3366 if (!(im->extra_flags & ONLY_GRAPH))
3367 grid_paint(im);
3368 if (!(im->extra_flags & ONLY_GRAPH))
3369 axis_paint(im);
3370 /* the RULES are the last thing to paint ... */
3371 for (i = 0; i < im->gdes_c; i++) {
3373 switch (im->gdes[i].gf) {
3374 case GF_HRULE:
3375 if (im->gdes[i].yrule >= im->minval
3376 && im->gdes[i].yrule <= im->maxval) {
3377 cairo_save(im->cr);
3378 if (im->gdes[i].dash) {
3379 cairo_set_dash(im->cr,
3380 im->gdes[i].p_dashes,
3381 im->gdes[i].ndash, im->gdes[i].offset);
3382 }
3383 gfx_line(im, im->xorigin,
3384 ytr(im, im->gdes[i].yrule),
3385 im->xorigin + im->xsize,
3386 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3387 cairo_stroke(im->cr);
3388 cairo_restore(im->cr);
3389 }
3390 break;
3391 case GF_VRULE:
3392 if (im->gdes[i].xrule >= im->start
3393 && im->gdes[i].xrule <= im->end) {
3394 cairo_save(im->cr);
3395 if (im->gdes[i].dash) {
3396 cairo_set_dash(im->cr,
3397 im->gdes[i].p_dashes,
3398 im->gdes[i].ndash, im->gdes[i].offset);
3399 }
3400 gfx_line(im,
3401 xtr(im, im->gdes[i].xrule),
3402 im->yorigin, xtr(im,
3403 im->
3404 gdes[i].
3405 xrule),
3406 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3407 cairo_stroke(im->cr);
3408 cairo_restore(im->cr);
3409 }
3410 break;
3411 default:
3412 break;
3413 }
3414 }
3417 switch (im->imgformat) {
3418 case IF_PNG:
3419 {
3420 cairo_status_t status;
3422 status = strlen(im->graphfile) ?
3423 cairo_surface_write_to_png(im->surface, im->graphfile)
3424 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3425 im);
3427 if (status != CAIRO_STATUS_SUCCESS) {
3428 rrd_set_error("Could not save png to '%s'", im->graphfile);
3429 return 1;
3430 }
3431 }
3432 break;
3433 default:
3434 if (strlen(im->graphfile)) {
3435 cairo_show_page(im->cr);
3436 } else {
3437 cairo_surface_finish(im->surface);
3438 }
3439 break;
3440 }
3442 return 0;
3443 }
3446 /*****************************************************
3447 * graph stuff
3448 *****************************************************/
3450 int gdes_alloc(
3451 image_desc_t *im)
3452 {
3454 im->gdes_c++;
3455 if ((im->gdes = (graph_desc_t *)
3456 rrd_realloc(im->gdes, (im->gdes_c)
3457 * sizeof(graph_desc_t))) == NULL) {
3458 rrd_set_error("realloc graph_descs");
3459 return -1;
3460 }
3463 im->gdes[im->gdes_c - 1].step = im->step;
3464 im->gdes[im->gdes_c - 1].step_orig = im->step;
3465 im->gdes[im->gdes_c - 1].stack = 0;
3466 im->gdes[im->gdes_c - 1].linewidth = 0;
3467 im->gdes[im->gdes_c - 1].debug = 0;
3468 im->gdes[im->gdes_c - 1].start = im->start;
3469 im->gdes[im->gdes_c - 1].start_orig = im->start;
3470 im->gdes[im->gdes_c - 1].end = im->end;
3471 im->gdes[im->gdes_c - 1].end_orig = im->end;
3472 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3473 im->gdes[im->gdes_c - 1].data = NULL;
3474 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3475 im->gdes[im->gdes_c - 1].data_first = 0;
3476 im->gdes[im->gdes_c - 1].p_data = NULL;
3477 im->gdes[im->gdes_c - 1].rpnp = NULL;
3478 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3479 im->gdes[im->gdes_c - 1].shift = 0.0;
3480 im->gdes[im->gdes_c - 1].dash = 0;
3481 im->gdes[im->gdes_c - 1].ndash = 0;
3482 im->gdes[im->gdes_c - 1].offset = 0;
3483 im->gdes[im->gdes_c - 1].col.red = 0.0;
3484 im->gdes[im->gdes_c - 1].col.green = 0.0;
3485 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3486 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3487 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3488 im->gdes[im->gdes_c - 1].format[0] = '\0';
3489 im->gdes[im->gdes_c - 1].strftm = 0;
3490 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3491 im->gdes[im->gdes_c - 1].ds = -1;
3492 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3493 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3494 im->gdes[im->gdes_c - 1].yrule = DNAN;
3495 im->gdes[im->gdes_c - 1].xrule = 0;
3496 return 0;
3497 }
3499 /* copies input untill the first unescaped colon is found
3500 or until input ends. backslashes have to be escaped as well */
3501 int scan_for_col(
3502 const char *const input,
3503 int len,
3504 char *const output)
3505 {
3506 int inp, outp = 0;
3508 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3509 if (input[inp] == '\\'
3510 && input[inp + 1] != '\0'
3511 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3512 output[outp++] = input[++inp];
3513 } else {
3514 output[outp++] = input[inp];
3515 }
3516 }
3517 output[outp] = '\0';
3518 return inp;
3519 }
3521 /* Now just a wrapper around rrd_graph_v */
3522 int rrd_graph(
3523 int argc,
3524 char **argv,
3525 char ***prdata,
3526 int *xsize,
3527 int *ysize,
3528 FILE * stream,
3529 double *ymin,
3530 double *ymax)
3531 {
3532 int prlines = 0;
3533 info_t *grinfo = NULL;
3534 info_t *walker;
3536 grinfo = rrd_graph_v(argc, argv);
3537 if (grinfo == NULL)
3538 return -1;
3539 walker = grinfo;
3540 (*prdata) = NULL;
3541 while (walker) {
3542 if (strcmp(walker->key, "image_info") == 0) {
3543 prlines++;
3544 if (((*prdata) =
3545 rrd_realloc((*prdata),
3546 (prlines + 1) * sizeof(char *))) == NULL) {
3547 rrd_set_error("realloc prdata");
3548 return 0;
3549 }
3550 /* imginfo goes to position 0 in the prdata array */
3551 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3552 + 2) * sizeof(char));
3553 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3554 (*prdata)[prlines] = NULL;
3555 }
3556 /* skip anything else */
3557 walker = walker->next;
3558 }
3559 walker = grinfo;
3560 while (walker) {
3561 if (strcmp(walker->key, "image_width") == 0) {
3562 *xsize = walker->value.u_int;
3563 } else if (strcmp(walker->key, "image_height") == 0) {
3564 *ysize = walker->value.u_int;
3565 } else if (strcmp(walker->key, "value_min") == 0) {
3566 *ymin = walker->value.u_val;
3567 } else if (strcmp(walker->key, "value_max") == 0) {
3568 *ymax = walker->value.u_val;
3569 } else if (strncmp(walker->key, "print", 6) == 0) { /* keys are prdate[0..] */
3570 prlines++;
3571 if (((*prdata) =
3572 rrd_realloc((*prdata),
3573 (prlines + 1) * sizeof(char *))) == NULL) {
3574 rrd_set_error("realloc prdata");
3575 return 0;
3576 }
3577 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3578 + 2) * sizeof(char));
3579 (*prdata)[prlines] = NULL;
3580 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3581 } else if (strcmp(walker->key, "image") == 0) {
3582 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3583 (stream ? stream : stdout));
3584 }
3585 /* skip anything else */
3586 walker = walker->next;
3587 }
3588 info_free(grinfo);
3589 return 0;
3590 }
3593 /* Some surgery done on this function, it became ridiculously big.
3594 ** Things moved:
3595 ** - initializing now in rrd_graph_init()
3596 ** - options parsing now in rrd_graph_options()
3597 ** - script parsing now in rrd_graph_script()
3598 */
3599 info_t *rrd_graph_v(
3600 int argc,
3601 char **argv)
3602 {
3603 image_desc_t im;
3604 info_t *grinfo;
3606 rrd_graph_init(&im);
3607 /* a dummy surface so that we can measure text sizes for placements */
3608 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3609 im.cr = cairo_create(im.surface);
3610 rrd_graph_options(argc, argv, &im);
3611 if (rrd_test_error()) {
3612 info_free(im.grinfo);
3613 im_free(&im);
3614 return NULL;
3615 }
3617 if (optind >= argc) {
3618 info_free(im.grinfo);
3619 im_free(&im);
3620 rrd_set_error("missing filename");
3621 return NULL;
3622 }
3624 if (strlen(argv[optind]) >= MAXPATH) {
3625 rrd_set_error("filename (including path) too long");
3626 info_free(im.grinfo);
3627 im_free(&im);
3628 return NULL;
3629 }
3631 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3632 im.graphfile[MAXPATH - 1] = '\0';
3634 if (strcmp(im.graphfile, "-") == 0) {
3635 im.graphfile[0] = '\0';
3636 }
3638 rrd_graph_script(argc, argv, &im, 1);
3639 if (rrd_test_error()) {
3640 info_free(im.grinfo);
3641 im_free(&im);
3642 return NULL;
3643 }
3645 /* Everything is now read and the actual work can start */
3647 if (graph_paint(&im) == -1) {
3648 info_free(im.grinfo);
3649 im_free(&im);
3650 return NULL;
3651 }
3654 /* The image is generated and needs to be output.
3655 ** Also, if needed, print a line with information about the image.
3656 */
3658 if (im.imginfo) {
3659 infoval info;
3661 info.u_str =
3662 sprintf_alloc(im.imginfo,
3663 im.graphfile,
3664 (long) (im.zoom *
3665 im.ximg), (long) (im.zoom * im.yimg));
3666 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3667 free(info.u_str);
3668 }
3669 if (im.rendered_image) {
3670 infoval img;
3672 img.u_blo.size = im.rendered_image_size;
3673 img.u_blo.ptr = im.rendered_image;
3674 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3675 }
3676 grinfo = im.grinfo;
3677 im_free(&im);
3678 return grinfo;
3679 }
3681 void rrd_graph_init(
3682 image_desc_t
3683 *im)
3684 {
3685 unsigned int i;
3687 #ifdef HAVE_TZSET
3688 tzset();
3689 #endif
3690 #ifdef HAVE_SETLOCALE
3691 setlocale(LC_TIME, "");
3692 #ifdef HAVE_MBSTOWCS
3693 setlocale(LC_CTYPE, "");
3694 #endif
3695 #endif
3696 im->base = 1000;
3697 im->cr = NULL;
3698 im->draw_x_grid = 1;
3699 im->draw_y_grid = 1;
3700 im->extra_flags = 0;
3701 im->font_options = cairo_font_options_create();
3702 im->forceleftspace = 0;
3703 im->gdes_c = 0;
3704 im->gdes = NULL;
3705 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3706 im->grid_dash_off = 1;
3707 im->grid_dash_on = 1;
3708 im->gridfit = 1;
3709 im->grinfo = (info_t *) NULL;
3710 im->grinfo_current = (info_t *) NULL;
3711 im->imgformat = IF_PNG;
3712 im->imginfo = NULL;
3713 im->lazy = 0;
3714 im->logarithmic = 0;
3715 im->maxval = DNAN;
3716 im->minval = 0;
3717 im->minval = DNAN;
3718 im->prt_c = 0;
3719 im->rigid = 0;
3720 im->rendered_image_size = 0;
3721 im->rendered_image = NULL;
3722 im->slopemode = 0;
3723 im->step = 0;
3724 im->surface = NULL;
3725 im->symbol = ' ';
3726 im->tabwidth = 40.0;
3727 im->title[0] = '\0';
3728 im->unitsexponent = 9999;
3729 im->unitslength = 6;
3730 im->viewfactor = 1.0;
3731 im->watermark[0] = '\0';
3732 im->ximg = 0;
3733 im->xlab_user.minsec = -1;
3734 im->xorigin = 0;
3735 im->xsize = 400;
3736 im->ygridstep = DNAN;
3737 im->yimg = 0;
3738 im->ylegend[0] = '\0';
3739 im->yorigin = 0;
3740 im->ysize = 100;
3741 im->zoom = 1;
3742 cairo_font_options_set_hint_style
3743 (im->font_options, CAIRO_HINT_STYLE_FULL);
3744 cairo_font_options_set_hint_metrics
3745 (im->font_options, CAIRO_HINT_METRICS_ON);
3746 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3747 for (i = 0; i < DIM(graph_col); i++)
3748 im->graph_col[i] = graph_col[i];
3749 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3750 {
3751 char *windir;
3752 char rrd_win_default_font[1000];
3754 windir = getenv("windir");
3755 /* %windir% is something like D:\windows or C:\winnt */
3756 if (windir != NULL) {
3757 strncpy(rrd_win_default_font, windir, 500);
3758 rrd_win_default_font[500] = '\0';
3759 strcat(rrd_win_default_font, "\\fonts\\");
3760 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3761 for (i = 0; i < DIM(text_prop); i++) {
3762 strncpy(text_prop[i].font,
3763 rrd_win_default_font, sizeof(text_prop[i].font) - 1);
3764 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3765 }
3766 }
3767 }
3768 #endif
3769 {
3770 char *deffont;
3772 deffont = getenv("RRD_DEFAULT_FONT");
3773 if (deffont != NULL) {
3774 for (i = 0; i < DIM(text_prop); i++) {
3775 strncpy(text_prop[i].font, deffont,
3776 sizeof(text_prop[i].font) - 1);
3777 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3778 }
3779 }
3780 }
3781 for (i = 0; i < DIM(text_prop); i++) {
3782 im->text_prop[i].size = text_prop[i].size;
3783 strcpy(im->text_prop[i].font, text_prop[i].font);
3784 }
3785 }
3787 void rrd_graph_options(
3788 int argc,
3789 char *argv[],
3790 image_desc_t
3791 *im)
3792 {
3793 int stroff;
3794 char *parsetime_error = NULL;
3795 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3796 time_t start_tmp = 0, end_tmp = 0;
3797 long long_tmp;
3798 struct rrd_time_value start_tv, end_tv;
3799 long unsigned int color;
3800 char *old_locale = "";
3802 /* defines for long options without a short equivalent. should be bytes,
3803 and may not collide with (the ASCII value of) short options */
3804 #define LONGOPT_UNITS_SI 255
3805 struct option long_options[] = {
3806 {
3807 "start", required_argument, 0, 's'}, {
3808 "end", required_argument, 0,
3809 'e'}, {
3810 "x-grid",
3811 required_argument, 0,
3812 'x'}, {
3813 "y-grid",
3814 required_argument,
3815 0, 'y'}, {
3816 "vertical-label",
3817 required_argument,
3818 0,
3819 'v'}, {
3820 "width",
3821 required_argument,
3822 0,
3823 'w'},
3824 {
3825 "height", required_argument, 0, 'h'}, {
3826 "full-size-mode", no_argument,
3827 0, 'D'}, {
3828 "interlaced",
3829 no_argument, 0,
3830 'i'}, {
3831 "upper-limit",
3832 required_argument,
3833 0,
3834 'u'}, {
3835 "lower-limit",
3836 required_argument,
3837 0,
3838 'l'}, {
3839 "rigid",
3840 no_argument,
3841 0,
3842 'r'},
3843 {
3844 "base", required_argument, 0, 'b'}, {
3845 "logarithmic", no_argument, 0,
3846 'o'}, {
3847 "color",
3848 required_argument, 0,
3849 'c'}, {
3850 "font",
3851 required_argument,
3852 0, 'n'}, {
3853 "title",
3854 required_argument,
3855 0, 't'},
3856 {
3857 "imginfo", required_argument, 0, 'f'}, {
3858 "imgformat",
3859 required_argument, 0, 'a'}, {
3860 "lazy",
3861 no_argument,
3862 0,
3863 'z'},
3864 {
3865 "zoom", required_argument, 0, 'm'}, {
3866 "no-legend", no_argument, 0,
3867 'g'}, {
3868 "force-rules-legend",
3869 no_argument,
3870 0, 'F'}, {
3871 "only-graph",
3872 no_argument, 0,
3873 'j'}, {
3874 "alt-y-grid",
3875 no_argument,
3876 0, 'Y'},
3877 {
3878 "no-minor", no_argument, 0, 'I'}, {
3879 "slope-mode", no_argument, 0,
3880 'E'}, {
3881 "alt-autoscale",
3882 no_argument, 0, 'A'}, {
3883 "alt-autoscale-min",
3884 no_argument,
3885 0,
3886 'J'}, {
3887 "alt-autoscale-max",
3888 no_argument,
3889 0,
3890 'M'}, {
3891 "no-gridfit",
3892 no_argument,
3893 0,
3894 'N'},
3895 {
3896 "units-exponent", required_argument,
3897 0, 'X'}, {
3898 "units-length", required_argument,
3899 0, 'L'}, {
3900 "units", required_argument, 0,
3901 LONGOPT_UNITS_SI}, {
3902 "step", required_argument, 0,
3903 'S'}, {
3904 "tabwidth",
3905 required_argument, 0,
3906 'T'}, {
3907 "font-render-mode",
3908 required_argument,
3909 0, 'R'}, {
3910 "graph-render-mode",
3911 required_argument,
3912 0,
3913 'G'},
3914 {
3915 "font-smoothing-threshold",
3916 required_argument, 0, 'B'}, {
3917 "watermark", required_argument, 0, 'W'}, {
3918 "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 */
3919 {
3920 0, 0, 0, 0}
3921 };
3922 optind = 0;
3923 opterr = 0; /* initialize getopt */
3924 parsetime("end-24h", &start_tv);
3925 parsetime("now", &end_tv);
3926 while (1) {
3927 int option_index = 0;
3928 int opt;
3929 int col_start, col_end;
3931 opt = getopt_long(argc, argv,
3932 "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",
3933 long_options, &option_index);
3934 if (opt == EOF)
3935 break;
3936 switch (opt) {
3937 case 'I':
3938 im->extra_flags |= NOMINOR;
3939 break;
3940 case 'Y':
3941 im->extra_flags |= ALTYGRID;
3942 break;
3943 case 'A':
3944 im->extra_flags |= ALTAUTOSCALE;
3945 break;
3946 case 'J':
3947 im->extra_flags |= ALTAUTOSCALE_MIN;
3948 break;
3949 case 'M':
3950 im->extra_flags |= ALTAUTOSCALE_MAX;
3951 break;
3952 case 'j':
3953 im->extra_flags |= ONLY_GRAPH;
3954 break;
3955 case 'g':
3956 im->extra_flags |= NOLEGEND;
3957 break;
3958 case 'F':
3959 im->extra_flags |= FORCE_RULES_LEGEND;
3960 break;
3961 case LONGOPT_UNITS_SI:
3962 if (im->extra_flags & FORCE_UNITS) {
3963 rrd_set_error("--units can only be used once!");
3964 setlocale(LC_NUMERIC, old_locale);
3965 return;
3966 }
3967 if (strcmp(optarg, "si") == 0)
3968 im->extra_flags |= FORCE_UNITS_SI;
3969 else {
3970 rrd_set_error("invalid argument for --units: %s", optarg);
3971 return;
3972 }
3973 break;
3974 case 'X':
3975 im->unitsexponent = atoi(optarg);
3976 break;
3977 case 'L':
3978 im->unitslength = atoi(optarg);
3979 im->forceleftspace = 1;
3980 break;
3981 case 'T':
3982 old_locale = setlocale(LC_NUMERIC, "C");
3983 im->tabwidth = atof(optarg);
3984 setlocale(LC_NUMERIC, old_locale);
3985 break;
3986 case 'S':
3987 old_locale = setlocale(LC_NUMERIC, "C");
3988 im->step = atoi(optarg);
3989 setlocale(LC_NUMERIC, old_locale);
3990 break;
3991 case 'N':
3992 im->gridfit = 0;
3993 break;
3994 case 's':
3995 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3996 rrd_set_error("start time: %s", parsetime_error);
3997 return;
3998 }
3999 break;
4000 case 'e':
4001 if ((parsetime_error = parsetime(optarg, &end_tv))) {
4002 rrd_set_error("end time: %s", parsetime_error);
4003 return;
4004 }
4005 break;
4006 case 'x':
4007 if (strcmp(optarg, "none") == 0) {
4008 im->draw_x_grid = 0;
4009 break;
4010 };
4011 if (sscanf(optarg,
4012 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4013 scan_gtm,
4014 &im->xlab_user.gridst,
4015 scan_mtm,
4016 &im->xlab_user.mgridst,
4017 scan_ltm,
4018 &im->xlab_user.labst,
4019 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4020 strncpy(im->xlab_form, optarg + stroff,
4021 sizeof(im->xlab_form) - 1);
4022 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4023 if ((int)
4024 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4025 rrd_set_error("unknown keyword %s", scan_gtm);
4026 return;
4027 } else if ((int)
4028 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4029 == -1) {
4030 rrd_set_error("unknown keyword %s", scan_mtm);
4031 return;
4032 } else if ((int)
4033 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4034 rrd_set_error("unknown keyword %s", scan_ltm);
4035 return;
4036 }
4037 im->xlab_user.minsec = 1;
4038 im->xlab_user.stst = im->xlab_form;
4039 } else {
4040 rrd_set_error("invalid x-grid format");
4041 return;
4042 }
4043 break;
4044 case 'y':
4046 if (strcmp(optarg, "none") == 0) {
4047 im->draw_y_grid = 0;
4048 break;
4049 };
4050 old_locale = setlocale(LC_NUMERIC, "C");
4051 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4052 setlocale(LC_NUMERIC, old_locale);
4053 if (im->ygridstep <= 0) {
4054 rrd_set_error("grid step must be > 0");
4055 return;
4056 } else if (im->ylabfact < 1) {
4057 rrd_set_error("label factor must be > 0");
4058 return;
4059 }
4060 } else {
4061 setlocale(LC_NUMERIC, old_locale);
4062 rrd_set_error("invalid y-grid format");
4063 return;
4064 }
4065 break;
4066 case 'v':
4067 strncpy(im->ylegend, optarg, 150);
4068 im->ylegend[150] = '\0';
4069 break;
4070 case 'u':
4071 old_locale = setlocale(LC_NUMERIC, "C");
4072 im->maxval = atof(optarg);
4073 setlocale(LC_NUMERIC, old_locale);
4074 break;
4075 case 'l':
4076 old_locale = setlocale(LC_NUMERIC, "C");
4077 im->minval = atof(optarg);
4078 setlocale(LC_NUMERIC, old_locale);
4079 break;
4080 case 'b':
4081 im->base = atol(optarg);
4082 if (im->base != 1024 && im->base != 1000) {
4083 rrd_set_error
4084 ("the only sensible value for base apart from 1000 is 1024");
4085 return;
4086 }
4087 break;
4088 case 'w':
4089 long_tmp = atol(optarg);
4090 if (long_tmp < 10) {
4091 rrd_set_error("width below 10 pixels");
4092 return;
4093 }
4094 im->xsize = long_tmp;
4095 break;
4096 case 'h':
4097 long_tmp = atol(optarg);
4098 if (long_tmp < 10) {
4099 rrd_set_error("height below 10 pixels");
4100 return;
4101 }
4102 im->ysize = long_tmp;
4103 break;
4104 case 'D':
4105 im->extra_flags |= FULL_SIZE_MODE;
4106 break;
4107 case 'i':
4108 /* interlaced png not supported at the moment */
4109 break;
4110 case 'r':
4111 im->rigid = 1;
4112 break;
4113 case 'f':
4114 im->imginfo = optarg;
4115 break;
4116 case 'a':
4117 if ((int)
4118 (im->imgformat = if_conv(optarg)) == -1) {
4119 rrd_set_error("unsupported graphics format '%s'", optarg);
4120 return;
4121 }
4122 break;
4123 case 'z':
4124 im->lazy = 1;
4125 break;
4126 case 'E':
4127 im->slopemode = 1;
4128 break;
4129 case 'o':
4130 im->logarithmic = 1;
4131 break;
4132 case 'c':
4133 if (sscanf(optarg,
4134 "%10[A-Z]#%n%8lx%n",
4135 col_nam, &col_start, &color, &col_end) == 2) {
4136 int ci;
4137 int col_len = col_end - col_start;
4139 switch (col_len) {
4140 case 3:
4141 color =
4142 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4143 0x011000) |
4144 ((color & 0x00F)
4145 * 0x001100)
4146 | 0x000000FF);
4147 break;
4148 case 4:
4149 color =
4150 (((color & 0xF000) *
4151 0x11000) | ((color & 0x0F00) *
4152 0x01100) | ((color &
4153 0x00F0) *
4154 0x00110) |
4155 ((color & 0x000F) * 0x00011)
4156 );
4157 break;
4158 case 6:
4159 color = (color << 8) + 0xff /* shift left by 8 */ ;
4160 break;
4161 case 8:
4162 break;
4163 default:
4164 rrd_set_error("the color format is #RRGGBB[AA]");
4165 return;
4166 }
4167 if ((ci = grc_conv(col_nam)) != -1) {
4168 im->graph_col[ci] = gfx_hex_to_col(color);
4169 } else {
4170 rrd_set_error("invalid color name '%s'", col_nam);
4171 return;
4172 }
4173 } else {
4174 rrd_set_error("invalid color def format");
4175 return;
4176 }
4177 break;
4178 case 'n':{
4179 char prop[15];
4180 double size = 1;
4181 int end;
4183 old_locale = setlocale(LC_NUMERIC, "C");
4184 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4185 int sindex, propidx;
4187 setlocale(LC_NUMERIC, old_locale);
4188 if ((sindex = text_prop_conv(prop)) != -1) {
4189 for (propidx = sindex;
4190 propidx < TEXT_PROP_LAST; propidx++) {
4191 if (size > 0) {
4192 im->text_prop[propidx].size = size;
4193 }
4194 if ((int) strlen(prop) > end) {
4195 if (prop[end] == ':') {
4196 strncpy(im->text_prop[propidx].font,
4197 prop + end + 1, 255);
4198 im->text_prop[propidx].font[255] = '\0';
4199 } else {
4200 rrd_set_error
4201 ("expected after font size in '%s'",
4202 prop);
4203 return;
4204 }
4205 }
4206 if (propidx == sindex && sindex != 0)
4207 break;
4208 }
4209 } else {
4210 rrd_set_error("invalid fonttag '%s'", prop);
4211 return;
4212 }
4213 } else {
4214 setlocale(LC_NUMERIC, old_locale);
4215 rrd_set_error("invalid text property format");
4216 return;
4217 }
4218 break;
4219 }
4220 case 'm':
4221 old_locale = setlocale(LC_NUMERIC, "C");
4222 im->zoom = atof(optarg);
4223 setlocale(LC_NUMERIC, old_locale);
4224 if (im->zoom <= 0.0) {
4225 rrd_set_error("zoom factor must be > 0");
4226 return;
4227 }
4228 break;
4229 case 't':
4230 strncpy(im->title, optarg, 150);
4231 im->title[150] = '\0';
4232 break;
4233 case 'R':
4234 if (strcmp(optarg, "normal") == 0) {
4235 cairo_font_options_set_antialias
4236 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4237 cairo_font_options_set_hint_style
4238 (im->font_options, CAIRO_HINT_STYLE_FULL);
4239 } else if (strcmp(optarg, "light") == 0) {
4240 cairo_font_options_set_antialias
4241 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4242 cairo_font_options_set_hint_style
4243 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4244 } else if (strcmp(optarg, "mono") == 0) {
4245 cairo_font_options_set_antialias
4246 (im->font_options, CAIRO_ANTIALIAS_NONE);
4247 cairo_font_options_set_hint_style
4248 (im->font_options, CAIRO_HINT_STYLE_FULL);
4249 } else {
4250 rrd_set_error("unknown font-render-mode '%s'", optarg);
4251 return;
4252 }
4253 break;
4254 case 'G':
4255 if (strcmp(optarg, "normal") == 0)
4256 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4257 else if (strcmp(optarg, "mono") == 0)
4258 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4259 else {
4260 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4261 return;
4262 }
4263 break;
4264 case 'B':
4265 /* not supported curently */
4266 break;
4267 case 'W':
4268 strncpy(im->watermark, optarg, 100);
4269 im->watermark[99] = '\0';
4270 break;
4271 case '?':
4272 if (optopt != 0)
4273 rrd_set_error("unknown option '%c'", optopt);
4274 else
4275 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4276 return;
4277 }
4278 }
4280 if (im->logarithmic == 1 && im->minval <= 0) {
4281 rrd_set_error
4282 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4283 return;
4284 }
4286 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4287 /* error string is set in parsetime.c */
4288 return;
4289 }
4291 if (start_tmp < 3600 * 24 * 365 * 10) {
4292 rrd_set_error
4293 ("the first entry to fetch should be after 1980 (%ld)",
4294 start_tmp);
4295 return;
4296 }
4298 if (end_tmp < start_tmp) {
4299 rrd_set_error
4300 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4301 return;
4302 }
4304 im->start = start_tmp;
4305 im->end = end_tmp;
4306 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4307 }
4309 int rrd_graph_color(
4310 image_desc_t
4311 *im,
4312 char *var,
4313 char *err,
4314 int optional)
4315 {
4316 char *color;
4317 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4319 color = strstr(var, "#");
4320 if (color == NULL) {
4321 if (optional == 0) {
4322 rrd_set_error("Found no color in %s", err);
4323 return 0;
4324 }
4325 return 0;
4326 } else {
4327 int n = 0;
4328 char *rest;
4329 long unsigned int col;
4331 rest = strstr(color, ":");
4332 if (rest != NULL)
4333 n = rest - color;
4334 else
4335 n = strlen(color);
4336 switch (n) {
4337 case 7:
4338 sscanf(color, "#%6lx%n", &col, &n);
4339 col = (col << 8) + 0xff /* shift left by 8 */ ;
4340 if (n != 7)
4341 rrd_set_error("Color problem in %s", err);
4342 break;
4343 case 9:
4344 sscanf(color, "#%8lx%n", &col, &n);
4345 if (n == 9)
4346 break;
4347 default:
4348 rrd_set_error("Color problem in %s", err);
4349 }
4350 if (rrd_test_error())
4351 return 0;
4352 gdp->col = gfx_hex_to_col(col);
4353 return n;
4354 }
4355 }
4358 int bad_format(
4359 char *fmt)
4360 {
4361 char *ptr;
4362 int n = 0;
4364 ptr = fmt;
4365 while (*ptr != '\0')
4366 if (*ptr++ == '%') {
4368 /* line cannot end with percent char */
4369 if (*ptr == '\0')
4370 return 1;
4371 /* '%s', '%S' and '%%' are allowed */
4372 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4373 ptr++;
4374 /* %c is allowed (but use only with vdef!) */
4375 else if (*ptr == 'c') {
4376 ptr++;
4377 n = 1;
4378 }
4380 /* or else '% 6.2lf' and such are allowed */
4381 else {
4382 /* optional padding character */
4383 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4384 ptr++;
4385 /* This should take care of 'm.n' with all three optional */
4386 while (*ptr >= '0' && *ptr <= '9')
4387 ptr++;
4388 if (*ptr == '.')
4389 ptr++;
4390 while (*ptr >= '0' && *ptr <= '9')
4391 ptr++;
4392 /* Either 'le', 'lf' or 'lg' must follow here */
4393 if (*ptr++ != 'l')
4394 return 1;
4395 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4396 ptr++;
4397 else
4398 return 1;
4399 n++;
4400 }
4401 }
4403 return (n != 1);
4404 }
4407 int vdef_parse(
4408 struct graph_desc_t
4409 *gdes,
4410 const char *const str)
4411 {
4412 /* A VDEF currently is either "func" or "param,func"
4413 * so the parsing is rather simple. Change if needed.
4414 */
4415 double param;
4416 char func[30];
4417 int n;
4418 char *old_locale;
4420 n = 0;
4421 old_locale = setlocale(LC_NUMERIC, "C");
4422 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4423 setlocale(LC_NUMERIC, old_locale);
4424 if (n == (int) strlen(str)) { /* matched */
4425 ;
4426 } else {
4427 n = 0;
4428 sscanf(str, "%29[A-Z]%n", func, &n);
4429 if (n == (int) strlen(str)) { /* matched */
4430 param = DNAN;
4431 } else {
4432 rrd_set_error
4433 ("Unknown function string '%s' in VDEF '%s'",
4434 str, gdes->vname);
4435 return -1;
4436 }
4437 }
4438 if (!strcmp("PERCENT", func))
4439 gdes->vf.op = VDEF_PERCENT;
4440 else if (!strcmp("MAXIMUM", func))
4441 gdes->vf.op = VDEF_MAXIMUM;
4442 else if (!strcmp("AVERAGE", func))
4443 gdes->vf.op = VDEF_AVERAGE;
4444 else if (!strcmp("STDEV", func))
4445 gdes->vf.op = VDEF_STDEV;
4446 else if (!strcmp("MINIMUM", func))
4447 gdes->vf.op = VDEF_MINIMUM;
4448 else if (!strcmp("TOTAL", func))
4449 gdes->vf.op = VDEF_TOTAL;
4450 else if (!strcmp("FIRST", func))
4451 gdes->vf.op = VDEF_FIRST;
4452 else if (!strcmp("LAST", func))
4453 gdes->vf.op = VDEF_LAST;
4454 else if (!strcmp("LSLSLOPE", func))
4455 gdes->vf.op = VDEF_LSLSLOPE;
4456 else if (!strcmp("LSLINT", func))
4457 gdes->vf.op = VDEF_LSLINT;
4458 else if (!strcmp("LSLCORREL", func))
4459 gdes->vf.op = VDEF_LSLCORREL;
4460 else {
4461 rrd_set_error
4462 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4463 return -1;
4464 };
4465 switch (gdes->vf.op) {
4466 case VDEF_PERCENT:
4467 if (isnan(param)) { /* no parameter given */
4468 rrd_set_error
4469 ("Function '%s' needs parameter in VDEF '%s'\n",
4470 func, gdes->vname);
4471 return -1;
4472 };
4473 if (param >= 0.0 && param <= 100.0) {
4474 gdes->vf.param = param;
4475 gdes->vf.val = DNAN; /* undefined */
4476 gdes->vf.when = 0; /* undefined */
4477 } else {
4478 rrd_set_error
4479 ("Parameter '%f' out of range in VDEF '%s'\n",
4480 param, gdes->vname);
4481 return -1;
4482 };
4483 break;
4484 case VDEF_MAXIMUM:
4485 case VDEF_AVERAGE:
4486 case VDEF_STDEV:
4487 case VDEF_MINIMUM:
4488 case VDEF_TOTAL:
4489 case VDEF_FIRST:
4490 case VDEF_LAST:
4491 case VDEF_LSLSLOPE:
4492 case VDEF_LSLINT:
4493 case VDEF_LSLCORREL:
4494 if (isnan(param)) {
4495 gdes->vf.param = DNAN;
4496 gdes->vf.val = DNAN;
4497 gdes->vf.when = 0;
4498 } else {
4499 rrd_set_error
4500 ("Function '%s' needs no parameter in VDEF '%s'\n",
4501 func, gdes->vname);
4502 return -1;
4503 };
4504 break;
4505 };
4506 return 0;
4507 }
4510 int vdef_calc(
4511 image_desc_t *im,
4512 int gdi)
4513 {
4514 graph_desc_t *src, *dst;
4515 rrd_value_t *data;
4516 long step, steps;
4518 dst = &im->gdes[gdi];
4519 src = &im->gdes[dst->vidx];
4520 data = src->data + src->ds;
4521 steps = (src->end - src->start) / src->step;
4522 #if 0
4523 printf
4524 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4525 src->start, src->end, steps);
4526 #endif
4527 switch (dst->vf.op) {
4528 case VDEF_PERCENT:{
4529 rrd_value_t *array;
4530 int field;
4531 if ((array = malloc(steps * sizeof(double))) == NULL) {
4532 rrd_set_error("malloc VDEV_PERCENT");
4533 return -1;
4534 }
4535 for (step = 0; step < steps; step++) {
4536 array[step] = data[step * src->ds_cnt];
4537 }
4538 qsort(array, step, sizeof(double), vdef_percent_compar);
4539 field = (steps - 1) * dst->vf.param / 100;
4540 dst->vf.val = array[field];
4541 dst->vf.when = 0; /* no time component */
4542 free(array);
4543 #if 0
4544 for (step = 0; step < steps; step++)
4545 printf("DEBUG: %3li:%10.2f %c\n",
4546 step, array[step], step == field ? '*' : ' ');
4547 #endif
4548 }
4549 break;
4550 case VDEF_MAXIMUM:
4551 step = 0;
4552 while (step != steps && isnan(data[step * src->ds_cnt]))
4553 step++;
4554 if (step == steps) {
4555 dst->vf.val = DNAN;
4556 dst->vf.when = 0;
4557 } else {
4558 dst->vf.val = data[step * src->ds_cnt];
4559 dst->vf.when = src->start + (step + 1) * src->step;
4560 }
4561 while (step != steps) {
4562 if (finite(data[step * src->ds_cnt])) {
4563 if (data[step * src->ds_cnt] > dst->vf.val) {
4564 dst->vf.val = data[step * src->ds_cnt];
4565 dst->vf.when = src->start + (step + 1) * src->step;
4566 }
4567 }
4568 step++;
4569 }
4570 break;
4571 case VDEF_TOTAL:
4572 case VDEF_STDEV:
4573 case VDEF_AVERAGE:{
4574 int cnt = 0;
4575 double sum = 0.0;
4576 double average = 0.0;
4578 for (step = 0; step < steps; step++) {
4579 if (finite(data[step * src->ds_cnt])) {
4580 sum += data[step * src->ds_cnt];
4581 cnt++;
4582 };
4583 }
4584 if (cnt) {
4585 if (dst->vf.op == VDEF_TOTAL) {
4586 dst->vf.val = sum * src->step;
4587 dst->vf.when = 0; /* no time component */
4588 } else if (dst->vf.op == VDEF_AVERAGE) {
4589 dst->vf.val = sum / cnt;
4590 dst->vf.when = 0; /* no time component */
4591 } else {
4592 average = sum / cnt;
4593 sum = 0.0;
4594 for (step = 0; step < steps; step++) {
4595 if (finite(data[step * src->ds_cnt])) {
4596 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4597 };
4598 }
4599 dst->vf.val = pow(sum / cnt, 0.5);
4600 dst->vf.when = 0; /* no time component */
4601 };
4602 } else {
4603 dst->vf.val = DNAN;
4604 dst->vf.when = 0;
4605 }
4606 }
4607 break;
4608 case VDEF_MINIMUM:
4609 step = 0;
4610 while (step != steps && isnan(data[step * src->ds_cnt]))
4611 step++;
4612 if (step == steps) {
4613 dst->vf.val = DNAN;
4614 dst->vf.when = 0;
4615 } else {
4616 dst->vf.val = data[step * src->ds_cnt];
4617 dst->vf.when = src->start + (step + 1) * src->step;
4618 }
4619 while (step != steps) {
4620 if (finite(data[step * src->ds_cnt])) {
4621 if (data[step * src->ds_cnt] < dst->vf.val) {
4622 dst->vf.val = data[step * src->ds_cnt];
4623 dst->vf.when = src->start + (step + 1) * src->step;
4624 }
4625 }
4626 step++;
4627 }
4628 break;
4629 case VDEF_FIRST:
4630 /* The time value returned here is one step before the
4631 * actual time value. This is the start of the first
4632 * non-NaN interval.
4633 */
4634 step = 0;
4635 while (step != steps && isnan(data[step * src->ds_cnt]))
4636 step++;
4637 if (step == steps) { /* all entries were NaN */
4638 dst->vf.val = DNAN;
4639 dst->vf.when = 0;
4640 } else {
4641 dst->vf.val = data[step * src->ds_cnt];
4642 dst->vf.when = src->start + step * src->step;
4643 }
4644 break;
4645 case VDEF_LAST:
4646 /* The time value returned here is the
4647 * actual time value. This is the end of the last
4648 * non-NaN interval.
4649 */
4650 step = steps - 1;
4651 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4652 step--;
4653 if (step < 0) { /* all entries were NaN */
4654 dst->vf.val = DNAN;
4655 dst->vf.when = 0;
4656 } else {
4657 dst->vf.val = data[step * src->ds_cnt];
4658 dst->vf.when = src->start + (step + 1) * src->step;
4659 }
4660 break;
4661 case VDEF_LSLSLOPE:
4662 case VDEF_LSLINT:
4663 case VDEF_LSLCORREL:{
4664 /* Bestfit line by linear least squares method */
4666 int cnt = 0;
4667 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4669 SUMx = 0;
4670 SUMy = 0;
4671 SUMxy = 0;
4672 SUMxx = 0;
4673 SUMyy = 0;
4674 for (step = 0; step < steps; step++) {
4675 if (finite(data[step * src->ds_cnt])) {
4676 cnt++;
4677 SUMx += step;
4678 SUMxx += step * step;
4679 SUMxy += step * data[step * src->ds_cnt];
4680 SUMy += data[step * src->ds_cnt];
4681 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4682 };
4683 }
4685 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4686 y_intercept = (SUMy - slope * SUMx) / cnt;
4687 correl =
4688 (SUMxy -
4689 (SUMx * SUMy) / cnt) /
4690 sqrt((SUMxx -
4691 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4692 if (cnt) {
4693 if (dst->vf.op == VDEF_LSLSLOPE) {
4694 dst->vf.val = slope;
4695 dst->vf.when = 0;
4696 } else if (dst->vf.op == VDEF_LSLINT) {
4697 dst->vf.val = y_intercept;
4698 dst->vf.when = 0;
4699 } else if (dst->vf.op == VDEF_LSLCORREL) {
4700 dst->vf.val = correl;
4701 dst->vf.when = 0;
4702 };
4703 } else {
4704 dst->vf.val = DNAN;
4705 dst->vf.when = 0;
4706 }
4707 }
4708 break;
4709 }
4710 return 0;
4711 }
4713 /* NaN < -INF < finite_values < INF */
4714 int vdef_percent_compar(
4715 const void
4716 *a,
4717 const void
4718 *b)
4719 {
4720 /* Equality is not returned; this doesn't hurt except
4721 * (maybe) for a little performance.
4722 */
4724 /* First catch NaN values. They are smallest */
4725 if (isnan(*(double *) a))
4726 return -1;
4727 if (isnan(*(double *) b))
4728 return 1;
4729 /* NaN doesn't reach this part so INF and -INF are extremes.
4730 * The sign from isinf() is compatible with the sign we return
4731 */
4732 if (isinf(*(double *) a))
4733 return isinf(*(double *) a);
4734 if (isinf(*(double *) b))
4735 return isinf(*(double *) b);
4736 /* If we reach this, both values must be finite */
4737 if (*(double *) a < *(double *) b)
4738 return -1;
4739 else
4740 return 1;
4741 }
4743 void grinfo_push(
4744 image_desc_t *im,
4745 char *key,
4746 enum info_type type,
4747 infoval value)
4748 {
4749 im->grinfo_current = info_push(im->grinfo_current, key, type, value);
4750 if (im->grinfo == NULL) {
4751 im->grinfo = im->grinfo_current;
4752 }
4753 }