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