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