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