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