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