5b185956c44340275b5b2c4c62697f08d29e163d
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);
1346 /* let mktime figure this dst on its own */
1347 tm.tm_isdst = -1;
1349 switch (baseint) {
1350 case TMT_SECOND:
1351 tm. tm_sec -= tm.tm_sec % basestep;
1353 break;
1354 case TMT_MINUTE:
1355 tm. tm_sec = 0;
1356 tm. tm_min -= tm.tm_min % basestep;
1358 break;
1359 case TMT_HOUR:
1360 tm. tm_sec = 0;
1361 tm. tm_min = 0;
1362 tm. tm_hour -= tm.tm_hour % basestep;
1364 break;
1365 case TMT_DAY:
1366 /* we do NOT look at the basestep for this ... */
1367 tm. tm_sec = 0;
1368 tm. tm_min = 0;
1369 tm. tm_hour = 0;
1371 break;
1372 case TMT_WEEK:
1373 /* we do NOT look at the basestep for this ... */
1374 tm. tm_sec = 0;
1375 tm. tm_min = 0;
1376 tm. tm_hour = 0;
1377 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1379 if (tm.tm_wday == 0)
1380 tm. tm_mday -= 7; /* we want the *previous* monday */
1382 break;
1383 case TMT_MONTH:
1384 tm. tm_sec = 0;
1385 tm. tm_min = 0;
1386 tm. tm_hour = 0;
1387 tm. tm_mday = 1;
1388 tm. tm_mon -= tm.tm_mon % basestep;
1390 break;
1392 case TMT_YEAR:
1393 tm. tm_sec = 0;
1394 tm. tm_min = 0;
1395 tm. tm_hour = 0;
1396 tm. tm_mday = 1;
1397 tm. tm_mon = 0;
1398 tm. tm_year -= (
1399 tm.tm_year + 1900) %basestep;
1401 }
1402 return mktime(&tm);
1403 }
1405 /* identify the point where the next gridline, label ... gets placed */
1406 time_t find_next_time(
1407 time_t current, /* what is the initial time */
1408 enum tmt_en baseint, /* what is the basic interval */
1409 long basestep /* how many if these do we jump a time */
1410 )
1411 {
1412 struct tm tm;
1413 time_t madetime;
1415 localtime_r(¤t, &tm);
1416 /* let mktime figure this dst on its own */
1417 tm.tm_isdst = -1;
1419 do {
1420 switch (baseint) {
1421 case TMT_SECOND:
1422 tm. tm_sec += basestep;
1424 break;
1425 case TMT_MINUTE:
1426 tm. tm_min += basestep;
1428 break;
1429 case TMT_HOUR:
1430 tm. tm_hour += basestep;
1432 break;
1433 case TMT_DAY:
1434 tm. tm_mday += basestep;
1436 break;
1437 case TMT_WEEK:
1438 tm. tm_mday += 7 * basestep;
1440 break;
1441 case TMT_MONTH:
1442 tm. tm_mon += basestep;
1444 break;
1445 case TMT_YEAR:
1446 tm. tm_year += basestep;
1447 }
1448 madetime = mktime(&tm);
1449 } while (madetime == -1); /* this is necessary to skip impssible times
1450 like the daylight saving time skips */
1451 return madetime;
1453 }
1456 /* calculate values required for PRINT and GPRINT functions */
1458 int print_calc(
1459 image_desc_t *im)
1460 {
1461 long i, ii, validsteps;
1462 double printval;
1463 struct tm tmvdef;
1464 int graphelement = 0;
1465 long vidx;
1466 int max_ii;
1467 double magfact = -1;
1468 char *si_symb = "";
1469 char *percent_s;
1470 int prline_cnt = 0;
1472 /* wow initializing tmvdef is quite a task :-) */
1473 time_t now = time(NULL);
1475 localtime_r(&now, &tmvdef);
1476 for (i = 0; i < im->gdes_c; i++) {
1477 vidx = im->gdes[i].vidx;
1478 switch (im->gdes[i].gf) {
1479 case GF_PRINT:
1480 case GF_GPRINT:
1481 /* PRINT and GPRINT can now print VDEF generated values.
1482 * There's no need to do any calculations on them as these
1483 * calculations were already made.
1484 */
1485 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1486 printval = im->gdes[vidx].vf.val;
1487 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1488 } else { /* need to calculate max,min,avg etcetera */
1489 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1490 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1491 printval = DNAN;
1492 validsteps = 0;
1493 for (ii = im->gdes[vidx].ds;
1494 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1495 if (!finite(im->gdes[vidx].data[ii]))
1496 continue;
1497 if (isnan(printval)) {
1498 printval = im->gdes[vidx].data[ii];
1499 validsteps++;
1500 continue;
1501 }
1503 switch (im->gdes[i].cf) {
1504 case CF_HWPREDICT:
1505 case CF_MHWPREDICT:
1506 case CF_DEVPREDICT:
1507 case CF_DEVSEASONAL:
1508 case CF_SEASONAL:
1509 case CF_AVERAGE:
1510 validsteps++;
1511 printval += im->gdes[vidx].data[ii];
1512 break;
1513 case CF_MINIMUM:
1514 printval = min(printval, im->gdes[vidx].data[ii]);
1515 break;
1516 case CF_FAILURES:
1517 case CF_MAXIMUM:
1518 printval = max(printval, im->gdes[vidx].data[ii]);
1519 break;
1520 case CF_LAST:
1521 printval = im->gdes[vidx].data[ii];
1522 }
1523 }
1524 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1525 if (validsteps > 1) {
1526 printval = (printval / validsteps);
1527 }
1528 }
1529 } /* prepare printval */
1531 if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1532 /* Magfact is set to -1 upon entry to print_calc. If it
1533 * is still less than 0, then we need to run auto_scale.
1534 * Otherwise, put the value into the correct units. If
1535 * the value is 0, then do not set the symbol or magnification
1536 * so next the calculation will be performed again. */
1537 if (magfact < 0.0) {
1538 auto_scale(im, &printval, &si_symb, &magfact);
1539 if (printval == 0.0)
1540 magfact = -1.0;
1541 } else {
1542 printval /= magfact;
1543 }
1544 *(++percent_s) = 's';
1545 } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1546 auto_scale(im, &printval, &si_symb, &magfact);
1547 }
1549 if (im->gdes[i].gf == GF_PRINT) {
1550 rrd_infoval_t prline;
1552 if (im->gdes[i].strftm) {
1553 prline.u_str = (char*)(malloc((FMT_LEG_LEN + 2) * sizeof(char)));
1554 strftime(prline.u_str,
1555 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1556 } else if (bad_format(im->gdes[i].format)) {
1557 rrd_set_error
1558 ("bad format for PRINT in '%s'", im->gdes[i].format);
1559 return -1;
1560 } else {
1561 prline.u_str =
1562 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1563 }
1564 grinfo_push(im,
1565 sprintf_alloc
1566 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1567 free(prline.u_str);
1568 } else {
1569 /* GF_GPRINT */
1571 if (im->gdes[i].strftm) {
1572 strftime(im->gdes[i].legend,
1573 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1574 } else {
1575 if (bad_format(im->gdes[i].format)) {
1576 rrd_set_error
1577 ("bad format for GPRINT in '%s'",
1578 im->gdes[i].format);
1579 return -1;
1580 }
1581 #ifdef HAVE_SNPRINTF
1582 snprintf(im->gdes[i].legend,
1583 FMT_LEG_LEN - 2,
1584 im->gdes[i].format, printval, si_symb);
1585 #else
1586 sprintf(im->gdes[i].legend,
1587 im->gdes[i].format, printval, si_symb);
1588 #endif
1589 }
1590 graphelement = 1;
1591 }
1592 break;
1593 case GF_LINE:
1594 case GF_AREA:
1595 case GF_TICK:
1596 graphelement = 1;
1597 break;
1598 case GF_HRULE:
1599 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1600 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1601 };
1602 graphelement = 1;
1603 break;
1604 case GF_VRULE:
1605 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1606 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1607 };
1608 graphelement = 1;
1609 break;
1610 case GF_COMMENT:
1611 case GF_TEXTALIGN:
1612 case GF_DEF:
1613 case GF_CDEF:
1614 case GF_VDEF:
1615 #ifdef WITH_PIECHART
1616 case GF_PART:
1617 #endif
1618 case GF_SHIFT:
1619 case GF_XPORT:
1620 break;
1621 case GF_STACK:
1622 rrd_set_error
1623 ("STACK should already be turned into LINE or AREA here");
1624 return -1;
1625 break;
1626 }
1627 }
1628 return graphelement;
1629 }
1632 /* place legends with color spots */
1633 int leg_place(
1634 image_desc_t *im,
1635 int *gY)
1636 {
1637 /* graph labels */
1638 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1639 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1640 int fill = 0, fill_last;
1641 int leg_c = 0;
1642 double leg_x = border;
1643 int leg_y = im->yimg;
1644 int leg_y_prev = im->yimg;
1645 int leg_cc;
1646 double glue = 0;
1647 int i, ii, mark = 0;
1648 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1649 int *legspace;
1650 char *tab;
1652 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1653 if ((legspace = (int*)(malloc(im->gdes_c * sizeof(int)))) == NULL) {
1654 rrd_set_error("malloc for legspace");
1655 return -1;
1656 }
1658 for (i = 0; i < im->gdes_c; i++) {
1659 char prt_fctn; /*special printfunctions */
1660 fill_last = fill;
1661 /* hide legends for rules which are not displayed */
1662 if (im->gdes[i].gf == GF_TEXTALIGN) {
1663 default_txtalign = im->gdes[i].txtalign;
1664 }
1666 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1667 if (im->gdes[i].gf == GF_HRULE
1668 && (im->gdes[i].yrule <
1669 im->minval || im->gdes[i].yrule > im->maxval))
1670 im->gdes[i].legend[0] = '\0';
1671 if (im->gdes[i].gf == GF_VRULE
1672 && (im->gdes[i].xrule <
1673 im->start || im->gdes[i].xrule > im->end))
1674 im->gdes[i].legend[0] = '\0';
1675 }
1677 /* turn \\t into tab */
1678 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1679 memmove(tab, tab + 1, strlen(tab));
1680 tab[0] = (char) 9;
1681 }
1682 leg_cc = strlen(im->gdes[i].legend);
1683 /* is there a controle code at the end of the legend string ? */
1684 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1685 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1686 leg_cc -= 2;
1687 im->gdes[i].legend[leg_cc] = '\0';
1688 } else {
1689 prt_fctn = '\0';
1690 }
1691 /* only valid control codes */
1692 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1693 prt_fctn != 'r' &&
1694 prt_fctn != 'j' &&
1695 prt_fctn != 'c' &&
1696 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1697 free(legspace);
1698 rrd_set_error
1699 ("Unknown control code at the end of '%s\\%c'",
1700 im->gdes[i].legend, prt_fctn);
1701 return -1;
1702 }
1703 /* \n -> \l */
1704 if (prt_fctn == 'n') {
1705 prt_fctn = 'l';
1706 }
1708 /* remove exess space from the end of the legend for \g */
1709 while (prt_fctn == 'g' &&
1710 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1711 leg_cc--;
1712 im->gdes[i].legend[leg_cc] = '\0';
1713 }
1715 if (leg_cc != 0) {
1717 /* no interleg space if string ends in \g */
1718 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1719 if (fill > 0) {
1720 fill += legspace[i];
1721 }
1722 fill +=
1723 gfx_get_text_width(im,
1724 fill + border,
1725 im->
1726 text_prop
1727 [TEXT_PROP_LEGEND].
1728 font_desc,
1729 im->tabwidth, im->gdes[i].legend);
1730 leg_c++;
1731 } else {
1732 legspace[i] = 0;
1733 }
1734 /* who said there was a special tag ... ? */
1735 if (prt_fctn == 'g') {
1736 prt_fctn = '\0';
1737 }
1739 if (prt_fctn == '\0') {
1740 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1741 /* just one legend item is left right or center */
1742 switch (default_txtalign) {
1743 case TXA_RIGHT:
1744 prt_fctn = 'r';
1745 break;
1746 case TXA_CENTER:
1747 prt_fctn = 'c';
1748 break;
1749 case TXA_JUSTIFIED:
1750 prt_fctn = 'j';
1751 break;
1752 default:
1753 prt_fctn = 'l';
1754 break;
1755 }
1756 }
1757 /* is it time to place the legends ? */
1758 if (fill > im->ximg - 2 * border) {
1759 if (leg_c > 1) {
1760 /* go back one */
1761 i--;
1762 fill = fill_last;
1763 leg_c--;
1764 }
1765 }
1766 if (leg_c == 1 && prt_fctn == 'j') {
1767 prt_fctn = 'l';
1768 }
1769 }
1772 if (prt_fctn != '\0') {
1773 leg_x = border;
1774 if (leg_c >= 2 && prt_fctn == 'j') {
1775 glue = (double)(im->ximg - fill - 2 * border) / (double)(leg_c - 1);
1776 } else {
1777 glue = 0;
1778 }
1779 if (prt_fctn == 'c')
1780 leg_x = (double)(im->ximg - fill) / 2.0;
1781 if (prt_fctn == 'r')
1782 leg_x = im->ximg - fill - border;
1783 for (ii = mark; ii <= i; ii++) {
1784 if (im->gdes[ii].legend[0] == '\0')
1785 continue; /* skip empty legends */
1786 im->gdes[ii].leg_x = leg_x;
1787 im->gdes[ii].leg_y = leg_y;
1788 leg_x +=
1789 (double)gfx_get_text_width(im, leg_x,
1790 im->
1791 text_prop
1792 [TEXT_PROP_LEGEND].
1793 font_desc,
1794 im->tabwidth, im->gdes[ii].legend)
1795 +(double)legspace[ii]
1796 + glue;
1797 }
1798 leg_y_prev = leg_y;
1799 if (leg_x > border || prt_fctn == 's')
1800 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1801 if (prt_fctn == 's')
1802 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1803 fill = 0;
1804 leg_c = 0;
1805 mark = ii;
1806 }
1807 }
1809 if (im->extra_flags & FULL_SIZE_MODE) {
1810 /* now for some backpaddeling. We have to shift up all the
1811 legend items into the graph and tell the caller about the
1812 space we used up. */
1813 long shift_up = leg_y - im->yimg - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 + border * 0.7;
1814 for (i = 0; i < im->gdes_c; i++) {
1815 im->gdes[i].leg_y -= shift_up;
1816 }
1817 im->yorigin = im->yorigin - leg_y + im->yimg - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 - border;
1818 *gY = im->yorigin;
1819 } else {
1820 im->yimg =
1821 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1822 border * 0.6;
1823 }
1824 free(legspace);
1825 }
1826 return 0;
1827 }
1829 /* create a grid on the graph. it determines what to do
1830 from the values of xsize, start and end */
1832 /* the xaxis labels are determined from the number of seconds per pixel
1833 in the requested graph */
1835 int calc_horizontal_grid(
1836 image_desc_t
1837 *im)
1838 {
1839 double range;
1840 double scaledrange;
1841 int pixel, i;
1842 int gridind = 0;
1843 int decimals, fractionals;
1845 im->ygrid_scale.labfact = 2;
1846 range = im->maxval - im->minval;
1847 scaledrange = range / im->magfact;
1848 /* does the scale of this graph make it impossible to put lines
1849 on it? If so, give up. */
1850 if (isnan(scaledrange)) {
1851 return 0;
1852 }
1854 /* find grid spaceing */
1855 pixel = 1;
1856 if (isnan(im->ygridstep)) {
1857 if (im->extra_flags & ALTYGRID) {
1858 /* find the value with max number of digits. Get number of digits */
1859 decimals =
1860 ceil(log10
1861 (max(fabs(im->maxval), fabs(im->minval)) *
1862 im->viewfactor / im->magfact));
1863 if (decimals <= 0) /* everything is small. make place for zero */
1864 decimals = 1;
1865 im->ygrid_scale.gridstep =
1866 pow((double) 10,
1867 floor(log10(range * im->viewfactor / im->magfact))) /
1868 im->viewfactor * im->magfact;
1869 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1870 im->ygrid_scale.gridstep = 0.1;
1871 /* should have at least 5 lines but no more then 15 */
1872 if (range / im->ygrid_scale.gridstep < 5
1873 && im->ygrid_scale.gridstep >= 30)
1874 im->ygrid_scale.gridstep /= 10;
1875 if (range / im->ygrid_scale.gridstep > 15)
1876 im->ygrid_scale.gridstep *= 10;
1877 if (range / im->ygrid_scale.gridstep > 5) {
1878 im->ygrid_scale.labfact = 1;
1879 if (range / im->ygrid_scale.gridstep > 8
1880 || im->ygrid_scale.gridstep <
1881 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1882 im->ygrid_scale.labfact = 2;
1883 } else {
1884 im->ygrid_scale.gridstep /= 5;
1885 im->ygrid_scale.labfact = 5;
1886 }
1887 fractionals =
1888 floor(log10
1889 (im->ygrid_scale.gridstep *
1890 (double) im->ygrid_scale.labfact * im->viewfactor /
1891 im->magfact));
1892 if (fractionals < 0) { /* small amplitude. */
1893 int len = decimals - fractionals + 1;
1895 if (im->unitslength < len + 2)
1896 im->unitslength = len + 2;
1897 sprintf(im->ygrid_scale.labfmt,
1898 "%%%d.%df%s", len,
1899 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1900 } else {
1901 int len = decimals + 1;
1903 if (im->unitslength < len + 2)
1904 im->unitslength = len + 2;
1905 sprintf(im->ygrid_scale.labfmt,
1906 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1907 }
1908 } else { /* classic rrd grid */
1909 for (i = 0; ylab[i].grid > 0; i++) {
1910 pixel = im->ysize / (scaledrange / ylab[i].grid);
1911 gridind = i;
1912 if (pixel >= 5)
1913 break;
1914 }
1916 for (i = 0; i < 4; i++) {
1917 if (pixel * ylab[gridind].lfac[i] >=
1918 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1919 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1920 break;
1921 }
1922 }
1924 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1925 }
1926 } else {
1927 im->ygrid_scale.gridstep = im->ygridstep;
1928 im->ygrid_scale.labfact = im->ylabfact;
1929 }
1930 return 1;
1931 }
1933 int draw_horizontal_grid(
1934 image_desc_t
1935 *im)
1936 {
1937 int i;
1938 double scaledstep;
1939 char graph_label[100];
1940 int nlabels = 0;
1941 double X0 = im->xorigin;
1942 double X1 = im->xorigin + im->xsize;
1943 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1944 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1945 double MaxY;
1946 double second_axis_magfact = 0;
1947 char *second_axis_symb = "";
1949 scaledstep =
1950 im->ygrid_scale.gridstep /
1951 (double) im->magfact * (double) im->viewfactor;
1952 MaxY = scaledstep * (double) egrid;
1953 for (i = sgrid; i <= egrid; i++) {
1954 double Y0 = ytr(im,
1955 im->ygrid_scale.gridstep * i);
1956 double YN = ytr(im,
1957 im->ygrid_scale.gridstep * (i + 1));
1959 if (floor(Y0 + 0.5) >=
1960 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1961 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1962 with the chosen settings. Add a label if required by settings, or if
1963 there is only one label so far and the next grid line is out of bounds. */
1964 if (i % im->ygrid_scale.labfact == 0
1965 || (nlabels == 1
1966 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1967 if (im->symbol == ' ') {
1968 if (im->extra_flags & ALTYGRID) {
1969 sprintf(graph_label,
1970 im->ygrid_scale.labfmt,
1971 scaledstep * (double) i);
1972 } else {
1973 if (MaxY < 10) {
1974 sprintf(graph_label, "%4.1f",
1975 scaledstep * (double) i);
1976 } else {
1977 sprintf(graph_label, "%4.0f",
1978 scaledstep * (double) i);
1979 }
1980 }
1981 } else {
1982 char sisym = (i == 0 ? ' ' : im->symbol);
1984 if (im->extra_flags & ALTYGRID) {
1985 sprintf(graph_label,
1986 im->ygrid_scale.labfmt,
1987 scaledstep * (double) i, sisym);
1988 } else {
1989 if (MaxY < 10) {
1990 sprintf(graph_label, "%4.1f %c",
1991 scaledstep * (double) i, sisym);
1992 } else {
1993 sprintf(graph_label, "%4.0f %c",
1994 scaledstep * (double) i, sisym);
1995 }
1996 }
1997 }
1998 nlabels++;
1999 if (im->second_axis_scale != 0){
2000 char graph_label_right[100];
2001 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2002 if (im->second_axis_format[0] == '\0'){
2003 if (!second_axis_magfact){
2004 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2005 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2006 }
2007 sval /= second_axis_magfact;
2009 if(MaxY < 10) {
2010 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2011 } else {
2012 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2013 }
2014 }
2015 else {
2016 sprintf(graph_label_right,im->second_axis_format,sval,"");
2017 }
2018 gfx_text ( im,
2019 X1+7, Y0,
2020 im->graph_col[GRC_FONT],
2021 im->text_prop[TEXT_PROP_AXIS].font_desc,
2022 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2023 graph_label_right );
2024 }
2026 gfx_text(im,
2027 X0 -
2028 im->
2029 text_prop[TEXT_PROP_AXIS].
2030 size, Y0,
2031 im->graph_col[GRC_FONT],
2032 im->
2033 text_prop[TEXT_PROP_AXIS].
2034 font_desc,
2035 im->tabwidth, 0.0,
2036 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2037 gfx_line(im, X0 - 2, Y0, X0, Y0,
2038 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2039 gfx_line(im, X1, Y0, X1 + 2, Y0,
2040 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2041 gfx_dashed_line(im, X0 - 2, Y0,
2042 X1 + 2, Y0,
2043 MGRIDWIDTH,
2044 im->
2045 graph_col
2046 [GRC_MGRID],
2047 im->grid_dash_on, im->grid_dash_off);
2048 } else if (!(im->extra_flags & NOMINOR)) {
2049 gfx_line(im,
2050 X0 - 2, Y0,
2051 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2052 gfx_line(im, X1, Y0, X1 + 2, Y0,
2053 GRIDWIDTH, im->graph_col[GRC_GRID]);
2054 gfx_dashed_line(im, X0 - 1, Y0,
2055 X1 + 1, Y0,
2056 GRIDWIDTH,
2057 im->
2058 graph_col[GRC_GRID],
2059 im->grid_dash_on, im->grid_dash_off);
2060 }
2061 }
2062 }
2063 return 1;
2064 }
2066 /* this is frexp for base 10 */
2067 double frexp10(
2068 double,
2069 double *);
2070 double frexp10(
2071 double x,
2072 double *e)
2073 {
2074 double mnt;
2075 int iexp;
2077 iexp = floor(log((double)(fabs(x))) / log(10.0));
2078 mnt = x / pow(10.0, iexp);
2079 if (mnt >= 10.0) {
2080 iexp++;
2081 mnt = x / pow(10.0, iexp);
2082 }
2083 *e = iexp;
2084 return mnt;
2085 }
2088 /* logaritmic horizontal grid */
2089 int horizontal_log_grid(
2090 image_desc_t
2091 *im)
2092 {
2093 double yloglab[][10] = {
2094 {
2095 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2096 0.0, 0.0, 0.0}, {
2097 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2098 0.0, 0.0, 0.0}, {
2099 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2100 0.0, 0.0, 0.0}, {
2101 1.0, 2.0, 4.0,
2102 6.0, 8.0, 10.,
2103 0.0,
2104 0.0, 0.0, 0.0}, {
2105 1.0,
2106 2.0,
2107 3.0,
2108 4.0,
2109 5.0,
2110 6.0,
2111 7.0,
2112 8.0,
2113 9.0,
2114 10.},
2115 {
2116 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2117 };
2118 int i, j, val_exp, min_exp;
2119 double nex; /* number of decades in data */
2120 double logscale; /* scale in logarithmic space */
2121 int exfrac = 1; /* decade spacing */
2122 int mid = -1; /* row in yloglab for major grid */
2123 double mspac; /* smallest major grid spacing (pixels) */
2124 int flab; /* first value in yloglab to use */
2125 double value, tmp, pre_value;
2126 double X0, X1, Y0;
2127 char graph_label[100];
2129 nex = log10(im->maxval / im->minval);
2130 logscale = im->ysize / nex;
2131 /* major spacing for data with high dynamic range */
2132 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2133 if (exfrac == 1)
2134 exfrac = 3;
2135 else
2136 exfrac += 3;
2137 }
2139 /* major spacing for less dynamic data */
2140 do {
2141 /* search best row in yloglab */
2142 mid++;
2143 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2144 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2145 }
2146 while (mspac >
2147 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2148 if (mid)
2149 mid--;
2150 /* find first value in yloglab */
2151 for (flab = 0;
2152 yloglab[mid][flab] < 10
2153 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2154 if (yloglab[mid][flab] == 10.0) {
2155 tmp += 1.0;
2156 flab = 0;
2157 }
2158 val_exp = tmp;
2159 if (val_exp % exfrac)
2160 val_exp += abs(-val_exp % exfrac);
2161 X0 = im->xorigin;
2162 X1 = im->xorigin + im->xsize;
2163 /* draw grid */
2164 pre_value = DNAN;
2165 while (1) {
2167 value = yloglab[mid][flab] * pow(10.0, val_exp);
2168 if (AlmostEqual2sComplement(value, pre_value, 4))
2169 break; /* it seems we are not converging */
2170 pre_value = value;
2171 Y0 = ytr(im, value);
2172 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2173 break;
2174 /* major grid line */
2175 gfx_line(im,
2176 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2177 gfx_line(im, X1, Y0, X1 + 2, Y0,
2178 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2179 gfx_dashed_line(im, X0 - 2, Y0,
2180 X1 + 2, Y0,
2181 MGRIDWIDTH,
2182 im->
2183 graph_col
2184 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2185 /* label */
2186 if (im->extra_flags & FORCE_UNITS_SI) {
2187 int scale;
2188 double pvalue;
2189 char symbol;
2191 scale = floor(val_exp / 3.0);
2192 if (value >= 1.0)
2193 pvalue = pow(10.0, val_exp % 3);
2194 else
2195 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2196 pvalue *= yloglab[mid][flab];
2197 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2198 && ((scale + si_symbcenter) >= 0))
2199 symbol = si_symbol[scale + si_symbcenter];
2200 else
2201 symbol = '?';
2202 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2203 } else {
2204 sprintf(graph_label, "%3.0e", value);
2205 }
2206 if (im->second_axis_scale != 0){
2207 char graph_label_right[100];
2208 double sval = value*im->second_axis_scale+im->second_axis_shift;
2209 if (im->second_axis_format[0] == '\0'){
2210 if (im->extra_flags & FORCE_UNITS_SI) {
2211 double mfac = 1;
2212 char *symb = "";
2213 auto_scale(im,&sval,&symb,&mfac);
2214 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2215 }
2216 else {
2217 sprintf(graph_label_right,"%3.0e", sval);
2218 }
2219 }
2220 else {
2221 sprintf(graph_label_right,im->second_axis_format,sval);
2222 }
2224 gfx_text ( im,
2225 X1+7, Y0,
2226 im->graph_col[GRC_FONT],
2227 im->text_prop[TEXT_PROP_AXIS].font_desc,
2228 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2229 graph_label_right );
2230 }
2232 gfx_text(im,
2233 X0 -
2234 im->
2235 text_prop[TEXT_PROP_AXIS].
2236 size, Y0,
2237 im->graph_col[GRC_FONT],
2238 im->
2239 text_prop[TEXT_PROP_AXIS].
2240 font_desc,
2241 im->tabwidth, 0.0,
2242 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2243 /* minor grid */
2244 if (mid < 4 && exfrac == 1) {
2245 /* find first and last minor line behind current major line
2246 * i is the first line and j tha last */
2247 if (flab == 0) {
2248 min_exp = val_exp - 1;
2249 for (i = 1; yloglab[mid][i] < 10.0; i++);
2250 i = yloglab[mid][i - 1] + 1;
2251 j = 10;
2252 } else {
2253 min_exp = val_exp;
2254 i = yloglab[mid][flab - 1] + 1;
2255 j = yloglab[mid][flab];
2256 }
2258 /* draw minor lines below current major line */
2259 for (; i < j; i++) {
2261 value = i * pow(10.0, min_exp);
2262 if (value < im->minval)
2263 continue;
2264 Y0 = ytr(im, value);
2265 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2266 break;
2267 /* draw lines */
2268 gfx_line(im,
2269 X0 - 2, Y0,
2270 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2271 gfx_line(im, X1, Y0, X1 + 2, Y0,
2272 GRIDWIDTH, im->graph_col[GRC_GRID]);
2273 gfx_dashed_line(im, X0 - 1, Y0,
2274 X1 + 1, Y0,
2275 GRIDWIDTH,
2276 im->
2277 graph_col[GRC_GRID],
2278 im->grid_dash_on, im->grid_dash_off);
2279 }
2280 } else if (exfrac > 1) {
2281 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2282 value = pow(10.0, i);
2283 if (value < im->minval)
2284 continue;
2285 Y0 = ytr(im, value);
2286 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2287 break;
2288 /* draw lines */
2289 gfx_line(im,
2290 X0 - 2, Y0,
2291 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2292 gfx_line(im, X1, Y0, X1 + 2, Y0,
2293 GRIDWIDTH, im->graph_col[GRC_GRID]);
2294 gfx_dashed_line(im, X0 - 1, Y0,
2295 X1 + 1, Y0,
2296 GRIDWIDTH,
2297 im->
2298 graph_col[GRC_GRID],
2299 im->grid_dash_on, im->grid_dash_off);
2300 }
2301 }
2303 /* next decade */
2304 if (yloglab[mid][++flab] == 10.0) {
2305 flab = 0;
2306 val_exp += exfrac;
2307 }
2308 }
2310 /* draw minor lines after highest major line */
2311 if (mid < 4 && exfrac == 1) {
2312 /* find first and last minor line below current major line
2313 * i is the first line and j tha last */
2314 if (flab == 0) {
2315 min_exp = val_exp - 1;
2316 for (i = 1; yloglab[mid][i] < 10.0; i++);
2317 i = yloglab[mid][i - 1] + 1;
2318 j = 10;
2319 } else {
2320 min_exp = val_exp;
2321 i = yloglab[mid][flab - 1] + 1;
2322 j = yloglab[mid][flab];
2323 }
2325 /* draw minor lines below current major line */
2326 for (; i < j; i++) {
2328 value = i * pow(10.0, min_exp);
2329 if (value < im->minval)
2330 continue;
2331 Y0 = ytr(im, value);
2332 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2333 break;
2334 /* draw lines */
2335 gfx_line(im,
2336 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2337 gfx_line(im, X1, Y0, X1 + 2, Y0,
2338 GRIDWIDTH, im->graph_col[GRC_GRID]);
2339 gfx_dashed_line(im, X0 - 1, Y0,
2340 X1 + 1, Y0,
2341 GRIDWIDTH,
2342 im->
2343 graph_col[GRC_GRID],
2344 im->grid_dash_on, im->grid_dash_off);
2345 }
2346 }
2347 /* fancy minor gridlines */
2348 else if (exfrac > 1) {
2349 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2350 value = pow(10.0, i);
2351 if (value < im->minval)
2352 continue;
2353 Y0 = ytr(im, value);
2354 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2355 break;
2356 /* draw lines */
2357 gfx_line(im,
2358 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2359 gfx_line(im, X1, Y0, X1 + 2, Y0,
2360 GRIDWIDTH, im->graph_col[GRC_GRID]);
2361 gfx_dashed_line(im, X0 - 1, Y0,
2362 X1 + 1, Y0,
2363 GRIDWIDTH,
2364 im->
2365 graph_col[GRC_GRID],
2366 im->grid_dash_on, im->grid_dash_off);
2367 }
2368 }
2370 return 1;
2371 }
2374 void vertical_grid(
2375 image_desc_t *im)
2376 {
2377 int xlab_sel; /* which sort of label and grid ? */
2378 time_t ti, tilab, timajor;
2379 long factor;
2380 char graph_label[100];
2381 double X0, Y0, Y1; /* points for filled graph and more */
2382 struct tm tm;
2384 /* the type of time grid is determined by finding
2385 the number of seconds per pixel in the graph */
2386 if (im->xlab_user.minsec == -1) {
2387 factor = (im->end - im->start) / im->xsize;
2388 xlab_sel = 0;
2389 while (xlab[xlab_sel + 1].minsec !=
2390 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2391 xlab_sel++;
2392 } /* pick the last one */
2393 while (xlab[xlab_sel - 1].minsec ==
2394 xlab[xlab_sel].minsec
2395 && xlab[xlab_sel].length > (im->end - im->start)) {
2396 xlab_sel--;
2397 } /* go back to the smallest size */
2398 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2399 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2400 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2401 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2402 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2403 im->xlab_user.labst = xlab[xlab_sel].labst;
2404 im->xlab_user.precis = xlab[xlab_sel].precis;
2405 im->xlab_user.stst = xlab[xlab_sel].stst;
2406 }
2408 /* y coords are the same for every line ... */
2409 Y0 = im->yorigin;
2410 Y1 = im->yorigin - im->ysize;
2411 /* paint the minor grid */
2412 if (!(im->extra_flags & NOMINOR)) {
2413 for (ti = find_first_time(im->start,
2414 im->
2415 xlab_user.
2416 gridtm,
2417 im->
2418 xlab_user.
2419 gridst),
2420 timajor =
2421 find_first_time(im->start,
2422 im->xlab_user.
2423 mgridtm,
2424 im->xlab_user.
2425 mgridst);
2426 ti < im->end;
2427 ti =
2428 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2429 ) {
2430 /* are we inside the graph ? */
2431 if (ti < im->start || ti > im->end)
2432 continue;
2433 while (timajor < ti) {
2434 timajor = find_next_time(timajor,
2435 im->
2436 xlab_user.
2437 mgridtm, im->xlab_user.mgridst);
2438 }
2439 if (ti == timajor)
2440 continue; /* skip as falls on major grid line */
2441 X0 = xtr(im, ti);
2442 gfx_line(im, X0, Y1 - 2, X0, Y1,
2443 GRIDWIDTH, im->graph_col[GRC_GRID]);
2444 gfx_line(im, X0, Y0, X0, Y0 + 2,
2445 GRIDWIDTH, im->graph_col[GRC_GRID]);
2446 gfx_dashed_line(im, X0, Y0 + 1, X0,
2447 Y1 - 1, GRIDWIDTH,
2448 im->
2449 graph_col[GRC_GRID],
2450 im->grid_dash_on, im->grid_dash_off);
2451 }
2452 }
2454 /* paint the major grid */
2455 for (ti = find_first_time(im->start,
2456 im->
2457 xlab_user.
2458 mgridtm,
2459 im->
2460 xlab_user.
2461 mgridst);
2462 ti < im->end;
2463 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2464 ) {
2465 /* are we inside the graph ? */
2466 if (ti < im->start || ti > im->end)
2467 continue;
2468 X0 = xtr(im, ti);
2469 gfx_line(im, X0, Y1 - 2, X0, Y1,
2470 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2471 gfx_line(im, X0, Y0, X0, Y0 + 3,
2472 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2473 gfx_dashed_line(im, X0, Y0 + 3, X0,
2474 Y1 - 2, MGRIDWIDTH,
2475 im->
2476 graph_col
2477 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2478 }
2479 /* paint the labels below the graph */
2480 for (ti =
2481 find_first_time(im->start -
2482 im->xlab_user.
2483 precis / 2,
2484 im->xlab_user.
2485 labtm,
2486 im->xlab_user.
2487 labst);
2488 ti <=
2489 im->end -
2490 im->xlab_user.precis / 2;
2491 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2492 ) {
2493 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2494 /* are we inside the graph ? */
2495 if (tilab < im->start || tilab > im->end)
2496 continue;
2497 #if HAVE_STRFTIME
2498 localtime_r(&tilab, &tm);
2499 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2500 #else
2501 # error "your libc has no strftime I guess we'll abort the exercise here."
2502 #endif
2503 gfx_text(im,
2504 xtr(im, tilab),
2505 Y0 + 3,
2506 im->graph_col[GRC_FONT],
2507 im->
2508 text_prop[TEXT_PROP_AXIS].
2509 font_desc,
2510 im->tabwidth, 0.0,
2511 GFX_H_CENTER, GFX_V_TOP, graph_label);
2512 }
2514 }
2517 void axis_paint(
2518 image_desc_t *im)
2519 {
2520 /* draw x and y axis */
2521 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2522 im->xorigin+im->xsize,im->yorigin-im->ysize,
2523 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2525 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2526 im->xorigin+im->xsize,im->yorigin-im->ysize,
2527 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2529 gfx_line(im, im->xorigin - 4,
2530 im->yorigin,
2531 im->xorigin + im->xsize +
2532 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2533 gfx_line(im, im->xorigin,
2534 im->yorigin + 4,
2535 im->xorigin,
2536 im->yorigin - im->ysize -
2537 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2538 /* arrow for X and Y axis direction */
2539 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 */
2540 im->graph_col[GRC_ARROW]);
2541 gfx_close_path(im);
2542 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 */
2543 im->graph_col[GRC_ARROW]);
2544 gfx_close_path(im);
2545 if (im->second_axis_scale != 0){
2546 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2547 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2548 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2549 gfx_new_area ( im,
2550 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2551 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2552 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2553 im->graph_col[GRC_ARROW]);
2554 gfx_close_path(im);
2555 }
2557 }
2559 void grid_paint(
2560 image_desc_t *im)
2561 {
2562 long i;
2563 int res = 0;
2564 double X0, Y0; /* points for filled graph and more */
2565 struct gfx_color_t water_color;
2567 /* draw 3d border */
2568 gfx_new_area(im, 0, im->yimg,
2569 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2570 gfx_add_point(im, im->ximg - 2, 2);
2571 gfx_add_point(im, im->ximg, 0);
2572 gfx_add_point(im, 0, 0);
2573 gfx_close_path(im);
2574 gfx_new_area(im, 2, im->yimg - 2,
2575 im->ximg - 2,
2576 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2577 gfx_add_point(im, im->ximg, 0);
2578 gfx_add_point(im, im->ximg, im->yimg);
2579 gfx_add_point(im, 0, im->yimg);
2580 gfx_close_path(im);
2581 if (im->draw_x_grid == 1)
2582 vertical_grid(im);
2583 if (im->draw_y_grid == 1) {
2584 if (im->logarithmic) {
2585 res = horizontal_log_grid(im);
2586 } else {
2587 res = draw_horizontal_grid(im);
2588 }
2590 /* dont draw horizontal grid if there is no min and max val */
2591 if (!res) {
2592 char *nodata = "No Data found";
2594 gfx_text(im, im->ximg / 2,
2595 (2 * im->yorigin -
2596 im->ysize) / 2,
2597 im->graph_col[GRC_FONT],
2598 im->
2599 text_prop[TEXT_PROP_AXIS].
2600 font_desc,
2601 im->tabwidth, 0.0,
2602 GFX_H_CENTER, GFX_V_CENTER, nodata);
2603 }
2604 }
2606 /* yaxis unit description */
2607 if (im->ylegend[0] != '\0'){
2608 gfx_text(im,
2609 10,
2610 (im->yorigin -
2611 im->ysize / 2),
2612 im->graph_col[GRC_FONT],
2613 im->
2614 text_prop[TEXT_PROP_UNIT].
2615 font_desc,
2616 im->tabwidth,
2617 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2618 }
2619 if (im->second_axis_legend[0] != '\0'){
2620 double Xylabel=gfx_get_text_width(im, 0,
2621 im->text_prop[TEXT_PROP_AXIS].font_desc,
2622 im->tabwidth,
2623 "0") * im->unitslength
2624 + im->text_prop[TEXT_PROP_UNIT].size *2;
2625 gfx_text( im,
2626 im->xorigin+im->xsize+Xylabel+8, (im->yorigin - im->ysize/2),
2627 im->graph_col[GRC_FONT],
2628 im->text_prop[TEXT_PROP_UNIT].font_desc,
2629 im->tabwidth,
2630 RRDGRAPH_YLEGEND_ANGLE,
2631 GFX_H_CENTER, GFX_V_CENTER,
2632 im->second_axis_legend);
2633 }
2635 /* graph title */
2636 gfx_text(im,
2637 im->ximg / 2, 6,
2638 im->graph_col[GRC_FONT],
2639 im->
2640 text_prop[TEXT_PROP_TITLE].
2641 font_desc,
2642 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2643 /* rrdtool 'logo' */
2644 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2645 water_color = im->graph_col[GRC_FONT];
2646 water_color.alpha = 0.3;
2647 gfx_text(im, im->ximg - 4, 5,
2648 water_color,
2649 im->
2650 text_prop[TEXT_PROP_WATERMARK].
2651 font_desc, im->tabwidth,
2652 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2653 }
2654 /* graph watermark */
2655 if (im->watermark[0] != '\0') {
2656 gfx_text(im,
2657 im->ximg / 2, im->yimg - 6,
2658 water_color,
2659 im->
2660 text_prop[TEXT_PROP_WATERMARK].
2661 font_desc, im->tabwidth, 0,
2662 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2663 }
2665 /* graph labels */
2666 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2667 for (i = 0; i < im->gdes_c; i++) {
2668 if (im->gdes[i].legend[0] == '\0')
2669 continue;
2670 /* im->gdes[i].leg_y is the bottom of the legend */
2671 X0 = im->gdes[i].leg_x;
2672 Y0 = im->gdes[i].leg_y;
2673 gfx_text(im, X0, Y0,
2674 im->graph_col[GRC_FONT],
2675 im->
2676 text_prop
2677 [TEXT_PROP_LEGEND].font_desc,
2678 im->tabwidth, 0.0,
2679 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2680 /* The legend for GRAPH items starts with "M " to have
2681 enough space for the box */
2682 if (im->gdes[i].gf != GF_PRINT &&
2683 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2684 double boxH, boxV;
2685 double X1, Y1;
2687 boxH = gfx_get_text_width(im, 0,
2688 im->
2689 text_prop
2690 [TEXT_PROP_LEGEND].
2691 font_desc,
2692 im->tabwidth, "o") * 1.2;
2693 boxV = boxH;
2694 /* shift the box up a bit */
2695 Y0 -= boxV * 0.4;
2696 /* make sure transparent colors show up the same way as in the graph */
2697 gfx_new_area(im,
2698 X0, Y0 - boxV,
2699 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2700 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2701 gfx_close_path(im);
2702 gfx_new_area(im, X0, Y0 - boxV, X0,
2703 Y0, X0 + boxH, Y0, im->gdes[i].col);
2704 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2705 gfx_close_path(im);
2706 cairo_save(im->cr);
2707 cairo_new_path(im->cr);
2708 cairo_set_line_width(im->cr, 1.0);
2709 X1 = X0 + boxH;
2710 Y1 = Y0 - boxV;
2711 gfx_line_fit(im, &X0, &Y0);
2712 gfx_line_fit(im, &X1, &Y1);
2713 cairo_move_to(im->cr, X0, Y0);
2714 cairo_line_to(im->cr, X1, Y0);
2715 cairo_line_to(im->cr, X1, Y1);
2716 cairo_line_to(im->cr, X0, Y1);
2717 cairo_close_path(im->cr);
2718 cairo_set_source_rgba(im->cr,
2719 im->
2720 graph_col
2721 [GRC_FRAME].
2722 red,
2723 im->
2724 graph_col
2725 [GRC_FRAME].
2726 green,
2727 im->
2728 graph_col
2729 [GRC_FRAME].
2730 blue, im->graph_col[GRC_FRAME].alpha);
2731 if (im->gdes[i].dash) {
2732 /* make box borders in legend dashed if the graph is dashed */
2733 double dashes[] = {
2734 3.0
2735 };
2736 cairo_set_dash(im->cr, dashes, 1, 0.0);
2737 }
2738 cairo_stroke(im->cr);
2739 cairo_restore(im->cr);
2740 }
2741 }
2742 }
2743 }
2746 /*****************************************************
2747 * lazy check make sure we rely need to create this graph
2748 *****************************************************/
2750 int lazy_check(
2751 image_desc_t *im)
2752 {
2753 FILE *fd = NULL;
2754 int size = 1;
2755 struct stat imgstat;
2757 if (im->lazy == 0)
2758 return 0; /* no lazy option */
2759 if (strlen(im->graphfile) == 0)
2760 return 0; /* inmemory option */
2761 if (stat(im->graphfile, &imgstat) != 0)
2762 return 0; /* can't stat */
2763 /* one pixel in the existing graph is more then what we would
2764 change here ... */
2765 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2766 return 0;
2767 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2768 return 0; /* the file does not exist */
2769 switch (im->imgformat) {
2770 case IF_PNG:
2771 size = PngSize(fd, &(im->ximg), &(im->yimg));
2772 break;
2773 default:
2774 size = 1;
2775 }
2776 fclose(fd);
2777 return size;
2778 }
2781 int graph_size_location(
2782 image_desc_t
2783 *im,
2784 int elements)
2785 {
2786 /* The actual size of the image to draw is determined from
2787 ** several sources. The size given on the command line is
2788 ** the graph area but we need more as we have to draw labels
2789 ** and other things outside the graph area
2790 */
2792 int Xvertical = 0, Ytitle =
2793 0, Xylabel = 0, Xmain = 0, Ymain =
2794 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2796 if (im->extra_flags & ONLY_GRAPH) {
2797 im->xorigin = 0;
2798 im->ximg = im->xsize;
2799 im->yimg = im->ysize;
2800 im->yorigin = im->ysize;
2801 ytr(im, DNAN);
2802 return 0;
2803 }
2805 /** +---+-----------------------------------+
2806 ** | y |...............graph title.........|
2807 ** | +---+-------------------------------+
2808 ** | a | y | |
2809 ** | x | | |
2810 ** | i | a | |
2811 ** | s | x | main graph area |
2812 ** | | i | |
2813 ** | t | s | |
2814 ** | i | | |
2815 ** | t | l | |
2816 ** | l | b +-------------------------------+
2817 ** | e | l | x axis labels |
2818 ** +---+---+-------------------------------+
2819 ** |....................legends............|
2820 ** +---------------------------------------+
2821 ** | watermark |
2822 ** +---------------------------------------+
2823 */
2825 if (im->ylegend[0] != '\0') {
2826 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2827 }
2829 if (im->title[0] != '\0') {
2830 /* The title is placed "inbetween" two text lines so it
2831 ** automatically has some vertical spacing. The horizontal
2832 ** spacing is added here, on each side.
2833 */
2834 /* if necessary, reduce the font size of the title until it fits the image width */
2835 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2836 }
2838 if (elements) {
2839 if (im->draw_x_grid) {
2840 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2841 }
2842 if (im->draw_y_grid || im->forceleftspace) {
2843 Xylabel =
2844 gfx_get_text_width(im, 0,
2845 im->
2846 text_prop
2847 [TEXT_PROP_AXIS].
2848 font_desc,
2849 im->tabwidth, "0") * im->unitslength;
2850 }
2851 }
2853 if (im->extra_flags & FULL_SIZE_MODE) {
2854 /* The actual size of the image to draw has been determined by the user.
2855 ** The graph area is the space remaining after accounting for the legend,
2856 ** the watermark, the axis labels, and the title.
2857 */
2858 im->xorigin = 0;
2859 im->ximg = im->xsize;
2860 im->yimg = im->ysize;
2861 im->yorigin = im->ysize;
2862 Xmain = im->ximg;
2863 Ymain = im->yimg;
2864 /* Now calculate the total size. Insert some spacing where
2865 desired. im->xorigin and im->yorigin need to correspond
2866 with the lower left corner of the main graph area or, if
2867 this one is not set, the imaginary box surrounding the
2868 pie chart area. */
2869 /* Initial size calculation for the main graph area */
2870 Xmain = im->ximg - Xylabel - 3 * Xspacing;
2872 im->xorigin = Xspacing + Xylabel;
2874 if (Xvertical) { /* unit description */
2875 Xmain -= Xvertical;
2876 im->xorigin += Xvertical;
2877 }
2879 /* adjust space for second axis */
2880 if (im->second_axis_scale != 0){
2881 Xmain -= Xylabel + Xspacing;
2882 }
2883 if (im->extra_flags & NO_RRDTOOL_TAG){
2884 Xmain += Xspacing;
2885 }
2886 if (im->second_axis_legend[0] != '\0' ) {
2887 Xmain -= im->text_prop[TEXT_PROP_UNIT].size * 1.5;
2888 }
2890 im->xsize = Xmain;
2892 xtr(im, 0);
2893 /* The vertical size of the image is known in advance. The main graph area
2894 ** (Ymain) and im->yorigin must be set according to the space requirements
2895 ** of the legend and the axis labels.
2896 */
2897 if (im->extra_flags & NOLEGEND) {
2898 im->yorigin = im->yimg -
2899 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2900 Ymain = im->yorigin;
2901 }
2902 else {
2903 /* Determine where to place the legends onto the image.
2904 ** Set Ymain and adjust im->yorigin to match the space requirements.
2905 */
2906 if (leg_place(im, &Ymain) == -1)
2907 return -1;
2908 }
2911 /* remove title space *or* some padding above the graph from the main graph area */
2912 if (Ytitle) {
2913 Ymain -= Ytitle;
2914 } else {
2915 Ymain -= 1.5 * Yspacing;
2916 }
2918 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2919 if (im->watermark[0] != '\0') {
2920 Ymain -= Ywatermark;
2921 }
2923 im->ysize = Ymain;
2924 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2926 /* The actual size of the image to draw is determined from
2927 ** several sources. The size given on the command line is
2928 ** the graph area but we need more as we have to draw labels
2929 ** and other things outside the graph area.
2930 */
2932 if (im->ylegend[0] != '\0') {
2933 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2934 }
2937 if (im->title[0] != '\0') {
2938 /* The title is placed "inbetween" two text lines so it
2939 ** automatically has some vertical spacing. The horizontal
2940 ** spacing is added here, on each side.
2941 */
2942 /* don't care for the with of the title
2943 Xtitle = gfx_get_text_width(im->canvas, 0,
2944 im->text_prop[TEXT_PROP_TITLE].font_desc,
2945 im->tabwidth,
2946 im->title, 0) + 2*Xspacing; */
2947 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2948 }
2950 if (elements) {
2951 Xmain = im->xsize;
2952 Ymain = im->ysize;
2953 }
2954 /* Now calculate the total size. Insert some spacing where
2955 desired. im->xorigin and im->yorigin need to correspond
2956 with the lower left corner of the main graph area or, if
2957 this one is not set, the imaginary box surrounding the
2958 pie chart area. */
2960 /* The legend width cannot yet be determined, as a result we
2961 ** have problems adjusting the image to it. For now, we just
2962 ** forget about it at all; the legend will have to fit in the
2963 ** size already allocated.
2964 */
2965 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2967 if (im->second_axis_scale != 0){
2968 im->ximg += Xylabel + Xspacing;
2969 }
2970 if (im->extra_flags & NO_RRDTOOL_TAG){
2971 im->ximg -= Xspacing;
2972 }
2974 if (Xmain)
2975 im->ximg += Xspacing;
2976 im->xorigin = Xspacing + Xylabel;
2977 /* the length of the title should not influence with width of the graph
2978 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2979 if (Xvertical) { /* unit description */
2980 im->ximg += Xvertical;
2981 im->xorigin += Xvertical;
2982 }
2983 if (im->second_axis_legend[0] != '\0' ) {
2984 im->ximg += Xvertical;
2985 }
2987 xtr(im, 0);
2988 /* The vertical size is interesting... we need to compare
2989 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2990 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2991 ** in order to start even thinking about Ylegend or Ywatermark.
2992 **
2993 ** Do it in three portions: First calculate the inner part,
2994 ** then do the legend, then adjust the total height of the img,
2995 ** adding space for a watermark if one exists;
2996 */
2997 /* reserve space for main and/or pie */
2998 im->yimg = Ymain + Yxlabel;
2999 im->yorigin = im->yimg - Yxlabel;
3000 /* reserve space for the title *or* some padding above the graph */
3001 if (Ytitle) {
3002 im->yimg += Ytitle;
3003 im->yorigin += Ytitle;
3004 } else {
3005 im->yimg += 1.5 * Yspacing;
3006 im->yorigin += 1.5 * Yspacing;
3007 }
3008 /* reserve space for padding below the graph */
3009 im->yimg += Yspacing;
3010 /* Determine where to place the legends onto the image.
3011 ** Adjust im->yimg to match the space requirements.
3012 */
3013 if (leg_place(im, 0) == -1)
3014 return -1;
3015 if (im->watermark[0] != '\0') {
3016 im->yimg += Ywatermark;
3017 }
3018 }
3020 ytr(im, DNAN);
3021 return 0;
3022 }
3024 static cairo_status_t cairo_output(
3025 void *closure,
3026 const unsigned char
3027 *data,
3028 unsigned int length)
3029 {
3030 image_desc_t *im = (image_desc_t*)(closure);
3032 im->rendered_image =
3033 (unsigned char*)(realloc(im->rendered_image, im->rendered_image_size + length));
3034 if (im->rendered_image == NULL)
3035 return CAIRO_STATUS_WRITE_ERROR;
3036 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3037 im->rendered_image_size += length;
3038 return CAIRO_STATUS_SUCCESS;
3039 }
3041 /* draw that picture thing ... */
3042 int graph_paint(
3043 image_desc_t *im)
3044 {
3045 int i, ii;
3046 int lazy = lazy_check(im);
3047 double areazero = 0.0;
3048 graph_desc_t *lastgdes = NULL;
3049 rrd_infoval_t info;
3052 /* pull the data from the rrd files ... */
3053 if (data_fetch(im) == -1)
3054 return -1;
3055 /* evaluate VDEF and CDEF operations ... */
3056 if (data_calc(im) == -1)
3057 return -1;
3058 /* calculate and PRINT and GPRINT definitions. We have to do it at
3059 * this point because it will affect the length of the legends
3060 * if there are no graph elements (i==0) we stop here ...
3061 * if we are lazy, try to quit ...
3062 */
3063 i = print_calc(im);
3064 if (i < 0)
3065 return -1;
3067 if (i == 0)
3068 return 0;
3070 /**************************************************************
3071 *** Calculating sizes and locations became a bit confusing ***
3072 *** so I moved this into a separate function. ***
3073 **************************************************************/
3074 if (graph_size_location(im, i) == -1)
3075 return -1;
3077 info.u_cnt = im->xorigin;
3078 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3079 info.u_cnt = im->yorigin - im->ysize;
3080 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3081 info.u_cnt = im->xsize;
3082 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3083 info.u_cnt = im->ysize;
3084 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3085 info.u_cnt = im->ximg;
3086 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3087 info.u_cnt = im->yimg;
3088 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3089 info.u_cnt = im->start;
3090 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3091 info.u_cnt = im->end;
3092 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3094 /* if we want and can be lazy ... quit now */
3095 if (lazy)
3096 return 0;
3098 /* get actual drawing data and find min and max values */
3099 if (data_proc(im) == -1)
3100 return -1;
3101 if (!im->logarithmic) {
3102 si_unit(im);
3103 }
3105 /* identify si magnitude Kilo, Mega Giga ? */
3106 if (!im->rigid && !im->logarithmic)
3107 expand_range(im); /* make sure the upper and lower limit are
3108 sensible values */
3110 info.u_val = im->minval;
3111 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3112 info.u_val = im->maxval;
3113 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3115 if (!calc_horizontal_grid(im))
3116 return -1;
3117 /* reset precalc */
3118 ytr(im, DNAN);
3119 /* if (im->gridfit)
3120 apply_gridfit(im); */
3121 /* the actual graph is created by going through the individual
3122 graph elements and then drawing them */
3123 cairo_surface_destroy(im->surface);
3124 switch (im->imgformat) {
3125 case IF_PNG:
3126 im->surface =
3127 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3128 im->ximg * im->zoom,
3129 im->yimg * im->zoom);
3130 break;
3131 case IF_PDF:
3132 im->gridfit = 0;
3133 im->surface = strlen(im->graphfile)
3134 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3135 im->yimg * im->zoom)
3136 : cairo_pdf_surface_create_for_stream
3137 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3138 break;
3139 case IF_EPS:
3140 im->gridfit = 0;
3141 im->surface = strlen(im->graphfile)
3142 ?
3143 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3144 im->yimg * im->zoom)
3145 : cairo_ps_surface_create_for_stream
3146 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3147 break;
3148 case IF_SVG:
3149 im->gridfit = 0;
3150 im->surface = strlen(im->graphfile)
3151 ?
3152 cairo_svg_surface_create(im->
3153 graphfile,
3154 im->ximg * im->zoom, im->yimg * im->zoom)
3155 : cairo_svg_surface_create_for_stream
3156 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3157 cairo_svg_surface_restrict_to_version
3158 (im->surface, CAIRO_SVG_VERSION_1_1);
3159 break;
3160 };
3161 cairo_destroy(im->cr);
3162 im->cr = cairo_create(im->surface);
3163 cairo_set_antialias(im->cr, im->graph_antialias);
3164 cairo_scale(im->cr, im->zoom, im->zoom);
3165 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3166 gfx_new_area(im, 0, 0, 0, im->yimg,
3167 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3168 gfx_add_point(im, im->ximg, 0);
3169 gfx_close_path(im);
3170 gfx_new_area(im, im->xorigin,
3171 im->yorigin,
3172 im->xorigin +
3173 im->xsize, im->yorigin,
3174 im->xorigin +
3175 im->xsize,
3176 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3177 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3178 gfx_close_path(im);
3179 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3180 im->xsize, im->ysize + 2.0);
3181 cairo_clip(im->cr);
3182 if (im->minval > 0.0)
3183 areazero = im->minval;
3184 if (im->maxval < 0.0)
3185 areazero = im->maxval;
3186 for (i = 0; i < im->gdes_c; i++) {
3187 switch (im->gdes[i].gf) {
3188 case GF_CDEF:
3189 case GF_VDEF:
3190 case GF_DEF:
3191 case GF_PRINT:
3192 case GF_GPRINT:
3193 case GF_COMMENT:
3194 case GF_TEXTALIGN:
3195 case GF_HRULE:
3196 case GF_VRULE:
3197 case GF_XPORT:
3198 case GF_SHIFT:
3199 break;
3200 case GF_TICK:
3201 for (ii = 0; ii < im->xsize; ii++) {
3202 if (!isnan(im->gdes[i].p_data[ii])
3203 && im->gdes[i].p_data[ii] != 0.0) {
3204 if (im->gdes[i].yrule > 0) {
3205 gfx_line(im,
3206 im->xorigin + ii,
3207 im->yorigin + 1.0,
3208 im->xorigin + ii,
3209 im->yorigin -
3210 im->gdes[i].yrule *
3211 im->ysize, 1.0, im->gdes[i].col);
3212 } else if (im->gdes[i].yrule < 0) {
3213 gfx_line(im,
3214 im->xorigin + ii,
3215 im->yorigin - im->ysize - 1.0,
3216 im->xorigin + ii,
3217 im->yorigin - im->ysize -
3218 im->gdes[i].
3219 yrule *
3220 im->ysize, 1.0, im->gdes[i].col);
3221 }
3222 }
3223 }
3224 break;
3225 case GF_LINE:
3226 case GF_AREA:
3227 /* fix data points at oo and -oo */
3228 for (ii = 0; ii < im->xsize; ii++) {
3229 if (isinf(im->gdes[i].p_data[ii])) {
3230 if (im->gdes[i].p_data[ii] > 0) {
3231 im->gdes[i].p_data[ii] = im->maxval;
3232 } else {
3233 im->gdes[i].p_data[ii] = im->minval;
3234 }
3236 }
3237 } /* for */
3239 /* *******************************************************
3240 a ___. (a,t)
3241 | | ___
3242 ____| | | |
3243 | |___|
3244 -------|--t-1--t--------------------------------
3246 if we know the value at time t was a then
3247 we draw a square from t-1 to t with the value a.
3249 ********************************************************* */
3250 if (im->gdes[i].col.alpha != 0.0) {
3251 /* GF_LINE and friend */
3252 if (im->gdes[i].gf == GF_LINE) {
3253 double last_y = 0.0;
3254 int draw_on = 0;
3256 cairo_save(im->cr);
3257 cairo_new_path(im->cr);
3258 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3259 if (im->gdes[i].dash) {
3260 cairo_set_dash(im->cr,
3261 im->gdes[i].p_dashes,
3262 im->gdes[i].ndash, im->gdes[i].offset);
3263 }
3265 for (ii = 1; ii < im->xsize; ii++) {
3266 if (isnan(im->gdes[i].p_data[ii])
3267 || (im->slopemode == 1
3268 && isnan(im->gdes[i].p_data[ii - 1]))) {
3269 draw_on = 0;
3270 continue;
3271 }
3272 if (draw_on == 0) {
3273 last_y = ytr(im, im->gdes[i].p_data[ii]);
3274 if (im->slopemode == 0) {
3275 double x = ii - 1 + im->xorigin;
3276 double y = last_y;
3278 gfx_line_fit(im, &x, &y);
3279 cairo_move_to(im->cr, x, y);
3280 x = ii + im->xorigin;
3281 y = last_y;
3282 gfx_line_fit(im, &x, &y);
3283 cairo_line_to(im->cr, x, y);
3284 } else {
3285 double x = ii - 1 + im->xorigin;
3286 double y =
3287 ytr(im, im->gdes[i].p_data[ii - 1]);
3288 gfx_line_fit(im, &x, &y);
3289 cairo_move_to(im->cr, x, y);
3290 x = ii + im->xorigin;
3291 y = last_y;
3292 gfx_line_fit(im, &x, &y);
3293 cairo_line_to(im->cr, x, y);
3294 }
3295 draw_on = 1;
3296 } else {
3297 double x1 = ii + im->xorigin;
3298 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3300 if (im->slopemode == 0
3301 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3302 double x = ii - 1 + im->xorigin;
3303 double y = y1;
3305 gfx_line_fit(im, &x, &y);
3306 cairo_line_to(im->cr, x, y);
3307 };
3308 last_y = y1;
3309 gfx_line_fit(im, &x1, &y1);
3310 cairo_line_to(im->cr, x1, y1);
3311 };
3312 }
3313 cairo_set_source_rgba(im->cr,
3314 im->gdes[i].
3315 col.red,
3316 im->gdes[i].
3317 col.green,
3318 im->gdes[i].
3319 col.blue, im->gdes[i].col.alpha);
3320 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3321 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3322 cairo_stroke(im->cr);
3323 cairo_restore(im->cr);
3324 } else {
3325 int idxI = -1;
3326 double *foreY =
3327 (double *) malloc(sizeof(double) * im->xsize * 2);
3328 double *foreX =
3329 (double *) malloc(sizeof(double) * im->xsize * 2);
3330 double *backY =
3331 (double *) malloc(sizeof(double) * im->xsize * 2);
3332 double *backX =
3333 (double *) malloc(sizeof(double) * im->xsize * 2);
3334 int drawem = 0;
3336 for (ii = 0; ii <= im->xsize; ii++) {
3337 double ybase, ytop;
3339 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3340 int cntI = 1;
3341 int lastI = 0;
3343 while (cntI < idxI
3344 &&
3345 AlmostEqual2sComplement(foreY
3346 [lastI],
3347 foreY[cntI], 4)
3348 &&
3349 AlmostEqual2sComplement(foreY
3350 [lastI],
3351 foreY
3352 [cntI + 1], 4)) {
3353 cntI++;
3354 }
3355 gfx_new_area(im,
3356 backX[0], backY[0],
3357 foreX[0], foreY[0],
3358 foreX[cntI],
3359 foreY[cntI], im->gdes[i].col);
3360 while (cntI < idxI) {
3361 lastI = cntI;
3362 cntI++;
3363 while (cntI < idxI
3364 &&
3365 AlmostEqual2sComplement(foreY
3366 [lastI],
3367 foreY[cntI], 4)
3368 &&
3369 AlmostEqual2sComplement(foreY
3370 [lastI],
3371 foreY
3372 [cntI
3373 + 1], 4)) {
3374 cntI++;
3375 }
3376 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3377 }
3378 gfx_add_point(im, backX[idxI], backY[idxI]);
3379 while (idxI > 1) {
3380 lastI = idxI;
3381 idxI--;
3382 while (idxI > 1
3383 &&
3384 AlmostEqual2sComplement(backY
3385 [lastI],
3386 backY[idxI], 4)
3387 &&
3388 AlmostEqual2sComplement(backY
3389 [lastI],
3390 backY
3391 [idxI
3392 - 1], 4)) {
3393 idxI--;
3394 }
3395 gfx_add_point(im, backX[idxI], backY[idxI]);
3396 }
3397 idxI = -1;
3398 drawem = 0;
3399 gfx_close_path(im);
3400 }
3401 if (drawem != 0) {
3402 drawem = 0;
3403 idxI = -1;
3404 }
3405 if (ii == im->xsize)
3406 break;
3407 if (im->slopemode == 0 && ii == 0) {
3408 continue;
3409 }
3410 if (isnan(im->gdes[i].p_data[ii])) {
3411 drawem = 1;
3412 continue;
3413 }
3414 ytop = ytr(im, im->gdes[i].p_data[ii]);
3415 if (lastgdes && im->gdes[i].stack) {
3416 ybase = ytr(im, lastgdes->p_data[ii]);
3417 } else {
3418 ybase = ytr(im, areazero);
3419 }
3420 if (ybase == ytop) {
3421 drawem = 1;
3422 continue;
3423 }
3425 if (ybase > ytop) {
3426 double extra = ytop;
3428 ytop = ybase;
3429 ybase = extra;
3430 }
3431 if (im->slopemode == 0) {
3432 backY[++idxI] = ybase - 0.2;
3433 backX[idxI] = ii + im->xorigin - 1;
3434 foreY[idxI] = ytop + 0.2;
3435 foreX[idxI] = ii + im->xorigin - 1;
3436 }
3437 backY[++idxI] = ybase - 0.2;
3438 backX[idxI] = ii + im->xorigin;
3439 foreY[idxI] = ytop + 0.2;
3440 foreX[idxI] = ii + im->xorigin;
3441 }
3442 /* close up any remaining area */
3443 free(foreY);
3444 free(foreX);
3445 free(backY);
3446 free(backX);
3447 } /* else GF_LINE */
3448 }
3449 /* if color != 0x0 */
3450 /* make sure we do not run into trouble when stacking on NaN */
3451 for (ii = 0; ii < im->xsize; ii++) {
3452 if (isnan(im->gdes[i].p_data[ii])) {
3453 if (lastgdes && (im->gdes[i].stack)) {
3454 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3455 } else {
3456 im->gdes[i].p_data[ii] = areazero;
3457 }
3458 }
3459 }
3460 lastgdes = &(im->gdes[i]);
3461 break;
3462 case GF_STACK:
3463 rrd_set_error
3464 ("STACK should already be turned into LINE or AREA here");
3465 return -1;
3466 break;
3467 } /* switch */
3468 }
3469 cairo_reset_clip(im->cr);
3471 /* grid_paint also does the text */
3472 if (!(im->extra_flags & ONLY_GRAPH))
3473 grid_paint(im);
3474 if (!(im->extra_flags & ONLY_GRAPH))
3475 axis_paint(im);
3476 /* the RULES are the last thing to paint ... */
3477 for (i = 0; i < im->gdes_c; i++) {
3479 switch (im->gdes[i].gf) {
3480 case GF_HRULE:
3481 if (im->gdes[i].yrule >= im->minval
3482 && im->gdes[i].yrule <= im->maxval) {
3483 cairo_save(im->cr);
3484 if (im->gdes[i].dash) {
3485 cairo_set_dash(im->cr,
3486 im->gdes[i].p_dashes,
3487 im->gdes[i].ndash, im->gdes[i].offset);
3488 }
3489 gfx_line(im, im->xorigin,
3490 ytr(im, im->gdes[i].yrule),
3491 im->xorigin + im->xsize,
3492 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3493 cairo_stroke(im->cr);
3494 cairo_restore(im->cr);
3495 }
3496 break;
3497 case GF_VRULE:
3498 if (im->gdes[i].xrule >= im->start
3499 && im->gdes[i].xrule <= im->end) {
3500 cairo_save(im->cr);
3501 if (im->gdes[i].dash) {
3502 cairo_set_dash(im->cr,
3503 im->gdes[i].p_dashes,
3504 im->gdes[i].ndash, im->gdes[i].offset);
3505 }
3506 gfx_line(im,
3507 xtr(im, im->gdes[i].xrule),
3508 im->yorigin, xtr(im,
3509 im->
3510 gdes[i].
3511 xrule),
3512 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3513 cairo_stroke(im->cr);
3514 cairo_restore(im->cr);
3515 }
3516 break;
3517 default:
3518 break;
3519 }
3520 }
3523 switch (im->imgformat) {
3524 case IF_PNG:
3525 {
3526 cairo_status_t status;
3528 status = strlen(im->graphfile) ?
3529 cairo_surface_write_to_png(im->surface, im->graphfile)
3530 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3531 im);
3533 if (status != CAIRO_STATUS_SUCCESS) {
3534 rrd_set_error("Could not save png to '%s'", im->graphfile);
3535 return 1;
3536 }
3537 break;
3538 }
3539 default:
3540 if (strlen(im->graphfile)) {
3541 cairo_show_page(im->cr);
3542 } else {
3543 cairo_surface_finish(im->surface);
3544 }
3545 break;
3546 }
3548 return 0;
3549 }
3552 /*****************************************************
3553 * graph stuff
3554 *****************************************************/
3556 int gdes_alloc(
3557 image_desc_t *im)
3558 {
3560 im->gdes_c++;
3561 if ((im->gdes = (graph_desc_t *)
3562 rrd_realloc(im->gdes, (im->gdes_c)
3563 * sizeof(graph_desc_t))) == NULL) {
3564 rrd_set_error("realloc graph_descs");
3565 return -1;
3566 }
3569 im->gdes[im->gdes_c - 1].step = im->step;
3570 im->gdes[im->gdes_c - 1].step_orig = im->step;
3571 im->gdes[im->gdes_c - 1].stack = 0;
3572 im->gdes[im->gdes_c - 1].linewidth = 0;
3573 im->gdes[im->gdes_c - 1].debug = 0;
3574 im->gdes[im->gdes_c - 1].start = im->start;
3575 im->gdes[im->gdes_c - 1].start_orig = im->start;
3576 im->gdes[im->gdes_c - 1].end = im->end;
3577 im->gdes[im->gdes_c - 1].end_orig = im->end;
3578 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3579 im->gdes[im->gdes_c - 1].data = NULL;
3580 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3581 im->gdes[im->gdes_c - 1].data_first = 0;
3582 im->gdes[im->gdes_c - 1].p_data = NULL;
3583 im->gdes[im->gdes_c - 1].rpnp = NULL;
3584 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3585 im->gdes[im->gdes_c - 1].shift = 0.0;
3586 im->gdes[im->gdes_c - 1].dash = 0;
3587 im->gdes[im->gdes_c - 1].ndash = 0;
3588 im->gdes[im->gdes_c - 1].offset = 0;
3589 im->gdes[im->gdes_c - 1].col.red = 0.0;
3590 im->gdes[im->gdes_c - 1].col.green = 0.0;
3591 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3592 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3593 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3594 im->gdes[im->gdes_c - 1].format[0] = '\0';
3595 im->gdes[im->gdes_c - 1].strftm = 0;
3596 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3597 im->gdes[im->gdes_c - 1].ds = -1;
3598 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3599 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3600 im->gdes[im->gdes_c - 1].yrule = DNAN;
3601 im->gdes[im->gdes_c - 1].xrule = 0;
3602 return 0;
3603 }
3605 /* copies input untill the first unescaped colon is found
3606 or until input ends. backslashes have to be escaped as well */
3607 int scan_for_col(
3608 const char *const input,
3609 int len,
3610 char *const output)
3611 {
3612 int inp, outp = 0;
3614 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3615 if (input[inp] == '\\'
3616 && input[inp + 1] != '\0'
3617 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3618 output[outp++] = input[++inp];
3619 } else {
3620 output[outp++] = input[inp];
3621 }
3622 }
3623 output[outp] = '\0';
3624 return inp;
3625 }
3627 /* Now just a wrapper around rrd_graph_v */
3628 int rrd_graph(
3629 int argc,
3630 char **argv,
3631 char ***prdata,
3632 int *xsize,
3633 int *ysize,
3634 FILE * stream,
3635 double *ymin,
3636 double *ymax)
3637 {
3638 int prlines = 0;
3639 rrd_info_t *grinfo = NULL;
3640 rrd_info_t *walker;
3642 grinfo = rrd_graph_v(argc, argv);
3643 if (grinfo == NULL)
3644 return -1;
3645 walker = grinfo;
3646 (*prdata) = NULL;
3647 while (walker) {
3648 if (strcmp(walker->key, "image_info") == 0) {
3649 prlines++;
3650 if (((*prdata) =
3651 (char**)(rrd_realloc((*prdata),
3652 (prlines + 1) * sizeof(char *)))) == NULL) {
3653 rrd_set_error("realloc prdata");
3654 return 0;
3655 }
3656 /* imginfo goes to position 0 in the prdata array */
3657 (*prdata)[prlines - 1] = (char*)(malloc((strlen(walker->value.u_str)
3658 + 2) * sizeof(char)));
3659 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3660 (*prdata)[prlines] = NULL;
3661 }
3662 /* skip anything else */
3663 walker = walker->next;
3664 }
3665 walker = grinfo;
3666 *xsize = 0;
3667 *ysize = 0;
3668 *ymin = 0;
3669 *ymax = 0;
3670 while (walker) {
3671 if (strcmp(walker->key, "image_width") == 0) {
3672 *xsize = walker->value.u_cnt;
3673 } else if (strcmp(walker->key, "image_height") == 0) {
3674 *ysize = walker->value.u_cnt;
3675 } else if (strcmp(walker->key, "value_min") == 0) {
3676 *ymin = walker->value.u_val;
3677 } else if (strcmp(walker->key, "value_max") == 0) {
3678 *ymax = walker->value.u_val;
3679 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3680 prlines++;
3681 if (((*prdata) =
3682 (char**)(rrd_realloc((*prdata),
3683 (prlines + 1) * sizeof(char *)))) == NULL) {
3684 rrd_set_error("realloc prdata");
3685 return 0;
3686 }
3687 (*prdata)[prlines - 1] = (char*)(malloc((strlen(walker->value.u_str)
3688 + 2) * sizeof(char)));
3689 (*prdata)[prlines] = NULL;
3690 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3691 } else if (strcmp(walker->key, "image") == 0) {
3692 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3693 (stream ? stream : stdout));
3694 }
3695 /* skip anything else */
3696 walker = walker->next;
3697 }
3698 rrd_info_free(grinfo);
3699 return 0;
3700 }
3703 /* Some surgery done on this function, it became ridiculously big.
3704 ** Things moved:
3705 ** - initializing now in rrd_graph_init()
3706 ** - options parsing now in rrd_graph_options()
3707 ** - script parsing now in rrd_graph_script()
3708 */
3709 rrd_info_t *rrd_graph_v(
3710 int argc,
3711 char **argv)
3712 {
3713 image_desc_t im;
3714 rrd_info_t *grinfo;
3715 rrd_graph_init(&im);
3716 /* a dummy surface so that we can measure text sizes for placements */
3717 old_locale = setlocale(LC_NUMERIC, NULL);
3718 setlocale(LC_NUMERIC, "C");
3720 rrd_graph_options(argc, argv, &im);
3721 if (rrd_test_error()) {
3722 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
3723 rrd_info_free(im.grinfo);
3724 im_free(&im);
3725 return NULL;
3726 }
3728 if (optind >= argc) {
3729 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
3730 rrd_info_free(im.grinfo);
3731 im_free(&im);
3732 rrd_set_error("missing filename");
3733 return NULL;
3734 }
3736 if (strlen(argv[optind]) >= MAXPATH) {
3737 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
3738 rrd_set_error("filename (including path) too long");
3739 rrd_info_free(im.grinfo);
3740 im_free(&im);
3741 return NULL;
3742 }
3744 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3745 im.graphfile[MAXPATH - 1] = '\0';
3747 if (strcmp(im.graphfile, "-") == 0) {
3748 im.graphfile[0] = '\0';
3749 }
3751 rrd_graph_script(argc, argv, &im, 1);
3752 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
3753 if (rrd_test_error()) {
3754 rrd_info_free(im.grinfo);
3755 im_free(&im);
3756 return NULL;
3757 }
3759 /* Everything is now read and the actual work can start */
3761 if (graph_paint(&im) == -1) {
3762 rrd_info_free(im.grinfo);
3763 im_free(&im);
3764 return NULL;
3765 }
3768 /* The image is generated and needs to be output.
3769 ** Also, if needed, print a line with information about the image.
3770 */
3772 if (im.imginfo) {
3773 rrd_infoval_t info;
3774 char *path;
3775 char *filename;
3777 path = strdup(im.graphfile);
3778 filename = basename(path);
3779 info.u_str =
3780 sprintf_alloc(im.imginfo,
3781 filename,
3782 (long) (im.zoom *
3783 im.ximg), (long) (im.zoom * im.yimg));
3784 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3785 free(info.u_str);
3786 free(path);
3787 }
3788 if (im.rendered_image) {
3789 rrd_infoval_t img;
3791 img.u_blo.size = im.rendered_image_size;
3792 img.u_blo.ptr = im.rendered_image;
3793 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3794 }
3795 grinfo = im.grinfo;
3796 im_free(&im);
3797 return grinfo;
3798 }
3800 static void
3801 rrd_set_font_desc (
3802 image_desc_t *im,int prop,char *font, double size ){
3803 if (font){
3804 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3805 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3806 /* if we already got one, drop it first */
3807 pango_font_description_free(im->text_prop[prop].font_desc);
3808 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3809 };
3810 if (size > 0){
3811 im->text_prop[prop].size = size;
3812 };
3813 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3814 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3815 };
3816 }
3818 void rrd_graph_init(
3819 image_desc_t
3820 *im)
3821 {
3822 unsigned int i;
3823 char *deffont = getenv("RRD_DEFAULT_FONT");
3824 static PangoFontMap *fontmap = NULL;
3825 PangoContext *context;
3827 #ifdef HAVE_TZSET
3828 tzset();
3829 #endif
3830 #ifdef HAVE_SETLOCALE
3831 setlocale(LC_TIME, "");
3832 #ifdef HAVE_MBSTOWCS
3833 setlocale(LC_CTYPE, "");
3834 #endif
3835 #endif
3836 im->base = 1000;
3837 im->draw_x_grid = 1;
3838 im->draw_y_grid = 1;
3839 im->extra_flags = 0;
3840 im->font_options = cairo_font_options_create();
3841 im->forceleftspace = 0;
3842 im->gdes_c = 0;
3843 im->gdes = NULL;
3844 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3845 im->grid_dash_off = 1;
3846 im->grid_dash_on = 1;
3847 im->gridfit = 1;
3848 im->grinfo = (rrd_info_t *) NULL;
3849 im->grinfo_current = (rrd_info_t *) NULL;
3850 im->imgformat = IF_PNG;
3851 im->imginfo = NULL;
3852 im->lazy = 0;
3853 im->logarithmic = 0;
3854 im->maxval = DNAN;
3855 im->minval = 0;
3856 im->minval = DNAN;
3857 im->magfact = 1;
3858 im->prt_c = 0;
3859 im->rigid = 0;
3860 im->rendered_image_size = 0;
3861 im->rendered_image = NULL;
3862 im->slopemode = 0;
3863 im->step = 0;
3864 im->symbol = ' ';
3865 im->tabwidth = 40.0;
3866 im->title[0] = '\0';
3867 im->unitsexponent = 9999;
3868 im->unitslength = 6;
3869 im->viewfactor = 1.0;
3870 im->watermark[0] = '\0';
3871 im->with_markup = 0;
3872 im->ximg = 0;
3873 im->xlab_user.minsec = -1;
3874 im->xorigin = 0;
3875 im->xsize = 400;
3876 im->ygridstep = DNAN;
3877 im->yimg = 0;
3878 im->ylegend[0] = '\0';
3879 im->second_axis_scale = 0; /* 0 disables it */
3880 im->second_axis_shift = 0; /* no shift by default */
3881 im->second_axis_legend[0] = '\0';
3882 im->second_axis_format[0] = '\0';
3883 im->yorigin = 0;
3884 im->ysize = 100;
3885 im->zoom = 1;
3887 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3888 im->cr = cairo_create(im->surface);
3890 for (i = 0; i < DIM(text_prop); i++) {
3891 im->text_prop[i].size = -1;
3892 im->text_prop[i].font_desc = NULL;
3893 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3894 }
3896 if (fontmap == NULL){
3897 fontmap = pango_cairo_font_map_get_default();
3898 }
3900 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3902 pango_cairo_context_set_resolution(context, 100);
3904 pango_cairo_update_context(im->cr,context);
3906 im->layout = pango_layout_new(context);
3907 g_object_unref (context);
3909 // im->layout = pango_cairo_create_layout(im->cr);
3912 cairo_font_options_set_hint_style
3913 (im->font_options, CAIRO_HINT_STYLE_FULL);
3914 cairo_font_options_set_hint_metrics
3915 (im->font_options, CAIRO_HINT_METRICS_ON);
3916 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3920 for (i = 0; i < DIM(graph_col); i++)
3921 im->graph_col[i] = graph_col[i];
3924 }
3927 void rrd_graph_options(
3928 int argc,
3929 char *argv[],
3930 image_desc_t
3931 *im)
3932 {
3933 int stroff;
3934 char *parsetime_error = NULL;
3935 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3936 time_t start_tmp = 0, end_tmp = 0;
3937 long long_tmp;
3938 rrd_time_value_t start_tv, end_tv;
3939 long unsigned int color;
3940 char *old_locale = "";
3942 /* defines for long options without a short equivalent. should be bytes,
3943 and may not collide with (the ASCII value of) short options */
3944 #define LONGOPT_UNITS_SI 255
3946 /* *INDENT-OFF* */
3947 struct option long_options[] = {
3948 { "start", required_argument, 0, 's'},
3949 { "end", required_argument, 0, 'e'},
3950 { "x-grid", required_argument, 0, 'x'},
3951 { "y-grid", required_argument, 0, 'y'},
3952 { "vertical-label", required_argument, 0, 'v'},
3953 { "width", required_argument, 0, 'w'},
3954 { "height", required_argument, 0, 'h'},
3955 { "full-size-mode", no_argument, 0, 'D'},
3956 { "interlaced", no_argument, 0, 'i'},
3957 { "upper-limit", required_argument, 0, 'u'},
3958 { "lower-limit", required_argument, 0, 'l'},
3959 { "rigid", no_argument, 0, 'r'},
3960 { "base", required_argument, 0, 'b'},
3961 { "logarithmic", no_argument, 0, 'o'},
3962 { "color", required_argument, 0, 'c'},
3963 { "font", required_argument, 0, 'n'},
3964 { "title", required_argument, 0, 't'},
3965 { "imginfo", required_argument, 0, 'f'},
3966 { "imgformat", required_argument, 0, 'a'},
3967 { "lazy", no_argument, 0, 'z'},
3968 { "zoom", required_argument, 0, 'm'},
3969 { "no-legend", no_argument, 0, 'g'},
3970 { "force-rules-legend", no_argument, 0, 'F'},
3971 { "only-graph", no_argument, 0, 'j'},
3972 { "alt-y-grid", no_argument, 0, 'Y'},
3973 {"disable-rrdtool-tag", no_argument, 0, 1001},
3974 {"right-axis", required_argument, 0, 1002},
3975 {"right-axis-label", required_argument, 0, 1003},
3976 {"right-axis-format", required_argument, 0, 1004},
3977 { "no-minor", no_argument, 0, 'I'},
3978 { "slope-mode", no_argument, 0, 'E'},
3979 { "alt-autoscale", no_argument, 0, 'A'},
3980 { "alt-autoscale-min", no_argument, 0, 'J'},
3981 { "alt-autoscale-max", no_argument, 0, 'M'},
3982 { "no-gridfit", no_argument, 0, 'N'},
3983 { "units-exponent", required_argument, 0, 'X'},
3984 { "units-length", required_argument, 0, 'L'},
3985 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3986 { "step", required_argument, 0, 'S'},
3987 { "tabwidth", required_argument, 0, 'T'},
3988 { "font-render-mode", required_argument, 0, 'R'},
3989 { "graph-render-mode", required_argument, 0, 'G'},
3990 { "font-smoothing-threshold", required_argument, 0, 'B'},
3991 { "watermark", required_argument, 0, 'W'},
3992 { "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 */
3993 { "pango-markup", no_argument, 0, 'P'},
3994 { 0, 0, 0, 0}
3995 };
3996 /* *INDENT-ON* */
3998 optind = 0;
3999 opterr = 0; /* initialize getopt */
4000 rrd_parsetime("end-24h", &start_tv);
4001 rrd_parsetime("now", &end_tv);
4002 while (1) {
4003 int option_index = 0;
4004 int opt;
4005 int col_start, col_end;
4007 opt = getopt_long(argc, argv,
4008 "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",
4009 long_options, &option_index);
4010 if (opt == EOF)
4011 break;
4012 switch (opt) {
4013 case 'I':
4014 im->extra_flags |= NOMINOR;
4015 break;
4016 case 'Y':
4017 im->extra_flags |= ALTYGRID;
4018 break;
4019 case 'A':
4020 im->extra_flags |= ALTAUTOSCALE;
4021 break;
4022 case 'J':
4023 im->extra_flags |= ALTAUTOSCALE_MIN;
4024 break;
4025 case 'M':
4026 im->extra_flags |= ALTAUTOSCALE_MAX;
4027 break;
4028 case 'j':
4029 im->extra_flags |= ONLY_GRAPH;
4030 break;
4031 case 'g':
4032 im->extra_flags |= NOLEGEND;
4033 break;
4034 case 'F':
4035 im->extra_flags |= FORCE_RULES_LEGEND;
4036 break;
4037 case 1001:
4038 im->extra_flags |= NO_RRDTOOL_TAG;
4039 break;
4040 case LONGOPT_UNITS_SI:
4041 if (im->extra_flags & FORCE_UNITS) {
4042 rrd_set_error("--units can only be used once!");
4043 setlocale(LC_NUMERIC, old_locale);
4044 return;
4045 }
4046 if (strcmp(optarg, "si") == 0)
4047 im->extra_flags |= FORCE_UNITS_SI;
4048 else {
4049 rrd_set_error("invalid argument for --units: %s", optarg);
4050 return;
4051 }
4052 break;
4053 case 'X':
4054 im->unitsexponent = atoi(optarg);
4055 break;
4056 case 'L':
4057 im->unitslength = atoi(optarg);
4058 im->forceleftspace = 1;
4059 break;
4060 case 'T':
4061 old_locale = setlocale(LC_NUMERIC, "C");
4062 im->tabwidth = atof(optarg);
4063 setlocale(LC_NUMERIC, old_locale);
4064 break;
4065 case 'S':
4066 old_locale = setlocale(LC_NUMERIC, "C");
4067 im->step = atoi(optarg);
4068 setlocale(LC_NUMERIC, old_locale);
4069 break;
4070 case 'N':
4071 im->gridfit = 0;
4072 break;
4073 case 'P':
4074 im->with_markup = 1;
4075 break;
4076 case 's':
4077 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4078 rrd_set_error("start time: %s", parsetime_error);
4079 return;
4080 }
4081 break;
4082 case 'e':
4083 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4084 rrd_set_error("end time: %s", parsetime_error);
4085 return;
4086 }
4087 break;
4088 case 'x':
4089 if (strcmp(optarg, "none") == 0) {
4090 im->draw_x_grid = 0;
4091 break;
4092 };
4093 if (sscanf(optarg,
4094 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4095 scan_gtm,
4096 &im->xlab_user.gridst,
4097 scan_mtm,
4098 &im->xlab_user.mgridst,
4099 scan_ltm,
4100 &im->xlab_user.labst,
4101 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4102 strncpy(im->xlab_form, optarg + stroff,
4103 sizeof(im->xlab_form) - 1);
4104 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4105 if ((int)
4106 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4107 rrd_set_error("unknown keyword %s", scan_gtm);
4108 return;
4109 } else if ((int)
4110 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4111 == -1) {
4112 rrd_set_error("unknown keyword %s", scan_mtm);
4113 return;
4114 } else if ((int)
4115 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4116 rrd_set_error("unknown keyword %s", scan_ltm);
4117 return;
4118 }
4119 im->xlab_user.minsec = 1;
4120 im->xlab_user.stst = im->xlab_form;
4121 } else {
4122 rrd_set_error("invalid x-grid format");
4123 return;
4124 }
4125 break;
4126 case 'y':
4128 if (strcmp(optarg, "none") == 0) {
4129 im->draw_y_grid = 0;
4130 break;
4131 };
4132 old_locale = setlocale(LC_NUMERIC, "C");
4133 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4134 setlocale(LC_NUMERIC, old_locale);
4135 if (im->ygridstep <= 0) {
4136 rrd_set_error("grid step must be > 0");
4137 return;
4138 } else if (im->ylabfact < 1) {
4139 rrd_set_error("label factor must be > 0");
4140 return;
4141 }
4142 } else {
4143 setlocale(LC_NUMERIC, old_locale);
4144 rrd_set_error("invalid y-grid format");
4145 return;
4146 }
4147 break;
4148 case 1002: /* right y axis */
4150 if(sscanf(optarg,
4151 "%lf:%lf",
4152 &im->second_axis_scale,
4153 &im->second_axis_shift) == 2) {
4154 if(im->second_axis_scale==0){
4155 rrd_set_error("the second_axis_scale must not be 0");
4156 return;
4157 }
4158 } else {
4159 rrd_set_error("invalid right-axis format expected scale:shift");
4160 return;
4161 }
4162 break;
4163 case 1003:
4164 strncpy(im->second_axis_legend,optarg,150);
4165 im->second_axis_legend[150]='\0';
4166 break;
4167 case 1004:
4168 if (bad_format(optarg)){
4169 rrd_set_error("use either %le or %lf formats");
4170 return;
4171 }
4172 strncpy(im->second_axis_format,optarg,150);
4173 im->second_axis_format[150]='\0';
4174 break;
4175 case 'v':
4176 strncpy(im->ylegend, optarg, 150);
4177 im->ylegend[150] = '\0';
4178 break;
4179 case 'u':
4180 old_locale = setlocale(LC_NUMERIC, "C");
4181 im->maxval = atof(optarg);
4182 setlocale(LC_NUMERIC, old_locale);
4183 break;
4184 case 'l':
4185 old_locale = setlocale(LC_NUMERIC, "C");
4186 im->minval = atof(optarg);
4187 setlocale(LC_NUMERIC, old_locale);
4188 break;
4189 case 'b':
4190 im->base = atol(optarg);
4191 if (im->base != 1024 && im->base != 1000) {
4192 rrd_set_error
4193 ("the only sensible value for base apart from 1000 is 1024");
4194 return;
4195 }
4196 break;
4197 case 'w':
4198 long_tmp = atol(optarg);
4199 if (long_tmp < 10) {
4200 rrd_set_error("width below 10 pixels");
4201 return;
4202 }
4203 im->xsize = long_tmp;
4204 break;
4205 case 'h':
4206 long_tmp = atol(optarg);
4207 if (long_tmp < 10) {
4208 rrd_set_error("height below 10 pixels");
4209 return;
4210 }
4211 im->ysize = long_tmp;
4212 break;
4213 case 'D':
4214 im->extra_flags |= FULL_SIZE_MODE;
4215 break;
4216 case 'i':
4217 /* interlaced png not supported at the moment */
4218 break;
4219 case 'r':
4220 im->rigid = 1;
4221 break;
4222 case 'f':
4223 im->imginfo = optarg;
4224 break;
4225 case 'a':
4226 if ((int)
4227 (im->imgformat = if_conv(optarg)) == -1) {
4228 rrd_set_error("unsupported graphics format '%s'", optarg);
4229 return;
4230 }
4231 break;
4232 case 'z':
4233 im->lazy = 1;
4234 break;
4235 case 'E':
4236 im->slopemode = 1;
4237 break;
4238 case 'o':
4239 im->logarithmic = 1;
4240 break;
4241 case 'c':
4242 if (sscanf(optarg,
4243 "%10[A-Z]#%n%8lx%n",
4244 col_nam, &col_start, &color, &col_end) == 2) {
4245 int ci;
4246 int col_len = col_end - col_start;
4248 switch (col_len) {
4249 case 3:
4250 color =
4251 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4252 0x011000) |
4253 ((color & 0x00F)
4254 * 0x001100)
4255 | 0x000000FF);
4256 break;
4257 case 4:
4258 color =
4259 (((color & 0xF000) *
4260 0x11000) | ((color & 0x0F00) *
4261 0x01100) | ((color &
4262 0x00F0) *
4263 0x00110) |
4264 ((color & 0x000F) * 0x00011)
4265 );
4266 break;
4267 case 6:
4268 color = (color << 8) + 0xff /* shift left by 8 */ ;
4269 break;
4270 case 8:
4271 break;
4272 default:
4273 rrd_set_error("the color format is #RRGGBB[AA]");
4274 return;
4275 }
4276 if ((ci = grc_conv(col_nam)) != -1) {
4277 im->graph_col[ci] = gfx_hex_to_col(color);
4278 } else {
4279 rrd_set_error("invalid color name '%s'", col_nam);
4280 return;
4281 }
4282 } else {
4283 rrd_set_error("invalid color def format");
4284 return;
4285 }
4286 break;
4287 case 'n':{
4288 char prop[15];
4289 double size = 1;
4290 int end;
4292 old_locale = setlocale(LC_NUMERIC, "C");
4293 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4294 int sindex, propidx;
4296 setlocale(LC_NUMERIC, old_locale);
4297 if ((sindex = text_prop_conv(prop)) != -1) {
4298 for (propidx = sindex;
4299 propidx < TEXT_PROP_LAST; propidx++) {
4300 if (size > 0) {
4301 rrd_set_font_desc(im,propidx,NULL,size);
4302 }
4303 if ((int) strlen(optarg) > end+2) {
4304 if (optarg[end] == ':') {
4305 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4306 } else {
4307 rrd_set_error
4308 ("expected : after font size in '%s'",
4309 optarg);
4310 return;
4311 }
4312 }
4313 /* only run the for loop for DEFAULT (0) for
4314 all others, we break here. woodo programming */
4315 if (propidx == sindex && sindex != 0)
4316 break;
4317 }
4318 } else {
4319 rrd_set_error("invalid fonttag '%s'", prop);
4320 return;
4321 }
4322 } else {
4323 setlocale(LC_NUMERIC, old_locale);
4324 rrd_set_error("invalid text property format");
4325 return;
4326 }
4327 break;
4328 }
4329 case 'm':
4330 old_locale = setlocale(LC_NUMERIC, "C");
4331 im->zoom = atof(optarg);
4332 setlocale(LC_NUMERIC, old_locale);
4333 if (im->zoom <= 0.0) {
4334 rrd_set_error("zoom factor must be > 0");
4335 return;
4336 }
4337 break;
4338 case 't':
4339 strncpy(im->title, optarg, 150);
4340 im->title[150] = '\0';
4341 break;
4342 case 'R':
4343 if (strcmp(optarg, "normal") == 0) {
4344 cairo_font_options_set_antialias
4345 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4346 cairo_font_options_set_hint_style
4347 (im->font_options, CAIRO_HINT_STYLE_FULL);
4348 } else if (strcmp(optarg, "light") == 0) {
4349 cairo_font_options_set_antialias
4350 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4351 cairo_font_options_set_hint_style
4352 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4353 } else if (strcmp(optarg, "mono") == 0) {
4354 cairo_font_options_set_antialias
4355 (im->font_options, CAIRO_ANTIALIAS_NONE);
4356 cairo_font_options_set_hint_style
4357 (im->font_options, CAIRO_HINT_STYLE_FULL);
4358 } else {
4359 rrd_set_error("unknown font-render-mode '%s'", optarg);
4360 return;
4361 }
4362 break;
4363 case 'G':
4364 if (strcmp(optarg, "normal") == 0)
4365 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4366 else if (strcmp(optarg, "mono") == 0)
4367 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4368 else {
4369 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4370 return;
4371 }
4372 break;
4373 case 'B':
4374 /* not supported curently */
4375 break;
4376 case 'W':
4377 strncpy(im->watermark, optarg, 100);
4378 im->watermark[99] = '\0';
4379 break;
4380 case '?':
4381 if (optopt != 0)
4382 rrd_set_error("unknown option '%c'", optopt);
4383 else
4384 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4385 return;
4386 }
4387 }
4389 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4390 pango_layout_context_changed(im->layout);
4394 if (im->logarithmic && im->minval <= 0) {
4395 rrd_set_error
4396 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4397 return;
4398 }
4400 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4401 /* error string is set in rrd_parsetime.c */
4402 return;
4403 }
4405 if (start_tmp < 3600 * 24 * 365 * 10) {
4406 rrd_set_error
4407 ("the first entry to fetch should be after 1980 (%ld)",
4408 start_tmp);
4409 return;
4410 }
4412 if (end_tmp < start_tmp) {
4413 rrd_set_error
4414 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4415 return;
4416 }
4418 im->start = start_tmp;
4419 im->end = end_tmp;
4420 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4421 }
4423 int rrd_graph_color(
4424 image_desc_t
4425 *im,
4426 char *var,
4427 char *err,
4428 int optional)
4429 {
4430 char *color;
4431 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4433 color = strstr(var, "#");
4434 if (color == NULL) {
4435 if (optional == 0) {
4436 rrd_set_error("Found no color in %s", err);
4437 return 0;
4438 }
4439 return 0;
4440 } else {
4441 int n = 0;
4442 char *rest;
4443 long unsigned int col;
4445 rest = strstr(color, ":");
4446 if (rest != NULL)
4447 n = rest - color;
4448 else
4449 n = strlen(color);
4450 switch (n) {
4451 case 7:
4452 sscanf(color, "#%6lx%n", &col, &n);
4453 col = (col << 8) + 0xff /* shift left by 8 */ ;
4454 if (n != 7)
4455 rrd_set_error("Color problem in %s", err);
4456 break;
4457 case 9:
4458 sscanf(color, "#%8lx%n", &col, &n);
4459 if (n == 9)
4460 break;
4461 default:
4462 rrd_set_error("Color problem in %s", err);
4463 }
4464 if (rrd_test_error())
4465 return 0;
4466 gdp->col = gfx_hex_to_col(col);
4467 return n;
4468 }
4469 }
4472 int bad_format(
4473 char *fmt)
4474 {
4475 char *ptr;
4476 int n = 0;
4478 ptr = fmt;
4479 while (*ptr != '\0')
4480 if (*ptr++ == '%') {
4482 /* line cannot end with percent char */
4483 if (*ptr == '\0')
4484 return 1;
4485 /* '%s', '%S' and '%%' are allowed */
4486 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4487 ptr++;
4488 /* %c is allowed (but use only with vdef!) */
4489 else if (*ptr == 'c') {
4490 ptr++;
4491 n = 1;
4492 }
4494 /* or else '% 6.2lf' and such are allowed */
4495 else {
4496 /* optional padding character */
4497 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4498 ptr++;
4499 /* This should take care of 'm.n' with all three optional */
4500 while (*ptr >= '0' && *ptr <= '9')
4501 ptr++;
4502 if (*ptr == '.')
4503 ptr++;
4504 while (*ptr >= '0' && *ptr <= '9')
4505 ptr++;
4506 /* Either 'le', 'lf' or 'lg' must follow here */
4507 if (*ptr++ != 'l')
4508 return 1;
4509 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4510 ptr++;
4511 else
4512 return 1;
4513 n++;
4514 }
4515 }
4517 return (n != 1);
4518 }
4521 int vdef_parse(
4522 struct graph_desc_t
4523 *gdes,
4524 const char *const str)
4525 {
4526 /* A VDEF currently is either "func" or "param,func"
4527 * so the parsing is rather simple. Change if needed.
4528 */
4529 double param;
4530 char func[30];
4531 int n;
4532 char *old_locale;
4534 n = 0;
4535 old_locale = setlocale(LC_NUMERIC, "C");
4536 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4537 setlocale(LC_NUMERIC, old_locale);
4538 if (n == (int) strlen(str)) { /* matched */
4539 ;
4540 } else {
4541 n = 0;
4542 sscanf(str, "%29[A-Z]%n", func, &n);
4543 if (n == (int) strlen(str)) { /* matched */
4544 param = DNAN;
4545 } else {
4546 rrd_set_error
4547 ("Unknown function string '%s' in VDEF '%s'",
4548 str, gdes->vname);
4549 return -1;
4550 }
4551 }
4552 if (!strcmp("PERCENT", func))
4553 gdes->vf.op = VDEF_PERCENT;
4554 else if (!strcmp("MAXIMUM", func))
4555 gdes->vf.op = VDEF_MAXIMUM;
4556 else if (!strcmp("AVERAGE", func))
4557 gdes->vf.op = VDEF_AVERAGE;
4558 else if (!strcmp("STDEV", func))
4559 gdes->vf.op = VDEF_STDEV;
4560 else if (!strcmp("MINIMUM", func))
4561 gdes->vf.op = VDEF_MINIMUM;
4562 else if (!strcmp("TOTAL", func))
4563 gdes->vf.op = VDEF_TOTAL;
4564 else if (!strcmp("FIRST", func))
4565 gdes->vf.op = VDEF_FIRST;
4566 else if (!strcmp("LAST", func))
4567 gdes->vf.op = VDEF_LAST;
4568 else if (!strcmp("LSLSLOPE", func))
4569 gdes->vf.op = VDEF_LSLSLOPE;
4570 else if (!strcmp("LSLINT", func))
4571 gdes->vf.op = VDEF_LSLINT;
4572 else if (!strcmp("LSLCORREL", func))
4573 gdes->vf.op = VDEF_LSLCORREL;
4574 else {
4575 rrd_set_error
4576 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4577 return -1;
4578 };
4579 switch (gdes->vf.op) {
4580 case VDEF_PERCENT:
4581 if (isnan(param)) { /* no parameter given */
4582 rrd_set_error
4583 ("Function '%s' needs parameter in VDEF '%s'\n",
4584 func, gdes->vname);
4585 return -1;
4586 };
4587 if (param >= 0.0 && param <= 100.0) {
4588 gdes->vf.param = param;
4589 gdes->vf.val = DNAN; /* undefined */
4590 gdes->vf.when = 0; /* undefined */
4591 } else {
4592 rrd_set_error
4593 ("Parameter '%f' out of range in VDEF '%s'\n",
4594 param, gdes->vname);
4595 return -1;
4596 };
4597 break;
4598 case VDEF_MAXIMUM:
4599 case VDEF_AVERAGE:
4600 case VDEF_STDEV:
4601 case VDEF_MINIMUM:
4602 case VDEF_TOTAL:
4603 case VDEF_FIRST:
4604 case VDEF_LAST:
4605 case VDEF_LSLSLOPE:
4606 case VDEF_LSLINT:
4607 case VDEF_LSLCORREL:
4608 if (isnan(param)) {
4609 gdes->vf.param = DNAN;
4610 gdes->vf.val = DNAN;
4611 gdes->vf.when = 0;
4612 } else {
4613 rrd_set_error
4614 ("Function '%s' needs no parameter in VDEF '%s'\n",
4615 func, gdes->vname);
4616 return -1;
4617 };
4618 break;
4619 };
4620 return 0;
4621 }
4624 int vdef_calc(
4625 image_desc_t *im,
4626 int gdi)
4627 {
4628 graph_desc_t *src, *dst;
4629 rrd_value_t *data;
4630 long step, steps;
4632 dst = &im->gdes[gdi];
4633 src = &im->gdes[dst->vidx];
4634 data = src->data + src->ds;
4636 steps = (src->end - src->start) / src->step;
4637 #if 0
4638 printf
4639 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4640 src->start, src->end, steps);
4641 #endif
4642 switch (dst->vf.op) {
4643 case VDEF_PERCENT:{
4644 rrd_value_t *array;
4645 int field;
4646 if ((array = (rrd_value_t*)(malloc(steps * sizeof(double)))) == NULL) {
4647 rrd_set_error("malloc VDEV_PERCENT");
4648 return -1;
4649 }
4650 for (step = 0; step < steps; step++) {
4651 array[step] = data[step * src->ds_cnt];
4652 }
4653 qsort(array, step, sizeof(double), vdef_percent_compar);
4654 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4655 dst->vf.val = array[field];
4656 dst->vf.when = 0; /* no time component */
4657 free(array);
4658 #if 0
4659 for (step = 0; step < steps; step++)
4660 printf("DEBUG: %3li:%10.2f %c\n",
4661 step, array[step], step == field ? '*' : ' ');
4662 #endif
4663 }
4664 break;
4665 case VDEF_MAXIMUM:
4666 step = 0;
4667 while (step != steps && isnan(data[step * src->ds_cnt]))
4668 step++;
4669 if (step == steps) {
4670 dst->vf.val = DNAN;
4671 dst->vf.when = 0;
4672 } else {
4673 dst->vf.val = data[step * src->ds_cnt];
4674 dst->vf.when = src->start + (step + 1) * src->step;
4675 }
4676 while (step != steps) {
4677 if (finite(data[step * src->ds_cnt])) {
4678 if (data[step * src->ds_cnt] > dst->vf.val) {
4679 dst->vf.val = data[step * src->ds_cnt];
4680 dst->vf.when = src->start + (step + 1) * src->step;
4681 }
4682 }
4683 step++;
4684 }
4685 break;
4686 case VDEF_TOTAL:
4687 case VDEF_STDEV:
4688 case VDEF_AVERAGE:{
4689 int cnt = 0;
4690 double sum = 0.0;
4691 double average = 0.0;
4693 for (step = 0; step < steps; step++) {
4694 if (finite(data[step * src->ds_cnt])) {
4695 sum += data[step * src->ds_cnt];
4696 cnt++;
4697 };
4698 }
4699 if (cnt) {
4700 if (dst->vf.op == VDEF_TOTAL) {
4701 dst->vf.val = sum * src->step;
4702 dst->vf.when = 0; /* no time component */
4703 } else if (dst->vf.op == VDEF_AVERAGE) {
4704 dst->vf.val = sum / cnt;
4705 dst->vf.when = 0; /* no time component */
4706 } else {
4707 average = sum / cnt;
4708 sum = 0.0;
4709 for (step = 0; step < steps; step++) {
4710 if (finite(data[step * src->ds_cnt])) {
4711 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4712 };
4713 }
4714 dst->vf.val = pow(sum / cnt, 0.5);
4715 dst->vf.when = 0; /* no time component */
4716 };
4717 } else {
4718 dst->vf.val = DNAN;
4719 dst->vf.when = 0;
4720 }
4721 }
4722 break;
4723 case VDEF_MINIMUM:
4724 step = 0;
4725 while (step != steps && isnan(data[step * src->ds_cnt]))
4726 step++;
4727 if (step == steps) {
4728 dst->vf.val = DNAN;
4729 dst->vf.when = 0;
4730 } else {
4731 dst->vf.val = data[step * src->ds_cnt];
4732 dst->vf.when = src->start + (step + 1) * src->step;
4733 }
4734 while (step != steps) {
4735 if (finite(data[step * src->ds_cnt])) {
4736 if (data[step * src->ds_cnt] < dst->vf.val) {
4737 dst->vf.val = data[step * src->ds_cnt];
4738 dst->vf.when = src->start + (step + 1) * src->step;
4739 }
4740 }
4741 step++;
4742 }
4743 break;
4744 case VDEF_FIRST:
4745 /* The time value returned here is one step before the
4746 * actual time value. This is the start of the first
4747 * non-NaN interval.
4748 */
4749 step = 0;
4750 while (step != steps && isnan(data[step * src->ds_cnt]))
4751 step++;
4752 if (step == steps) { /* all entries were NaN */
4753 dst->vf.val = DNAN;
4754 dst->vf.when = 0;
4755 } else {
4756 dst->vf.val = data[step * src->ds_cnt];
4757 dst->vf.when = src->start + step * src->step;
4758 }
4759 break;
4760 case VDEF_LAST:
4761 /* The time value returned here is the
4762 * actual time value. This is the end of the last
4763 * non-NaN interval.
4764 */
4765 step = steps - 1;
4766 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4767 step--;
4768 if (step < 0) { /* all entries were NaN */
4769 dst->vf.val = DNAN;
4770 dst->vf.when = 0;
4771 } else {
4772 dst->vf.val = data[step * src->ds_cnt];
4773 dst->vf.when = src->start + (step + 1) * src->step;
4774 }
4775 break;
4776 case VDEF_LSLSLOPE:
4777 case VDEF_LSLINT:
4778 case VDEF_LSLCORREL:{
4779 /* Bestfit line by linear least squares method */
4781 int cnt = 0;
4782 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4784 SUMx = 0;
4785 SUMy = 0;
4786 SUMxy = 0;
4787 SUMxx = 0;
4788 SUMyy = 0;
4789 for (step = 0; step < steps; step++) {
4790 if (finite(data[step * src->ds_cnt])) {
4791 cnt++;
4792 SUMx += step;
4793 SUMxx += step * step;
4794 SUMxy += step * data[step * src->ds_cnt];
4795 SUMy += data[step * src->ds_cnt];
4796 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4797 };
4798 }
4800 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4801 y_intercept = (SUMy - slope * SUMx) / cnt;
4802 correl =
4803 (SUMxy -
4804 (SUMx * SUMy) / cnt) /
4805 sqrt((SUMxx -
4806 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4807 if (cnt) {
4808 if (dst->vf.op == VDEF_LSLSLOPE) {
4809 dst->vf.val = slope;
4810 dst->vf.when = 0;
4811 } else if (dst->vf.op == VDEF_LSLINT) {
4812 dst->vf.val = y_intercept;
4813 dst->vf.when = 0;
4814 } else if (dst->vf.op == VDEF_LSLCORREL) {
4815 dst->vf.val = correl;
4816 dst->vf.when = 0;
4817 };
4818 } else {
4819 dst->vf.val = DNAN;
4820 dst->vf.when = 0;
4821 }
4822 }
4823 break;
4824 }
4825 return 0;
4826 }
4828 /* NaN < -INF < finite_values < INF */
4829 int vdef_percent_compar(
4830 const void
4831 *a,
4832 const void
4833 *b)
4834 {
4835 /* Equality is not returned; this doesn't hurt except
4836 * (maybe) for a little performance.
4837 */
4839 /* First catch NaN values. They are smallest */
4840 if (isnan(*(double *) a))
4841 return -1;
4842 if (isnan(*(double *) b))
4843 return 1;
4844 /* NaN doesn't reach this part so INF and -INF are extremes.
4845 * The sign from isinf() is compatible with the sign we return
4846 */
4847 if (isinf(*(double *) a))
4848 return isinf(*(double *) a);
4849 if (isinf(*(double *) b))
4850 return isinf(*(double *) b);
4851 /* If we reach this, both values must be finite */
4852 if (*(double *) a < *(double *) b)
4853 return -1;
4854 else
4855 return 1;
4856 }
4858 void grinfo_push(
4859 image_desc_t *im,
4860 char *key,
4861 rrd_info_type_t type,
4862 rrd_infoval_t value)
4863 {
4864 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4865 if (im->grinfo == NULL) {
4866 im->grinfo = im->grinfo_current;
4867 }
4868 }