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