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