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);
3118 info.u_cnt = im->start;
3119 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3120 info.u_cnt = im->end;
3121 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3123 /* get actual drawing data and find min and max values */
3124 if (data_proc(im) == -1)
3125 return -1;
3126 if (!im->logarithmic) {
3127 si_unit(im);
3128 }
3130 /* identify si magnitude Kilo, Mega Giga ? */
3131 if (!im->rigid && !im->logarithmic)
3132 expand_range(im); /* make sure the upper and lower limit are
3133 sensible values */
3135 info.u_val = im->minval;
3136 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3137 info.u_val = im->maxval;
3138 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3140 if (!calc_horizontal_grid(im))
3141 return -1;
3142 /* reset precalc */
3143 ytr(im, DNAN);
3144 /* if (im->gridfit)
3145 apply_gridfit(im); */
3146 /* the actual graph is created by going through the individual
3147 graph elements and then drawing them */
3148 cairo_surface_destroy(im->surface);
3149 switch (im->imgformat) {
3150 case IF_PNG:
3151 im->surface =
3152 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3153 im->ximg * im->zoom,
3154 im->yimg * im->zoom);
3155 break;
3156 case IF_PDF:
3157 im->gridfit = 0;
3158 im->surface = strlen(im->graphfile)
3159 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3160 im->yimg * im->zoom)
3161 : cairo_pdf_surface_create_for_stream
3162 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3163 break;
3164 case IF_EPS:
3165 im->gridfit = 0;
3166 im->surface = strlen(im->graphfile)
3167 ?
3168 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3169 im->yimg * im->zoom)
3170 : cairo_ps_surface_create_for_stream
3171 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3172 break;
3173 case IF_SVG:
3174 im->gridfit = 0;
3175 im->surface = strlen(im->graphfile)
3176 ?
3177 cairo_svg_surface_create(im->
3178 graphfile,
3179 im->ximg * im->zoom, im->yimg * im->zoom)
3180 : cairo_svg_surface_create_for_stream
3181 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3182 cairo_svg_surface_restrict_to_version
3183 (im->surface, CAIRO_SVG_VERSION_1_1);
3184 break;
3185 };
3186 cairo_destroy(im->cr);
3187 im->cr = cairo_create(im->surface);
3188 cairo_set_antialias(im->cr, im->graph_antialias);
3189 cairo_scale(im->cr, im->zoom, im->zoom);
3190 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3191 gfx_new_area(im, 0, 0, 0, im->yimg,
3192 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3193 gfx_add_point(im, im->ximg, 0);
3194 gfx_close_path(im);
3195 gfx_new_area(im, im->xorigin,
3196 im->yorigin,
3197 im->xorigin +
3198 im->xsize, im->yorigin,
3199 im->xorigin +
3200 im->xsize,
3201 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3202 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3203 gfx_close_path(im);
3204 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3205 im->xsize, im->ysize + 2.0);
3206 cairo_clip(im->cr);
3207 if (im->minval > 0.0)
3208 areazero = im->minval;
3209 if (im->maxval < 0.0)
3210 areazero = im->maxval;
3211 for (i = 0; i < im->gdes_c; i++) {
3212 switch (im->gdes[i].gf) {
3213 case GF_CDEF:
3214 case GF_VDEF:
3215 case GF_DEF:
3216 case GF_PRINT:
3217 case GF_GPRINT:
3218 case GF_COMMENT:
3219 case GF_TEXTALIGN:
3220 case GF_HRULE:
3221 case GF_VRULE:
3222 case GF_XPORT:
3223 case GF_SHIFT:
3224 break;
3225 case GF_TICK:
3226 for (ii = 0; ii < im->xsize; ii++) {
3227 if (!isnan(im->gdes[i].p_data[ii])
3228 && im->gdes[i].p_data[ii] != 0.0) {
3229 if (im->gdes[i].yrule > 0) {
3230 gfx_line(im,
3231 im->xorigin + ii,
3232 im->yorigin + 1.0,
3233 im->xorigin + ii,
3234 im->yorigin -
3235 im->gdes[i].yrule *
3236 im->ysize, 1.0, im->gdes[i].col);
3237 } else if (im->gdes[i].yrule < 0) {
3238 gfx_line(im,
3239 im->xorigin + ii,
3240 im->yorigin - im->ysize - 1.0,
3241 im->xorigin + ii,
3242 im->yorigin - im->ysize -
3243 im->gdes[i].
3244 yrule *
3245 im->ysize, 1.0, im->gdes[i].col);
3246 }
3247 }
3248 }
3249 break;
3250 case GF_LINE:
3251 case GF_AREA:
3252 /* fix data points at oo and -oo */
3253 for (ii = 0; ii < im->xsize; ii++) {
3254 if (isinf(im->gdes[i].p_data[ii])) {
3255 if (im->gdes[i].p_data[ii] > 0) {
3256 im->gdes[i].p_data[ii] = im->maxval;
3257 } else {
3258 im->gdes[i].p_data[ii] = im->minval;
3259 }
3261 }
3262 } /* for */
3264 /* *******************************************************
3265 a ___. (a,t)
3266 | | ___
3267 ____| | | |
3268 | |___|
3269 -------|--t-1--t--------------------------------
3271 if we know the value at time t was a then
3272 we draw a square from t-1 to t with the value a.
3274 ********************************************************* */
3275 if (im->gdes[i].col.alpha != 0.0) {
3276 /* GF_LINE and friend */
3277 if (im->gdes[i].gf == GF_LINE) {
3278 double last_y = 0.0;
3279 int draw_on = 0;
3281 cairo_save(im->cr);
3282 cairo_new_path(im->cr);
3283 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3284 if (im->gdes[i].dash) {
3285 cairo_set_dash(im->cr,
3286 im->gdes[i].p_dashes,
3287 im->gdes[i].ndash, im->gdes[i].offset);
3288 }
3290 for (ii = 1; ii < im->xsize; ii++) {
3291 if (isnan(im->gdes[i].p_data[ii])
3292 || (im->slopemode == 1
3293 && isnan(im->gdes[i].p_data[ii - 1]))) {
3294 draw_on = 0;
3295 continue;
3296 }
3297 if (draw_on == 0) {
3298 last_y = ytr(im, im->gdes[i].p_data[ii]);
3299 if (im->slopemode == 0) {
3300 double x = ii - 1 + im->xorigin;
3301 double y = last_y;
3303 gfx_line_fit(im, &x, &y);
3304 cairo_move_to(im->cr, x, y);
3305 x = ii + im->xorigin;
3306 y = last_y;
3307 gfx_line_fit(im, &x, &y);
3308 cairo_line_to(im->cr, x, y);
3309 } else {
3310 double x = ii - 1 + im->xorigin;
3311 double y =
3312 ytr(im, im->gdes[i].p_data[ii - 1]);
3313 gfx_line_fit(im, &x, &y);
3314 cairo_move_to(im->cr, x, y);
3315 x = ii + im->xorigin;
3316 y = last_y;
3317 gfx_line_fit(im, &x, &y);
3318 cairo_line_to(im->cr, x, y);
3319 }
3320 draw_on = 1;
3321 } else {
3322 double x1 = ii + im->xorigin;
3323 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3325 if (im->slopemode == 0
3326 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3327 double x = ii - 1 + im->xorigin;
3328 double y = y1;
3330 gfx_line_fit(im, &x, &y);
3331 cairo_line_to(im->cr, x, y);
3332 };
3333 last_y = y1;
3334 gfx_line_fit(im, &x1, &y1);
3335 cairo_line_to(im->cr, x1, y1);
3336 };
3337 }
3338 cairo_set_source_rgba(im->cr,
3339 im->gdes[i].
3340 col.red,
3341 im->gdes[i].
3342 col.green,
3343 im->gdes[i].
3344 col.blue, im->gdes[i].col.alpha);
3345 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3346 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3347 cairo_stroke(im->cr);
3348 cairo_restore(im->cr);
3349 } else {
3350 int idxI = -1;
3351 double *foreY =
3352 (double *) malloc(sizeof(double) * im->xsize * 2);
3353 double *foreX =
3354 (double *) malloc(sizeof(double) * im->xsize * 2);
3355 double *backY =
3356 (double *) malloc(sizeof(double) * im->xsize * 2);
3357 double *backX =
3358 (double *) malloc(sizeof(double) * im->xsize * 2);
3359 int drawem = 0;
3361 for (ii = 0; ii <= im->xsize; ii++) {
3362 double ybase, ytop;
3364 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3365 int cntI = 1;
3366 int lastI = 0;
3368 while (cntI < idxI
3369 &&
3370 AlmostEqual2sComplement(foreY
3371 [lastI],
3372 foreY[cntI], 4)
3373 &&
3374 AlmostEqual2sComplement(foreY
3375 [lastI],
3376 foreY
3377 [cntI + 1], 4)) {
3378 cntI++;
3379 }
3380 gfx_new_area(im,
3381 backX[0], backY[0],
3382 foreX[0], foreY[0],
3383 foreX[cntI],
3384 foreY[cntI], im->gdes[i].col);
3385 while (cntI < idxI) {
3386 lastI = cntI;
3387 cntI++;
3388 while (cntI < idxI
3389 &&
3390 AlmostEqual2sComplement(foreY
3391 [lastI],
3392 foreY[cntI], 4)
3393 &&
3394 AlmostEqual2sComplement(foreY
3395 [lastI],
3396 foreY
3397 [cntI
3398 + 1], 4)) {
3399 cntI++;
3400 }
3401 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3402 }
3403 gfx_add_point(im, backX[idxI], backY[idxI]);
3404 while (idxI > 1) {
3405 lastI = idxI;
3406 idxI--;
3407 while (idxI > 1
3408 &&
3409 AlmostEqual2sComplement(backY
3410 [lastI],
3411 backY[idxI], 4)
3412 &&
3413 AlmostEqual2sComplement(backY
3414 [lastI],
3415 backY
3416 [idxI
3417 - 1], 4)) {
3418 idxI--;
3419 }
3420 gfx_add_point(im, backX[idxI], backY[idxI]);
3421 }
3422 idxI = -1;
3423 drawem = 0;
3424 gfx_close_path(im);
3425 }
3426 if (drawem != 0) {
3427 drawem = 0;
3428 idxI = -1;
3429 }
3430 if (ii == im->xsize)
3431 break;
3432 if (im->slopemode == 0 && ii == 0) {
3433 continue;
3434 }
3435 if (isnan(im->gdes[i].p_data[ii])) {
3436 drawem = 1;
3437 continue;
3438 }
3439 ytop = ytr(im, im->gdes[i].p_data[ii]);
3440 if (lastgdes && im->gdes[i].stack) {
3441 ybase = ytr(im, lastgdes->p_data[ii]);
3442 } else {
3443 ybase = ytr(im, areazero);
3444 }
3445 if (ybase == ytop) {
3446 drawem = 1;
3447 continue;
3448 }
3450 if (ybase > ytop) {
3451 double extra = ytop;
3453 ytop = ybase;
3454 ybase = extra;
3455 }
3456 if (im->slopemode == 0) {
3457 backY[++idxI] = ybase - 0.2;
3458 backX[idxI] = ii + im->xorigin - 1;
3459 foreY[idxI] = ytop + 0.2;
3460 foreX[idxI] = ii + im->xorigin - 1;
3461 }
3462 backY[++idxI] = ybase - 0.2;
3463 backX[idxI] = ii + im->xorigin;
3464 foreY[idxI] = ytop + 0.2;
3465 foreX[idxI] = ii + im->xorigin;
3466 }
3467 /* close up any remaining area */
3468 free(foreY);
3469 free(foreX);
3470 free(backY);
3471 free(backX);
3472 } /* else GF_LINE */
3473 }
3474 /* if color != 0x0 */
3475 /* make sure we do not run into trouble when stacking on NaN */
3476 for (ii = 0; ii < im->xsize; ii++) {
3477 if (isnan(im->gdes[i].p_data[ii])) {
3478 if (lastgdes && (im->gdes[i].stack)) {
3479 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3480 } else {
3481 im->gdes[i].p_data[ii] = areazero;
3482 }
3483 }
3484 }
3485 lastgdes = &(im->gdes[i]);
3486 break;
3487 case GF_STACK:
3488 rrd_set_error
3489 ("STACK should already be turned into LINE or AREA here");
3490 return -1;
3491 break;
3492 } /* switch */
3493 }
3494 cairo_reset_clip(im->cr);
3496 /* grid_paint also does the text */
3497 if (!(im->extra_flags & ONLY_GRAPH))
3498 grid_paint(im);
3499 if (!(im->extra_flags & ONLY_GRAPH))
3500 axis_paint(im);
3501 /* the RULES are the last thing to paint ... */
3502 for (i = 0; i < im->gdes_c; i++) {
3504 switch (im->gdes[i].gf) {
3505 case GF_HRULE:
3506 if (im->gdes[i].yrule >= im->minval
3507 && im->gdes[i].yrule <= im->maxval) {
3508 cairo_save(im->cr);
3509 if (im->gdes[i].dash) {
3510 cairo_set_dash(im->cr,
3511 im->gdes[i].p_dashes,
3512 im->gdes[i].ndash, im->gdes[i].offset);
3513 }
3514 gfx_line(im, im->xorigin,
3515 ytr(im, im->gdes[i].yrule),
3516 im->xorigin + im->xsize,
3517 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3518 cairo_stroke(im->cr);
3519 cairo_restore(im->cr);
3520 }
3521 break;
3522 case GF_VRULE:
3523 if (im->gdes[i].xrule >= im->start
3524 && im->gdes[i].xrule <= im->end) {
3525 cairo_save(im->cr);
3526 if (im->gdes[i].dash) {
3527 cairo_set_dash(im->cr,
3528 im->gdes[i].p_dashes,
3529 im->gdes[i].ndash, im->gdes[i].offset);
3530 }
3531 gfx_line(im,
3532 xtr(im, im->gdes[i].xrule),
3533 im->yorigin, xtr(im,
3534 im->
3535 gdes[i].
3536 xrule),
3537 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3538 cairo_stroke(im->cr);
3539 cairo_restore(im->cr);
3540 }
3541 break;
3542 default:
3543 break;
3544 }
3545 }
3548 switch (im->imgformat) {
3549 case IF_PNG:
3550 {
3551 cairo_status_t status;
3553 status = strlen(im->graphfile) ?
3554 cairo_surface_write_to_png(im->surface, im->graphfile)
3555 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3556 im);
3558 if (status != CAIRO_STATUS_SUCCESS) {
3559 rrd_set_error("Could not save png to '%s'", im->graphfile);
3560 return 1;
3561 }
3562 break;
3563 }
3564 default:
3565 if (strlen(im->graphfile)) {
3566 cairo_show_page(im->cr);
3567 } else {
3568 cairo_surface_finish(im->surface);
3569 }
3570 break;
3571 }
3573 return 0;
3574 }
3577 /*****************************************************
3578 * graph stuff
3579 *****************************************************/
3581 int gdes_alloc(
3582 image_desc_t *im)
3583 {
3585 im->gdes_c++;
3586 if ((im->gdes = (graph_desc_t *)
3587 rrd_realloc(im->gdes, (im->gdes_c)
3588 * sizeof(graph_desc_t))) == NULL) {
3589 rrd_set_error("realloc graph_descs");
3590 return -1;
3591 }
3594 im->gdes[im->gdes_c - 1].step = im->step;
3595 im->gdes[im->gdes_c - 1].step_orig = im->step;
3596 im->gdes[im->gdes_c - 1].stack = 0;
3597 im->gdes[im->gdes_c - 1].linewidth = 0;
3598 im->gdes[im->gdes_c - 1].debug = 0;
3599 im->gdes[im->gdes_c - 1].start = im->start;
3600 im->gdes[im->gdes_c - 1].start_orig = im->start;
3601 im->gdes[im->gdes_c - 1].end = im->end;
3602 im->gdes[im->gdes_c - 1].end_orig = im->end;
3603 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3604 im->gdes[im->gdes_c - 1].data = NULL;
3605 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3606 im->gdes[im->gdes_c - 1].data_first = 0;
3607 im->gdes[im->gdes_c - 1].p_data = NULL;
3608 im->gdes[im->gdes_c - 1].rpnp = NULL;
3609 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3610 im->gdes[im->gdes_c - 1].shift = 0.0;
3611 im->gdes[im->gdes_c - 1].dash = 0;
3612 im->gdes[im->gdes_c - 1].ndash = 0;
3613 im->gdes[im->gdes_c - 1].offset = 0;
3614 im->gdes[im->gdes_c - 1].col.red = 0.0;
3615 im->gdes[im->gdes_c - 1].col.green = 0.0;
3616 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3617 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3618 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3619 im->gdes[im->gdes_c - 1].format[0] = '\0';
3620 im->gdes[im->gdes_c - 1].strftm = 0;
3621 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3622 im->gdes[im->gdes_c - 1].ds = -1;
3623 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3624 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3625 im->gdes[im->gdes_c - 1].yrule = DNAN;
3626 im->gdes[im->gdes_c - 1].xrule = 0;
3627 return 0;
3628 }
3630 /* copies input untill the first unescaped colon is found
3631 or until input ends. backslashes have to be escaped as well */
3632 int scan_for_col(
3633 const char *const input,
3634 int len,
3635 char *const output)
3636 {
3637 int inp, outp = 0;
3639 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3640 if (input[inp] == '\\'
3641 && input[inp + 1] != '\0'
3642 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3643 output[outp++] = input[++inp];
3644 } else {
3645 output[outp++] = input[inp];
3646 }
3647 }
3648 output[outp] = '\0';
3649 return inp;
3650 }
3652 /* Now just a wrapper around rrd_graph_v */
3653 int rrd_graph(
3654 int argc,
3655 char **argv,
3656 char ***prdata,
3657 int *xsize,
3658 int *ysize,
3659 FILE * stream,
3660 double *ymin,
3661 double *ymax)
3662 {
3663 int prlines = 0;
3664 rrd_info_t *grinfo = NULL;
3665 rrd_info_t *walker;
3667 grinfo = rrd_graph_v(argc, argv);
3668 if (grinfo == NULL)
3669 return -1;
3670 walker = grinfo;
3671 (*prdata) = NULL;
3672 while (walker) {
3673 if (strcmp(walker->key, "image_info") == 0) {
3674 prlines++;
3675 if (((*prdata) =
3676 (char**)rrd_realloc((*prdata),
3677 (prlines + 1) * sizeof(char *))) == NULL) {
3678 rrd_set_error("realloc prdata");
3679 return 0;
3680 }
3681 /* imginfo goes to position 0 in the prdata array */
3682 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3683 + 2) * sizeof(char));
3684 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3685 (*prdata)[prlines] = NULL;
3686 }
3687 /* skip anything else */
3688 walker = walker->next;
3689 }
3690 walker = grinfo;
3691 *xsize = 0;
3692 *ysize = 0;
3693 *ymin = 0;
3694 *ymax = 0;
3695 while (walker) {
3696 if (strcmp(walker->key, "image_width") == 0) {
3697 *xsize = walker->value.u_cnt;
3698 } else if (strcmp(walker->key, "image_height") == 0) {
3699 *ysize = walker->value.u_cnt;
3700 } else if (strcmp(walker->key, "value_min") == 0) {
3701 *ymin = walker->value.u_val;
3702 } else if (strcmp(walker->key, "value_max") == 0) {
3703 *ymax = walker->value.u_val;
3704 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3705 prlines++;
3706 if (((*prdata) =
3707 (char**)rrd_realloc((*prdata),
3708 (prlines + 1) * sizeof(char *))) == NULL) {
3709 rrd_set_error("realloc prdata");
3710 return 0;
3711 }
3712 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3713 + 2) * sizeof(char));
3714 (*prdata)[prlines] = NULL;
3715 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3716 } else if (strcmp(walker->key, "image") == 0) {
3717 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3718 (stream ? stream : stdout));
3719 }
3720 /* skip anything else */
3721 walker = walker->next;
3722 }
3723 rrd_info_free(grinfo);
3724 return 0;
3725 }
3728 /* Some surgery done on this function, it became ridiculously big.
3729 ** Things moved:
3730 ** - initializing now in rrd_graph_init()
3731 ** - options parsing now in rrd_graph_options()
3732 ** - script parsing now in rrd_graph_script()
3733 */
3734 rrd_info_t *rrd_graph_v(
3735 int argc,
3736 char **argv)
3737 {
3738 image_desc_t im;
3739 rrd_info_t *grinfo;
3740 rrd_graph_init(&im);
3741 /* a dummy surface so that we can measure text sizes for placements */
3743 rrd_graph_options(argc, argv, &im);
3744 if (rrd_test_error()) {
3745 rrd_info_free(im.grinfo);
3746 im_free(&im);
3747 return NULL;
3748 }
3750 if (optind >= argc) {
3751 rrd_info_free(im.grinfo);
3752 im_free(&im);
3753 rrd_set_error("missing filename");
3754 return NULL;
3755 }
3757 if (strlen(argv[optind]) >= MAXPATH) {
3758 rrd_set_error("filename (including path) too long");
3759 rrd_info_free(im.grinfo);
3760 im_free(&im);
3761 return NULL;
3762 }
3764 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3765 im.graphfile[MAXPATH - 1] = '\0';
3767 if (strcmp(im.graphfile, "-") == 0) {
3768 im.graphfile[0] = '\0';
3769 }
3771 rrd_graph_script(argc, argv, &im, 1);
3772 if (rrd_test_error()) {
3773 rrd_info_free(im.grinfo);
3774 im_free(&im);
3775 return NULL;
3776 }
3778 /* Everything is now read and the actual work can start */
3780 if (graph_paint(&im) == -1) {
3781 rrd_info_free(im.grinfo);
3782 im_free(&im);
3783 return NULL;
3784 }
3787 /* The image is generated and needs to be output.
3788 ** Also, if needed, print a line with information about the image.
3789 */
3791 if (im.imginfo) {
3792 rrd_infoval_t info;
3793 char *path;
3794 char *filename;
3796 path = strdup(im.graphfile);
3797 filename = basename(path);
3798 info.u_str =
3799 sprintf_alloc(im.imginfo,
3800 filename,
3801 (long) (im.zoom *
3802 im.ximg), (long) (im.zoom * im.yimg));
3803 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3804 free(info.u_str);
3805 free(path);
3806 }
3807 if (im.rendered_image) {
3808 rrd_infoval_t img;
3810 img.u_blo.size = im.rendered_image_size;
3811 img.u_blo.ptr = im.rendered_image;
3812 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3813 }
3814 grinfo = im.grinfo;
3815 im_free(&im);
3816 return grinfo;
3817 }
3819 static void
3820 rrd_set_font_desc (
3821 image_desc_t *im,int prop,char *font, double size ){
3822 if (font){
3823 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3824 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3825 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3826 };
3827 if (size > 0){
3828 im->text_prop[prop].size = size;
3829 };
3830 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3831 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3832 };
3833 }
3835 void rrd_graph_init(
3836 image_desc_t
3837 *im)
3838 {
3839 unsigned int i;
3840 char *deffont = getenv("RRD_DEFAULT_FONT");
3841 static PangoFontMap *fontmap = NULL;
3842 PangoContext *context;
3844 #ifdef HAVE_TZSET
3845 tzset();
3846 #endif
3847 #ifdef HAVE_SETLOCALE
3848 setlocale(LC_TIME, "");
3849 #ifdef HAVE_MBSTOWCS
3850 setlocale(LC_CTYPE, "");
3851 #endif
3852 #endif
3853 im->base = 1000;
3854 im->daemon_addr = NULL;
3855 im->draw_x_grid = 1;
3856 im->draw_y_grid = 1;
3857 im->extra_flags = 0;
3858 im->font_options = cairo_font_options_create();
3859 im->forceleftspace = 0;
3860 im->gdes_c = 0;
3861 im->gdes = NULL;
3862 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3863 im->grid_dash_off = 1;
3864 im->grid_dash_on = 1;
3865 im->gridfit = 1;
3866 im->grinfo = (rrd_info_t *) NULL;
3867 im->grinfo_current = (rrd_info_t *) NULL;
3868 im->imgformat = IF_PNG;
3869 im->imginfo = NULL;
3870 im->lazy = 0;
3871 im->logarithmic = 0;
3872 im->maxval = DNAN;
3873 im->minval = 0;
3874 im->minval = DNAN;
3875 im->prt_c = 0;
3876 im->rigid = 0;
3877 im->rendered_image_size = 0;
3878 im->rendered_image = NULL;
3879 im->slopemode = 0;
3880 im->step = 0;
3881 im->symbol = ' ';
3882 im->tabwidth = 40.0;
3883 im->title[0] = '\0';
3884 im->unitsexponent = 9999;
3885 im->unitslength = 6;
3886 im->viewfactor = 1.0;
3887 im->watermark[0] = '\0';
3888 im->with_markup = 0;
3889 im->ximg = 0;
3890 im->xlab_user.minsec = -1;
3891 im->xorigin = 0;
3892 im->xsize = 400;
3893 im->ygridstep = DNAN;
3894 im->yimg = 0;
3895 im->ylegend[0] = '\0';
3896 im->second_axis_scale = 0; /* 0 disables it */
3897 im->second_axis_shift = 0; /* no shift by default */
3898 im->second_axis_legend[0] = '\0';
3899 im->second_axis_format[0] = '\0';
3900 im->yorigin = 0;
3901 im->ysize = 100;
3902 im->zoom = 1;
3904 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3905 im->cr = cairo_create(im->surface);
3907 for (i = 0; i < DIM(text_prop); i++) {
3908 im->text_prop[i].size = -1;
3909 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3910 }
3912 if (fontmap == NULL){
3913 fontmap = pango_cairo_font_map_get_default();
3914 }
3916 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3918 pango_cairo_context_set_resolution(context, 100);
3920 pango_cairo_update_context(im->cr,context);
3922 im->layout = pango_layout_new(context);
3924 // im->layout = pango_cairo_create_layout(im->cr);
3927 cairo_font_options_set_hint_style
3928 (im->font_options, CAIRO_HINT_STYLE_FULL);
3929 cairo_font_options_set_hint_metrics
3930 (im->font_options, CAIRO_HINT_METRICS_ON);
3931 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3935 for (i = 0; i < DIM(graph_col); i++)
3936 im->graph_col[i] = graph_col[i];
3939 }
3942 void rrd_graph_options(
3943 int argc,
3944 char *argv[],
3945 image_desc_t
3946 *im)
3947 {
3948 int stroff;
3949 char *parsetime_error = NULL;
3950 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3951 time_t start_tmp = 0, end_tmp = 0;
3952 long long_tmp;
3953 rrd_time_value_t start_tv, end_tv;
3954 long unsigned int color;
3955 char *old_locale = "";
3957 /* defines for long options without a short equivalent. should be bytes,
3958 and may not collide with (the ASCII value of) short options */
3959 #define LONGOPT_UNITS_SI 255
3961 /* *INDENT-OFF* */
3962 struct option long_options[] = {
3963 { "start", required_argument, 0, 's'},
3964 { "end", required_argument, 0, 'e'},
3965 { "x-grid", required_argument, 0, 'x'},
3966 { "y-grid", required_argument, 0, 'y'},
3967 { "vertical-label", required_argument, 0, 'v'},
3968 { "width", required_argument, 0, 'w'},
3969 { "height", required_argument, 0, 'h'},
3970 { "full-size-mode", no_argument, 0, 'D'},
3971 { "interlaced", no_argument, 0, 'i'},
3972 { "upper-limit", required_argument, 0, 'u'},
3973 { "lower-limit", required_argument, 0, 'l'},
3974 { "rigid", no_argument, 0, 'r'},
3975 { "base", required_argument, 0, 'b'},
3976 { "logarithmic", no_argument, 0, 'o'},
3977 { "color", required_argument, 0, 'c'},
3978 { "font", required_argument, 0, 'n'},
3979 { "title", required_argument, 0, 't'},
3980 { "imginfo", required_argument, 0, 'f'},
3981 { "imgformat", required_argument, 0, 'a'},
3982 { "lazy", no_argument, 0, 'z'},
3983 { "zoom", required_argument, 0, 'm'},
3984 { "no-legend", no_argument, 0, 'g'},
3985 { "force-rules-legend", no_argument, 0, 'F'},
3986 { "only-graph", no_argument, 0, 'j'},
3987 { "alt-y-grid", no_argument, 0, 'Y'},
3988 {"disable-rrdtool-tag", no_argument, 0, 1001},
3989 {"right-axis", required_argument, 0, 1002},
3990 {"right-axis-label", required_argument, 0, 1003},
3991 {"right-axis-format", required_argument, 0, 1004},
3992 { "no-minor", no_argument, 0, 'I'},
3993 { "slope-mode", no_argument, 0, 'E'},
3994 { "alt-autoscale", no_argument, 0, 'A'},
3995 { "alt-autoscale-min", no_argument, 0, 'J'},
3996 { "alt-autoscale-max", no_argument, 0, 'M'},
3997 { "no-gridfit", no_argument, 0, 'N'},
3998 { "units-exponent", required_argument, 0, 'X'},
3999 { "units-length", required_argument, 0, 'L'},
4000 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4001 { "step", required_argument, 0, 'S'},
4002 { "tabwidth", required_argument, 0, 'T'},
4003 { "font-render-mode", required_argument, 0, 'R'},
4004 { "graph-render-mode", required_argument, 0, 'G'},
4005 { "font-smoothing-threshold", required_argument, 0, 'B'},
4006 { "watermark", required_argument, 0, 'W'},
4007 { "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 */
4008 { "pango-markup", no_argument, 0, 'P'},
4009 { "daemon", required_argument, 0, 'd'},
4010 { 0, 0, 0, 0}
4011 };
4012 /* *INDENT-ON* */
4014 optind = 0;
4015 opterr = 0; /* initialize getopt */
4016 rrd_parsetime("end-24h", &start_tv);
4017 rrd_parsetime("now", &end_tv);
4018 while (1) {
4019 int option_index = 0;
4020 int opt;
4021 int col_start, col_end;
4023 opt = getopt_long(argc, argv,
4024 "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:",
4025 long_options, &option_index);
4026 if (opt == EOF)
4027 break;
4028 switch (opt) {
4029 case 'I':
4030 im->extra_flags |= NOMINOR;
4031 break;
4032 case 'Y':
4033 im->extra_flags |= ALTYGRID;
4034 break;
4035 case 'A':
4036 im->extra_flags |= ALTAUTOSCALE;
4037 break;
4038 case 'J':
4039 im->extra_flags |= ALTAUTOSCALE_MIN;
4040 break;
4041 case 'M':
4042 im->extra_flags |= ALTAUTOSCALE_MAX;
4043 break;
4044 case 'j':
4045 im->extra_flags |= ONLY_GRAPH;
4046 break;
4047 case 'g':
4048 im->extra_flags |= NOLEGEND;
4049 break;
4050 case 'F':
4051 im->extra_flags |= FORCE_RULES_LEGEND;
4052 break;
4053 case 1001:
4054 im->extra_flags |= NO_RRDTOOL_TAG;
4055 break;
4056 case LONGOPT_UNITS_SI:
4057 if (im->extra_flags & FORCE_UNITS) {
4058 rrd_set_error("--units can only be used once!");
4059 setlocale(LC_NUMERIC, old_locale);
4060 return;
4061 }
4062 if (strcmp(optarg, "si") == 0)
4063 im->extra_flags |= FORCE_UNITS_SI;
4064 else {
4065 rrd_set_error("invalid argument for --units: %s", optarg);
4066 return;
4067 }
4068 break;
4069 case 'X':
4070 im->unitsexponent = atoi(optarg);
4071 break;
4072 case 'L':
4073 im->unitslength = atoi(optarg);
4074 im->forceleftspace = 1;
4075 break;
4076 case 'T':
4077 old_locale = setlocale(LC_NUMERIC, "C");
4078 im->tabwidth = atof(optarg);
4079 setlocale(LC_NUMERIC, old_locale);
4080 break;
4081 case 'S':
4082 old_locale = setlocale(LC_NUMERIC, "C");
4083 im->step = atoi(optarg);
4084 setlocale(LC_NUMERIC, old_locale);
4085 break;
4086 case 'N':
4087 im->gridfit = 0;
4088 break;
4089 case 'P':
4090 im->with_markup = 1;
4091 break;
4092 case 's':
4093 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4094 rrd_set_error("start time: %s", parsetime_error);
4095 return;
4096 }
4097 break;
4098 case 'e':
4099 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4100 rrd_set_error("end time: %s", parsetime_error);
4101 return;
4102 }
4103 break;
4104 case 'x':
4105 if (strcmp(optarg, "none") == 0) {
4106 im->draw_x_grid = 0;
4107 break;
4108 };
4109 if (sscanf(optarg,
4110 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4111 scan_gtm,
4112 &im->xlab_user.gridst,
4113 scan_mtm,
4114 &im->xlab_user.mgridst,
4115 scan_ltm,
4116 &im->xlab_user.labst,
4117 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4118 strncpy(im->xlab_form, optarg + stroff,
4119 sizeof(im->xlab_form) - 1);
4120 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4121 if ((int)
4122 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4123 rrd_set_error("unknown keyword %s", scan_gtm);
4124 return;
4125 } else if ((int)
4126 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4127 == -1) {
4128 rrd_set_error("unknown keyword %s", scan_mtm);
4129 return;
4130 } else if ((int)
4131 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4132 rrd_set_error("unknown keyword %s", scan_ltm);
4133 return;
4134 }
4135 im->xlab_user.minsec = 1;
4136 im->xlab_user.stst = im->xlab_form;
4137 } else {
4138 rrd_set_error("invalid x-grid format");
4139 return;
4140 }
4141 break;
4142 case 'y':
4144 if (strcmp(optarg, "none") == 0) {
4145 im->draw_y_grid = 0;
4146 break;
4147 };
4148 old_locale = setlocale(LC_NUMERIC, "C");
4149 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4150 setlocale(LC_NUMERIC, old_locale);
4151 if (im->ygridstep <= 0) {
4152 rrd_set_error("grid step must be > 0");
4153 return;
4154 } else if (im->ylabfact < 1) {
4155 rrd_set_error("label factor must be > 0");
4156 return;
4157 }
4158 } else {
4159 setlocale(LC_NUMERIC, old_locale);
4160 rrd_set_error("invalid y-grid format");
4161 return;
4162 }
4163 break;
4164 case 1002: /* right y axis */
4166 if(sscanf(optarg,
4167 "%lf:%lf",
4168 &im->second_axis_scale,
4169 &im->second_axis_shift) == 2) {
4170 if(im->second_axis_scale==0){
4171 rrd_set_error("the second_axis_scale must not be 0");
4172 return;
4173 }
4174 } else {
4175 rrd_set_error("invalid right-axis format expected scale:shift");
4176 return;
4177 }
4178 break;
4179 case 1003:
4180 strncpy(im->second_axis_legend,optarg,150);
4181 im->second_axis_legend[150]='\0';
4182 break;
4183 case 1004:
4184 if (bad_format(optarg)){
4185 rrd_set_error("use either %le or %lf formats");
4186 return;
4187 }
4188 strncpy(im->second_axis_format,optarg,150);
4189 im->second_axis_format[150]='\0';
4190 break;
4191 case 'v':
4192 strncpy(im->ylegend, optarg, 150);
4193 im->ylegend[150] = '\0';
4194 break;
4195 case 'u':
4196 old_locale = setlocale(LC_NUMERIC, "C");
4197 im->maxval = atof(optarg);
4198 setlocale(LC_NUMERIC, old_locale);
4199 break;
4200 case 'l':
4201 old_locale = setlocale(LC_NUMERIC, "C");
4202 im->minval = atof(optarg);
4203 setlocale(LC_NUMERIC, old_locale);
4204 break;
4205 case 'b':
4206 im->base = atol(optarg);
4207 if (im->base != 1024 && im->base != 1000) {
4208 rrd_set_error
4209 ("the only sensible value for base apart from 1000 is 1024");
4210 return;
4211 }
4212 break;
4213 case 'w':
4214 long_tmp = atol(optarg);
4215 if (long_tmp < 10) {
4216 rrd_set_error("width below 10 pixels");
4217 return;
4218 }
4219 im->xsize = long_tmp;
4220 break;
4221 case 'h':
4222 long_tmp = atol(optarg);
4223 if (long_tmp < 10) {
4224 rrd_set_error("height below 10 pixels");
4225 return;
4226 }
4227 im->ysize = long_tmp;
4228 break;
4229 case 'D':
4230 im->extra_flags |= FULL_SIZE_MODE;
4231 break;
4232 case 'i':
4233 /* interlaced png not supported at the moment */
4234 break;
4235 case 'r':
4236 im->rigid = 1;
4237 break;
4238 case 'f':
4239 im->imginfo = optarg;
4240 break;
4241 case 'a':
4242 if ((int)
4243 (im->imgformat = if_conv(optarg)) == -1) {
4244 rrd_set_error("unsupported graphics format '%s'", optarg);
4245 return;
4246 }
4247 break;
4248 case 'z':
4249 im->lazy = 1;
4250 break;
4251 case 'E':
4252 im->slopemode = 1;
4253 break;
4254 case 'o':
4255 im->logarithmic = 1;
4256 break;
4257 case 'c':
4258 if (sscanf(optarg,
4259 "%10[A-Z]#%n%8lx%n",
4260 col_nam, &col_start, &color, &col_end) == 2) {
4261 int ci;
4262 int col_len = col_end - col_start;
4264 switch (col_len) {
4265 case 3:
4266 color =
4267 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4268 0x011000) |
4269 ((color & 0x00F)
4270 * 0x001100)
4271 | 0x000000FF);
4272 break;
4273 case 4:
4274 color =
4275 (((color & 0xF000) *
4276 0x11000) | ((color & 0x0F00) *
4277 0x01100) | ((color &
4278 0x00F0) *
4279 0x00110) |
4280 ((color & 0x000F) * 0x00011)
4281 );
4282 break;
4283 case 6:
4284 color = (color << 8) + 0xff /* shift left by 8 */ ;
4285 break;
4286 case 8:
4287 break;
4288 default:
4289 rrd_set_error("the color format is #RRGGBB[AA]");
4290 return;
4291 }
4292 if ((ci = grc_conv(col_nam)) != -1) {
4293 im->graph_col[ci] = gfx_hex_to_col(color);
4294 } else {
4295 rrd_set_error("invalid color name '%s'", col_nam);
4296 return;
4297 }
4298 } else {
4299 rrd_set_error("invalid color def format");
4300 return;
4301 }
4302 break;
4303 case 'n':{
4304 char prop[15];
4305 double size = 1;
4306 int end;
4308 old_locale = setlocale(LC_NUMERIC, "C");
4309 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4310 int sindex, propidx;
4312 setlocale(LC_NUMERIC, old_locale);
4313 if ((sindex = text_prop_conv(prop)) != -1) {
4314 for (propidx = sindex;
4315 propidx < TEXT_PROP_LAST; propidx++) {
4316 if (size > 0) {
4317 rrd_set_font_desc(im,propidx,NULL,size);
4318 }
4319 if ((int) strlen(optarg) > end+2) {
4320 if (optarg[end] == ':') {
4321 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4322 } else {
4323 rrd_set_error
4324 ("expected : after font size in '%s'",
4325 optarg);
4326 return;
4327 }
4328 }
4329 /* only run the for loop for DEFAULT (0) for
4330 all others, we break here. woodo programming */
4331 if (propidx == sindex && sindex != 0)
4332 break;
4333 }
4334 } else {
4335 rrd_set_error("invalid fonttag '%s'", prop);
4336 return;
4337 }
4338 } else {
4339 setlocale(LC_NUMERIC, old_locale);
4340 rrd_set_error("invalid text property format");
4341 return;
4342 }
4343 break;
4344 }
4345 case 'm':
4346 old_locale = setlocale(LC_NUMERIC, "C");
4347 im->zoom = atof(optarg);
4348 setlocale(LC_NUMERIC, old_locale);
4349 if (im->zoom <= 0.0) {
4350 rrd_set_error("zoom factor must be > 0");
4351 return;
4352 }
4353 break;
4354 case 't':
4355 strncpy(im->title, optarg, 150);
4356 im->title[150] = '\0';
4357 break;
4358 case 'R':
4359 if (strcmp(optarg, "normal") == 0) {
4360 cairo_font_options_set_antialias
4361 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4362 cairo_font_options_set_hint_style
4363 (im->font_options, CAIRO_HINT_STYLE_FULL);
4364 } else if (strcmp(optarg, "light") == 0) {
4365 cairo_font_options_set_antialias
4366 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4367 cairo_font_options_set_hint_style
4368 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4369 } else if (strcmp(optarg, "mono") == 0) {
4370 cairo_font_options_set_antialias
4371 (im->font_options, CAIRO_ANTIALIAS_NONE);
4372 cairo_font_options_set_hint_style
4373 (im->font_options, CAIRO_HINT_STYLE_FULL);
4374 } else {
4375 rrd_set_error("unknown font-render-mode '%s'", optarg);
4376 return;
4377 }
4378 break;
4379 case 'G':
4380 if (strcmp(optarg, "normal") == 0)
4381 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4382 else if (strcmp(optarg, "mono") == 0)
4383 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4384 else {
4385 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4386 return;
4387 }
4388 break;
4389 case 'B':
4390 /* not supported curently */
4391 break;
4392 case 'W':
4393 strncpy(im->watermark, optarg, 100);
4394 im->watermark[99] = '\0';
4395 break;
4396 case 'd':
4397 {
4398 if (im->daemon_addr != NULL)
4399 {
4400 rrd_set_error ("You cannot specify --daemon "
4401 "more than once.");
4402 return;
4403 }
4405 im->daemon_addr = strdup(optarg);
4406 if (im->daemon_addr == NULL)
4407 {
4408 rrd_set_error("strdup failed");
4409 return;
4410 }
4412 break;
4413 }
4414 case '?':
4415 if (optopt != 0)
4416 rrd_set_error("unknown option '%c'", optopt);
4417 else
4418 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4419 return;
4420 }
4421 } /* while (1) */
4423 { /* try to connect to rrdcached */
4424 int status = rrdc_connect(im->daemon_addr);
4425 if (status != 0) return;
4426 }
4428 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4429 pango_layout_context_changed(im->layout);
4433 if (im->logarithmic && im->minval <= 0) {
4434 rrd_set_error
4435 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4436 return;
4437 }
4439 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4440 /* error string is set in rrd_parsetime.c */
4441 return;
4442 }
4444 if (start_tmp < 3600 * 24 * 365 * 10) {
4445 rrd_set_error
4446 ("the first entry to fetch should be after 1980 (%ld)",
4447 start_tmp);
4448 return;
4449 }
4451 if (end_tmp < start_tmp) {
4452 rrd_set_error
4453 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4454 return;
4455 }
4457 im->start = start_tmp;
4458 im->end = end_tmp;
4459 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4460 }
4462 int rrd_graph_color(
4463 image_desc_t
4464 *im,
4465 char *var,
4466 char *err,
4467 int optional)
4468 {
4469 char *color;
4470 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4472 color = strstr(var, "#");
4473 if (color == NULL) {
4474 if (optional == 0) {
4475 rrd_set_error("Found no color in %s", err);
4476 return 0;
4477 }
4478 return 0;
4479 } else {
4480 int n = 0;
4481 char *rest;
4482 long unsigned int col;
4484 rest = strstr(color, ":");
4485 if (rest != NULL)
4486 n = rest - color;
4487 else
4488 n = strlen(color);
4489 switch (n) {
4490 case 7:
4491 sscanf(color, "#%6lx%n", &col, &n);
4492 col = (col << 8) + 0xff /* shift left by 8 */ ;
4493 if (n != 7)
4494 rrd_set_error("Color problem in %s", err);
4495 break;
4496 case 9:
4497 sscanf(color, "#%8lx%n", &col, &n);
4498 if (n == 9)
4499 break;
4500 default:
4501 rrd_set_error("Color problem in %s", err);
4502 }
4503 if (rrd_test_error())
4504 return 0;
4505 gdp->col = gfx_hex_to_col(col);
4506 return n;
4507 }
4508 }
4511 int bad_format(
4512 char *fmt)
4513 {
4514 char *ptr;
4515 int n = 0;
4517 ptr = fmt;
4518 while (*ptr != '\0')
4519 if (*ptr++ == '%') {
4521 /* line cannot end with percent char */
4522 if (*ptr == '\0')
4523 return 1;
4524 /* '%s', '%S' and '%%' are allowed */
4525 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4526 ptr++;
4527 /* %c is allowed (but use only with vdef!) */
4528 else if (*ptr == 'c') {
4529 ptr++;
4530 n = 1;
4531 }
4533 /* or else '% 6.2lf' and such are allowed */
4534 else {
4535 /* optional padding character */
4536 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4537 ptr++;
4538 /* This should take care of 'm.n' with all three optional */
4539 while (*ptr >= '0' && *ptr <= '9')
4540 ptr++;
4541 if (*ptr == '.')
4542 ptr++;
4543 while (*ptr >= '0' && *ptr <= '9')
4544 ptr++;
4545 /* Either 'le', 'lf' or 'lg' must follow here */
4546 if (*ptr++ != 'l')
4547 return 1;
4548 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4549 ptr++;
4550 else
4551 return 1;
4552 n++;
4553 }
4554 }
4556 return (n != 1);
4557 }
4560 int vdef_parse(
4561 struct graph_desc_t
4562 *gdes,
4563 const char *const str)
4564 {
4565 /* A VDEF currently is either "func" or "param,func"
4566 * so the parsing is rather simple. Change if needed.
4567 */
4568 double param;
4569 char func[30];
4570 int n;
4571 char *old_locale;
4573 n = 0;
4574 old_locale = setlocale(LC_NUMERIC, "C");
4575 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4576 setlocale(LC_NUMERIC, old_locale);
4577 if (n == (int) strlen(str)) { /* matched */
4578 ;
4579 } else {
4580 n = 0;
4581 sscanf(str, "%29[A-Z]%n", func, &n);
4582 if (n == (int) strlen(str)) { /* matched */
4583 param = DNAN;
4584 } else {
4585 rrd_set_error
4586 ("Unknown function string '%s' in VDEF '%s'",
4587 str, gdes->vname);
4588 return -1;
4589 }
4590 }
4591 if (!strcmp("PERCENT", func))
4592 gdes->vf.op = VDEF_PERCENT;
4593 else if (!strcmp("PERCENTNAN", func))
4594 gdes->vf.op = VDEF_PERCENTNAN;
4595 else if (!strcmp("MAXIMUM", func))
4596 gdes->vf.op = VDEF_MAXIMUM;
4597 else if (!strcmp("AVERAGE", func))
4598 gdes->vf.op = VDEF_AVERAGE;
4599 else if (!strcmp("STDEV", func))
4600 gdes->vf.op = VDEF_STDEV;
4601 else if (!strcmp("MINIMUM", func))
4602 gdes->vf.op = VDEF_MINIMUM;
4603 else if (!strcmp("TOTAL", func))
4604 gdes->vf.op = VDEF_TOTAL;
4605 else if (!strcmp("FIRST", func))
4606 gdes->vf.op = VDEF_FIRST;
4607 else if (!strcmp("LAST", func))
4608 gdes->vf.op = VDEF_LAST;
4609 else if (!strcmp("LSLSLOPE", func))
4610 gdes->vf.op = VDEF_LSLSLOPE;
4611 else if (!strcmp("LSLINT", func))
4612 gdes->vf.op = VDEF_LSLINT;
4613 else if (!strcmp("LSLCORREL", func))
4614 gdes->vf.op = VDEF_LSLCORREL;
4615 else {
4616 rrd_set_error
4617 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4618 return -1;
4619 };
4620 switch (gdes->vf.op) {
4621 case VDEF_PERCENT:
4622 case VDEF_PERCENTNAN:
4623 if (isnan(param)) { /* no parameter given */
4624 rrd_set_error
4625 ("Function '%s' needs parameter in VDEF '%s'\n",
4626 func, gdes->vname);
4627 return -1;
4628 };
4629 if (param >= 0.0 && param <= 100.0) {
4630 gdes->vf.param = param;
4631 gdes->vf.val = DNAN; /* undefined */
4632 gdes->vf.when = 0; /* undefined */
4633 } else {
4634 rrd_set_error
4635 ("Parameter '%f' out of range in VDEF '%s'\n",
4636 param, gdes->vname);
4637 return -1;
4638 };
4639 break;
4640 case VDEF_MAXIMUM:
4641 case VDEF_AVERAGE:
4642 case VDEF_STDEV:
4643 case VDEF_MINIMUM:
4644 case VDEF_TOTAL:
4645 case VDEF_FIRST:
4646 case VDEF_LAST:
4647 case VDEF_LSLSLOPE:
4648 case VDEF_LSLINT:
4649 case VDEF_LSLCORREL:
4650 if (isnan(param)) {
4651 gdes->vf.param = DNAN;
4652 gdes->vf.val = DNAN;
4653 gdes->vf.when = 0;
4654 } else {
4655 rrd_set_error
4656 ("Function '%s' needs no parameter in VDEF '%s'\n",
4657 func, gdes->vname);
4658 return -1;
4659 };
4660 break;
4661 };
4662 return 0;
4663 }
4666 int vdef_calc(
4667 image_desc_t *im,
4668 int gdi)
4669 {
4670 graph_desc_t *src, *dst;
4671 rrd_value_t *data;
4672 long step, steps;
4674 dst = &im->gdes[gdi];
4675 src = &im->gdes[dst->vidx];
4676 data = src->data + src->ds;
4678 steps = (src->end - src->start) / src->step;
4679 #if 0
4680 printf
4681 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4682 src->start, src->end, steps);
4683 #endif
4684 switch (dst->vf.op) {
4685 case VDEF_PERCENT:{
4686 rrd_value_t *array;
4687 int field;
4688 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4689 rrd_set_error("malloc VDEV_PERCENT");
4690 return -1;
4691 }
4692 for (step = 0; step < steps; step++) {
4693 array[step] = data[step * src->ds_cnt];
4694 }
4695 qsort(array, step, sizeof(double), vdef_percent_compar);
4696 field = (steps - 1) * dst->vf.param / 100;
4697 dst->vf.val = array[field];
4698 dst->vf.when = 0; /* no time component */
4699 free(array);
4700 #if 0
4701 for (step = 0; step < steps; step++)
4702 printf("DEBUG: %3li:%10.2f %c\n",
4703 step, array[step], step == field ? '*' : ' ');
4704 #endif
4705 }
4706 break;
4707 case VDEF_PERCENTNAN:{
4708 rrd_value_t *array;
4709 int field;
4710 /* count number of "valid" values */
4711 int nancount=0;
4712 for (step = 0; step < steps; step++) {
4713 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4714 }
4715 /* and allocate it */
4716 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4717 rrd_set_error("malloc VDEV_PERCENT");
4718 return -1;
4719 }
4720 /* and fill it in */
4721 field=0;
4722 for (step = 0; step < steps; step++) {
4723 if (!isnan(data[step * src->ds_cnt])) {
4724 array[field] = data[step * src->ds_cnt];
4725 field++;
4726 }
4727 }
4728 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4729 field = (nancount - 1) * dst->vf.param / 100;
4730 dst->vf.val = array[field];
4731 dst->vf.when = 0; /* no time component */
4732 free(array);
4733 }
4734 break;
4735 case VDEF_MAXIMUM:
4736 step = 0;
4737 while (step != steps && isnan(data[step * src->ds_cnt]))
4738 step++;
4739 if (step == steps) {
4740 dst->vf.val = DNAN;
4741 dst->vf.when = 0;
4742 } else {
4743 dst->vf.val = data[step * src->ds_cnt];
4744 dst->vf.when = src->start + (step + 1) * src->step;
4745 }
4746 while (step != steps) {
4747 if (finite(data[step * src->ds_cnt])) {
4748 if (data[step * src->ds_cnt] > dst->vf.val) {
4749 dst->vf.val = data[step * src->ds_cnt];
4750 dst->vf.when = src->start + (step + 1) * src->step;
4751 }
4752 }
4753 step++;
4754 }
4755 break;
4756 case VDEF_TOTAL:
4757 case VDEF_STDEV:
4758 case VDEF_AVERAGE:{
4759 int cnt = 0;
4760 double sum = 0.0;
4761 double average = 0.0;
4763 for (step = 0; step < steps; step++) {
4764 if (finite(data[step * src->ds_cnt])) {
4765 sum += data[step * src->ds_cnt];
4766 cnt++;
4767 };
4768 }
4769 if (cnt) {
4770 if (dst->vf.op == VDEF_TOTAL) {
4771 dst->vf.val = sum * src->step;
4772 dst->vf.when = 0; /* no time component */
4773 } else if (dst->vf.op == VDEF_AVERAGE) {
4774 dst->vf.val = sum / cnt;
4775 dst->vf.when = 0; /* no time component */
4776 } else {
4777 average = sum / cnt;
4778 sum = 0.0;
4779 for (step = 0; step < steps; step++) {
4780 if (finite(data[step * src->ds_cnt])) {
4781 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4782 };
4783 }
4784 dst->vf.val = pow(sum / cnt, 0.5);
4785 dst->vf.when = 0; /* no time component */
4786 };
4787 } else {
4788 dst->vf.val = DNAN;
4789 dst->vf.when = 0;
4790 }
4791 }
4792 break;
4793 case VDEF_MINIMUM:
4794 step = 0;
4795 while (step != steps && isnan(data[step * src->ds_cnt]))
4796 step++;
4797 if (step == steps) {
4798 dst->vf.val = DNAN;
4799 dst->vf.when = 0;
4800 } else {
4801 dst->vf.val = data[step * src->ds_cnt];
4802 dst->vf.when = src->start + (step + 1) * src->step;
4803 }
4804 while (step != steps) {
4805 if (finite(data[step * src->ds_cnt])) {
4806 if (data[step * src->ds_cnt] < dst->vf.val) {
4807 dst->vf.val = data[step * src->ds_cnt];
4808 dst->vf.when = src->start + (step + 1) * src->step;
4809 }
4810 }
4811 step++;
4812 }
4813 break;
4814 case VDEF_FIRST:
4815 /* The time value returned here is one step before the
4816 * actual time value. This is the start of the first
4817 * non-NaN interval.
4818 */
4819 step = 0;
4820 while (step != steps && isnan(data[step * src->ds_cnt]))
4821 step++;
4822 if (step == steps) { /* all entries were NaN */
4823 dst->vf.val = DNAN;
4824 dst->vf.when = 0;
4825 } else {
4826 dst->vf.val = data[step * src->ds_cnt];
4827 dst->vf.when = src->start + step * src->step;
4828 }
4829 break;
4830 case VDEF_LAST:
4831 /* The time value returned here is the
4832 * actual time value. This is the end of the last
4833 * non-NaN interval.
4834 */
4835 step = steps - 1;
4836 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4837 step--;
4838 if (step < 0) { /* all entries were NaN */
4839 dst->vf.val = DNAN;
4840 dst->vf.when = 0;
4841 } else {
4842 dst->vf.val = data[step * src->ds_cnt];
4843 dst->vf.when = src->start + (step + 1) * src->step;
4844 }
4845 break;
4846 case VDEF_LSLSLOPE:
4847 case VDEF_LSLINT:
4848 case VDEF_LSLCORREL:{
4849 /* Bestfit line by linear least squares method */
4851 int cnt = 0;
4852 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4854 SUMx = 0;
4855 SUMy = 0;
4856 SUMxy = 0;
4857 SUMxx = 0;
4858 SUMyy = 0;
4859 for (step = 0; step < steps; step++) {
4860 if (finite(data[step * src->ds_cnt])) {
4861 cnt++;
4862 SUMx += step;
4863 SUMxx += step * step;
4864 SUMxy += step * data[step * src->ds_cnt];
4865 SUMy += data[step * src->ds_cnt];
4866 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4867 };
4868 }
4870 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4871 y_intercept = (SUMy - slope * SUMx) / cnt;
4872 correl =
4873 (SUMxy -
4874 (SUMx * SUMy) / cnt) /
4875 sqrt((SUMxx -
4876 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4877 if (cnt) {
4878 if (dst->vf.op == VDEF_LSLSLOPE) {
4879 dst->vf.val = slope;
4880 dst->vf.when = 0;
4881 } else if (dst->vf.op == VDEF_LSLINT) {
4882 dst->vf.val = y_intercept;
4883 dst->vf.when = 0;
4884 } else if (dst->vf.op == VDEF_LSLCORREL) {
4885 dst->vf.val = correl;
4886 dst->vf.when = 0;
4887 };
4888 } else {
4889 dst->vf.val = DNAN;
4890 dst->vf.when = 0;
4891 }
4892 }
4893 break;
4894 }
4895 return 0;
4896 }
4898 /* NaN < -INF < finite_values < INF */
4899 int vdef_percent_compar(
4900 const void
4901 *a,
4902 const void
4903 *b)
4904 {
4905 /* Equality is not returned; this doesn't hurt except
4906 * (maybe) for a little performance.
4907 */
4909 /* First catch NaN values. They are smallest */
4910 if (isnan(*(double *) a))
4911 return -1;
4912 if (isnan(*(double *) b))
4913 return 1;
4914 /* NaN doesn't reach this part so INF and -INF are extremes.
4915 * The sign from isinf() is compatible with the sign we return
4916 */
4917 if (isinf(*(double *) a))
4918 return isinf(*(double *) a);
4919 if (isinf(*(double *) b))
4920 return isinf(*(double *) b);
4921 /* If we reach this, both values must be finite */
4922 if (*(double *) a < *(double *) b)
4923 return -1;
4924 else
4925 return 1;
4926 }
4928 void grinfo_push(
4929 image_desc_t *im,
4930 char *key,
4931 rrd_info_type_t type,
4932 rrd_infoval_t value)
4933 {
4934 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4935 if (im->grinfo == NULL) {
4936 im->grinfo = im->grinfo_current;
4937 }
4938 }