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