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