2d1785f40d8102f829aa8204dd7a82bb985812c7
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_DEVSEASONAL:
721 case CF_DEVPREDICT:
722 case CF_SEASONAL:
723 case CF_AVERAGE:
724 newval += srcptr[i * (*ds_cnt) + col];
725 break;
726 case CF_MINIMUM:
727 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
728 break;
729 case CF_FAILURES:
730 /* an interval contains a failure if any subintervals contained a failure */
731 case CF_MAXIMUM:
732 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
733 break;
734 case CF_LAST:
735 newval = srcptr[i * (*ds_cnt) + col];
736 break;
737 }
738 }
739 }
740 if (validval == 0) {
741 newval = DNAN;
742 } else {
743 switch (cf) {
744 case CF_HWPREDICT:
745 case CF_DEVSEASONAL:
746 case CF_DEVPREDICT:
747 case CF_SEASONAL:
748 case CF_AVERAGE:
749 newval /= validval;
750 break;
751 case CF_MINIMUM:
752 case CF_FAILURES:
753 case CF_MAXIMUM:
754 case CF_LAST:
755 break;
756 }
757 }
758 *dstptr++ = newval;
759 }
760 srcptr += (*ds_cnt) * reduce_factor;
761 row_cnt -= reduce_factor;
762 }
763 /* If we had to alter the endtime, we didn't have enough
764 ** source rows to fill the last row. Fill it with NaN.
765 */
766 if (end_offset)
767 for (col = 0; col < (*ds_cnt); col++)
768 *dstptr++ = DNAN;
769 #ifdef DEBUG_REDUCE
770 row_cnt = ((*end) - (*start)) / *step;
771 srcptr = *data;
772 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
773 row_cnt, *start, *end, *step);
774 for (col = 0; col < row_cnt; col++) {
775 printf("time %10lu: ", *start + (col + 1) * (*step));
776 for (i = 0; i < *ds_cnt; i++)
777 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
778 printf("\n");
779 }
780 #endif
781 }
784 /* get the data required for the graphs from the
785 relevant rrds ... */
787 int data_fetch(
788 image_desc_t *im)
789 {
790 int i, ii;
791 int skip;
793 /* pull the data from the rrd files ... */
794 for (i = 0; i < (int) im->gdes_c; i++) {
795 /* only GF_DEF elements fetch data */
796 if (im->gdes[i].gf != GF_DEF)
797 continue;
799 skip = 0;
800 /* do we have it already ? */
801 for (ii = 0; ii < i; ii++) {
802 if (im->gdes[ii].gf != GF_DEF)
803 continue;
804 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
805 && (im->gdes[i].cf == im->gdes[ii].cf)
806 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
807 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
808 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
809 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
810 /* OK, the data is already there.
811 ** Just copy the header portion
812 */
813 im->gdes[i].start = im->gdes[ii].start;
814 im->gdes[i].end = im->gdes[ii].end;
815 im->gdes[i].step = im->gdes[ii].step;
816 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
817 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
818 im->gdes[i].data = im->gdes[ii].data;
819 im->gdes[i].data_first = 0;
820 skip = 1;
821 }
822 if (skip)
823 break;
824 }
825 if (!skip) {
826 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
828 if ((rrd_fetch_fn(im->gdes[i].rrd,
829 im->gdes[i].cf,
830 &im->gdes[i].start,
831 &im->gdes[i].end,
832 &ft_step,
833 &im->gdes[i].ds_cnt,
834 &im->gdes[i].ds_namv,
835 &im->gdes[i].data)) == -1) {
836 return -1;
837 }
838 im->gdes[i].data_first = 1;
840 if (ft_step < im->gdes[i].step) {
841 reduce_data(im->gdes[i].cf_reduce,
842 ft_step,
843 &im->gdes[i].start,
844 &im->gdes[i].end,
845 &im->gdes[i].step,
846 &im->gdes[i].ds_cnt, &im->gdes[i].data);
847 } else {
848 im->gdes[i].step = ft_step;
849 }
850 }
852 /* lets see if the required data source is really there */
853 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
854 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
855 im->gdes[i].ds = ii;
856 }
857 }
858 if (im->gdes[i].ds == -1) {
859 rrd_set_error("No DS called '%s' in '%s'",
860 im->gdes[i].ds_nam, im->gdes[i].rrd);
861 return -1;
862 }
864 }
865 return 0;
866 }
868 /* evaluate the expressions in the CDEF functions */
870 /*************************************************************
871 * CDEF stuff
872 *************************************************************/
874 long find_var_wrapper(
875 void *arg1,
876 char *key)
877 {
878 return find_var((image_desc_t *) arg1, key);
879 }
881 /* find gdes containing var*/
882 long find_var(
883 image_desc_t *im,
884 char *key)
885 {
886 long ii;
888 for (ii = 0; ii < im->gdes_c - 1; ii++) {
889 if ((im->gdes[ii].gf == GF_DEF
890 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
891 && (strcmp(im->gdes[ii].vname, key) == 0)) {
892 return ii;
893 }
894 }
895 return -1;
896 }
898 /* find the largest common denominator for all the numbers
899 in the 0 terminated num array */
900 long lcd(
901 long *num)
902 {
903 long rest;
904 int i;
906 for (i = 0; num[i + 1] != 0; i++) {
907 do {
908 rest = num[i] % num[i + 1];
909 num[i] = num[i + 1];
910 num[i + 1] = rest;
911 } while (rest != 0);
912 num[i + 1] = num[i];
913 }
914 /* return i==0?num[i]:num[i-1]; */
915 return num[i];
916 }
918 /* run the rpn calculator on all the VDEF and CDEF arguments */
919 int data_calc(
920 image_desc_t *im)
921 {
923 int gdi;
924 int dataidx;
925 long *steparray, rpi;
926 int stepcnt;
927 time_t now;
928 rpnstack_t rpnstack;
930 rpnstack_init(&rpnstack);
932 for (gdi = 0; gdi < im->gdes_c; gdi++) {
933 /* Look for GF_VDEF and GF_CDEF in the same loop,
934 * so CDEFs can use VDEFs and vice versa
935 */
936 switch (im->gdes[gdi].gf) {
937 case GF_XPORT:
938 break;
939 case GF_SHIFT:{
940 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
942 /* remove current shift */
943 vdp->start -= vdp->shift;
944 vdp->end -= vdp->shift;
946 /* vdef */
947 if (im->gdes[gdi].shidx >= 0)
948 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
949 /* constant */
950 else
951 vdp->shift = im->gdes[gdi].shval;
953 /* normalize shift to multiple of consolidated step */
954 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
956 /* apply shift */
957 vdp->start += vdp->shift;
958 vdp->end += vdp->shift;
959 break;
960 }
961 case GF_VDEF:
962 /* A VDEF has no DS. This also signals other parts
963 * of rrdtool that this is a VDEF value, not a CDEF.
964 */
965 im->gdes[gdi].ds_cnt = 0;
966 if (vdef_calc(im, gdi)) {
967 rrd_set_error("Error processing VDEF '%s'",
968 im->gdes[gdi].vname);
969 rpnstack_free(&rpnstack);
970 return -1;
971 }
972 break;
973 case GF_CDEF:
974 im->gdes[gdi].ds_cnt = 1;
975 im->gdes[gdi].ds = 0;
976 im->gdes[gdi].data_first = 1;
977 im->gdes[gdi].start = 0;
978 im->gdes[gdi].end = 0;
979 steparray = NULL;
980 stepcnt = 0;
981 dataidx = -1;
983 /* Find the variables in the expression.
984 * - VDEF variables are substituted by their values
985 * and the opcode is changed into OP_NUMBER.
986 * - CDEF variables are analized for their step size,
987 * the lowest common denominator of all the step
988 * sizes of the data sources involved is calculated
989 * and the resulting number is the step size for the
990 * resulting data source.
991 */
992 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
993 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
994 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
995 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
997 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
998 #if 0
999 printf
1000 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1001 im->gdes[gdi].vname, im->gdes[ptr].vname);
1002 printf("DEBUG: value from vdef is %f\n",
1003 im->gdes[ptr].vf.val);
1004 #endif
1005 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1006 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1007 } else { /* normal variables and PREF(variables) */
1009 /* add one entry to the array that keeps track of the step sizes of the
1010 * data sources going into the CDEF. */
1011 if ((steparray =
1012 rrd_realloc(steparray,
1013 (++stepcnt +
1014 1) * sizeof(*steparray))) == NULL) {
1015 rrd_set_error("realloc steparray");
1016 rpnstack_free(&rpnstack);
1017 return -1;
1018 };
1020 steparray[stepcnt - 1] = im->gdes[ptr].step;
1022 /* adjust start and end of cdef (gdi) so
1023 * that it runs from the latest start point
1024 * to the earliest endpoint of any of the
1025 * rras involved (ptr)
1026 */
1028 if (im->gdes[gdi].start < im->gdes[ptr].start)
1029 im->gdes[gdi].start = im->gdes[ptr].start;
1031 if (im->gdes[gdi].end == 0 ||
1032 im->gdes[gdi].end > im->gdes[ptr].end)
1033 im->gdes[gdi].end = im->gdes[ptr].end;
1035 /* store pointer to the first element of
1036 * the rra providing data for variable,
1037 * further save step size and data source
1038 * count of this rra
1039 */
1040 im->gdes[gdi].rpnp[rpi].data =
1041 im->gdes[ptr].data + im->gdes[ptr].ds;
1042 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1043 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1045 /* backoff the *.data ptr; this is done so
1046 * rpncalc() function doesn't have to treat
1047 * the first case differently
1048 */
1049 } /* if ds_cnt != 0 */
1050 } /* if OP_VARIABLE */
1051 } /* loop through all rpi */
1053 /* move the data pointers to the correct period */
1054 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1055 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1056 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1057 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1058 long diff =
1059 im->gdes[gdi].start - im->gdes[ptr].start;
1061 if (diff > 0)
1062 im->gdes[gdi].rpnp[rpi].data +=
1063 (diff / im->gdes[ptr].step) *
1064 im->gdes[ptr].ds_cnt;
1065 }
1066 }
1068 if (steparray == NULL) {
1069 rrd_set_error("rpn expressions without DEF"
1070 " or CDEF variables are not supported");
1071 rpnstack_free(&rpnstack);
1072 return -1;
1073 }
1074 steparray[stepcnt] = 0;
1075 /* Now find the resulting step. All steps in all
1076 * used RRAs have to be visited
1077 */
1078 im->gdes[gdi].step = lcd(steparray);
1079 free(steparray);
1080 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1081 im->gdes[gdi].start)
1082 / im->gdes[gdi].step)
1083 * sizeof(double))) == NULL) {
1084 rrd_set_error("malloc im->gdes[gdi].data");
1085 rpnstack_free(&rpnstack);
1086 return -1;
1087 }
1089 /* Step through the new cdef results array and
1090 * calculate the values
1091 */
1092 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1093 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1094 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1096 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1097 * in this case we are advancing by timesteps;
1098 * we use the fact that time_t is a synonym for long
1099 */
1100 if (rpn_calc(rpnp, &rpnstack, (long) now,
1101 im->gdes[gdi].data, ++dataidx) == -1) {
1102 /* rpn_calc sets the error string */
1103 rpnstack_free(&rpnstack);
1104 return -1;
1105 }
1106 } /* enumerate over time steps within a CDEF */
1107 break;
1108 default:
1109 continue;
1110 }
1111 } /* enumerate over CDEFs */
1112 rpnstack_free(&rpnstack);
1113 return 0;
1114 }
1116 /* massage data so, that we get one value for each x coordinate in the graph */
1117 int data_proc(
1118 image_desc_t *im)
1119 {
1120 long i, ii;
1121 double pixstep = (double) (im->end - im->start)
1122 / (double) im->xsize; /* how much time
1123 passes in one pixel */
1124 double paintval;
1125 double minval = DNAN, maxval = DNAN;
1127 unsigned long gr_time;
1129 /* memory for the processed data */
1130 for (i = 0; i < im->gdes_c; i++) {
1131 if ((im->gdes[i].gf == GF_LINE) ||
1132 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1133 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1134 * sizeof(rrd_value_t))) == NULL) {
1135 rrd_set_error("malloc data_proc");
1136 return -1;
1137 }
1138 }
1139 }
1141 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1142 long vidx;
1144 gr_time = im->start + pixstep * i; /* time of the current step */
1145 paintval = 0.0;
1147 for (ii = 0; ii < im->gdes_c; ii++) {
1148 double value;
1150 switch (im->gdes[ii].gf) {
1151 case GF_LINE:
1152 case GF_AREA:
1153 case GF_TICK:
1154 if (!im->gdes[ii].stack)
1155 paintval = 0.0;
1156 value = im->gdes[ii].yrule;
1157 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1158 /* The time of the data doesn't necessarily match
1159 ** the time of the graph. Beware.
1160 */
1161 vidx = im->gdes[ii].vidx;
1162 if (im->gdes[vidx].gf == GF_VDEF) {
1163 value = im->gdes[vidx].vf.val;
1164 } else
1165 if (((long int) gr_time >=
1166 (long int) im->gdes[vidx].start)
1167 && ((long int) gr_time <=
1168 (long int) im->gdes[vidx].end)) {
1169 value = im->gdes[vidx].data[(unsigned long)
1170 floor((double)
1171 (gr_time -
1172 im->gdes[vidx].
1173 start)
1174 /
1175 im->gdes[vidx].step)
1176 * im->gdes[vidx].ds_cnt +
1177 im->gdes[vidx].ds];
1178 } else {
1179 value = DNAN;
1180 }
1181 };
1183 if (!isnan(value)) {
1184 paintval += value;
1185 im->gdes[ii].p_data[i] = paintval;
1186 /* GF_TICK: the data values are not
1187 ** relevant for min and max
1188 */
1189 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1190 if ((isnan(minval) || paintval < minval) &&
1191 !(im->logarithmic && paintval <= 0.0))
1192 minval = paintval;
1193 if (isnan(maxval) || paintval > maxval)
1194 maxval = paintval;
1195 }
1196 } else {
1197 im->gdes[ii].p_data[i] = DNAN;
1198 }
1199 break;
1200 case GF_STACK:
1201 rrd_set_error
1202 ("STACK should already be turned into LINE or AREA here");
1203 return -1;
1204 break;
1205 default:
1206 break;
1207 }
1208 }
1209 }
1211 /* if min or max have not been asigned a value this is because
1212 there was no data in the graph ... this is not good ...
1213 lets set these to dummy values then ... */
1215 if (im->logarithmic) {
1216 if (isnan(minval))
1217 minval = 0.2;
1218 if (isnan(maxval))
1219 maxval = 5.1;
1220 } else {
1221 if (isnan(minval))
1222 minval = 0.0;
1223 if (isnan(maxval))
1224 maxval = 1.0;
1225 }
1227 /* adjust min and max values */
1228 if (isnan(im->minval)
1229 /* don't adjust low-end with log scale *//* why not? */
1230 || ((!im->rigid) && im->minval > minval)
1231 ) {
1232 if (im->logarithmic)
1233 im->minval = minval * 0.5;
1234 else
1235 im->minval = minval;
1236 }
1237 if (isnan(im->maxval)
1238 || (!im->rigid && im->maxval < maxval)
1239 ) {
1240 if (im->logarithmic)
1241 im->maxval = maxval * 2.0;
1242 else
1243 im->maxval = maxval;
1244 }
1245 /* make sure min is smaller than max */
1246 if (im->minval > im->maxval) {
1247 im->minval = 0.99 * im->maxval;
1248 }
1250 /* make sure min and max are not equal */
1251 if (im->minval == im->maxval) {
1252 im->maxval *= 1.01;
1253 if (!im->logarithmic) {
1254 im->minval *= 0.99;
1255 }
1256 /* make sure min and max are not both zero */
1257 if (im->maxval == 0.0) {
1258 im->maxval = 1.0;
1259 }
1260 }
1261 return 0;
1262 }
1266 /* identify the point where the first gridline, label ... gets placed */
1268 time_t find_first_time(
1269 time_t start, /* what is the initial time */
1270 enum tmt_en baseint, /* what is the basic interval */
1271 long basestep /* how many if these do we jump a time */
1272 )
1273 {
1274 struct tm tm;
1276 localtime_r(&start, &tm);
1278 switch (baseint) {
1279 case TMT_SECOND:
1280 tm. tm_sec -= tm.tm_sec % basestep;
1282 break;
1283 case TMT_MINUTE:
1284 tm. tm_sec = 0;
1285 tm. tm_min -= tm.tm_min % basestep;
1287 break;
1288 case TMT_HOUR:
1289 tm. tm_sec = 0;
1290 tm. tm_min = 0;
1291 tm. tm_hour -= tm.tm_hour % basestep;
1293 break;
1294 case TMT_DAY:
1295 /* we do NOT look at the basestep for this ... */
1296 tm. tm_sec = 0;
1297 tm. tm_min = 0;
1298 tm. tm_hour = 0;
1300 break;
1301 case TMT_WEEK:
1302 /* we do NOT look at the basestep for this ... */
1303 tm. tm_sec = 0;
1304 tm. tm_min = 0;
1305 tm. tm_hour = 0;
1306 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1308 if (tm.tm_wday == 0)
1309 tm. tm_mday -= 7; /* we want the *previous* monday */
1311 break;
1312 case TMT_MONTH:
1313 tm. tm_sec = 0;
1314 tm. tm_min = 0;
1315 tm. tm_hour = 0;
1316 tm. tm_mday = 1;
1317 tm. tm_mon -= tm.tm_mon % basestep;
1319 break;
1321 case TMT_YEAR:
1322 tm. tm_sec = 0;
1323 tm. tm_min = 0;
1324 tm. tm_hour = 0;
1325 tm. tm_mday = 1;
1326 tm. tm_mon = 0;
1327 tm. tm_year -= (
1328 tm.tm_year + 1900) %basestep;
1330 }
1331 return mktime(&tm);
1332 }
1334 /* identify the point where the next gridline, label ... gets placed */
1335 time_t find_next_time(
1336 time_t current, /* what is the initial time */
1337 enum tmt_en baseint, /* what is the basic interval */
1338 long basestep /* how many if these do we jump a time */
1339 )
1340 {
1341 struct tm tm;
1342 time_t madetime;
1344 localtime_r(¤t, &tm);
1346 do {
1347 switch (baseint) {
1348 case TMT_SECOND:
1349 tm. tm_sec += basestep;
1351 break;
1352 case TMT_MINUTE:
1353 tm. tm_min += basestep;
1355 break;
1356 case TMT_HOUR:
1357 tm. tm_hour += basestep;
1359 break;
1360 case TMT_DAY:
1361 tm. tm_mday += basestep;
1363 break;
1364 case TMT_WEEK:
1365 tm. tm_mday += 7 * basestep;
1367 break;
1368 case TMT_MONTH:
1369 tm. tm_mon += basestep;
1371 break;
1372 case TMT_YEAR:
1373 tm. tm_year += basestep;
1374 }
1375 madetime = mktime(&tm);
1376 } while (madetime == -1); /* this is necessary to skip impssible times
1377 like the daylight saving time skips */
1378 return madetime;
1380 }
1383 /* calculate values required for PRINT and GPRINT functions */
1385 int print_calc(
1386 image_desc_t *im,
1387 char ***prdata)
1388 {
1389 long i, ii, validsteps;
1390 double printval;
1391 struct tm tmvdef;
1392 int graphelement = 0;
1393 long vidx;
1394 int max_ii;
1395 double magfact = -1;
1396 char *si_symb = "";
1397 char *percent_s;
1398 int prlines = 1;
1400 /* wow initializing tmvdef is quite a task :-) */
1401 time_t now = time(NULL);
1403 localtime_r(&now, &tmvdef);
1404 if (im->imginfo)
1405 prlines++;
1406 for (i = 0; i < im->gdes_c; i++) {
1407 vidx = im->gdes[i].vidx;
1408 switch (im->gdes[i].gf) {
1409 case GF_PRINT:
1410 prlines++;
1411 if (((*prdata) =
1412 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1413 rrd_set_error("realloc prdata");
1414 return 0;
1415 }
1416 case GF_GPRINT:
1417 /* PRINT and GPRINT can now print VDEF generated values.
1418 * There's no need to do any calculations on them as these
1419 * calculations were already made.
1420 */
1421 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1422 printval = im->gdes[vidx].vf.val;
1423 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1424 } else { /* need to calculate max,min,avg etcetera */
1425 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1426 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1427 printval = DNAN;
1428 validsteps = 0;
1429 for (ii = im->gdes[vidx].ds;
1430 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1431 if (!finite(im->gdes[vidx].data[ii]))
1432 continue;
1433 if (isnan(printval)) {
1434 printval = im->gdes[vidx].data[ii];
1435 validsteps++;
1436 continue;
1437 }
1439 switch (im->gdes[i].cf) {
1440 case CF_HWPREDICT:
1441 case CF_DEVPREDICT:
1442 case CF_DEVSEASONAL:
1443 case CF_SEASONAL:
1444 case CF_AVERAGE:
1445 validsteps++;
1446 printval += im->gdes[vidx].data[ii];
1447 break;
1448 case CF_MINIMUM:
1449 printval = min(printval, im->gdes[vidx].data[ii]);
1450 break;
1451 case CF_FAILURES:
1452 case CF_MAXIMUM:
1453 printval = max(printval, im->gdes[vidx].data[ii]);
1454 break;
1455 case CF_LAST:
1456 printval = im->gdes[vidx].data[ii];
1457 }
1458 }
1459 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1460 if (validsteps > 1) {
1461 printval = (printval / validsteps);
1462 }
1463 }
1464 } /* prepare printval */
1466 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1467 /* Magfact is set to -1 upon entry to print_calc. If it
1468 * is still less than 0, then we need to run auto_scale.
1469 * Otherwise, put the value into the correct units. If
1470 * the value is 0, then do not set the symbol or magnification
1471 * so next the calculation will be performed again. */
1472 if (magfact < 0.0) {
1473 auto_scale(im, &printval, &si_symb, &magfact);
1474 if (printval == 0.0)
1475 magfact = -1.0;
1476 } else {
1477 printval /= magfact;
1478 }
1479 *(++percent_s) = 's';
1480 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1481 auto_scale(im, &printval, &si_symb, &magfact);
1482 }
1484 if (im->gdes[i].gf == GF_PRINT) {
1485 (*prdata)[prlines - 2] =
1486 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1487 (*prdata)[prlines - 1] = NULL;
1488 if (im->gdes[i].strftm) {
1489 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1490 im->gdes[i].format, &tmvdef);
1491 } else {
1492 if (bad_format(im->gdes[i].format)) {
1493 rrd_set_error("bad format for PRINT in '%s'",
1494 im->gdes[i].format);
1495 return -1;
1496 }
1497 #ifdef HAVE_SNPRINTF
1498 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1499 im->gdes[i].format, printval, si_symb);
1500 #else
1501 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1502 printval, si_symb);
1503 #endif
1504 }
1505 } else {
1506 /* GF_GPRINT */
1508 if (im->gdes[i].strftm) {
1509 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1510 im->gdes[i].format, &tmvdef);
1511 } else {
1512 if (bad_format(im->gdes[i].format)) {
1513 rrd_set_error("bad format for GPRINT in '%s'",
1514 im->gdes[i].format);
1515 return -1;
1516 }
1517 #ifdef HAVE_SNPRINTF
1518 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1519 im->gdes[i].format, printval, si_symb);
1520 #else
1521 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1522 si_symb);
1523 #endif
1524 }
1525 graphelement = 1;
1526 }
1527 break;
1528 case GF_LINE:
1529 case GF_AREA:
1530 case GF_TICK:
1531 graphelement = 1;
1532 break;
1533 case GF_HRULE:
1534 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1535 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1536 };
1537 graphelement = 1;
1538 break;
1539 case GF_VRULE:
1540 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1541 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1542 };
1543 graphelement = 1;
1544 break;
1545 case GF_COMMENT:
1546 case GF_TEXTALIGN:
1547 case GF_DEF:
1548 case GF_CDEF:
1549 case GF_VDEF:
1550 #ifdef WITH_PIECHART
1551 case GF_PART:
1552 #endif
1553 case GF_SHIFT:
1554 case GF_XPORT:
1555 break;
1556 case GF_STACK:
1557 rrd_set_error
1558 ("STACK should already be turned into LINE or AREA here");
1559 return -1;
1560 break;
1561 }
1562 }
1563 return graphelement;
1564 }
1567 /* place legends with color spots */
1568 int leg_place(
1569 image_desc_t *im,
1570 int *gY)
1571 {
1572 /* graph labels */
1573 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1574 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1575 int fill = 0, fill_last;
1576 int leg_c = 0;
1577 int leg_x = border, leg_y = im->yimg;
1578 int leg_y_prev = im->yimg;
1579 int leg_cc;
1580 int glue = 0;
1581 int i, ii, mark = 0;
1582 char prt_fctn; /*special printfunctions */
1583 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1584 int *legspace;
1586 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1587 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1588 rrd_set_error("malloc for legspace");
1589 return -1;
1590 }
1592 if (im->extra_flags & FULL_SIZE_MODE)
1593 leg_y = leg_y_prev =
1594 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1596 for (i = 0; i < im->gdes_c; i++) {
1597 fill_last = fill;
1599 /* hide legends for rules which are not displayed */
1601 if (im->gdes[i].gf == GF_TEXTALIGN) {
1602 default_txtalign = im->gdes[i].txtalign;
1603 }
1605 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1606 if (im->gdes[i].gf == GF_HRULE &&
1607 (im->gdes[i].yrule < im->minval
1608 || im->gdes[i].yrule > im->maxval))
1609 im->gdes[i].legend[0] = '\0';
1611 if (im->gdes[i].gf == GF_VRULE &&
1612 (im->gdes[i].xrule < im->start
1613 || im->gdes[i].xrule > im->end))
1614 im->gdes[i].legend[0] = '\0';
1615 }
1617 leg_cc = strlen(im->gdes[i].legend);
1619 /* is there a controle code ant the end of the legend string ? */
1620 /* and it is not a tab \\t */
1621 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1622 && im->gdes[i].legend[leg_cc - 1] != 't') {
1623 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1624 leg_cc -= 2;
1625 im->gdes[i].legend[leg_cc] = '\0';
1626 } else {
1627 prt_fctn = '\0';
1628 }
1629 /* only valid control codes */
1630 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1631 prt_fctn != 'r' &&
1632 prt_fctn != 'j' &&
1633 prt_fctn != 'c' &&
1634 prt_fctn != 's' &&
1635 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1636 free(legspace);
1637 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1638 im->gdes[i].legend, prt_fctn);
1639 return -1;
1641 }
1642 /* \n -> \l */
1643 if (prt_fctn == 'n') {
1644 prt_fctn = 'l';
1645 }
1647 /* remove exess space from the end of the legend for \g */
1648 while (prt_fctn == 'g' &&
1649 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1650 leg_cc--;
1651 im->gdes[i].legend[leg_cc] = '\0';
1652 }
1654 if (leg_cc != 0) {
1656 /* no interleg space if string ends in \g */
1657 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1659 if (fill > 0) {
1660 fill += legspace[i];
1661 }
1662 fill += gfx_get_text_width(im, fill + border,
1663 im->text_prop[TEXT_PROP_LEGEND].
1664 font,
1665 im->text_prop[TEXT_PROP_LEGEND].
1666 size, im->tabwidth,
1667 im->gdes[i].legend);
1668 leg_c++;
1669 } else {
1670 legspace[i] = 0;
1671 }
1672 /* who said there was a special tag ... ? */
1673 if (prt_fctn == 'g') {
1674 prt_fctn = '\0';
1675 }
1677 if (prt_fctn == '\0') {
1678 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1679 /* just one legend item is left right or center */
1680 switch (default_txtalign) {
1681 case TXA_RIGHT:
1682 prt_fctn = 'r';
1683 break;
1684 case TXA_CENTER:
1685 prt_fctn = 'c';
1686 break;
1687 case TXA_JUSTIFIED:
1688 prt_fctn = 'j';
1689 break;
1690 default:
1691 prt_fctn = 'l';
1692 break;
1693 }
1694 }
1695 /* is it time to place the legends ? */
1696 if (fill > im->ximg - 2 * border) {
1697 if (leg_c > 1) {
1698 /* go back one */
1699 i--;
1700 fill = fill_last;
1701 leg_c--;
1702 }
1703 }
1704 if (leg_c == 1 && prt_fctn == 'j') {
1705 prt_fctn = 'l';
1706 }
1707 }
1710 if (prt_fctn != '\0') {
1711 leg_x = border;
1712 if (leg_c >= 2 && prt_fctn == 'j') {
1713 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1714 } else {
1715 glue = 0;
1716 }
1717 if (prt_fctn == 'c')
1718 leg_x = (im->ximg - fill) / 2.0;
1719 if (prt_fctn == 'r')
1720 leg_x = im->ximg - fill - border;
1722 for (ii = mark; ii <= i; ii++) {
1723 if (im->gdes[ii].legend[0] == '\0')
1724 continue; /* skip empty legends */
1725 im->gdes[ii].leg_x = leg_x;
1726 im->gdes[ii].leg_y = leg_y;
1727 leg_x +=
1728 gfx_get_text_width(im, leg_x,
1729 im->text_prop[TEXT_PROP_LEGEND].
1730 font,
1731 im->text_prop[TEXT_PROP_LEGEND].
1732 size, im->tabwidth,
1733 im->gdes[ii].legend)
1734 + legspace[ii]
1735 + glue;
1736 }
1737 leg_y_prev = leg_y;
1738 if (im->extra_flags & FULL_SIZE_MODE) {
1739 /* only add y space if there was text on the line */
1740 if (leg_x > border || prt_fctn == 's')
1741 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1742 if (prt_fctn == 's')
1743 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1744 } else {
1745 if (leg_x > border || prt_fctn == 's')
1746 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1747 if (prt_fctn == 's')
1748 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1749 }
1750 fill = 0;
1751 leg_c = 0;
1752 mark = ii;
1753 }
1754 }
1756 if (im->extra_flags & FULL_SIZE_MODE) {
1757 if (leg_y != leg_y_prev) {
1758 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1759 im->yorigin =
1760 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1761 }
1762 } else {
1763 im->yimg = leg_y_prev;
1764 /* if we did place some legends we have to add vertical space */
1765 if (leg_y != im->yimg)
1766 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1767 }
1768 free(legspace);
1769 }
1770 return 0;
1771 }
1773 /* create a grid on the graph. it determines what to do
1774 from the values of xsize, start and end */
1776 /* the xaxis labels are determined from the number of seconds per pixel
1777 in the requested graph */
1781 int calc_horizontal_grid(
1782 image_desc_t *im)
1783 {
1784 double range;
1785 double scaledrange;
1786 int pixel, i;
1787 int gridind = 0;
1788 int decimals, fractionals;
1790 im->ygrid_scale.labfact = 2;
1791 range = im->maxval - im->minval;
1792 scaledrange = range / im->magfact;
1794 /* does the scale of this graph make it impossible to put lines
1795 on it? If so, give up. */
1796 if (isnan(scaledrange)) {
1797 return 0;
1798 }
1800 /* find grid spaceing */
1801 pixel = 1;
1802 if (isnan(im->ygridstep)) {
1803 if (im->extra_flags & ALTYGRID) {
1804 /* find the value with max number of digits. Get number of digits */
1805 decimals =
1806 ceil(log10
1807 (max(fabs(im->maxval), fabs(im->minval)) *
1808 im->viewfactor / im->magfact));
1809 if (decimals <= 0) /* everything is small. make place for zero */
1810 decimals = 1;
1812 im->ygrid_scale.gridstep =
1813 pow((double) 10,
1814 floor(log10(range * im->viewfactor / im->magfact))) /
1815 im->viewfactor * im->magfact;
1817 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1818 im->ygrid_scale.gridstep = 0.1;
1819 /* should have at least 5 lines but no more then 15 */
1820 if (range / im->ygrid_scale.gridstep < 5)
1821 im->ygrid_scale.gridstep /= 10;
1822 if (range / im->ygrid_scale.gridstep > 15)
1823 im->ygrid_scale.gridstep *= 10;
1824 if (range / im->ygrid_scale.gridstep > 5) {
1825 im->ygrid_scale.labfact = 1;
1826 if (range / im->ygrid_scale.gridstep > 8)
1827 im->ygrid_scale.labfact = 2;
1828 } else {
1829 im->ygrid_scale.gridstep /= 5;
1830 im->ygrid_scale.labfact = 5;
1831 }
1832 fractionals =
1833 floor(log10
1834 (im->ygrid_scale.gridstep *
1835 (double) im->ygrid_scale.labfact * im->viewfactor /
1836 im->magfact));
1837 if (fractionals < 0) { /* small amplitude. */
1838 int len = decimals - fractionals + 1;
1840 if (im->unitslength < len + 2)
1841 im->unitslength = len + 2;
1842 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1843 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1844 } else {
1845 int len = decimals + 1;
1847 if (im->unitslength < len + 2)
1848 im->unitslength = len + 2;
1849 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1850 (im->symbol != ' ' ? " %c" : ""));
1851 }
1852 } else {
1853 for (i = 0; ylab[i].grid > 0; i++) {
1854 pixel = im->ysize / (scaledrange / ylab[i].grid);
1855 gridind = i;
1856 if (pixel > 7)
1857 break;
1858 }
1860 for (i = 0; i < 4; i++) {
1861 if (pixel * ylab[gridind].lfac[i] >=
1862 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1863 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1864 break;
1865 }
1866 }
1868 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1869 }
1870 } else {
1871 im->ygrid_scale.gridstep = im->ygridstep;
1872 im->ygrid_scale.labfact = im->ylabfact;
1873 }
1874 return 1;
1875 }
1877 int draw_horizontal_grid(
1878 image_desc_t *im)
1879 {
1880 int i;
1881 double scaledstep;
1882 char graph_label[100];
1883 int nlabels = 0;
1884 double X0 = im->xorigin;
1885 double X1 = im->xorigin + im->xsize;
1887 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1888 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1889 double MaxY;
1891 scaledstep =
1892 im->ygrid_scale.gridstep / (double) im->magfact *
1893 (double) im->viewfactor;
1894 MaxY = scaledstep * (double) egrid;
1895 for (i = sgrid; i <= egrid; i++) {
1896 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1897 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1899 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1900 && floor(Y0 + 0.5) <= im->yorigin) {
1901 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1902 with the chosen settings. Add a label if required by settings, or if
1903 there is only one label so far and the next grid line is out of bounds. */
1904 if (i % im->ygrid_scale.labfact == 0
1905 || (nlabels == 1
1906 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1907 if (im->symbol == ' ') {
1908 if (im->extra_flags & ALTYGRID) {
1909 sprintf(graph_label, im->ygrid_scale.labfmt,
1910 scaledstep * (double) i);
1911 } else {
1912 if (MaxY < 10) {
1913 sprintf(graph_label, "%4.1f",
1914 scaledstep * (double) i);
1915 } else {
1916 sprintf(graph_label, "%4.0f",
1917 scaledstep * (double) i);
1918 }
1919 }
1920 } else {
1921 char sisym = (i == 0 ? ' ' : im->symbol);
1923 if (im->extra_flags & ALTYGRID) {
1924 sprintf(graph_label, im->ygrid_scale.labfmt,
1925 scaledstep * (double) i, sisym);
1926 } else {
1927 if (MaxY < 10) {
1928 sprintf(graph_label, "%4.1f %c",
1929 scaledstep * (double) i, sisym);
1930 } else {
1931 sprintf(graph_label, "%4.0f %c",
1932 scaledstep * (double) i, sisym);
1933 }
1934 }
1935 }
1936 nlabels++;
1938 gfx_text(im,
1939 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1940 im->graph_col[GRC_FONT],
1941 im->text_prop[TEXT_PROP_AXIS].font,
1942 im->text_prop[TEXT_PROP_AXIS].size,
1943 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1944 graph_label);
1945 gfx_line(im,
1946 X0 - 2, Y0,
1947 X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1948 gfx_line(im,
1949 X1, Y0,
1950 X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1951 gfx_dashed_line(im,
1952 X0 - 2, Y0,
1953 X1 + 2, Y0,
1954 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1955 im->grid_dash_on, im->grid_dash_off);
1957 } else if (!(im->extra_flags & NOMINOR)) {
1958 gfx_line(im,
1959 X0 - 2, Y0,
1960 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1961 gfx_line(im,
1962 X1, Y0,
1963 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1964 gfx_dashed_line(im,
1965 X0 - 1, Y0,
1966 X1 + 1, Y0,
1967 GRIDWIDTH, im->graph_col[GRC_GRID],
1968 im->grid_dash_on, im->grid_dash_off);
1970 }
1971 }
1972 }
1973 return 1;
1974 }
1976 /* this is frexp for base 10 */
1977 double frexp10(
1978 double,
1979 double *);
1980 double frexp10(
1981 double x,
1982 double *e)
1983 {
1984 double mnt;
1985 int iexp;
1987 iexp = floor(log(fabs(x)) / log(10));
1988 mnt = x / pow(10.0, iexp);
1989 if (mnt >= 10.0) {
1990 iexp++;
1991 mnt = x / pow(10.0, iexp);
1992 }
1993 *e = iexp;
1994 return mnt;
1995 }
1997 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1998 /* yes we are loosing precision by doing tos with floats instead of doubles
1999 but it seems more stable this way. */
2001 static int AlmostEqual2sComplement(
2002 float A,
2003 float B,
2004 int maxUlps)
2005 {
2007 int aInt = *(int *) &A;
2008 int bInt = *(int *) &B;
2009 int intDiff;
2011 /* Make sure maxUlps is non-negative and small enough that the
2012 default NAN won't compare as equal to anything. */
2014 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2016 /* Make aInt lexicographically ordered as a twos-complement int */
2018 if (aInt < 0)
2019 aInt = 0x80000000l - aInt;
2021 /* Make bInt lexicographically ordered as a twos-complement int */
2023 if (bInt < 0)
2024 bInt = 0x80000000l - bInt;
2026 intDiff = abs(aInt - bInt);
2028 if (intDiff <= maxUlps)
2029 return 1;
2031 return 0;
2032 }
2034 /* logaritmic horizontal grid */
2035 int horizontal_log_grid(
2036 image_desc_t *im)
2037 {
2038 double yloglab[][10] = {
2039 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2040 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2041 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2042 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2043 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2044 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2045 };
2047 int i, j, val_exp, min_exp;
2048 double nex; /* number of decades in data */
2049 double logscale; /* scale in logarithmic space */
2050 int exfrac = 1; /* decade spacing */
2051 int mid = -1; /* row in yloglab for major grid */
2052 double mspac; /* smallest major grid spacing (pixels) */
2053 int flab; /* first value in yloglab to use */
2054 double value, tmp, pre_value;
2055 double X0, X1, Y0;
2056 char graph_label[100];
2058 nex = log10(im->maxval / im->minval);
2059 logscale = im->ysize / nex;
2061 /* major spacing for data with high dynamic range */
2062 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2063 if (exfrac == 1)
2064 exfrac = 3;
2065 else
2066 exfrac += 3;
2067 }
2069 /* major spacing for less dynamic data */
2070 do {
2071 /* search best row in yloglab */
2072 mid++;
2073 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2074 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2075 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2076 && yloglab[mid][0] > 0);
2077 if (mid)
2078 mid--;
2080 /* find first value in yloglab */
2081 for (flab = 0;
2082 yloglab[mid][flab] < 10
2083 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2084 if (yloglab[mid][flab] == 10.0) {
2085 tmp += 1.0;
2086 flab = 0;
2087 }
2088 val_exp = tmp;
2089 if (val_exp % exfrac)
2090 val_exp += abs(-val_exp % exfrac);
2092 X0 = im->xorigin;
2093 X1 = im->xorigin + im->xsize;
2095 /* draw grid */
2096 pre_value = DNAN;
2097 while (1) {
2099 value = yloglab[mid][flab] * pow(10.0, val_exp);
2100 if (AlmostEqual2sComplement(value, pre_value, 4))
2101 break; /* it seems we are not converging */
2103 pre_value = value;
2105 Y0 = ytr(im, value);
2106 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2107 break;
2109 /* major grid line */
2111 gfx_line(im,
2112 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2113 gfx_line(im,
2114 X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2117 gfx_dashed_line(im,
2118 X0 - 2, Y0,
2119 X1 + 2, Y0,
2120 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2121 im->grid_dash_on, im->grid_dash_off);
2123 /* label */
2124 if (im->extra_flags & FORCE_UNITS_SI) {
2125 int scale;
2126 double pvalue;
2127 char symbol;
2129 scale = floor(val_exp / 3.0);
2130 if (value >= 1.0)
2131 pvalue = pow(10.0, val_exp % 3);
2132 else
2133 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2134 pvalue *= yloglab[mid][flab];
2136 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2137 ((scale + si_symbcenter) >= 0))
2138 symbol = si_symbol[scale + si_symbcenter];
2139 else
2140 symbol = '?';
2142 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2143 } else
2144 sprintf(graph_label, "%3.0e", value);
2145 gfx_text(im,
2146 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2147 im->graph_col[GRC_FONT],
2148 im->text_prop[TEXT_PROP_AXIS].font,
2149 im->text_prop[TEXT_PROP_AXIS].size,
2150 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2152 /* minor grid */
2153 if (mid < 4 && exfrac == 1) {
2154 /* find first and last minor line behind current major line
2155 * i is the first line and j tha last */
2156 if (flab == 0) {
2157 min_exp = val_exp - 1;
2158 for (i = 1; yloglab[mid][i] < 10.0; i++);
2159 i = yloglab[mid][i - 1] + 1;
2160 j = 10;
2161 } else {
2162 min_exp = val_exp;
2163 i = yloglab[mid][flab - 1] + 1;
2164 j = yloglab[mid][flab];
2165 }
2167 /* draw minor lines below current major line */
2168 for (; i < j; i++) {
2170 value = i * pow(10.0, min_exp);
2171 if (value < im->minval)
2172 continue;
2174 Y0 = ytr(im, value);
2175 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2176 break;
2178 /* draw lines */
2179 gfx_line(im,
2180 X0 - 2, Y0,
2181 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2182 gfx_line(im,
2183 X1, Y0,
2184 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2185 gfx_dashed_line(im,
2186 X0 - 1, Y0,
2187 X1 + 1, Y0,
2188 GRIDWIDTH, im->graph_col[GRC_GRID],
2189 im->grid_dash_on, im->grid_dash_off);
2190 }
2191 } else if (exfrac > 1) {
2192 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2193 value = pow(10.0, i);
2194 if (value < im->minval)
2195 continue;
2197 Y0 = ytr(im, value);
2198 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2199 break;
2201 /* draw lines */
2202 gfx_line(im,
2203 X0 - 2, Y0,
2204 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2205 gfx_line(im,
2206 X1, Y0,
2207 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2208 gfx_dashed_line(im,
2209 X0 - 1, Y0,
2210 X1 + 1, Y0,
2211 GRIDWIDTH, im->graph_col[GRC_GRID],
2212 im->grid_dash_on, im->grid_dash_off);
2213 }
2214 }
2216 /* next decade */
2217 if (yloglab[mid][++flab] == 10.0) {
2218 flab = 0;
2219 val_exp += exfrac;
2220 }
2221 }
2223 /* draw minor lines after highest major line */
2224 if (mid < 4 && exfrac == 1) {
2225 /* find first and last minor line below current major line
2226 * i is the first line and j tha last */
2227 if (flab == 0) {
2228 min_exp = val_exp - 1;
2229 for (i = 1; yloglab[mid][i] < 10.0; i++);
2230 i = yloglab[mid][i - 1] + 1;
2231 j = 10;
2232 } else {
2233 min_exp = val_exp;
2234 i = yloglab[mid][flab - 1] + 1;
2235 j = yloglab[mid][flab];
2236 }
2238 /* draw minor lines below current major line */
2239 for (; i < j; i++) {
2241 value = i * pow(10.0, min_exp);
2242 if (value < im->minval)
2243 continue;
2245 Y0 = ytr(im, value);
2246 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2247 break;
2249 /* draw lines */
2250 gfx_line(im,
2251 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2252 gfx_line(im,
2253 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2254 gfx_dashed_line(im,
2255 X0 - 1, Y0,
2256 X1 + 1, Y0,
2257 GRIDWIDTH, im->graph_col[GRC_GRID],
2258 im->grid_dash_on, im->grid_dash_off);
2259 }
2260 }
2261 /* fancy minor gridlines */
2262 else if (exfrac > 1) {
2263 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2264 value = pow(10.0, i);
2265 if (value < im->minval)
2266 continue;
2268 Y0 = ytr(im, value);
2269 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2270 break;
2272 /* draw lines */
2273 gfx_line(im,
2274 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2275 gfx_line(im,
2276 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2277 gfx_dashed_line(im,
2278 X0 - 1, Y0,
2279 X1 + 1, Y0,
2280 GRIDWIDTH, im->graph_col[GRC_GRID],
2281 im->grid_dash_on, im->grid_dash_off);
2282 }
2283 }
2285 return 1;
2286 }
2289 void vertical_grid(
2290 image_desc_t *im)
2291 {
2292 int xlab_sel; /* which sort of label and grid ? */
2293 time_t ti, tilab, timajor;
2294 long factor;
2295 char graph_label[100];
2296 double X0, Y0, Y1; /* points for filled graph and more */
2297 struct tm tm;
2299 /* the type of time grid is determined by finding
2300 the number of seconds per pixel in the graph */
2303 if (im->xlab_user.minsec == -1) {
2304 factor = (im->end - im->start) / im->xsize;
2305 xlab_sel = 0;
2306 while (xlab[xlab_sel + 1].minsec != -1
2307 && xlab[xlab_sel + 1].minsec <= factor) {
2308 xlab_sel++;
2309 } /* pick the last one */
2310 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2311 && xlab[xlab_sel].length > (im->end - im->start)) {
2312 xlab_sel--;
2313 } /* go back to the smallest size */
2314 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2315 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2316 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2317 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2318 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2319 im->xlab_user.labst = xlab[xlab_sel].labst;
2320 im->xlab_user.precis = xlab[xlab_sel].precis;
2321 im->xlab_user.stst = xlab[xlab_sel].stst;
2322 }
2324 /* y coords are the same for every line ... */
2325 Y0 = im->yorigin;
2326 Y1 = im->yorigin - im->ysize;
2329 /* paint the minor grid */
2330 if (!(im->extra_flags & NOMINOR)) {
2331 for (ti = find_first_time(im->start,
2332 im->xlab_user.gridtm,
2333 im->xlab_user.gridst),
2334 timajor = find_first_time(im->start,
2335 im->xlab_user.mgridtm,
2336 im->xlab_user.mgridst);
2337 ti < im->end;
2338 ti =
2339 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2340 ) {
2341 /* are we inside the graph ? */
2342 if (ti < im->start || ti > im->end)
2343 continue;
2344 while (timajor < ti) {
2345 timajor = find_next_time(timajor,
2346 im->xlab_user.mgridtm,
2347 im->xlab_user.mgridst);
2348 }
2349 if (ti == timajor)
2350 continue; /* skip as falls on major grid line */
2351 X0 = xtr(im, ti);
2352 gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2353 im->graph_col[GRC_GRID]);
2354 gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2355 im->graph_col[GRC_GRID]);
2356 gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2357 im->graph_col[GRC_GRID],
2358 im->grid_dash_on, im->grid_dash_off);
2360 }
2361 }
2363 /* paint the major grid */
2364 for (ti = find_first_time(im->start,
2365 im->xlab_user.mgridtm,
2366 im->xlab_user.mgridst);
2367 ti < im->end;
2368 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2369 ) {
2370 /* are we inside the graph ? */
2371 if (ti < im->start || ti > im->end)
2372 continue;
2373 X0 = xtr(im, ti);
2374 gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2375 im->graph_col[GRC_MGRID]);
2376 gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2377 im->graph_col[GRC_MGRID]);
2378 gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2379 im->graph_col[GRC_MGRID],
2380 im->grid_dash_on, im->grid_dash_off);
2382 }
2383 /* paint the labels below the graph */
2384 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2385 im->xlab_user.labtm,
2386 im->xlab_user.labst);
2387 ti <= im->end - im->xlab_user.precis / 2;
2388 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2389 ) {
2390 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2391 /* are we inside the graph ? */
2392 if (tilab < im->start || tilab > im->end)
2393 continue;
2395 #if HAVE_STRFTIME
2396 localtime_r(&tilab, &tm);
2397 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2398 #else
2399 # error "your libc has no strftime I guess we'll abort the exercise here."
2400 #endif
2401 gfx_text(im,
2402 xtr(im, tilab),
2403 Y0 + 3,
2404 im->graph_col[GRC_FONT],
2405 im->text_prop[TEXT_PROP_AXIS].font,
2406 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2407 GFX_H_CENTER, GFX_V_TOP, graph_label);
2409 }
2411 }
2414 void axis_paint(
2415 image_desc_t *im)
2416 {
2417 /* draw x and y axis */
2418 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2419 im->xorigin+im->xsize,im->yorigin-im->ysize,
2420 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2422 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2423 im->xorigin+im->xsize,im->yorigin-im->ysize,
2424 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2426 gfx_line(im, im->xorigin - 4, im->yorigin,
2427 im->xorigin + im->xsize + 4, im->yorigin,
2428 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2430 gfx_line(im, im->xorigin, im->yorigin + 4,
2431 im->xorigin, im->yorigin - im->ysize - 4,
2432 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2435 /* arrow for X and Y axis direction */
2437 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 */
2438 im->graph_col[GRC_ARROW]);
2439 gfx_close_path(im);
2441 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 */
2442 im->graph_col[GRC_ARROW]);
2443 gfx_close_path(im);
2446 }
2448 void grid_paint(
2449 image_desc_t *im)
2450 {
2451 long i;
2452 int res = 0;
2453 double X0, Y0; /* points for filled graph and more */
2454 struct gfx_color_t water_color;
2456 /* draw 3d border */
2457 gfx_new_area(im, 0, im->yimg,
2458 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2459 gfx_add_point(im, im->ximg - 2, 2);
2460 gfx_add_point(im, im->ximg, 0);
2461 gfx_add_point(im, 0, 0);
2462 gfx_close_path(im);
2464 gfx_new_area(im, 2, im->yimg - 2,
2465 im->ximg - 2, im->yimg - 2,
2466 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2467 gfx_add_point(im, im->ximg, 0);
2468 gfx_add_point(im, im->ximg, im->yimg);
2469 gfx_add_point(im, 0, im->yimg);
2470 gfx_close_path(im);
2473 if (im->draw_x_grid == 1)
2474 vertical_grid(im);
2476 if (im->draw_y_grid == 1) {
2477 if (im->logarithmic) {
2478 res = horizontal_log_grid(im);
2479 } else {
2480 res = draw_horizontal_grid(im);
2481 }
2483 /* dont draw horizontal grid if there is no min and max val */
2484 if (!res) {
2485 char *nodata = "No Data found";
2487 gfx_text(im, im->ximg / 2,
2488 (2 * im->yorigin - im->ysize) / 2,
2489 im->graph_col[GRC_FONT],
2490 im->text_prop[TEXT_PROP_AXIS].font,
2491 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2492 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2493 }
2494 }
2496 /* yaxis unit description */
2497 gfx_text(im,
2498 10, (im->yorigin - im->ysize / 2),
2499 im->graph_col[GRC_FONT],
2500 im->text_prop[TEXT_PROP_UNIT].font,
2501 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2502 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2504 /* graph title */
2505 gfx_text(im,
2506 im->ximg / 2, 6,
2507 im->graph_col[GRC_FONT],
2508 im->text_prop[TEXT_PROP_TITLE].font,
2509 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2510 GFX_H_CENTER, GFX_V_TOP, im->title);
2511 /* rrdtool 'logo' */
2512 water_color = im->graph_col[GRC_FONT];
2513 water_color.alpha = 0.3;
2514 gfx_text(im,
2515 im->ximg - 4, 5,
2516 water_color,
2517 im->text_prop[TEXT_PROP_AXIS].font,
2518 5.5, im->tabwidth, -90,
2519 GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2521 /* graph watermark */
2522 if (im->watermark[0] != '\0') {
2523 gfx_text(im,
2524 im->ximg / 2, im->yimg - 6,
2525 water_color,
2526 im->text_prop[TEXT_PROP_AXIS].font,
2527 5.5, im->tabwidth, 0,
2528 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2529 }
2531 /* graph labels */
2532 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2533 for (i = 0; i < im->gdes_c; i++) {
2534 if (im->gdes[i].legend[0] == '\0')
2535 continue;
2537 /* im->gdes[i].leg_y is the bottom of the legend */
2538 X0 = im->gdes[i].leg_x;
2539 Y0 = im->gdes[i].leg_y;
2540 gfx_text(im, X0, Y0,
2541 im->graph_col[GRC_FONT],
2542 im->text_prop[TEXT_PROP_LEGEND].font,
2543 im->text_prop[TEXT_PROP_LEGEND].size,
2544 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2545 im->gdes[i].legend);
2546 /* The legend for GRAPH items starts with "M " to have
2547 enough space for the box */
2548 if (im->gdes[i].gf != GF_PRINT &&
2549 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2550 double boxH, boxV;
2551 double X1, Y1;
2554 boxH = gfx_get_text_width(im, 0,
2555 im->text_prop[TEXT_PROP_LEGEND].
2556 font,
2557 im->text_prop[TEXT_PROP_LEGEND].
2558 size, im->tabwidth, "o") * 1.2;
2559 boxV = boxH;
2561 /* shift the box up a bit */
2562 Y0 -= boxV * 0.4;
2564 /* make sure transparent colors show up the same way as in the graph */
2566 gfx_new_area(im,
2567 X0, Y0 - boxV,
2568 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2569 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2570 gfx_close_path(im);
2572 gfx_new_area(im,
2573 X0, Y0 - boxV,
2574 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2575 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2576 gfx_close_path(im);
2578 cairo_save(im->cr);
2579 cairo_new_path(im->cr);
2580 cairo_set_line_width(im->cr, 1.0);
2581 X1 = X0 + boxH;
2582 Y1 = Y0 - boxV;
2583 gfx_line_fit(im, &X0, &Y0);
2584 gfx_line_fit(im, &X1, &Y1);
2585 cairo_move_to(im->cr, X0, Y0);
2586 cairo_line_to(im->cr, X1, Y0);
2587 cairo_line_to(im->cr, X1, Y1);
2588 cairo_line_to(im->cr, X0, Y1);
2589 cairo_close_path(im->cr);
2590 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2591 im->graph_col[GRC_FRAME].green,
2592 im->graph_col[GRC_FRAME].blue,
2593 im->graph_col[GRC_FRAME].alpha);
2594 cairo_stroke(im->cr);
2595 cairo_restore(im->cr);
2596 }
2597 }
2598 }
2599 }
2602 /*****************************************************
2603 * lazy check make sure we rely need to create this graph
2604 *****************************************************/
2606 int lazy_check(
2607 image_desc_t *im)
2608 {
2609 FILE *fd = NULL;
2610 int size = 1;
2611 struct stat imgstat;
2613 if (im->lazy == 0)
2614 return 0; /* no lazy option */
2615 if (stat(im->graphfile, &imgstat) != 0)
2616 return 0; /* can't stat */
2617 /* one pixel in the existing graph is more then what we would
2618 change here ... */
2619 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2620 return 0;
2621 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2622 return 0; /* the file does not exist */
2623 switch (im->imgformat) {
2624 case IF_PNG:
2625 size = PngSize(fd, &(im->ximg), &(im->yimg));
2626 break;
2627 default:
2628 size = 1;
2629 }
2630 fclose(fd);
2631 return size;
2632 }
2635 int graph_size_location(
2636 image_desc_t *im,
2637 int elements)
2638 {
2639 /* The actual size of the image to draw is determined from
2640 ** several sources. The size given on the command line is
2641 ** the graph area but we need more as we have to draw labels
2642 ** and other things outside the graph area
2643 */
2645 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2646 Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2648 if (im->extra_flags & ONLY_GRAPH) {
2649 im->xorigin = 0;
2650 im->ximg = im->xsize;
2651 im->yimg = im->ysize;
2652 im->yorigin = im->ysize;
2653 ytr(im, DNAN);
2654 return 0;
2655 }
2657 /** +---+--------------------------------------------+
2658 ** | y |...............graph title..................|
2659 ** | +---+-------------------------------+--------+
2660 ** | a | y | | |
2661 ** | x | | | |
2662 ** | i | a | | pie |
2663 ** | s | x | main graph area | chart |
2664 ** | | i | | area |
2665 ** | t | s | | |
2666 ** | i | | | |
2667 ** | t | l | | |
2668 ** | l | b +-------------------------------+--------+
2669 ** | e | l | x axis labels | |
2670 ** +---+---+-------------------------------+--------+
2671 ** |....................legends.....................|
2672 ** +------------------------------------------------+
2673 ** | watermark |
2674 ** +------------------------------------------------+
2675 */
2677 if (im->ylegend[0] != '\0') {
2678 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2679 }
2681 if (im->title[0] != '\0') {
2682 /* The title is placed "inbetween" two text lines so it
2683 ** automatically has some vertical spacing. The horizontal
2684 ** spacing is added here, on each side.
2685 */
2686 /* if necessary, reduce the font size of the title until it fits the image width */
2687 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2688 }
2690 if (elements) {
2691 if (im->draw_x_grid) {
2692 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2693 }
2694 if (im->draw_y_grid || im->forceleftspace) {
2695 Xylabel = gfx_get_text_width(im, 0,
2696 im->text_prop[TEXT_PROP_AXIS].font,
2697 im->text_prop[TEXT_PROP_AXIS].size,
2698 im->tabwidth, "0") * im->unitslength;
2699 }
2700 }
2702 if (im->extra_flags & FULL_SIZE_MODE) {
2703 /* The actual size of the image to draw has been determined by the user.
2704 ** The graph area is the space remaining after accounting for the legend,
2705 ** the watermark, the pie chart, the axis labels, and the title.
2706 */
2707 im->xorigin = 0;
2708 im->ximg = im->xsize;
2709 im->yimg = im->ysize;
2710 im->yorigin = im->ysize;
2711 Xmain = im->ximg;
2712 Ymain = im->yimg;
2714 im->yorigin += Ytitle;
2716 /* Now calculate the total size. Insert some spacing where
2717 desired. im->xorigin and im->yorigin need to correspond
2718 with the lower left corner of the main graph area or, if
2719 this one is not set, the imaginary box surrounding the
2720 pie chart area. */
2722 /* Initial size calculation for the main graph area */
2723 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2724 if (Xmain)
2725 Xmain -= Xspacing; /* put space between main graph area and right edge */
2727 im->xorigin = Xspacing + Xylabel;
2729 /* the length of the title should not influence with width of the graph
2730 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2732 if (Xvertical) { /* unit description */
2733 Xmain -= Xvertical;
2734 im->xorigin += Xvertical;
2735 }
2736 im->xsize = Xmain;
2737 xtr(im, 0);
2739 /* The vertical size of the image is known in advance. The main graph area
2740 ** (Ymain) and im->yorigin must be set according to the space requirements
2741 ** of the legend and the axis labels.
2742 */
2744 if (im->extra_flags & NOLEGEND) {
2745 /* set dimensions correctly if using full size mode with no legend */
2746 im->yorigin =
2747 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2748 Yspacing;
2749 Ymain = im->yorigin;
2750 } else {
2751 /* Determine where to place the legends onto the image.
2752 ** Set Ymain and adjust im->yorigin to match the space requirements.
2753 */
2754 if (leg_place(im, &Ymain) == -1)
2755 return -1;
2756 }
2759 /* remove title space *or* some padding above the graph from the main graph area */
2760 if (Ytitle) {
2761 Ymain -= Ytitle;
2762 } else {
2763 Ymain -= 1.5 * Yspacing;
2764 }
2766 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2767 if (im->watermark[0] != '\0') {
2768 Ymain -= Ywatermark;
2769 }
2771 im->ysize = Ymain;
2773 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2775 /* The actual size of the image to draw is determined from
2776 ** several sources. The size given on the command line is
2777 ** the graph area but we need more as we have to draw labels
2778 ** and other things outside the graph area.
2779 */
2781 if (im->ylegend[0] != '\0') {
2782 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2783 }
2786 if (im->title[0] != '\0') {
2787 /* The title is placed "inbetween" two text lines so it
2788 ** automatically has some vertical spacing. The horizontal
2789 ** spacing is added here, on each side.
2790 */
2791 /* don't care for the with of the title
2792 Xtitle = gfx_get_text_width(im->canvas, 0,
2793 im->text_prop[TEXT_PROP_TITLE].font,
2794 im->text_prop[TEXT_PROP_TITLE].size,
2795 im->tabwidth,
2796 im->title, 0) + 2*Xspacing; */
2797 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2798 }
2800 if (elements) {
2801 Xmain = im->xsize;
2802 Ymain = im->ysize;
2803 }
2804 /* Now calculate the total size. Insert some spacing where
2805 desired. im->xorigin and im->yorigin need to correspond
2806 with the lower left corner of the main graph area or, if
2807 this one is not set, the imaginary box surrounding the
2808 pie chart area. */
2810 /* The legend width cannot yet be determined, as a result we
2811 ** have problems adjusting the image to it. For now, we just
2812 ** forget about it at all; the legend will have to fit in the
2813 ** size already allocated.
2814 */
2815 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2817 if (Xmain)
2818 im->ximg += Xspacing;
2820 im->xorigin = Xspacing + Xylabel;
2822 /* the length of the title should not influence with width of the graph
2823 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2825 if (Xvertical) { /* unit description */
2826 im->ximg += Xvertical;
2827 im->xorigin += Xvertical;
2828 }
2829 xtr(im, 0);
2831 /* The vertical size is interesting... we need to compare
2832 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2833 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2834 ** in order to start even thinking about Ylegend or Ywatermark.
2835 **
2836 ** Do it in three portions: First calculate the inner part,
2837 ** then do the legend, then adjust the total height of the img,
2838 ** adding space for a watermark if one exists;
2839 */
2841 /* reserve space for main and/or pie */
2843 im->yimg = Ymain + Yxlabel;
2846 im->yorigin = im->yimg - Yxlabel;
2848 /* reserve space for the title *or* some padding above the graph */
2849 if (Ytitle) {
2850 im->yimg += Ytitle;
2851 im->yorigin += Ytitle;
2852 } else {
2853 im->yimg += 1.5 * Yspacing;
2854 im->yorigin += 1.5 * Yspacing;
2855 }
2856 /* reserve space for padding below the graph */
2857 im->yimg += Yspacing;
2859 /* Determine where to place the legends onto the image.
2860 ** Adjust im->yimg to match the space requirements.
2861 */
2862 if (leg_place(im, 0) == -1)
2863 return -1;
2865 if (im->watermark[0] != '\0') {
2866 im->yimg += Ywatermark;
2867 }
2868 }
2870 ytr(im, DNAN);
2871 return 0;
2872 }
2876 static cairo_status_t cairo_write_func_filehandle(
2877 void *closure,
2878 const unsigned char *data,
2879 unsigned int length)
2880 {
2881 if (fwrite(data, length, 1, closure) != 1)
2882 return CAIRO_STATUS_WRITE_ERROR;
2883 return CAIRO_STATUS_SUCCESS;
2884 }
2886 static cairo_status_t cairo_copy_to_buffer(
2887 void *closure,
2888 const unsigned char *data,
2889 unsigned int length)
2890 {
2891 image_desc_t *im = closure;
2893 im->rendered_image =
2894 realloc(im->rendered_image, im->rendered_image_size + length);
2895 if (im->rendered_image == NULL) {
2896 return CAIRO_STATUS_WRITE_ERROR;
2897 }
2899 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2901 im->rendered_image_size += length;
2903 return CAIRO_STATUS_SUCCESS;
2904 }
2906 /* draw that picture thing ... */
2907 int graph_paint(
2908 image_desc_t *im,
2909 char ***calcpr)
2910 {
2911 int i, ii;
2912 int lazy = lazy_check(im);
2914 double areazero = 0.0;
2915 graph_desc_t *lastgdes = NULL;
2917 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2920 /* if we are lazy and there is nothing to PRINT ... quit now */
2921 if (lazy && im->prt_c == 0)
2922 return 0;
2924 /* pull the data from the rrd files ... */
2926 if (data_fetch(im) == -1)
2927 return -1;
2929 /* evaluate VDEF and CDEF operations ... */
2930 if (data_calc(im) == -1)
2931 return -1;
2934 /* calculate and PRINT and GPRINT definitions. We have to do it at
2935 * this point because it will affect the length of the legends
2936 * if there are no graph elements we stop here ...
2937 * if we are lazy, try to quit ...
2938 */
2939 i = print_calc(im, calcpr);
2940 if (i < 0)
2941 return -1;
2942 if ((i == 0) || lazy)
2943 return 0;
2945 /**************************************************************
2946 *** Calculating sizes and locations became a bit confusing ***
2947 *** so I moved this into a separate function. ***
2948 **************************************************************/
2949 if (graph_size_location(im, i) == -1)
2950 return -1;
2952 /* get actual drawing data and find min and max values */
2953 if (data_proc(im) == -1)
2954 return -1;
2956 if (!im->logarithmic) {
2957 si_unit(im);
2958 }
2959 /* identify si magnitude Kilo, Mega Giga ? */
2960 if (!im->rigid && !im->logarithmic)
2961 expand_range(im); /* make sure the upper and lower limit are
2962 sensible values */
2964 if (!calc_horizontal_grid(im))
2965 return -1;
2967 /* reset precalc */
2968 ytr(im, DNAN);
2970 /* if (im->gridfit)
2971 apply_gridfit(im); */
2974 /* the actual graph is created by going through the individual
2975 graph elements and then drawing them */
2976 cairo_surface_destroy(im->surface);
2978 switch (im->imgformat) {
2979 case IF_PNG:
2980 im->surface =
2981 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2982 im->ximg * im->zoom,
2983 im->yimg * im->zoom);
2984 break;
2985 case IF_PDF:
2986 im->gridfit = 0;
2987 im->surface = strlen(im->graphfile)
2988 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2989 im->yimg * im->zoom)
2990 : cairo_pdf_surface_create_for_stream(&cairo_copy_to_buffer, im,
2991 im->ximg * im->zoom,
2992 im->yimg * im->zoom);
2993 break;
2994 case IF_EPS:
2995 im->gridfit = 0;
2996 im->surface = strlen(im->graphfile)
2997 ? cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
2998 im->yimg * im->zoom)
2999 : cairo_ps_surface_create_for_stream(&cairo_copy_to_buffer, im,
3000 im->ximg * im->zoom,
3001 im->yimg * im->zoom);
3002 break;
3003 case IF_SVG:
3004 im->gridfit = 0;
3005 im->surface = strlen(im->graphfile)
3006 ? cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
3007 im->yimg * im->zoom)
3008 : cairo_svg_surface_create_for_stream(&cairo_copy_to_buffer, im,
3009 im->ximg * im->zoom,
3010 im->yimg * im->zoom);
3011 cairo_svg_surface_restrict_to_version(im->surface,
3012 CAIRO_SVG_VERSION_1_1);
3013 break;
3014 };
3015 im->cr = cairo_create(im->surface);
3016 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3017 cairo_set_antialias(im->cr, im->graph_antialias);
3018 cairo_scale(im->cr, im->zoom, im->zoom);
3020 gfx_new_area(im,
3021 0, 0,
3022 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3024 gfx_add_point(im, im->ximg, 0);
3025 gfx_close_path(im);
3027 gfx_new_area(im,
3028 im->xorigin, im->yorigin,
3029 im->xorigin + im->xsize, im->yorigin,
3030 im->xorigin + im->xsize, im->yorigin - im->ysize,
3031 im->graph_col[GRC_CANVAS]);
3033 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3034 gfx_close_path(im);
3036 if (im->minval > 0.0)
3037 areazero = im->minval;
3038 if (im->maxval < 0.0)
3039 areazero = im->maxval;
3041 for (i = 0; i < im->gdes_c; i++) {
3042 switch (im->gdes[i].gf) {
3043 case GF_CDEF:
3044 case GF_VDEF:
3045 case GF_DEF:
3046 case GF_PRINT:
3047 case GF_GPRINT:
3048 case GF_COMMENT:
3049 case GF_TEXTALIGN:
3050 case GF_HRULE:
3051 case GF_VRULE:
3052 case GF_XPORT:
3053 case GF_SHIFT:
3054 break;
3055 case GF_TICK:
3056 for (ii = 0; ii < im->xsize; ii++) {
3057 if (!isnan(im->gdes[i].p_data[ii]) &&
3058 im->gdes[i].p_data[ii] != 0.0) {
3059 if (im->gdes[i].yrule > 0) {
3060 gfx_line(im,
3061 im->xorigin + ii, im->yorigin,
3062 im->xorigin + ii,
3063 im->yorigin -
3064 im->gdes[i].yrule * im->ysize, 1.0,
3065 im->gdes[i].col);
3066 } else if (im->gdes[i].yrule < 0) {
3067 gfx_line(im,
3068 im->xorigin + ii,
3069 im->yorigin - im->ysize,
3070 im->xorigin + ii,
3071 im->yorigin - (1 -
3072 im->gdes[i].yrule) *
3073 im->ysize, 1.0, im->gdes[i].col);
3075 }
3076 }
3077 }
3078 break;
3079 case GF_LINE:
3080 case GF_AREA:
3081 /* fix data points at oo and -oo */
3082 for (ii = 0; ii < im->xsize; ii++) {
3083 if (isinf(im->gdes[i].p_data[ii])) {
3084 if (im->gdes[i].p_data[ii] > 0) {
3085 im->gdes[i].p_data[ii] = im->maxval;
3086 } else {
3087 im->gdes[i].p_data[ii] = im->minval;
3088 }
3090 }
3091 } /* for */
3093 /* *******************************************************
3094 a ___. (a,t)
3095 | | ___
3096 ____| | | |
3097 | |___|
3098 -------|--t-1--t--------------------------------
3100 if we know the value at time t was a then
3101 we draw a square from t-1 to t with the value a.
3103 ********************************************************* */
3104 if (im->gdes[i].col.alpha != 0.0) {
3105 /* GF_LINE and friend */
3106 if (im->gdes[i].gf == GF_LINE) {
3107 double last_y = 0.0;
3108 int draw_on = 0;
3110 cairo_save(im->cr);
3111 cairo_new_path(im->cr);
3113 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3114 for (ii = 1; ii < im->xsize; ii++) {
3115 if (isnan(im->gdes[i].p_data[ii])
3116 || (im->slopemode == 1
3117 && isnan(im->gdes[i].p_data[ii - 1]))) {
3118 draw_on = 0;
3119 continue;
3120 }
3121 if (draw_on == 0) {
3122 last_y = ytr(im, im->gdes[i].p_data[ii]);
3123 if (im->slopemode == 0) {
3124 double x = ii - 1 + im->xorigin;
3125 double y = last_y;
3127 gfx_line_fit(im, &x, &y);
3128 cairo_move_to(im->cr, x, y);
3129 x = ii + im->xorigin;
3130 y = last_y;
3131 gfx_line_fit(im, &x, &y);
3132 cairo_line_to(im->cr, x, y);
3133 } else {
3134 double x = ii - 1 + im->xorigin;
3135 double y = ytr(im,
3136 im->gdes[i].p_data[ii - 1]);
3138 gfx_line_fit(im, &x, &y);
3139 cairo_move_to(im->cr, x, y);
3140 x = ii + im->xorigin;
3141 y = last_y;
3142 gfx_line_fit(im, &x, &y);
3143 cairo_line_to(im->cr, x, y);
3144 }
3145 draw_on = 1;
3146 } else {
3147 double x1 = ii + im->xorigin;
3148 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3150 if (im->slopemode == 0
3151 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3152 double x = ii - 1 + im->xorigin;
3153 double y = y1;
3155 gfx_line_fit(im, &x, &y);
3156 cairo_line_to(im->cr, x, y);
3157 };
3158 last_y = y1;
3159 gfx_line_fit(im, &x1, &y1);
3160 cairo_line_to(im->cr, x1, y1);
3161 };
3163 }
3164 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3165 im->gdes[i].col.green,
3166 im->gdes[i].col.blue,
3167 im->gdes[i].col.alpha);
3168 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3169 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3170 cairo_stroke(im->cr);
3171 cairo_restore(im->cr);
3172 } else {
3173 int idxI = -1;
3174 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3175 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3176 double *backY = malloc(sizeof(double) * im->xsize * 2);
3177 double *backX = malloc(sizeof(double) * im->xsize * 2);
3178 int drawem = 0;
3180 for (ii = 0; ii <= im->xsize; ii++) {
3181 double ybase, ytop;
3183 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3184 int cntI = 1;
3185 int lastI = 0;
3187 while (cntI < idxI
3188 && AlmostEqual2sComplement(foreY[lastI],
3189 foreY[cntI], 4)
3190 && AlmostEqual2sComplement(foreY[lastI],
3191 foreY[cntI + 1],
3192 4)) {
3193 cntI++;
3194 }
3195 gfx_new_area(im,
3196 backX[0], backY[0],
3197 foreX[0], foreY[0],
3198 foreX[cntI], foreY[cntI],
3199 im->gdes[i].col);
3200 while (cntI < idxI) {
3201 lastI = cntI;
3202 cntI++;
3203 while (cntI < idxI
3204 &&
3205 AlmostEqual2sComplement(foreY[lastI],
3206 foreY[cntI], 4)
3207 &&
3208 AlmostEqual2sComplement(foreY[lastI],
3209 foreY[cntI +
3210 1], 4)) {
3211 cntI++;
3212 }
3213 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3214 }
3215 gfx_add_point(im, backX[idxI], backY[idxI]);
3216 while (idxI > 1) {
3217 lastI = idxI;
3218 idxI--;
3219 while (idxI > 1
3220 &&
3221 AlmostEqual2sComplement(backY[lastI],
3222 backY[idxI], 4)
3223 &&
3224 AlmostEqual2sComplement(backY[lastI],
3225 backY[idxI -
3226 1], 4)) {
3227 idxI--;
3228 }
3229 gfx_add_point(im, backX[idxI], backY[idxI]);
3230 }
3231 idxI = -1;
3232 drawem = 0;
3233 gfx_close_path(im);
3234 }
3235 if (drawem != 0) {
3236 drawem = 0;
3237 idxI = -1;
3238 }
3239 if (ii == im->xsize)
3240 break;
3242 if (im->slopemode == 0 && ii == 0) {
3243 continue;
3244 }
3245 if (isnan(im->gdes[i].p_data[ii])) {
3246 drawem = 1;
3247 continue;
3248 }
3249 ytop = ytr(im, im->gdes[i].p_data[ii]);
3250 if (lastgdes && im->gdes[i].stack) {
3251 ybase = ytr(im, lastgdes->p_data[ii]);
3252 } else {
3253 ybase = ytr(im, areazero);
3254 }
3255 if (ybase == ytop) {
3256 drawem = 1;
3257 continue;
3258 }
3260 if (ybase > ytop) {
3261 double extra = ytop;
3263 ytop = ybase;
3264 ybase = extra;
3265 }
3266 if (im->slopemode == 0) {
3267 backY[++idxI] = ybase - 0.2;
3268 backX[idxI] = ii + im->xorigin - 1;
3269 foreY[idxI] = ytop + 0.2;
3270 foreX[idxI] = ii + im->xorigin - 1;
3271 }
3272 backY[++idxI] = ybase - 0.2;
3273 backX[idxI] = ii + im->xorigin;
3274 foreY[idxI] = ytop + 0.2;
3275 foreX[idxI] = ii + im->xorigin;
3276 }
3277 /* close up any remaining area */
3278 free(foreY);
3279 free(foreX);
3280 free(backY);
3281 free(backX);
3282 } /* else GF_LINE */
3283 }
3284 /* if color != 0x0 */
3285 /* make sure we do not run into trouble when stacking on NaN */
3286 for (ii = 0; ii < im->xsize; ii++) {
3287 if (isnan(im->gdes[i].p_data[ii])) {
3288 if (lastgdes && (im->gdes[i].stack)) {
3289 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3290 } else {
3291 im->gdes[i].p_data[ii] = areazero;
3292 }
3293 }
3294 }
3295 lastgdes = &(im->gdes[i]);
3296 break;
3297 case GF_STACK:
3298 rrd_set_error
3299 ("STACK should already be turned into LINE or AREA here");
3300 return -1;
3301 break;
3303 } /* switch */
3304 }
3306 /* grid_paint also does the text */
3307 if (!(im->extra_flags & ONLY_GRAPH))
3308 grid_paint(im);
3311 if (!(im->extra_flags & ONLY_GRAPH))
3312 axis_paint(im);
3314 /* the RULES are the last thing to paint ... */
3315 for (i = 0; i < im->gdes_c; i++) {
3317 switch (im->gdes[i].gf) {
3318 case GF_HRULE:
3319 if (im->gdes[i].yrule >= im->minval
3320 && im->gdes[i].yrule <= im->maxval)
3321 gfx_line(im,
3322 im->xorigin, ytr(im, im->gdes[i].yrule),
3323 im->xorigin + im->xsize, ytr(im,
3324 im->gdes[i].yrule),
3325 1.0, im->gdes[i].col);
3326 break;
3327 case GF_VRULE:
3328 if (im->gdes[i].xrule >= im->start
3329 && im->gdes[i].xrule <= im->end)
3330 gfx_line(im,
3331 xtr(im, im->gdes[i].xrule), im->yorigin,
3332 xtr(im, im->gdes[i].xrule),
3333 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3334 break;
3335 default:
3336 break;
3337 }
3338 }
3341 switch (im->imgformat) {
3342 case IF_PNG:
3343 {
3344 cairo_status_t status;
3346 if (strlen(im->graphfile) == 0) {
3347 status =
3348 cairo_surface_write_to_png_stream(im->surface,
3349 &cairo_copy_to_buffer, im);
3350 } else if (strcmp(im->graphfile, "-") == 0) {
3351 status =
3352 cairo_surface_write_to_png_stream(im->surface,
3353 &cairo_write_func_filehandle,
3354 (void *) stdout);
3355 } else {
3356 status = cairo_surface_write_to_png(im->surface, im->graphfile);
3357 }
3359 if (status != CAIRO_STATUS_SUCCESS) {
3360 rrd_set_error("Could not save png to '%s'", im->graphfile);
3361 return 1;
3362 }
3363 }
3364 break;
3365 default:
3366 if (strlen(im->graphfile)) {
3367 cairo_show_page(im->cr);
3368 } else {
3369 cairo_surface_finish(im->surface);
3370 }
3371 break;
3372 }
3373 return 0;
3374 }
3377 /*****************************************************
3378 * graph stuff
3379 *****************************************************/
3381 int gdes_alloc(
3382 image_desc_t *im)
3383 {
3385 im->gdes_c++;
3386 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3387 * sizeof(graph_desc_t))) ==
3388 NULL) {
3389 rrd_set_error("realloc graph_descs");
3390 return -1;
3391 }
3394 im->gdes[im->gdes_c - 1].step = im->step;
3395 im->gdes[im->gdes_c - 1].step_orig = im->step;
3396 im->gdes[im->gdes_c - 1].stack = 0;
3397 im->gdes[im->gdes_c - 1].linewidth = 0;
3398 im->gdes[im->gdes_c - 1].debug = 0;
3399 im->gdes[im->gdes_c - 1].start = im->start;
3400 im->gdes[im->gdes_c - 1].start_orig = im->start;
3401 im->gdes[im->gdes_c - 1].end = im->end;
3402 im->gdes[im->gdes_c - 1].end_orig = im->end;
3403 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3404 im->gdes[im->gdes_c - 1].data = NULL;
3405 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3406 im->gdes[im->gdes_c - 1].data_first = 0;
3407 im->gdes[im->gdes_c - 1].p_data = NULL;
3408 im->gdes[im->gdes_c - 1].rpnp = NULL;
3409 im->gdes[im->gdes_c - 1].shift = 0.0;
3410 im->gdes[im->gdes_c - 1].col.red = 0.0;
3411 im->gdes[im->gdes_c - 1].col.green = 0.0;
3412 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3413 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3414 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3415 im->gdes[im->gdes_c - 1].format[0] = '\0';
3416 im->gdes[im->gdes_c - 1].strftm = 0;
3417 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3418 im->gdes[im->gdes_c - 1].ds = -1;
3419 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3420 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3421 im->gdes[im->gdes_c - 1].p_data = NULL;
3422 im->gdes[im->gdes_c - 1].yrule = DNAN;
3423 im->gdes[im->gdes_c - 1].xrule = 0;
3424 return 0;
3425 }
3427 /* copies input untill the first unescaped colon is found
3428 or until input ends. backslashes have to be escaped as well */
3429 int scan_for_col(
3430 const char *const input,
3431 int len,
3432 char *const output)
3433 {
3434 int inp, outp = 0;
3436 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3437 if (input[inp] == '\\' &&
3438 input[inp + 1] != '\0' &&
3439 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3440 output[outp++] = input[++inp];
3441 } else {
3442 output[outp++] = input[inp];
3443 }
3444 }
3445 output[outp] = '\0';
3446 return inp;
3447 }
3449 /* Some surgery done on this function, it became ridiculously big.
3450 ** Things moved:
3451 ** - initializing now in rrd_graph_init()
3452 ** - options parsing now in rrd_graph_options()
3453 ** - script parsing now in rrd_graph_script()
3454 */
3455 int rrd_graph(
3456 int argc,
3457 char **argv,
3458 char ***prdata,
3459 int *xsize,
3460 int *ysize,
3461 FILE * stream,
3462 double *ymin,
3463 double *ymax)
3464 {
3465 image_desc_t im;
3467 rrd_graph_init(&im);
3469 /* a dummy surface so that we can measure text sizes for placements */
3470 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3471 im.cr = cairo_create(im.surface);
3472 im.graphhandle = stream;
3474 rrd_graph_options(argc, argv, &im);
3475 if (rrd_test_error()) {
3476 im_free(&im);
3477 return -1;
3478 }
3480 if (optind >= argc) {
3481 rrd_set_error("missing filename");
3482 return -1;
3483 }
3485 if (strlen(argv[optind]) >= MAXPATH) {
3486 rrd_set_error("filename (including path) too long");
3487 im_free(&im);
3488 return -1;
3489 }
3491 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3492 im.graphfile[MAXPATH - 1] = '\0';
3494 rrd_graph_script(argc, argv, &im, 1);
3495 if (rrd_test_error()) {
3496 im_free(&im);
3497 return -1;
3498 }
3500 /* Everything is now read and the actual work can start */
3502 (*prdata) = NULL;
3503 if (graph_paint(&im, prdata) == -1) {
3504 im_free(&im);
3505 return -1;
3506 }
3508 /* The image is generated and needs to be output.
3509 ** Also, if needed, print a line with information about the image.
3510 */
3512 *xsize = im.ximg;
3513 *ysize = im.yimg;
3514 *ymin = im.minval;
3515 *ymax = im.maxval;
3516 if (im.imginfo) {
3517 char *filename;
3519 if (!(*prdata)) {
3520 /* maybe prdata is not allocated yet ... lets do it now */
3521 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3522 rrd_set_error("malloc imginfo");
3523 return -1;
3524 };
3525 }
3526 if (((*prdata)[0] =
3527 malloc((strlen(im.imginfo) + 200 +
3528 strlen(im.graphfile)) * sizeof(char)))
3529 == NULL) {
3530 rrd_set_error("malloc imginfo");
3531 return -1;
3532 }
3533 filename = im.graphfile + strlen(im.graphfile);
3534 while (filename > im.graphfile) {
3535 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3536 break;
3537 filename--;
3538 }
3540 sprintf((*prdata)[0], im.imginfo, filename,
3541 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3542 }
3543 im_free(&im);
3544 return 0;
3545 }
3547 /* a simplified version of the above that just creates the graph in memory
3548 and returns a pointer to it. */
3550 unsigned char *rrd_graph_in_memory(
3551 int argc,
3552 char **argv,
3553 char ***prdata,
3554 int *xsize,
3555 int *ysize,
3556 double *ymin,
3557 double *ymax,
3558 size_t * img_size)
3559 {
3560 image_desc_t im;
3562 rrd_graph_init(&im);
3564 /* a dummy surface so that we can measure text sizes for placements */
3565 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3566 im.cr = cairo_create(im.surface);
3568 rrd_graph_options(argc, argv, &im);
3569 if (rrd_test_error()) {
3570 im_free(&im);
3571 return NULL;
3572 }
3574 rrd_graph_script(argc, argv, &im, 1);
3575 if (rrd_test_error()) {
3576 im_free(&im);
3577 return NULL;
3578 }
3580 /* Everything is now read and the actual work can start */
3582 /* by not assigning a name to im.graphfile data will be written to
3583 newly allocated memory on im.rendered_image ... */
3585 (*prdata) = NULL;
3586 if (graph_paint(&im, prdata) == -1) {
3587 im_free(&im);
3588 return NULL;
3589 }
3591 *xsize = im.ximg;
3592 *ysize = im.yimg;
3593 *ymin = im.minval;
3594 *ymax = im.maxval;
3595 *img_size = im.rendered_image_size;
3596 im_free(&im);
3598 return im.rendered_image;
3599 }
3601 void rrd_graph_init(
3602 image_desc_t *im)
3603 {
3604 unsigned int i;
3606 #ifdef HAVE_TZSET
3607 tzset();
3608 #endif
3609 #ifdef HAVE_SETLOCALE
3610 setlocale(LC_TIME, "");
3611 #ifdef HAVE_MBSTOWCS
3612 setlocale(LC_CTYPE, "");
3613 #endif
3614 #endif
3615 im->yorigin = 0;
3616 im->xorigin = 0;
3617 im->minval = 0;
3618 im->xlab_user.minsec = -1;
3619 im->ximg = 0;
3620 im->yimg = 0;
3621 im->xsize = 400;
3622 im->ysize = 100;
3623 im->rendered_image_size = 0;
3624 im->rendered_image = NULL;
3625 im->step = 0;
3626 im->ylegend[0] = '\0';
3627 im->title[0] = '\0';
3628 im->watermark[0] = '\0';
3629 im->minval = DNAN;
3630 im->maxval = DNAN;
3631 im->unitsexponent = 9999;
3632 im->unitslength = 6;
3633 im->forceleftspace = 0;
3634 im->symbol = ' ';
3635 im->viewfactor = 1.0;
3636 im->imgformat = IF_PNG;
3637 im->graphfile[0] = '\0';
3638 im->cr = NULL;
3639 im->surface = NULL;
3640 im->extra_flags = 0;
3641 im->rigid = 0;
3642 im->gridfit = 1;
3643 im->imginfo = NULL;
3644 im->lazy = 0;
3645 im->slopemode = 0;
3646 im->logarithmic = 0;
3647 im->ygridstep = DNAN;
3648 im->draw_x_grid = 1;
3649 im->draw_y_grid = 1;
3650 im->base = 1000;
3651 im->prt_c = 0;
3652 im->gdes_c = 0;
3653 im->gdes = NULL;
3654 im->grid_dash_on = 1;
3655 im->grid_dash_off = 1;
3656 im->tabwidth = 40.0;
3657 im->zoom = 1;
3658 im->font_options = cairo_font_options_create();
3659 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3661 cairo_font_options_set_hint_style(im->font_options,
3662 CAIRO_HINT_STYLE_FULL);
3663 cairo_font_options_set_hint_metrics(im->font_options,
3664 CAIRO_HINT_METRICS_ON);
3665 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3668 for (i = 0; i < DIM(graph_col); i++)
3669 im->graph_col[i] = graph_col[i];
3671 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3672 {
3673 char *windir;
3674 char rrd_win_default_font[1000];
3676 windir = getenv("windir");
3677 /* %windir% is something like D:\windows or C:\winnt */
3678 if (windir != NULL) {
3679 strncpy(rrd_win_default_font, windir, 500);
3680 rrd_win_default_font[500] = '\0';
3681 strcat(rrd_win_default_font, "\\fonts\\");
3682 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3683 for (i = 0; i < DIM(text_prop); i++) {
3684 strncpy(text_prop[i].font, rrd_win_default_font,
3685 sizeof(text_prop[i].font) - 1);
3686 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3687 }
3688 }
3689 }
3690 #endif
3691 {
3692 char *deffont;
3694 deffont = getenv("RRD_DEFAULT_FONT");
3695 if (deffont != NULL) {
3696 for (i = 0; i < DIM(text_prop); i++) {
3697 strncpy(text_prop[i].font, deffont,
3698 sizeof(text_prop[i].font) - 1);
3699 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3700 }
3701 }
3702 }
3703 for (i = 0; i < DIM(text_prop); i++) {
3704 im->text_prop[i].size = text_prop[i].size;
3705 strcpy(im->text_prop[i].font, text_prop[i].font);
3706 }
3707 }
3709 void rrd_graph_options(
3710 int argc,
3711 char *argv[],
3712 image_desc_t *im)
3713 {
3714 int stroff;
3715 char *parsetime_error = NULL;
3716 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3717 time_t start_tmp = 0, end_tmp = 0;
3718 long long_tmp;
3719 struct rrd_time_value start_tv, end_tv;
3720 long unsigned int color;
3722 optind = 0;
3723 opterr = 0; /* initialize getopt */
3725 parsetime("end-24h", &start_tv);
3726 parsetime("now", &end_tv);
3728 /* defines for long options without a short equivalent. should be bytes,
3729 and may not collide with (the ASCII value of) short options */
3730 #define LONGOPT_UNITS_SI 255
3732 while (1) {
3733 static struct option long_options[] = {
3734 {"start", required_argument, 0, 's'},
3735 {"end", required_argument, 0, 'e'},
3736 {"x-grid", required_argument, 0, 'x'},
3737 {"y-grid", required_argument, 0, 'y'},
3738 {"vertical-label", required_argument, 0, 'v'},
3739 {"width", required_argument, 0, 'w'},
3740 {"height", required_argument, 0, 'h'},
3741 {"full-size-mode", no_argument, 0, 'D'},
3742 {"interlaced", no_argument, 0, 'i'},
3743 {"upper-limit", required_argument, 0, 'u'},
3744 {"lower-limit", required_argument, 0, 'l'},
3745 {"rigid", no_argument, 0, 'r'},
3746 {"base", required_argument, 0, 'b'},
3747 {"logarithmic", no_argument, 0, 'o'},
3748 {"color", required_argument, 0, 'c'},
3749 {"font", required_argument, 0, 'n'},
3750 {"title", required_argument, 0, 't'},
3751 {"imginfo", required_argument, 0, 'f'},
3752 {"imgformat", required_argument, 0, 'a'},
3753 {"lazy", no_argument, 0, 'z'},
3754 {"zoom", required_argument, 0, 'm'},
3755 {"no-legend", no_argument, 0, 'g'},
3756 {"force-rules-legend", no_argument, 0, 'F'},
3757 {"only-graph", no_argument, 0, 'j'},
3758 {"alt-y-grid", no_argument, 0, 'Y'},
3759 {"no-minor", no_argument, 0, 'I'},
3760 {"slope-mode", no_argument, 0, 'E'},
3761 {"alt-autoscale", no_argument, 0, 'A'},
3762 {"alt-autoscale-min", no_argument, 0, 'J'},
3763 {"alt-autoscale-max", no_argument, 0, 'M'},
3764 {"no-gridfit", no_argument, 0, 'N'},
3765 {"units-exponent", required_argument, 0, 'X'},
3766 {"units-length", required_argument, 0, 'L'},
3767 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3768 {"step", required_argument, 0, 'S'},
3769 {"tabwidth", required_argument, 0, 'T'},
3770 {"font-render-mode", required_argument, 0, 'R'},
3771 {"graph-render-mode", required_argument, 0, 'G'},
3772 {"font-smoothing-threshold", required_argument, 0, 'B'},
3773 {"watermark", required_argument, 0, 'W'},
3774 {"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 */
3775 {0, 0, 0, 0}
3776 };
3777 int option_index = 0;
3778 int opt;
3779 int col_start, col_end;
3781 opt = getopt_long(argc, argv,
3782 "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:",
3783 long_options, &option_index);
3785 if (opt == EOF)
3786 break;
3788 switch (opt) {
3789 case 'I':
3790 im->extra_flags |= NOMINOR;
3791 break;
3792 case 'Y':
3793 im->extra_flags |= ALTYGRID;
3794 break;
3795 case 'A':
3796 im->extra_flags |= ALTAUTOSCALE;
3797 break;
3798 case 'J':
3799 im->extra_flags |= ALTAUTOSCALE_MIN;
3800 break;
3801 case 'M':
3802 im->extra_flags |= ALTAUTOSCALE_MAX;
3803 break;
3804 case 'j':
3805 im->extra_flags |= ONLY_GRAPH;
3806 break;
3807 case 'g':
3808 im->extra_flags |= NOLEGEND;
3809 break;
3810 case 'F':
3811 im->extra_flags |= FORCE_RULES_LEGEND;
3812 break;
3813 case LONGOPT_UNITS_SI:
3814 if (im->extra_flags & FORCE_UNITS) {
3815 rrd_set_error("--units can only be used once!");
3816 return;
3817 }
3818 if (strcmp(optarg, "si") == 0)
3819 im->extra_flags |= FORCE_UNITS_SI;
3820 else {
3821 rrd_set_error("invalid argument for --units: %s", optarg);
3822 return;
3823 }
3824 break;
3825 case 'X':
3826 im->unitsexponent = atoi(optarg);
3827 break;
3828 case 'L':
3829 im->unitslength = atoi(optarg);
3830 im->forceleftspace = 1;
3831 break;
3832 case 'T':
3833 im->tabwidth = atof(optarg);
3834 break;
3835 case 'S':
3836 im->step = atoi(optarg);
3837 break;
3838 case 'N':
3839 im->gridfit = 0;
3840 break;
3841 case 's':
3842 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3843 rrd_set_error("start time: %s", parsetime_error);
3844 return;
3845 }
3846 break;
3847 case 'e':
3848 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3849 rrd_set_error("end time: %s", parsetime_error);
3850 return;
3851 }
3852 break;
3853 case 'x':
3854 if (strcmp(optarg, "none") == 0) {
3855 im->draw_x_grid = 0;
3856 break;
3857 };
3859 if (sscanf(optarg,
3860 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3861 scan_gtm,
3862 &im->xlab_user.gridst,
3863 scan_mtm,
3864 &im->xlab_user.mgridst,
3865 scan_ltm,
3866 &im->xlab_user.labst,
3867 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3868 strncpy(im->xlab_form, optarg + stroff,
3869 sizeof(im->xlab_form) - 1);
3870 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3871 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3872 rrd_set_error("unknown keyword %s", scan_gtm);
3873 return;
3874 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3875 == -1) {
3876 rrd_set_error("unknown keyword %s", scan_mtm);
3877 return;
3878 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3879 -1) {
3880 rrd_set_error("unknown keyword %s", scan_ltm);
3881 return;
3882 }
3883 im->xlab_user.minsec = 1;
3884 im->xlab_user.stst = im->xlab_form;
3885 } else {
3886 rrd_set_error("invalid x-grid format");
3887 return;
3888 }
3889 break;
3890 case 'y':
3892 if (strcmp(optarg, "none") == 0) {
3893 im->draw_y_grid = 0;
3894 break;
3895 };
3897 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3898 if (im->ygridstep <= 0) {
3899 rrd_set_error("grid step must be > 0");
3900 return;
3901 } else if (im->ylabfact < 1) {
3902 rrd_set_error("label factor must be > 0");
3903 return;
3904 }
3905 } else {
3906 rrd_set_error("invalid y-grid format");
3907 return;
3908 }
3909 break;
3910 case 'v':
3911 strncpy(im->ylegend, optarg, 150);
3912 im->ylegend[150] = '\0';
3913 break;
3914 case 'u':
3915 im->maxval = atof(optarg);
3916 break;
3917 case 'l':
3918 im->minval = atof(optarg);
3919 break;
3920 case 'b':
3921 im->base = atol(optarg);
3922 if (im->base != 1024 && im->base != 1000) {
3923 rrd_set_error
3924 ("the only sensible value for base apart from 1000 is 1024");
3925 return;
3926 }
3927 break;
3928 case 'w':
3929 long_tmp = atol(optarg);
3930 if (long_tmp < 10) {
3931 rrd_set_error("width below 10 pixels");
3932 return;
3933 }
3934 im->xsize = long_tmp;
3935 break;
3936 case 'h':
3937 long_tmp = atol(optarg);
3938 if (long_tmp < 10) {
3939 rrd_set_error("height below 10 pixels");
3940 return;
3941 }
3942 im->ysize = long_tmp;
3943 break;
3944 case 'D':
3945 im->extra_flags |= FULL_SIZE_MODE;
3946 break;
3947 case 'i':
3948 /* interlaced png not supported at the moment */
3949 break;
3950 case 'r':
3951 im->rigid = 1;
3952 break;
3953 case 'f':
3954 im->imginfo = optarg;
3955 break;
3956 case 'a':
3957 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3958 rrd_set_error("unsupported graphics format '%s'", optarg);
3959 return;
3960 }
3961 break;
3962 case 'z':
3963 im->lazy = 1;
3964 break;
3965 case 'E':
3966 im->slopemode = 1;
3967 break;
3969 case 'o':
3970 im->logarithmic = 1;
3971 break;
3972 case 'c':
3973 if (sscanf(optarg,
3974 "%10[A-Z]#%n%8lx%n",
3975 col_nam, &col_start, &color, &col_end) == 2) {
3976 int ci;
3977 int col_len = col_end - col_start;
3979 switch (col_len) {
3980 case 3:
3981 color = (((color & 0xF00) * 0x110000) |
3982 ((color & 0x0F0) * 0x011000) |
3983 ((color & 0x00F) * 0x001100) | 0x000000FF);
3984 break;
3985 case 4:
3986 color = (((color & 0xF000) * 0x11000) |
3987 ((color & 0x0F00) * 0x01100) |
3988 ((color & 0x00F0) * 0x00110) |
3989 ((color & 0x000F) * 0x00011)
3990 );
3991 break;
3992 case 6:
3993 color = (color << 8) + 0xff /* shift left by 8 */ ;
3994 break;
3995 case 8:
3996 break;
3997 default:
3998 rrd_set_error("the color format is #RRGGBB[AA]");
3999 return;
4000 }
4001 if ((ci = grc_conv(col_nam)) != -1) {
4002 im->graph_col[ci] = gfx_hex_to_col(color);
4003 } else {
4004 rrd_set_error("invalid color name '%s'", col_nam);
4005 return;
4006 }
4007 } else {
4008 rrd_set_error("invalid color def format");
4009 return;
4010 }
4011 break;
4012 case 'n':{
4013 char prop[15];
4014 double size = 1;
4015 char font[1024] = "";
4017 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
4018 int sindex, propidx;
4020 if ((sindex = text_prop_conv(prop)) != -1) {
4021 for (propidx = sindex; propidx < TEXT_PROP_LAST;
4022 propidx++) {
4023 if (size > 0) {
4024 im->text_prop[propidx].size = size;
4025 }
4026 if (strlen(font) > 0) {
4027 strcpy(im->text_prop[propidx].font, font);
4028 }
4029 if (propidx == sindex && sindex != 0)
4030 break;
4031 }
4032 } else {
4033 rrd_set_error("invalid fonttag '%s'", prop);
4034 return;
4035 }
4036 } else {
4037 rrd_set_error("invalid text property format");
4038 return;
4039 }
4040 break;
4041 }
4042 case 'm':
4043 im->zoom = atof(optarg);
4044 if (im->zoom <= 0.0) {
4045 rrd_set_error("zoom factor must be > 0");
4046 return;
4047 }
4048 break;
4049 case 't':
4050 strncpy(im->title, optarg, 150);
4051 im->title[150] = '\0';
4052 break;
4054 case 'R':
4055 if (strcmp(optarg, "normal") == 0) {
4056 cairo_font_options_set_antialias(im->font_options,
4057 CAIRO_ANTIALIAS_GRAY);
4058 cairo_font_options_set_hint_style(im->font_options,
4059 CAIRO_HINT_STYLE_FULL);
4060 } else if (strcmp(optarg, "light") == 0) {
4061 cairo_font_options_set_antialias(im->font_options,
4062 CAIRO_ANTIALIAS_GRAY);
4063 cairo_font_options_set_hint_style(im->font_options,
4064 CAIRO_HINT_STYLE_SLIGHT);
4065 } else if (strcmp(optarg, "mono") == 0) {
4066 cairo_font_options_set_antialias(im->font_options,
4067 CAIRO_ANTIALIAS_NONE);
4068 cairo_font_options_set_hint_style(im->font_options,
4069 CAIRO_HINT_STYLE_FULL);
4070 } else {
4071 rrd_set_error("unknown font-render-mode '%s'", optarg);
4072 return;
4073 }
4074 break;
4075 case 'G':
4076 if (strcmp(optarg, "normal") == 0)
4077 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4078 else if (strcmp(optarg, "mono") == 0)
4079 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4080 else {
4081 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4082 return;
4083 }
4084 break;
4085 case 'B':
4086 /* not supported curently */
4087 break;
4089 case 'W':
4090 strncpy(im->watermark, optarg, 100);
4091 im->watermark[99] = '\0';
4092 break;
4094 case '?':
4095 if (optopt != 0)
4096 rrd_set_error("unknown option '%c'", optopt);
4097 else
4098 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4099 return;
4100 }
4101 }
4103 if (im->logarithmic == 1 && im->minval <= 0) {
4104 rrd_set_error
4105 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4106 return;
4107 }
4109 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4110 /* error string is set in parsetime.c */
4111 return;
4112 }
4114 if (start_tmp < 3600 * 24 * 365 * 10) {
4115 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4116 start_tmp);
4117 return;
4118 }
4120 if (end_tmp < start_tmp) {
4121 rrd_set_error("start (%ld) should be less than end (%ld)",
4122 start_tmp, end_tmp);
4123 return;
4124 }
4126 im->start = start_tmp;
4127 im->end = end_tmp;
4128 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4129 }
4131 int rrd_graph_color(
4132 image_desc_t *im,
4133 char *var,
4134 char *err,
4135 int optional)
4136 {
4137 char *color;
4138 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4140 color = strstr(var, "#");
4141 if (color == NULL) {
4142 if (optional == 0) {
4143 rrd_set_error("Found no color in %s", err);
4144 return 0;
4145 }
4146 return 0;
4147 } else {
4148 int n = 0;
4149 char *rest;
4150 long unsigned int col;
4152 rest = strstr(color, ":");
4153 if (rest != NULL)
4154 n = rest - color;
4155 else
4156 n = strlen(color);
4158 switch (n) {
4159 case 7:
4160 sscanf(color, "#%6lx%n", &col, &n);
4161 col = (col << 8) + 0xff /* shift left by 8 */ ;
4162 if (n != 7)
4163 rrd_set_error("Color problem in %s", err);
4164 break;
4165 case 9:
4166 sscanf(color, "#%8lx%n", &col, &n);
4167 if (n == 9)
4168 break;
4169 default:
4170 rrd_set_error("Color problem in %s", err);
4171 }
4172 if (rrd_test_error())
4173 return 0;
4174 gdp->col = gfx_hex_to_col(col);
4175 return n;
4176 }
4177 }
4180 int bad_format(
4181 char *fmt)
4182 {
4183 char *ptr;
4184 int n = 0;
4186 ptr = fmt;
4187 while (*ptr != '\0')
4188 if (*ptr++ == '%') {
4190 /* line cannot end with percent char */
4191 if (*ptr == '\0')
4192 return 1;
4194 /* '%s', '%S' and '%%' are allowed */
4195 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4196 ptr++;
4198 /* %c is allowed (but use only with vdef!) */
4199 else if (*ptr == 'c') {
4200 ptr++;
4201 n = 1;
4202 }
4204 /* or else '% 6.2lf' and such are allowed */
4205 else {
4206 /* optional padding character */
4207 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4208 ptr++;
4210 /* This should take care of 'm.n' with all three optional */
4211 while (*ptr >= '0' && *ptr <= '9')
4212 ptr++;
4213 if (*ptr == '.')
4214 ptr++;
4215 while (*ptr >= '0' && *ptr <= '9')
4216 ptr++;
4218 /* Either 'le', 'lf' or 'lg' must follow here */
4219 if (*ptr++ != 'l')
4220 return 1;
4221 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4222 ptr++;
4223 else
4224 return 1;
4225 n++;
4226 }
4227 }
4229 return (n != 1);
4230 }
4233 int vdef_parse(
4234 gdes,
4235 str)
4236 struct graph_desc_t *gdes;
4237 const char *const str;
4238 {
4239 /* A VDEF currently is either "func" or "param,func"
4240 * so the parsing is rather simple. Change if needed.
4241 */
4242 double param;
4243 char func[30];
4244 int n;
4246 n = 0;
4247 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4248 if (n == (int) strlen(str)) { /* matched */
4249 ;
4250 } else {
4251 n = 0;
4252 sscanf(str, "%29[A-Z]%n", func, &n);
4253 if (n == (int) strlen(str)) { /* matched */
4254 param = DNAN;
4255 } else {
4256 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4257 gdes->vname);
4258 return -1;
4259 }
4260 }
4261 if (!strcmp("PERCENT", func))
4262 gdes->vf.op = VDEF_PERCENT;
4263 else if (!strcmp("MAXIMUM", func))
4264 gdes->vf.op = VDEF_MAXIMUM;
4265 else if (!strcmp("AVERAGE", func))
4266 gdes->vf.op = VDEF_AVERAGE;
4267 else if (!strcmp("MINIMUM", func))
4268 gdes->vf.op = VDEF_MINIMUM;
4269 else if (!strcmp("TOTAL", func))
4270 gdes->vf.op = VDEF_TOTAL;
4271 else if (!strcmp("FIRST", func))
4272 gdes->vf.op = VDEF_FIRST;
4273 else if (!strcmp("LAST", func))
4274 gdes->vf.op = VDEF_LAST;
4275 else if (!strcmp("LSLSLOPE", func))
4276 gdes->vf.op = VDEF_LSLSLOPE;
4277 else if (!strcmp("LSLINT", func))
4278 gdes->vf.op = VDEF_LSLINT;
4279 else if (!strcmp("LSLCORREL", func))
4280 gdes->vf.op = VDEF_LSLCORREL;
4281 else {
4282 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4283 gdes->vname);
4284 return -1;
4285 };
4287 switch (gdes->vf.op) {
4288 case VDEF_PERCENT:
4289 if (isnan(param)) { /* no parameter given */
4290 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4291 func, gdes->vname);
4292 return -1;
4293 };
4294 if (param >= 0.0 && param <= 100.0) {
4295 gdes->vf.param = param;
4296 gdes->vf.val = DNAN; /* undefined */
4297 gdes->vf.when = 0; /* undefined */
4298 } else {
4299 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4300 gdes->vname);
4301 return -1;
4302 };
4303 break;
4304 case VDEF_MAXIMUM:
4305 case VDEF_AVERAGE:
4306 case VDEF_MINIMUM:
4307 case VDEF_TOTAL:
4308 case VDEF_FIRST:
4309 case VDEF_LAST:
4310 case VDEF_LSLSLOPE:
4311 case VDEF_LSLINT:
4312 case VDEF_LSLCORREL:
4313 if (isnan(param)) {
4314 gdes->vf.param = DNAN;
4315 gdes->vf.val = DNAN;
4316 gdes->vf.when = 0;
4317 } else {
4318 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4319 func, gdes->vname);
4320 return -1;
4321 };
4322 break;
4323 };
4324 return 0;
4325 }
4328 int vdef_calc(
4329 im,
4330 gdi)
4331 image_desc_t *im;
4332 int gdi;
4333 {
4334 graph_desc_t *src, *dst;
4335 rrd_value_t *data;
4336 long step, steps;
4338 dst = &im->gdes[gdi];
4339 src = &im->gdes[dst->vidx];
4340 data = src->data + src->ds;
4341 steps = (src->end - src->start) / src->step;
4343 #if 0
4344 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4345 src->end, steps);
4346 #endif
4348 switch (dst->vf.op) {
4349 case VDEF_PERCENT:{
4350 rrd_value_t *array;
4351 int field;
4354 if ((array = malloc(steps * sizeof(double))) == NULL) {
4355 rrd_set_error("malloc VDEV_PERCENT");
4356 return -1;
4357 }
4358 for (step = 0; step < steps; step++) {
4359 array[step] = data[step * src->ds_cnt];
4360 }
4361 qsort(array, step, sizeof(double), vdef_percent_compar);
4363 field = (steps - 1) * dst->vf.param / 100;
4364 dst->vf.val = array[field];
4365 dst->vf.when = 0; /* no time component */
4366 free(array);
4367 #if 0
4368 for (step = 0; step < steps; step++)
4369 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4370 step == field ? '*' : ' ');
4371 #endif
4372 }
4373 break;
4374 case VDEF_MAXIMUM:
4375 step = 0;
4376 while (step != steps && isnan(data[step * src->ds_cnt]))
4377 step++;
4378 if (step == steps) {
4379 dst->vf.val = DNAN;
4380 dst->vf.when = 0;
4381 } else {
4382 dst->vf.val = data[step * src->ds_cnt];
4383 dst->vf.when = src->start + (step + 1) * src->step;
4384 }
4385 while (step != steps) {
4386 if (finite(data[step * src->ds_cnt])) {
4387 if (data[step * src->ds_cnt] > dst->vf.val) {
4388 dst->vf.val = data[step * src->ds_cnt];
4389 dst->vf.when = src->start + (step + 1) * src->step;
4390 }
4391 }
4392 step++;
4393 }
4394 break;
4395 case VDEF_TOTAL:
4396 case VDEF_AVERAGE:{
4397 int cnt = 0;
4398 double sum = 0.0;
4400 for (step = 0; step < steps; step++) {
4401 if (finite(data[step * src->ds_cnt])) {
4402 sum += data[step * src->ds_cnt];
4403 cnt++;
4404 };
4405 }
4406 if (cnt) {
4407 if (dst->vf.op == VDEF_TOTAL) {
4408 dst->vf.val = sum * src->step;
4409 dst->vf.when = 0; /* no time component */
4410 } else {
4411 dst->vf.val = sum / cnt;
4412 dst->vf.when = 0; /* no time component */
4413 };
4414 } else {
4415 dst->vf.val = DNAN;
4416 dst->vf.when = 0;
4417 }
4418 }
4419 break;
4420 case VDEF_MINIMUM:
4421 step = 0;
4422 while (step != steps && isnan(data[step * src->ds_cnt]))
4423 step++;
4424 if (step == steps) {
4425 dst->vf.val = DNAN;
4426 dst->vf.when = 0;
4427 } else {
4428 dst->vf.val = data[step * src->ds_cnt];
4429 dst->vf.when = src->start + (step + 1) * src->step;
4430 }
4431 while (step != steps) {
4432 if (finite(data[step * src->ds_cnt])) {
4433 if (data[step * src->ds_cnt] < dst->vf.val) {
4434 dst->vf.val = data[step * src->ds_cnt];
4435 dst->vf.when = src->start + (step + 1) * src->step;
4436 }
4437 }
4438 step++;
4439 }
4440 break;
4441 case VDEF_FIRST:
4442 /* The time value returned here is one step before the
4443 * actual time value. This is the start of the first
4444 * non-NaN interval.
4445 */
4446 step = 0;
4447 while (step != steps && isnan(data[step * src->ds_cnt]))
4448 step++;
4449 if (step == steps) { /* all entries were NaN */
4450 dst->vf.val = DNAN;
4451 dst->vf.when = 0;
4452 } else {
4453 dst->vf.val = data[step * src->ds_cnt];
4454 dst->vf.when = src->start + step * src->step;
4455 }
4456 break;
4457 case VDEF_LAST:
4458 /* The time value returned here is the
4459 * actual time value. This is the end of the last
4460 * non-NaN interval.
4461 */
4462 step = steps - 1;
4463 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4464 step--;
4465 if (step < 0) { /* all entries were NaN */
4466 dst->vf.val = DNAN;
4467 dst->vf.when = 0;
4468 } else {
4469 dst->vf.val = data[step * src->ds_cnt];
4470 dst->vf.when = src->start + (step + 1) * src->step;
4471 }
4472 break;
4473 case VDEF_LSLSLOPE:
4474 case VDEF_LSLINT:
4475 case VDEF_LSLCORREL:{
4476 /* Bestfit line by linear least squares method */
4478 int cnt = 0;
4479 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4481 SUMx = 0;
4482 SUMy = 0;
4483 SUMxy = 0;
4484 SUMxx = 0;
4485 SUMyy = 0;
4487 for (step = 0; step < steps; step++) {
4488 if (finite(data[step * src->ds_cnt])) {
4489 cnt++;
4490 SUMx += step;
4491 SUMxx += step * step;
4492 SUMxy += step * data[step * src->ds_cnt];
4493 SUMy += data[step * src->ds_cnt];
4494 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4495 };
4496 }
4498 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4499 y_intercept = (SUMy - slope * SUMx) / cnt;
4500 correl =
4501 (SUMxy -
4502 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4503 (SUMx * SUMx) / cnt) * (SUMyy -
4504 (SUMy *
4505 SUMy) /
4506 cnt));
4508 if (cnt) {
4509 if (dst->vf.op == VDEF_LSLSLOPE) {
4510 dst->vf.val = slope;
4511 dst->vf.when = 0;
4512 } else if (dst->vf.op == VDEF_LSLINT) {
4513 dst->vf.val = y_intercept;
4514 dst->vf.when = 0;
4515 } else if (dst->vf.op == VDEF_LSLCORREL) {
4516 dst->vf.val = correl;
4517 dst->vf.when = 0;
4518 };
4520 } else {
4521 dst->vf.val = DNAN;
4522 dst->vf.when = 0;
4523 }
4524 }
4525 break;
4526 }
4527 return 0;
4528 }
4530 /* NaN < -INF < finite_values < INF */
4531 int vdef_percent_compar(
4532 a,
4533 b)
4534 const void *a, *b;
4535 {
4536 /* Equality is not returned; this doesn't hurt except
4537 * (maybe) for a little performance.
4538 */
4540 /* First catch NaN values. They are smallest */
4541 if (isnan(*(double *) a))
4542 return -1;
4543 if (isnan(*(double *) b))
4544 return 1;
4546 /* NaN doesn't reach this part so INF and -INF are extremes.
4547 * The sign from isinf() is compatible with the sign we return
4548 */
4549 if (isinf(*(double *) a))
4550 return isinf(*(double *) a);
4551 if (isinf(*(double *) b))
4552 return isinf(*(double *) b);
4554 /* If we reach this, both values must be finite */
4555 if (*(double *) a < *(double *) b)
4556 return -1;
4557 else
4558 return 1;
4559 }