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