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 }
1653 /* place legends with color spots */
1654 int leg_place(
1655 image_desc_t *im,
1656 int calc_width)
1657 {
1658 /* graph labels */
1659 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1660 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1661 int fill = 0, fill_last;
1662 double legendwidth; // = im->ximg - 2 * border;
1663 int leg_c = 0;
1664 double leg_x = border;
1665 int leg_y = 0; //im->yimg;
1666 int leg_y_prev = 0; // im->yimg;
1667 int leg_cc;
1668 double glue = 0;
1669 int i, ii, mark = 0;
1670 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1671 int *legspace;
1672 char *tab;
1673 char saved_legend[FMT_LEG_LEN + 5];
1675 if(calc_width){
1676 legendwidth = 0;
1677 }
1678 else{
1679 legendwidth = im->legendwidth - 2 * border;
1680 }
1683 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1684 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1685 rrd_set_error("malloc for legspace");
1686 return -1;
1687 }
1689 for (i = 0; i < im->gdes_c; i++) {
1690 char prt_fctn; /*special printfunctions */
1691 if(calc_width){
1692 strcpy(saved_legend, im->gdes[i].legend);
1693 }
1695 fill_last = fill;
1696 /* hide legends for rules which are not displayed */
1697 if (im->gdes[i].gf == GF_TEXTALIGN) {
1698 default_txtalign = im->gdes[i].txtalign;
1699 }
1701 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1702 if (im->gdes[i].gf == GF_HRULE
1703 && (im->gdes[i].yrule <
1704 im->minval || im->gdes[i].yrule > im->maxval))
1705 im->gdes[i].legend[0] = '\0';
1706 if (im->gdes[i].gf == GF_VRULE
1707 && (im->gdes[i].xrule <
1708 im->start || im->gdes[i].xrule > im->end))
1709 im->gdes[i].legend[0] = '\0';
1710 }
1712 /* turn \\t into tab */
1713 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1714 memmove(tab, tab + 1, strlen(tab));
1715 tab[0] = (char) 9;
1716 }
1718 leg_cc = strlen(im->gdes[i].legend);
1719 /* is there a controle code at the end of the legend string ? */
1720 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1721 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1722 leg_cc -= 2;
1723 im->gdes[i].legend[leg_cc] = '\0';
1724 } else {
1725 prt_fctn = '\0';
1726 }
1727 /* only valid control codes */
1728 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1729 prt_fctn != 'r' &&
1730 prt_fctn != 'j' &&
1731 prt_fctn != 'c' &&
1732 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1733 free(legspace);
1734 rrd_set_error
1735 ("Unknown control code at the end of '%s\\%c'",
1736 im->gdes[i].legend, prt_fctn);
1737 return -1;
1738 }
1739 /* \n -> \l */
1740 if (prt_fctn == 'n') {
1741 prt_fctn = 'l';
1742 }
1744 /* remove exess space from the end of the legend for \g */
1745 while (prt_fctn == 'g' &&
1746 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1747 leg_cc--;
1748 im->gdes[i].legend[leg_cc] = '\0';
1749 }
1751 if (leg_cc != 0) {
1753 /* no interleg space if string ends in \g */
1754 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1755 if (fill > 0) {
1756 fill += legspace[i];
1757 }
1758 fill +=
1759 gfx_get_text_width(im,
1760 fill + border,
1761 im->
1762 text_prop
1763 [TEXT_PROP_LEGEND].
1764 font_desc,
1765 im->tabwidth, im->gdes[i].legend);
1766 leg_c++;
1767 } else {
1768 legspace[i] = 0;
1769 }
1770 /* who said there was a special tag ... ? */
1771 if (prt_fctn == 'g') {
1772 prt_fctn = '\0';
1773 }
1775 if (prt_fctn == '\0') {
1776 if(calc_width && (fill > legendwidth)){
1777 legendwidth = fill;
1778 }
1779 if (i == im->gdes_c - 1 || fill > legendwidth) {
1780 /* just one legend item is left right or center */
1781 switch (default_txtalign) {
1782 case TXA_RIGHT:
1783 prt_fctn = 'r';
1784 break;
1785 case TXA_CENTER:
1786 prt_fctn = 'c';
1787 break;
1788 case TXA_JUSTIFIED:
1789 prt_fctn = 'j';
1790 break;
1791 default:
1792 prt_fctn = 'l';
1793 break;
1794 }
1795 }
1796 /* is it time to place the legends ? */
1797 if (fill > legendwidth) {
1798 if (leg_c > 1) {
1799 /* go back one */
1800 i--;
1801 fill = fill_last;
1802 leg_c--;
1803 }
1804 }
1805 if (leg_c == 1 && prt_fctn == 'j') {
1806 prt_fctn = 'l';
1807 }
1808 }
1810 if (prt_fctn != '\0') {
1811 leg_x = border;
1812 if (leg_c >= 2 && prt_fctn == 'j') {
1813 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1814 } else {
1815 glue = 0;
1816 }
1817 if (prt_fctn == 'c')
1818 leg_x = (double)(legendwidth - fill) / 2.0;
1819 if (prt_fctn == 'r')
1820 leg_x = legendwidth - fill - border;
1821 for (ii = mark; ii <= i; ii++) {
1822 if (im->gdes[ii].legend[0] == '\0')
1823 continue; /* skip empty legends */
1824 im->gdes[ii].leg_x = leg_x;
1825 im->gdes[ii].leg_y = leg_y + border;
1826 leg_x +=
1827 (double)gfx_get_text_width(im, leg_x,
1828 im->
1829 text_prop
1830 [TEXT_PROP_LEGEND].
1831 font_desc,
1832 im->tabwidth, im->gdes[ii].legend)
1833 +(double)legspace[ii]
1834 + glue;
1835 }
1836 leg_y_prev = leg_y;
1837 if (leg_x > border || prt_fctn == 's')
1838 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1839 if (prt_fctn == 's')
1840 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1842 if(calc_width && (fill > legendwidth)){
1843 legendwidth = fill;
1844 }
1845 fill = 0;
1846 leg_c = 0;
1847 mark = ii;
1848 }
1850 if(calc_width){
1851 strcpy(im->gdes[i].legend, saved_legend);
1852 }
1853 }
1855 if(calc_width){
1856 im->legendwidth = legendwidth + 2 * border;
1857 }
1858 else{
1859 im->legendheight = leg_y + border * 0.6;
1860 }
1861 free(legspace);
1862 }
1863 return 0;
1864 }
1866 /* create a grid on the graph. it determines what to do
1867 from the values of xsize, start and end */
1869 /* the xaxis labels are determined from the number of seconds per pixel
1870 in the requested graph */
1872 int calc_horizontal_grid(
1873 image_desc_t
1874 *im)
1875 {
1876 double range;
1877 double scaledrange;
1878 int pixel, i;
1879 int gridind = 0;
1880 int decimals, fractionals;
1882 im->ygrid_scale.labfact = 2;
1883 range = im->maxval - im->minval;
1884 scaledrange = range / im->magfact;
1885 /* does the scale of this graph make it impossible to put lines
1886 on it? If so, give up. */
1887 if (isnan(scaledrange)) {
1888 return 0;
1889 }
1891 /* find grid spaceing */
1892 pixel = 1;
1893 if (isnan(im->ygridstep)) {
1894 if (im->extra_flags & ALTYGRID) {
1895 /* find the value with max number of digits. Get number of digits */
1896 decimals =
1897 ceil(log10
1898 (max(fabs(im->maxval), fabs(im->minval)) *
1899 im->viewfactor / im->magfact));
1900 if (decimals <= 0) /* everything is small. make place for zero */
1901 decimals = 1;
1902 im->ygrid_scale.gridstep =
1903 pow((double) 10,
1904 floor(log10(range * im->viewfactor / im->magfact))) /
1905 im->viewfactor * im->magfact;
1906 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1907 im->ygrid_scale.gridstep = 0.1;
1908 /* should have at least 5 lines but no more then 15 */
1909 if (range / im->ygrid_scale.gridstep < 5
1910 && im->ygrid_scale.gridstep >= 30)
1911 im->ygrid_scale.gridstep /= 10;
1912 if (range / im->ygrid_scale.gridstep > 15)
1913 im->ygrid_scale.gridstep *= 10;
1914 if (range / im->ygrid_scale.gridstep > 5) {
1915 im->ygrid_scale.labfact = 1;
1916 if (range / im->ygrid_scale.gridstep > 8
1917 || im->ygrid_scale.gridstep <
1918 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1919 im->ygrid_scale.labfact = 2;
1920 } else {
1921 im->ygrid_scale.gridstep /= 5;
1922 im->ygrid_scale.labfact = 5;
1923 }
1924 fractionals =
1925 floor(log10
1926 (im->ygrid_scale.gridstep *
1927 (double) im->ygrid_scale.labfact * im->viewfactor /
1928 im->magfact));
1929 if (fractionals < 0) { /* small amplitude. */
1930 int len = decimals - fractionals + 1;
1932 if (im->unitslength < len + 2)
1933 im->unitslength = len + 2;
1934 sprintf(im->ygrid_scale.labfmt,
1935 "%%%d.%df%s", len,
1936 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1937 } else {
1938 int len = decimals + 1;
1940 if (im->unitslength < len + 2)
1941 im->unitslength = len + 2;
1942 sprintf(im->ygrid_scale.labfmt,
1943 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1944 }
1945 } else { /* classic rrd grid */
1946 for (i = 0; ylab[i].grid > 0; i++) {
1947 pixel = im->ysize / (scaledrange / ylab[i].grid);
1948 gridind = i;
1949 if (pixel >= 5)
1950 break;
1951 }
1953 for (i = 0; i < 4; i++) {
1954 if (pixel * ylab[gridind].lfac[i] >=
1955 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1956 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1957 break;
1958 }
1959 }
1961 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1962 }
1963 } else {
1964 im->ygrid_scale.gridstep = im->ygridstep;
1965 im->ygrid_scale.labfact = im->ylabfact;
1966 }
1967 return 1;
1968 }
1970 int draw_horizontal_grid(
1971 image_desc_t
1972 *im)
1973 {
1974 int i;
1975 double scaledstep;
1976 char graph_label[100];
1977 int nlabels = 0;
1978 double X0 = im->xorigin;
1979 double X1 = im->xorigin + im->xsize;
1980 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1981 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1982 double MaxY;
1983 double second_axis_magfact = 0;
1984 char *second_axis_symb = "";
1986 scaledstep =
1987 im->ygrid_scale.gridstep /
1988 (double) im->magfact * (double) im->viewfactor;
1989 MaxY = scaledstep * (double) egrid;
1990 for (i = sgrid; i <= egrid; i++) {
1991 double Y0 = ytr(im,
1992 im->ygrid_scale.gridstep * i);
1993 double YN = ytr(im,
1994 im->ygrid_scale.gridstep * (i + 1));
1996 if (floor(Y0 + 0.5) >=
1997 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1998 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1999 with the chosen settings. Add a label if required by settings, or if
2000 there is only one label so far and the next grid line is out of bounds. */
2001 if (i % im->ygrid_scale.labfact == 0
2002 || (nlabels == 1
2003 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2004 if (im->symbol == ' ') {
2005 if (im->extra_flags & ALTYGRID) {
2006 sprintf(graph_label,
2007 im->ygrid_scale.labfmt,
2008 scaledstep * (double) i);
2009 } else {
2010 if (MaxY < 10) {
2011 sprintf(graph_label, "%4.1f",
2012 scaledstep * (double) i);
2013 } else {
2014 sprintf(graph_label, "%4.0f",
2015 scaledstep * (double) i);
2016 }
2017 }
2018 } else {
2019 char sisym = (i == 0 ? ' ' : im->symbol);
2021 if (im->extra_flags & ALTYGRID) {
2022 sprintf(graph_label,
2023 im->ygrid_scale.labfmt,
2024 scaledstep * (double) i, sisym);
2025 } else {
2026 if (MaxY < 10) {
2027 sprintf(graph_label, "%4.1f %c",
2028 scaledstep * (double) i, sisym);
2029 } else {
2030 sprintf(graph_label, "%4.0f %c",
2031 scaledstep * (double) i, sisym);
2032 }
2033 }
2034 }
2035 nlabels++;
2036 if (im->second_axis_scale != 0){
2037 char graph_label_right[100];
2038 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2039 if (im->second_axis_format[0] == '\0'){
2040 if (!second_axis_magfact){
2041 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2042 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2043 }
2044 sval /= second_axis_magfact;
2046 if(MaxY < 10) {
2047 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2048 } else {
2049 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2050 }
2051 }
2052 else {
2053 sprintf(graph_label_right,im->second_axis_format,sval);
2054 }
2055 gfx_text ( im,
2056 X1+7, Y0,
2057 im->graph_col[GRC_FONT],
2058 im->text_prop[TEXT_PROP_AXIS].font_desc,
2059 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2060 graph_label_right );
2061 }
2063 gfx_text(im,
2064 X0 -
2065 im->
2066 text_prop[TEXT_PROP_AXIS].
2067 size, Y0,
2068 im->graph_col[GRC_FONT],
2069 im->
2070 text_prop[TEXT_PROP_AXIS].
2071 font_desc,
2072 im->tabwidth, 0.0,
2073 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2074 gfx_line(im, X0 - 2, Y0, X0, Y0,
2075 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2076 gfx_line(im, X1, Y0, X1 + 2, Y0,
2077 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2078 gfx_dashed_line(im, X0 - 2, Y0,
2079 X1 + 2, Y0,
2080 MGRIDWIDTH,
2081 im->
2082 graph_col
2083 [GRC_MGRID],
2084 im->grid_dash_on, im->grid_dash_off);
2085 } else if (!(im->extra_flags & NOMINOR)) {
2086 gfx_line(im,
2087 X0 - 2, Y0,
2088 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2089 gfx_line(im, X1, Y0, X1 + 2, Y0,
2090 GRIDWIDTH, im->graph_col[GRC_GRID]);
2091 gfx_dashed_line(im, X0 - 1, Y0,
2092 X1 + 1, Y0,
2093 GRIDWIDTH,
2094 im->
2095 graph_col[GRC_GRID],
2096 im->grid_dash_on, im->grid_dash_off);
2097 }
2098 }
2099 }
2100 return 1;
2101 }
2103 /* this is frexp for base 10 */
2104 double frexp10(
2105 double,
2106 double *);
2107 double frexp10(
2108 double x,
2109 double *e)
2110 {
2111 double mnt;
2112 int iexp;
2114 iexp = floor(log((double)fabs(x)) / log((double)10));
2115 mnt = x / pow(10.0, iexp);
2116 if (mnt >= 10.0) {
2117 iexp++;
2118 mnt = x / pow(10.0, iexp);
2119 }
2120 *e = iexp;
2121 return mnt;
2122 }
2125 /* logaritmic horizontal grid */
2126 int horizontal_log_grid(
2127 image_desc_t
2128 *im)
2129 {
2130 double yloglab[][10] = {
2131 {
2132 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2133 0.0, 0.0, 0.0}, {
2134 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2135 0.0, 0.0, 0.0}, {
2136 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2137 0.0, 0.0, 0.0}, {
2138 1.0, 2.0, 4.0,
2139 6.0, 8.0, 10.,
2140 0.0,
2141 0.0, 0.0, 0.0}, {
2142 1.0,
2143 2.0,
2144 3.0,
2145 4.0,
2146 5.0,
2147 6.0,
2148 7.0,
2149 8.0,
2150 9.0,
2151 10.},
2152 {
2153 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2154 };
2155 int i, j, val_exp, min_exp;
2156 double nex; /* number of decades in data */
2157 double logscale; /* scale in logarithmic space */
2158 int exfrac = 1; /* decade spacing */
2159 int mid = -1; /* row in yloglab for major grid */
2160 double mspac; /* smallest major grid spacing (pixels) */
2161 int flab; /* first value in yloglab to use */
2162 double value, tmp, pre_value;
2163 double X0, X1, Y0;
2164 char graph_label[100];
2166 nex = log10(im->maxval / im->minval);
2167 logscale = im->ysize / nex;
2168 /* major spacing for data with high dynamic range */
2169 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2170 if (exfrac == 1)
2171 exfrac = 3;
2172 else
2173 exfrac += 3;
2174 }
2176 /* major spacing for less dynamic data */
2177 do {
2178 /* search best row in yloglab */
2179 mid++;
2180 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2181 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2182 }
2183 while (mspac >
2184 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2185 if (mid)
2186 mid--;
2187 /* find first value in yloglab */
2188 for (flab = 0;
2189 yloglab[mid][flab] < 10
2190 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2191 if (yloglab[mid][flab] == 10.0) {
2192 tmp += 1.0;
2193 flab = 0;
2194 }
2195 val_exp = tmp;
2196 if (val_exp % exfrac)
2197 val_exp += abs(-val_exp % exfrac);
2198 X0 = im->xorigin;
2199 X1 = im->xorigin + im->xsize;
2200 /* draw grid */
2201 pre_value = DNAN;
2202 while (1) {
2204 value = yloglab[mid][flab] * pow(10.0, val_exp);
2205 if (AlmostEqual2sComplement(value, pre_value, 4))
2206 break; /* it seems we are not converging */
2207 pre_value = value;
2208 Y0 = ytr(im, value);
2209 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2210 break;
2211 /* major grid line */
2212 gfx_line(im,
2213 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2214 gfx_line(im, X1, Y0, X1 + 2, Y0,
2215 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2216 gfx_dashed_line(im, X0 - 2, Y0,
2217 X1 + 2, Y0,
2218 MGRIDWIDTH,
2219 im->
2220 graph_col
2221 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2222 /* label */
2223 if (im->extra_flags & FORCE_UNITS_SI) {
2224 int scale;
2225 double pvalue;
2226 char symbol;
2228 scale = floor(val_exp / 3.0);
2229 if (value >= 1.0)
2230 pvalue = pow(10.0, val_exp % 3);
2231 else
2232 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2233 pvalue *= yloglab[mid][flab];
2234 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2235 && ((scale + si_symbcenter) >= 0))
2236 symbol = si_symbol[scale + si_symbcenter];
2237 else
2238 symbol = '?';
2239 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2240 } else {
2241 sprintf(graph_label, "%3.0e", value);
2242 }
2243 if (im->second_axis_scale != 0){
2244 char graph_label_right[100];
2245 double sval = value*im->second_axis_scale+im->second_axis_shift;
2246 if (im->second_axis_format[0] == '\0'){
2247 if (im->extra_flags & FORCE_UNITS_SI) {
2248 double mfac = 1;
2249 char *symb = "";
2250 auto_scale(im,&sval,&symb,&mfac);
2251 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2252 }
2253 else {
2254 sprintf(graph_label_right,"%3.0e", sval);
2255 }
2256 }
2257 else {
2258 sprintf(graph_label_right,im->second_axis_format,sval);
2259 }
2261 gfx_text ( im,
2262 X1+7, Y0,
2263 im->graph_col[GRC_FONT],
2264 im->text_prop[TEXT_PROP_AXIS].font_desc,
2265 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2266 graph_label_right );
2267 }
2269 gfx_text(im,
2270 X0 -
2271 im->
2272 text_prop[TEXT_PROP_AXIS].
2273 size, Y0,
2274 im->graph_col[GRC_FONT],
2275 im->
2276 text_prop[TEXT_PROP_AXIS].
2277 font_desc,
2278 im->tabwidth, 0.0,
2279 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2280 /* minor grid */
2281 if (mid < 4 && exfrac == 1) {
2282 /* find first and last minor line behind current major line
2283 * i is the first line and j tha last */
2284 if (flab == 0) {
2285 min_exp = val_exp - 1;
2286 for (i = 1; yloglab[mid][i] < 10.0; i++);
2287 i = yloglab[mid][i - 1] + 1;
2288 j = 10;
2289 } else {
2290 min_exp = val_exp;
2291 i = yloglab[mid][flab - 1] + 1;
2292 j = yloglab[mid][flab];
2293 }
2295 /* draw minor lines below current major line */
2296 for (; i < j; i++) {
2298 value = i * pow(10.0, min_exp);
2299 if (value < im->minval)
2300 continue;
2301 Y0 = ytr(im, value);
2302 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2303 break;
2304 /* draw lines */
2305 gfx_line(im,
2306 X0 - 2, Y0,
2307 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2308 gfx_line(im, X1, Y0, X1 + 2, Y0,
2309 GRIDWIDTH, im->graph_col[GRC_GRID]);
2310 gfx_dashed_line(im, X0 - 1, Y0,
2311 X1 + 1, Y0,
2312 GRIDWIDTH,
2313 im->
2314 graph_col[GRC_GRID],
2315 im->grid_dash_on, im->grid_dash_off);
2316 }
2317 } else if (exfrac > 1) {
2318 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2319 value = pow(10.0, i);
2320 if (value < im->minval)
2321 continue;
2322 Y0 = ytr(im, value);
2323 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2324 break;
2325 /* draw lines */
2326 gfx_line(im,
2327 X0 - 2, Y0,
2328 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2329 gfx_line(im, X1, Y0, X1 + 2, Y0,
2330 GRIDWIDTH, im->graph_col[GRC_GRID]);
2331 gfx_dashed_line(im, X0 - 1, Y0,
2332 X1 + 1, Y0,
2333 GRIDWIDTH,
2334 im->
2335 graph_col[GRC_GRID],
2336 im->grid_dash_on, im->grid_dash_off);
2337 }
2338 }
2340 /* next decade */
2341 if (yloglab[mid][++flab] == 10.0) {
2342 flab = 0;
2343 val_exp += exfrac;
2344 }
2345 }
2347 /* draw minor lines after highest major line */
2348 if (mid < 4 && exfrac == 1) {
2349 /* find first and last minor line below current major line
2350 * i is the first line and j tha last */
2351 if (flab == 0) {
2352 min_exp = val_exp - 1;
2353 for (i = 1; yloglab[mid][i] < 10.0; i++);
2354 i = yloglab[mid][i - 1] + 1;
2355 j = 10;
2356 } else {
2357 min_exp = val_exp;
2358 i = yloglab[mid][flab - 1] + 1;
2359 j = yloglab[mid][flab];
2360 }
2362 /* draw minor lines below current major line */
2363 for (; i < j; i++) {
2365 value = i * pow(10.0, min_exp);
2366 if (value < im->minval)
2367 continue;
2368 Y0 = ytr(im, value);
2369 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2370 break;
2371 /* draw lines */
2372 gfx_line(im,
2373 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2374 gfx_line(im, X1, Y0, X1 + 2, Y0,
2375 GRIDWIDTH, im->graph_col[GRC_GRID]);
2376 gfx_dashed_line(im, X0 - 1, Y0,
2377 X1 + 1, Y0,
2378 GRIDWIDTH,
2379 im->
2380 graph_col[GRC_GRID],
2381 im->grid_dash_on, im->grid_dash_off);
2382 }
2383 }
2384 /* fancy minor gridlines */
2385 else if (exfrac > 1) {
2386 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2387 value = pow(10.0, i);
2388 if (value < im->minval)
2389 continue;
2390 Y0 = ytr(im, value);
2391 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2392 break;
2393 /* draw lines */
2394 gfx_line(im,
2395 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2396 gfx_line(im, X1, Y0, X1 + 2, Y0,
2397 GRIDWIDTH, im->graph_col[GRC_GRID]);
2398 gfx_dashed_line(im, X0 - 1, Y0,
2399 X1 + 1, Y0,
2400 GRIDWIDTH,
2401 im->
2402 graph_col[GRC_GRID],
2403 im->grid_dash_on, im->grid_dash_off);
2404 }
2405 }
2407 return 1;
2408 }
2411 void vertical_grid(
2412 image_desc_t *im)
2413 {
2414 int xlab_sel; /* which sort of label and grid ? */
2415 time_t ti, tilab, timajor;
2416 long factor;
2417 char graph_label[100];
2418 double X0, Y0, Y1; /* points for filled graph and more */
2419 struct tm tm;
2421 /* the type of time grid is determined by finding
2422 the number of seconds per pixel in the graph */
2423 if (im->xlab_user.minsec == -1) {
2424 factor = (im->end - im->start) / im->xsize;
2425 xlab_sel = 0;
2426 while (xlab[xlab_sel + 1].minsec !=
2427 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2428 xlab_sel++;
2429 } /* pick the last one */
2430 while (xlab[xlab_sel - 1].minsec ==
2431 xlab[xlab_sel].minsec
2432 && xlab[xlab_sel].length > (im->end - im->start)) {
2433 xlab_sel--;
2434 } /* go back to the smallest size */
2435 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2436 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2437 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2438 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2439 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2440 im->xlab_user.labst = xlab[xlab_sel].labst;
2441 im->xlab_user.precis = xlab[xlab_sel].precis;
2442 im->xlab_user.stst = xlab[xlab_sel].stst;
2443 }
2445 /* y coords are the same for every line ... */
2446 Y0 = im->yorigin;
2447 Y1 = im->yorigin - im->ysize;
2448 /* paint the minor grid */
2449 if (!(im->extra_flags & NOMINOR)) {
2450 for (ti = find_first_time(im->start,
2451 im->
2452 xlab_user.
2453 gridtm,
2454 im->
2455 xlab_user.
2456 gridst),
2457 timajor =
2458 find_first_time(im->start,
2459 im->xlab_user.
2460 mgridtm,
2461 im->xlab_user.
2462 mgridst);
2463 ti < im->end;
2464 ti =
2465 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2466 ) {
2467 /* are we inside the graph ? */
2468 if (ti < im->start || ti > im->end)
2469 continue;
2470 while (timajor < ti) {
2471 timajor = find_next_time(timajor,
2472 im->
2473 xlab_user.
2474 mgridtm, im->xlab_user.mgridst);
2475 }
2476 if (ti == timajor)
2477 continue; /* skip as falls on major grid line */
2478 X0 = xtr(im, ti);
2479 gfx_line(im, X0, Y1 - 2, X0, Y1,
2480 GRIDWIDTH, im->graph_col[GRC_GRID]);
2481 gfx_line(im, X0, Y0, X0, Y0 + 2,
2482 GRIDWIDTH, im->graph_col[GRC_GRID]);
2483 gfx_dashed_line(im, X0, Y0 + 1, X0,
2484 Y1 - 1, GRIDWIDTH,
2485 im->
2486 graph_col[GRC_GRID],
2487 im->grid_dash_on, im->grid_dash_off);
2488 }
2489 }
2491 /* paint the major grid */
2492 for (ti = find_first_time(im->start,
2493 im->
2494 xlab_user.
2495 mgridtm,
2496 im->
2497 xlab_user.
2498 mgridst);
2499 ti < im->end;
2500 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2501 ) {
2502 /* are we inside the graph ? */
2503 if (ti < im->start || ti > im->end)
2504 continue;
2505 X0 = xtr(im, ti);
2506 gfx_line(im, X0, Y1 - 2, X0, Y1,
2507 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2508 gfx_line(im, X0, Y0, X0, Y0 + 3,
2509 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2510 gfx_dashed_line(im, X0, Y0 + 3, X0,
2511 Y1 - 2, MGRIDWIDTH,
2512 im->
2513 graph_col
2514 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2515 }
2516 /* paint the labels below the graph */
2517 for (ti =
2518 find_first_time(im->start -
2519 im->xlab_user.
2520 precis / 2,
2521 im->xlab_user.
2522 labtm,
2523 im->xlab_user.
2524 labst);
2525 ti <=
2526 im->end -
2527 im->xlab_user.precis / 2;
2528 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2529 ) {
2530 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2531 /* are we inside the graph ? */
2532 if (tilab < im->start || tilab > im->end)
2533 continue;
2534 #if HAVE_STRFTIME
2535 localtime_r(&tilab, &tm);
2536 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2537 #else
2538 # error "your libc has no strftime I guess we'll abort the exercise here."
2539 #endif
2540 gfx_text(im,
2541 xtr(im, tilab),
2542 Y0 + 3,
2543 im->graph_col[GRC_FONT],
2544 im->
2545 text_prop[TEXT_PROP_AXIS].
2546 font_desc,
2547 im->tabwidth, 0.0,
2548 GFX_H_CENTER, GFX_V_TOP, graph_label);
2549 }
2551 }
2554 void axis_paint(
2555 image_desc_t *im)
2556 {
2557 /* draw x and y axis */
2558 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2559 im->xorigin+im->xsize,im->yorigin-im->ysize,
2560 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2562 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2563 im->xorigin+im->xsize,im->yorigin-im->ysize,
2564 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2566 gfx_line(im, im->xorigin - 4,
2567 im->yorigin,
2568 im->xorigin + im->xsize +
2569 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2570 gfx_line(im, im->xorigin,
2571 im->yorigin + 4,
2572 im->xorigin,
2573 im->yorigin - im->ysize -
2574 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2575 /* arrow for X and Y axis direction */
2576 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 */
2577 im->graph_col[GRC_ARROW]);
2578 gfx_close_path(im);
2579 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 */
2580 im->graph_col[GRC_ARROW]);
2581 gfx_close_path(im);
2582 if (im->second_axis_scale != 0){
2583 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2584 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2585 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2586 gfx_new_area ( im,
2587 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2588 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2589 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2590 im->graph_col[GRC_ARROW]);
2591 gfx_close_path(im);
2592 }
2594 }
2596 void grid_paint(
2597 image_desc_t *im)
2598 {
2599 long i;
2600 int res = 0;
2601 double X0, Y0; /* points for filled graph and more */
2602 struct gfx_color_t water_color;
2604 /* draw 3d border */
2605 gfx_new_area(im, 0, im->yimg,
2606 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2607 gfx_add_point(im, im->ximg - 2, 2);
2608 gfx_add_point(im, im->ximg, 0);
2609 gfx_add_point(im, 0, 0);
2610 gfx_close_path(im);
2611 gfx_new_area(im, 2, im->yimg - 2,
2612 im->ximg - 2,
2613 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2614 gfx_add_point(im, im->ximg, 0);
2615 gfx_add_point(im, im->ximg, im->yimg);
2616 gfx_add_point(im, 0, im->yimg);
2617 gfx_close_path(im);
2618 if (im->draw_x_grid == 1)
2619 vertical_grid(im);
2620 if (im->draw_y_grid == 1) {
2621 if (im->logarithmic) {
2622 res = horizontal_log_grid(im);
2623 } else {
2624 res = draw_horizontal_grid(im);
2625 }
2627 /* dont draw horizontal grid if there is no min and max val */
2628 if (!res) {
2629 char *nodata = "No Data found";
2631 gfx_text(im, im->ximg / 2,
2632 (2 * im->yorigin -
2633 im->ysize) / 2,
2634 im->graph_col[GRC_FONT],
2635 im->
2636 text_prop[TEXT_PROP_AXIS].
2637 font_desc,
2638 im->tabwidth, 0.0,
2639 GFX_H_CENTER, GFX_V_CENTER, nodata);
2640 }
2641 }
2643 /* yaxis unit description */
2644 if (im->ylegend[0] != '\0'){
2645 gfx_text(im,
2646 im->xOriginLegendY+10,
2647 im->yOriginLegendY,
2648 im->graph_col[GRC_FONT],
2649 im->
2650 text_prop[TEXT_PROP_UNIT].
2651 font_desc,
2652 im->tabwidth,
2653 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2655 }
2656 if (im->second_axis_legend[0] != '\0'){
2657 gfx_text( im,
2658 im->xOriginLegendY2+10,
2659 im->yOriginLegendY2,
2660 im->graph_col[GRC_FONT],
2661 im->text_prop[TEXT_PROP_UNIT].font_desc,
2662 im->tabwidth,
2663 RRDGRAPH_YLEGEND_ANGLE,
2664 GFX_H_CENTER, GFX_V_CENTER,
2665 im->second_axis_legend);
2666 }
2668 /* graph title */
2669 gfx_text(im,
2670 im->xOriginTitle, im->yOriginTitle+6,
2671 im->graph_col[GRC_FONT],
2672 im->
2673 text_prop[TEXT_PROP_TITLE].
2674 font_desc,
2675 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2676 /* rrdtool 'logo' */
2677 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2678 water_color = im->graph_col[GRC_FONT];
2679 water_color.alpha = 0.3;
2680 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2681 gfx_text(im, xpos, 5,
2682 water_color,
2683 im->
2684 text_prop[TEXT_PROP_WATERMARK].
2685 font_desc, im->tabwidth,
2686 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2687 }
2688 /* graph watermark */
2689 if (im->watermark[0] != '\0') {
2690 water_color = im->graph_col[GRC_FONT];
2691 water_color.alpha = 0.3;
2692 gfx_text(im,
2693 im->ximg / 2, im->yimg - 6,
2694 water_color,
2695 im->
2696 text_prop[TEXT_PROP_WATERMARK].
2697 font_desc, im->tabwidth, 0,
2698 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2699 }
2701 /* graph labels */
2702 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2703 for (i = 0; i < im->gdes_c; i++) {
2704 if (im->gdes[i].legend[0] == '\0')
2705 continue;
2706 /* im->gdes[i].leg_y is the bottom of the legend */
2707 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2708 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2709 gfx_text(im, X0, Y0,
2710 im->graph_col[GRC_FONT],
2711 im->
2712 text_prop
2713 [TEXT_PROP_LEGEND].font_desc,
2714 im->tabwidth, 0.0,
2715 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2716 /* The legend for GRAPH items starts with "M " to have
2717 enough space for the box */
2718 if (im->gdes[i].gf != GF_PRINT &&
2719 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2720 double boxH, boxV;
2721 double X1, Y1;
2723 boxH = gfx_get_text_width(im, 0,
2724 im->
2725 text_prop
2726 [TEXT_PROP_LEGEND].
2727 font_desc,
2728 im->tabwidth, "o") * 1.2;
2729 boxV = boxH;
2730 /* shift the box up a bit */
2731 Y0 -= boxV * 0.4;
2732 /* make sure transparent colors show up the same way as in the graph */
2733 gfx_new_area(im,
2734 X0, Y0 - boxV,
2735 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2736 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2737 gfx_close_path(im);
2738 gfx_new_area(im, X0, Y0 - boxV, X0,
2739 Y0, X0 + boxH, Y0, im->gdes[i].col);
2740 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2741 gfx_close_path(im);
2742 cairo_save(im->cr);
2743 cairo_new_path(im->cr);
2744 cairo_set_line_width(im->cr, 1.0);
2745 X1 = X0 + boxH;
2746 Y1 = Y0 - boxV;
2747 gfx_line_fit(im, &X0, &Y0);
2748 gfx_line_fit(im, &X1, &Y1);
2749 cairo_move_to(im->cr, X0, Y0);
2750 cairo_line_to(im->cr, X1, Y0);
2751 cairo_line_to(im->cr, X1, Y1);
2752 cairo_line_to(im->cr, X0, Y1);
2753 cairo_close_path(im->cr);
2754 cairo_set_source_rgba(im->cr,
2755 im->
2756 graph_col
2757 [GRC_FRAME].
2758 red,
2759 im->
2760 graph_col
2761 [GRC_FRAME].
2762 green,
2763 im->
2764 graph_col
2765 [GRC_FRAME].
2766 blue, im->graph_col[GRC_FRAME].alpha);
2767 if (im->gdes[i].dash) {
2768 /* make box borders in legend dashed if the graph is dashed */
2769 double dashes[] = {
2770 3.0
2771 };
2772 cairo_set_dash(im->cr, dashes, 1, 0.0);
2773 }
2774 cairo_stroke(im->cr);
2775 cairo_restore(im->cr);
2776 }
2777 }
2778 }
2779 }
2782 /*****************************************************
2783 * lazy check make sure we rely need to create this graph
2784 *****************************************************/
2786 int lazy_check(
2787 image_desc_t *im)
2788 {
2789 FILE *fd = NULL;
2790 int size = 1;
2791 struct stat imgstat;
2793 if (im->lazy == 0)
2794 return 0; /* no lazy option */
2795 if (strlen(im->graphfile) == 0)
2796 return 0; /* inmemory option */
2797 if (stat(im->graphfile, &imgstat) != 0)
2798 return 0; /* can't stat */
2799 /* one pixel in the existing graph is more then what we would
2800 change here ... */
2801 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2802 return 0;
2803 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2804 return 0; /* the file does not exist */
2805 switch (im->imgformat) {
2806 case IF_PNG:
2807 size = PngSize(fd, &(im->ximg), &(im->yimg));
2808 break;
2809 default:
2810 size = 1;
2811 }
2812 fclose(fd);
2813 return size;
2814 }
2817 int graph_size_location(
2818 image_desc_t
2819 *im,
2820 int elements)
2821 {
2822 /* The actual size of the image to draw is determined from
2823 ** several sources. The size given on the command line is
2824 ** the graph area but we need more as we have to draw labels
2825 ** and other things outside the graph area. If the option
2826 ** --full-size-mode is selected the size defines the total
2827 ** image size and the size available for the graph is
2828 ** calculated.
2829 */
2831 /** +---+-----------------------------------+
2832 ** | y |...............graph title.........|
2833 ** | +---+-------------------------------+
2834 ** | a | y | |
2835 ** | x | | |
2836 ** | i | a | |
2837 ** | s | x | main graph area |
2838 ** | | i | |
2839 ** | t | s | |
2840 ** | i | | |
2841 ** | t | l | |
2842 ** | l | b +-------------------------------+
2843 ** | e | l | x axis labels |
2844 ** +---+---+-------------------------------+
2845 ** |....................legends............|
2846 ** +---------------------------------------+
2847 ** | watermark |
2848 ** +---------------------------------------+
2849 */
2851 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2852 0, Xylabel = 0, Xmain = 0, Ymain =
2853 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2855 // no legends and no the shall be plotted it's easy
2856 if (im->extra_flags & ONLY_GRAPH) {
2857 im->xorigin = 0;
2858 im->ximg = im->xsize;
2859 im->yimg = im->ysize;
2860 im->yorigin = im->ysize;
2861 ytr(im, DNAN);
2862 return 0;
2863 }
2865 if(im->watermark[0] != '\0') {
2866 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2867 }
2869 // calculate the width of the left vertical legend
2870 if (im->ylegend[0] != '\0') {
2871 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2872 }
2874 // calculate the width of the right vertical legend
2875 if (im->second_axis_legend[0] != '\0') {
2876 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2877 }
2878 else{
2879 Xvertical2 = Xspacing;
2880 }
2882 if (im->title[0] != '\0') {
2883 /* The title is placed "inbetween" two text lines so it
2884 ** automatically has some vertical spacing. The horizontal
2885 ** spacing is added here, on each side.
2886 */
2887 /* if necessary, reduce the font size of the title until it fits the image width */
2888 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2889 }
2890 else{
2891 // we have no title; get a little clearing from the top
2892 Ytitle = 1.5 * Yspacing;
2893 }
2895 if (elements) {
2896 if (im->draw_x_grid) {
2897 // calculate the height of the horizontal labelling
2898 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2899 }
2900 if (im->draw_y_grid || im->forceleftspace) {
2901 // calculate the width of the vertical labelling
2902 Xylabel =
2903 gfx_get_text_width(im, 0,
2904 im->text_prop[TEXT_PROP_AXIS].font_desc,
2905 im->tabwidth, "0") * im->unitslength;
2906 }
2907 }
2909 // add some space to the labelling
2910 Xylabel += Xspacing;
2912 /* If the legend is printed besides the graph the width has to be
2913 ** calculated first. Placing the legend north or south of the
2914 ** graph requires the width calculation first, so the legend is
2915 ** skipped for the moment.
2916 */
2917 im->legendheight = 0;
2918 im->legendwidth = 0;
2919 if (!(im->extra_flags & NOLEGEND)) {
2920 if(im->legendposition == WEST || im->legendposition == EAST){
2921 if (leg_place(im, 1) == -1){
2922 return -1;
2923 }
2924 }
2925 }
2927 if (im->extra_flags & FULL_SIZE_MODE) {
2929 /* The actual size of the image to draw has been determined by the user.
2930 ** The graph area is the space remaining after accounting for the legend,
2931 ** the watermark, the axis labels, and the title.
2932 */
2933 im->ximg = im->xsize;
2934 im->yimg = im->ysize;
2935 Xmain = im->ximg;
2936 Ymain = im->yimg;
2938 /* Now calculate the total size. Insert some spacing where
2939 desired. im->xorigin and im->yorigin need to correspond
2940 with the lower left corner of the main graph area or, if
2941 this one is not set, the imaginary box surrounding the
2942 pie chart area. */
2943 /* Initial size calculation for the main graph area */
2945 Xmain -= Xylabel;// + Xspacing;
2946 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2947 Xmain -= im->legendwidth;// + Xspacing;
2948 }
2949 if (im->second_axis_scale != 0){
2950 Xmain -= Xylabel;
2951 }
2952 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2953 Xmain -= Xspacing;
2954 }
2956 Xmain -= Xvertical + Xvertical2;
2958 /* limit the remaining space to 0 */
2959 if(Xmain < 1){
2960 Xmain = 1;
2961 }
2962 im->xsize = Xmain;
2964 /* Putting the legend north or south, the height can now be calculated */
2965 if (!(im->extra_flags & NOLEGEND)) {
2966 if(im->legendposition == NORTH || im->legendposition == SOUTH){
2967 im->legendwidth = im->ximg;
2968 if (leg_place(im, 0) == -1){
2969 return -1;
2970 }
2971 }
2972 }
2974 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
2975 Ymain -= Yxlabel + im->legendheight;
2976 }
2977 else{
2978 Ymain -= Yxlabel;
2979 }
2981 /* reserve space for the title *or* some padding above the graph */
2982 Ymain -= Ytitle;
2984 /* reserve space for padding below the graph */
2985 if (im->extra_flags & NOLEGEND) {
2986 Ymain -= Yspacing;
2987 }
2989 if (im->watermark[0] != '\0') {
2990 Ymain -= Ywatermark;
2991 }
2992 /* limit the remaining height to 0 */
2993 if(Ymain < 1){
2994 Ymain = 1;
2995 }
2996 im->ysize = Ymain;
2997 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2999 /* The actual size of the image to draw is determined from
3000 ** several sources. The size given on the command line is
3001 ** the graph area but we need more as we have to draw labels
3002 ** and other things outside the graph area.
3003 */
3005 if (elements) {
3006 Xmain = im->xsize; // + Xspacing;
3007 Ymain = im->ysize;
3008 }
3010 im->ximg = Xmain + Xylabel;
3011 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3012 im->ximg += Xspacing;
3013 }
3015 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3016 im->ximg += im->legendwidth;// + Xspacing;
3017 }
3018 if (im->second_axis_scale != 0){
3019 im->ximg += Xylabel;
3020 }
3022 im->ximg += Xvertical + Xvertical2;
3024 if (!(im->extra_flags & NOLEGEND)) {
3025 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3026 im->legendwidth = im->ximg;
3027 if (leg_place(im, 0) == -1){
3028 return -1;
3029 }
3030 }
3031 }
3033 im->yimg = Ymain + Yxlabel;
3034 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3035 im->yimg += im->legendheight;
3036 }
3038 /* reserve space for the title *or* some padding above the graph */
3039 if (Ytitle) {
3040 im->yimg += Ytitle;
3041 } else {
3042 im->yimg += 1.5 * Yspacing;
3043 }
3044 /* reserve space for padding below the graph */
3045 if (im->extra_flags & NOLEGEND) {
3046 im->yimg += Yspacing;
3047 }
3049 if (im->watermark[0] != '\0') {
3050 im->yimg += Ywatermark;
3051 }
3052 }
3055 /* In case of putting the legend in west or east position the first
3056 ** legend calculation might lead to wrong positions if some items
3057 ** are not aligned on the left hand side (e.g. centered) as the
3058 ** legendwidth wight have been increased after the item was placed.
3059 ** In this case the positions have to be recalculated.
3060 */
3061 if (!(im->extra_flags & NOLEGEND)) {
3062 if(im->legendposition == WEST || im->legendposition == EAST){
3063 if (leg_place(im, 0) == -1){
3064 return -1;
3065 }
3066 }
3067 }
3069 /* After calculating all dimensions
3070 ** it is now possible to calculate
3071 ** all offsets.
3072 */
3073 switch(im->legendposition){
3074 case NORTH:
3075 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3076 im->yOriginTitle = 0;
3078 im->xOriginLegend = 0;
3079 im->yOriginLegend = Ytitle;
3081 im->xOriginLegendY = 0;
3082 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3084 im->xorigin = Xvertical + Xylabel;
3085 im->yorigin = Ytitle + im->legendheight + Ymain;
3087 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3088 if (im->second_axis_scale != 0){
3089 im->xOriginLegendY2 += Xylabel;
3090 }
3091 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3093 break;
3095 case WEST:
3096 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3097 im->yOriginTitle = 0;
3099 im->xOriginLegend = 0;
3100 im->yOriginLegend = Ytitle;
3102 im->xOriginLegendY = im->legendwidth;
3103 im->yOriginLegendY = Ytitle + (Ymain / 2);
3105 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3106 im->yorigin = Ytitle + Ymain;
3108 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3109 if (im->second_axis_scale != 0){
3110 im->xOriginLegendY2 += Xylabel;
3111 }
3112 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3114 break;
3116 case SOUTH:
3117 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3118 im->yOriginTitle = 0;
3120 im->xOriginLegend = 0;
3121 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3123 im->xOriginLegendY = 0;
3124 im->yOriginLegendY = Ytitle + (Ymain / 2);
3126 im->xorigin = Xvertical + Xylabel;
3127 im->yorigin = Ytitle + Ymain;
3129 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3130 if (im->second_axis_scale != 0){
3131 im->xOriginLegendY2 += Xylabel;
3132 }
3133 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3135 break;
3137 case EAST:
3138 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3139 im->yOriginTitle = 0;
3141 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3142 if (im->second_axis_scale != 0){
3143 im->xOriginLegend += Xylabel;
3144 }
3145 im->yOriginLegend = Ytitle;
3147 im->xOriginLegendY = 0;
3148 im->yOriginLegendY = Ytitle + (Ymain / 2);
3150 im->xorigin = Xvertical + Xylabel;
3151 im->yorigin = Ytitle + Ymain;
3153 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3154 if (im->second_axis_scale != 0){
3155 im->xOriginLegendY2 += Xylabel;
3156 }
3157 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3159 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3160 im->xOriginTitle += Xspacing;
3161 im->xOriginLegend += Xspacing;
3162 im->xOriginLegendY += Xspacing;
3163 im->xorigin += Xspacing;
3164 im->xOriginLegendY2 += Xspacing;
3165 }
3166 break;
3167 }
3169 xtr(im, 0);
3170 ytr(im, DNAN);
3171 return 0;
3172 }
3174 static cairo_status_t cairo_output(
3175 void *closure,
3176 const unsigned char
3177 *data,
3178 unsigned int length)
3179 {
3180 image_desc_t *im = (image_desc_t*)closure;
3182 im->rendered_image =
3183 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3184 if (im->rendered_image == NULL)
3185 return CAIRO_STATUS_WRITE_ERROR;
3186 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3187 im->rendered_image_size += length;
3188 return CAIRO_STATUS_SUCCESS;
3189 }
3191 /* draw that picture thing ... */
3192 int graph_paint(
3193 image_desc_t *im)
3194 {
3195 int i, ii;
3196 int lazy = lazy_check(im);
3197 double areazero = 0.0;
3198 graph_desc_t *lastgdes = NULL;
3199 rrd_infoval_t info;
3201 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3203 /* pull the data from the rrd files ... */
3204 if (data_fetch(im) == -1)
3205 return -1;
3206 /* evaluate VDEF and CDEF operations ... */
3207 if (data_calc(im) == -1)
3208 return -1;
3209 /* calculate and PRINT and GPRINT definitions. We have to do it at
3210 * this point because it will affect the length of the legends
3211 * if there are no graph elements (i==0) we stop here ...
3212 * if we are lazy, try to quit ...
3213 */
3214 i = print_calc(im);
3215 if (i < 0)
3216 return -1;
3218 /* if we want and can be lazy ... quit now */
3219 if (i == 0)
3220 return 0;
3222 /**************************************************************
3223 *** Calculating sizes and locations became a bit confusing ***
3224 *** so I moved this into a separate function. ***
3225 **************************************************************/
3226 if (graph_size_location(im, i) == -1)
3227 return -1;
3229 info.u_cnt = im->xorigin;
3230 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3231 info.u_cnt = im->yorigin - im->ysize;
3232 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3233 info.u_cnt = im->xsize;
3234 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3235 info.u_cnt = im->ysize;
3236 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3237 info.u_cnt = im->ximg;
3238 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3239 info.u_cnt = im->yimg;
3240 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3241 info.u_cnt = im->start;
3242 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3243 info.u_cnt = im->end;
3244 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3246 /* if we want and can be lazy ... quit now */
3247 if (lazy)
3248 return 0;
3250 /* get actual drawing data and find min and max values */
3251 if (data_proc(im) == -1)
3252 return -1;
3253 if (!im->logarithmic) {
3254 si_unit(im);
3255 }
3257 /* identify si magnitude Kilo, Mega Giga ? */
3258 if (!im->rigid && !im->logarithmic)
3259 expand_range(im); /* make sure the upper and lower limit are
3260 sensible values */
3262 info.u_val = im->minval;
3263 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3264 info.u_val = im->maxval;
3265 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3268 if (!calc_horizontal_grid(im))
3269 return -1;
3270 /* reset precalc */
3271 ytr(im, DNAN);
3272 /* if (im->gridfit)
3273 apply_gridfit(im); */
3274 /* the actual graph is created by going through the individual
3275 graph elements and then drawing them */
3276 cairo_surface_destroy(im->surface);
3277 switch (im->imgformat) {
3278 case IF_PNG:
3279 im->surface =
3280 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3281 im->ximg * im->zoom,
3282 im->yimg * im->zoom);
3283 break;
3284 case IF_PDF:
3285 im->gridfit = 0;
3286 im->surface = strlen(im->graphfile)
3287 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3288 im->yimg * im->zoom)
3289 : cairo_pdf_surface_create_for_stream
3290 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3291 break;
3292 case IF_EPS:
3293 im->gridfit = 0;
3294 im->surface = strlen(im->graphfile)
3295 ?
3296 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3297 im->yimg * im->zoom)
3298 : cairo_ps_surface_create_for_stream
3299 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3300 break;
3301 case IF_SVG:
3302 im->gridfit = 0;
3303 im->surface = strlen(im->graphfile)
3304 ?
3305 cairo_svg_surface_create(im->
3306 graphfile,
3307 im->ximg * im->zoom, im->yimg * im->zoom)
3308 : cairo_svg_surface_create_for_stream
3309 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3310 cairo_svg_surface_restrict_to_version
3311 (im->surface, CAIRO_SVG_VERSION_1_1);
3312 break;
3313 };
3314 cairo_destroy(im->cr);
3315 im->cr = cairo_create(im->surface);
3316 cairo_set_antialias(im->cr, im->graph_antialias);
3317 cairo_scale(im->cr, im->zoom, im->zoom);
3318 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3319 gfx_new_area(im, 0, 0, 0, im->yimg,
3320 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3321 gfx_add_point(im, im->ximg, 0);
3322 gfx_close_path(im);
3323 gfx_new_area(im, im->xorigin,
3324 im->yorigin,
3325 im->xorigin +
3326 im->xsize, im->yorigin,
3327 im->xorigin +
3328 im->xsize,
3329 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3330 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3331 gfx_close_path(im);
3332 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3333 im->xsize, im->ysize + 2.0);
3334 cairo_clip(im->cr);
3335 if (im->minval > 0.0)
3336 areazero = im->minval;
3337 if (im->maxval < 0.0)
3338 areazero = im->maxval;
3339 for (i = 0; i < im->gdes_c; i++) {
3340 switch (im->gdes[i].gf) {
3341 case GF_CDEF:
3342 case GF_VDEF:
3343 case GF_DEF:
3344 case GF_PRINT:
3345 case GF_GPRINT:
3346 case GF_COMMENT:
3347 case GF_TEXTALIGN:
3348 case GF_HRULE:
3349 case GF_VRULE:
3350 case GF_XPORT:
3351 case GF_SHIFT:
3352 break;
3353 case GF_TICK:
3354 for (ii = 0; ii < im->xsize; ii++) {
3355 if (!isnan(im->gdes[i].p_data[ii])
3356 && im->gdes[i].p_data[ii] != 0.0) {
3357 if (im->gdes[i].yrule > 0) {
3358 gfx_line(im,
3359 im->xorigin + ii,
3360 im->yorigin + 1.0,
3361 im->xorigin + ii,
3362 im->yorigin -
3363 im->gdes[i].yrule *
3364 im->ysize, 1.0, im->gdes[i].col);
3365 } else if (im->gdes[i].yrule < 0) {
3366 gfx_line(im,
3367 im->xorigin + ii,
3368 im->yorigin - im->ysize - 1.0,
3369 im->xorigin + ii,
3370 im->yorigin - im->ysize -
3371 im->gdes[i].
3372 yrule *
3373 im->ysize, 1.0, im->gdes[i].col);
3374 }
3375 }
3376 }
3377 break;
3378 case GF_LINE:
3379 case GF_AREA:
3380 /* fix data points at oo and -oo */
3381 for (ii = 0; ii < im->xsize; ii++) {
3382 if (isinf(im->gdes[i].p_data[ii])) {
3383 if (im->gdes[i].p_data[ii] > 0) {
3384 im->gdes[i].p_data[ii] = im->maxval;
3385 } else {
3386 im->gdes[i].p_data[ii] = im->minval;
3387 }
3389 }
3390 } /* for */
3392 /* *******************************************************
3393 a ___. (a,t)
3394 | | ___
3395 ____| | | |
3396 | |___|
3397 -------|--t-1--t--------------------------------
3399 if we know the value at time t was a then
3400 we draw a square from t-1 to t with the value a.
3402 ********************************************************* */
3403 if (im->gdes[i].col.alpha != 0.0) {
3404 /* GF_LINE and friend */
3405 if (im->gdes[i].gf == GF_LINE) {
3406 double last_y = 0.0;
3407 int draw_on = 0;
3409 cairo_save(im->cr);
3410 cairo_new_path(im->cr);
3411 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3412 if (im->gdes[i].dash) {
3413 cairo_set_dash(im->cr,
3414 im->gdes[i].p_dashes,
3415 im->gdes[i].ndash, im->gdes[i].offset);
3416 }
3418 for (ii = 1; ii < im->xsize; ii++) {
3419 if (isnan(im->gdes[i].p_data[ii])
3420 || (im->slopemode == 1
3421 && isnan(im->gdes[i].p_data[ii - 1]))) {
3422 draw_on = 0;
3423 continue;
3424 }
3425 if (draw_on == 0) {
3426 last_y = ytr(im, im->gdes[i].p_data[ii]);
3427 if (im->slopemode == 0) {
3428 double x = ii - 1 + im->xorigin;
3429 double y = last_y;
3431 gfx_line_fit(im, &x, &y);
3432 cairo_move_to(im->cr, x, y);
3433 x = ii + im->xorigin;
3434 y = last_y;
3435 gfx_line_fit(im, &x, &y);
3436 cairo_line_to(im->cr, x, y);
3437 } else {
3438 double x = ii - 1 + im->xorigin;
3439 double y =
3440 ytr(im, im->gdes[i].p_data[ii - 1]);
3441 gfx_line_fit(im, &x, &y);
3442 cairo_move_to(im->cr, x, y);
3443 x = ii + im->xorigin;
3444 y = last_y;
3445 gfx_line_fit(im, &x, &y);
3446 cairo_line_to(im->cr, x, y);
3447 }
3448 draw_on = 1;
3449 } else {
3450 double x1 = ii + im->xorigin;
3451 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3453 if (im->slopemode == 0
3454 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3455 double x = ii - 1 + im->xorigin;
3456 double y = y1;
3458 gfx_line_fit(im, &x, &y);
3459 cairo_line_to(im->cr, x, y);
3460 };
3461 last_y = y1;
3462 gfx_line_fit(im, &x1, &y1);
3463 cairo_line_to(im->cr, x1, y1);
3464 };
3465 }
3466 cairo_set_source_rgba(im->cr,
3467 im->gdes[i].
3468 col.red,
3469 im->gdes[i].
3470 col.green,
3471 im->gdes[i].
3472 col.blue, im->gdes[i].col.alpha);
3473 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3474 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3475 cairo_stroke(im->cr);
3476 cairo_restore(im->cr);
3477 } else {
3478 int idxI = -1;
3479 double *foreY =
3480 (double *) malloc(sizeof(double) * im->xsize * 2);
3481 double *foreX =
3482 (double *) malloc(sizeof(double) * im->xsize * 2);
3483 double *backY =
3484 (double *) malloc(sizeof(double) * im->xsize * 2);
3485 double *backX =
3486 (double *) malloc(sizeof(double) * im->xsize * 2);
3487 int drawem = 0;
3489 for (ii = 0; ii <= im->xsize; ii++) {
3490 double ybase, ytop;
3492 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3493 int cntI = 1;
3494 int lastI = 0;
3496 while (cntI < idxI
3497 &&
3498 AlmostEqual2sComplement(foreY
3499 [lastI],
3500 foreY[cntI], 4)
3501 &&
3502 AlmostEqual2sComplement(foreY
3503 [lastI],
3504 foreY
3505 [cntI + 1], 4)) {
3506 cntI++;
3507 }
3508 gfx_new_area(im,
3509 backX[0], backY[0],
3510 foreX[0], foreY[0],
3511 foreX[cntI],
3512 foreY[cntI], im->gdes[i].col);
3513 while (cntI < idxI) {
3514 lastI = cntI;
3515 cntI++;
3516 while (cntI < idxI
3517 &&
3518 AlmostEqual2sComplement(foreY
3519 [lastI],
3520 foreY[cntI], 4)
3521 &&
3522 AlmostEqual2sComplement(foreY
3523 [lastI],
3524 foreY
3525 [cntI
3526 + 1], 4)) {
3527 cntI++;
3528 }
3529 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3530 }
3531 gfx_add_point(im, backX[idxI], backY[idxI]);
3532 while (idxI > 1) {
3533 lastI = idxI;
3534 idxI--;
3535 while (idxI > 1
3536 &&
3537 AlmostEqual2sComplement(backY
3538 [lastI],
3539 backY[idxI], 4)
3540 &&
3541 AlmostEqual2sComplement(backY
3542 [lastI],
3543 backY
3544 [idxI
3545 - 1], 4)) {
3546 idxI--;
3547 }
3548 gfx_add_point(im, backX[idxI], backY[idxI]);
3549 }
3550 idxI = -1;
3551 drawem = 0;
3552 gfx_close_path(im);
3553 }
3554 if (drawem != 0) {
3555 drawem = 0;
3556 idxI = -1;
3557 }
3558 if (ii == im->xsize)
3559 break;
3560 if (im->slopemode == 0 && ii == 0) {
3561 continue;
3562 }
3563 if (isnan(im->gdes[i].p_data[ii])) {
3564 drawem = 1;
3565 continue;
3566 }
3567 ytop = ytr(im, im->gdes[i].p_data[ii]);
3568 if (lastgdes && im->gdes[i].stack) {
3569 ybase = ytr(im, lastgdes->p_data[ii]);
3570 } else {
3571 ybase = ytr(im, areazero);
3572 }
3573 if (ybase == ytop) {
3574 drawem = 1;
3575 continue;
3576 }
3578 if (ybase > ytop) {
3579 double extra = ytop;
3581 ytop = ybase;
3582 ybase = extra;
3583 }
3584 if (im->slopemode == 0) {
3585 backY[++idxI] = ybase - 0.2;
3586 backX[idxI] = ii + im->xorigin - 1;
3587 foreY[idxI] = ytop + 0.2;
3588 foreX[idxI] = ii + im->xorigin - 1;
3589 }
3590 backY[++idxI] = ybase - 0.2;
3591 backX[idxI] = ii + im->xorigin;
3592 foreY[idxI] = ytop + 0.2;
3593 foreX[idxI] = ii + im->xorigin;
3594 }
3595 /* close up any remaining area */
3596 free(foreY);
3597 free(foreX);
3598 free(backY);
3599 free(backX);
3600 } /* else GF_LINE */
3601 }
3602 /* if color != 0x0 */
3603 /* make sure we do not run into trouble when stacking on NaN */
3604 for (ii = 0; ii < im->xsize; ii++) {
3605 if (isnan(im->gdes[i].p_data[ii])) {
3606 if (lastgdes && (im->gdes[i].stack)) {
3607 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3608 } else {
3609 im->gdes[i].p_data[ii] = areazero;
3610 }
3611 }
3612 }
3613 lastgdes = &(im->gdes[i]);
3614 break;
3615 case GF_STACK:
3616 rrd_set_error
3617 ("STACK should already be turned into LINE or AREA here");
3618 return -1;
3619 break;
3620 } /* switch */
3621 }
3622 cairo_reset_clip(im->cr);
3624 /* grid_paint also does the text */
3625 if (!(im->extra_flags & ONLY_GRAPH))
3626 grid_paint(im);
3627 if (!(im->extra_flags & ONLY_GRAPH))
3628 axis_paint(im);
3629 /* the RULES are the last thing to paint ... */
3630 for (i = 0; i < im->gdes_c; i++) {
3632 switch (im->gdes[i].gf) {
3633 case GF_HRULE:
3634 if (im->gdes[i].yrule >= im->minval
3635 && im->gdes[i].yrule <= im->maxval) {
3636 cairo_save(im->cr);
3637 if (im->gdes[i].dash) {
3638 cairo_set_dash(im->cr,
3639 im->gdes[i].p_dashes,
3640 im->gdes[i].ndash, im->gdes[i].offset);
3641 }
3642 gfx_line(im, im->xorigin,
3643 ytr(im, im->gdes[i].yrule),
3644 im->xorigin + im->xsize,
3645 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3646 cairo_stroke(im->cr);
3647 cairo_restore(im->cr);
3648 }
3649 break;
3650 case GF_VRULE:
3651 if (im->gdes[i].xrule >= im->start
3652 && im->gdes[i].xrule <= im->end) {
3653 cairo_save(im->cr);
3654 if (im->gdes[i].dash) {
3655 cairo_set_dash(im->cr,
3656 im->gdes[i].p_dashes,
3657 im->gdes[i].ndash, im->gdes[i].offset);
3658 }
3659 gfx_line(im,
3660 xtr(im, im->gdes[i].xrule),
3661 im->yorigin, xtr(im,
3662 im->
3663 gdes[i].
3664 xrule),
3665 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3666 cairo_stroke(im->cr);
3667 cairo_restore(im->cr);
3668 }
3669 break;
3670 default:
3671 break;
3672 }
3673 }
3676 switch (im->imgformat) {
3677 case IF_PNG:
3678 {
3679 cairo_status_t status;
3681 status = strlen(im->graphfile) ?
3682 cairo_surface_write_to_png(im->surface, im->graphfile)
3683 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3684 im);
3686 if (status != CAIRO_STATUS_SUCCESS) {
3687 rrd_set_error("Could not save png to '%s'", im->graphfile);
3688 return 1;
3689 }
3690 break;
3691 }
3692 default:
3693 if (strlen(im->graphfile)) {
3694 cairo_show_page(im->cr);
3695 } else {
3696 cairo_surface_finish(im->surface);
3697 }
3698 break;
3699 }
3701 return 0;
3702 }
3705 /*****************************************************
3706 * graph stuff
3707 *****************************************************/
3709 int gdes_alloc(
3710 image_desc_t *im)
3711 {
3713 im->gdes_c++;
3714 if ((im->gdes = (graph_desc_t *)
3715 rrd_realloc(im->gdes, (im->gdes_c)
3716 * sizeof(graph_desc_t))) == NULL) {
3717 rrd_set_error("realloc graph_descs");
3718 return -1;
3719 }
3722 im->gdes[im->gdes_c - 1].step = im->step;
3723 im->gdes[im->gdes_c - 1].step_orig = im->step;
3724 im->gdes[im->gdes_c - 1].stack = 0;
3725 im->gdes[im->gdes_c - 1].linewidth = 0;
3726 im->gdes[im->gdes_c - 1].debug = 0;
3727 im->gdes[im->gdes_c - 1].start = im->start;
3728 im->gdes[im->gdes_c - 1].start_orig = im->start;
3729 im->gdes[im->gdes_c - 1].end = im->end;
3730 im->gdes[im->gdes_c - 1].end_orig = im->end;
3731 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3732 im->gdes[im->gdes_c - 1].data = NULL;
3733 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3734 im->gdes[im->gdes_c - 1].data_first = 0;
3735 im->gdes[im->gdes_c - 1].p_data = NULL;
3736 im->gdes[im->gdes_c - 1].rpnp = NULL;
3737 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3738 im->gdes[im->gdes_c - 1].shift = 0.0;
3739 im->gdes[im->gdes_c - 1].dash = 0;
3740 im->gdes[im->gdes_c - 1].ndash = 0;
3741 im->gdes[im->gdes_c - 1].offset = 0;
3742 im->gdes[im->gdes_c - 1].col.red = 0.0;
3743 im->gdes[im->gdes_c - 1].col.green = 0.0;
3744 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3745 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3746 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3747 im->gdes[im->gdes_c - 1].format[0] = '\0';
3748 im->gdes[im->gdes_c - 1].strftm = 0;
3749 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3750 im->gdes[im->gdes_c - 1].ds = -1;
3751 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3752 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3753 im->gdes[im->gdes_c - 1].yrule = DNAN;
3754 im->gdes[im->gdes_c - 1].xrule = 0;
3755 return 0;
3756 }
3758 /* copies input untill the first unescaped colon is found
3759 or until input ends. backslashes have to be escaped as well */
3760 int scan_for_col(
3761 const char *const input,
3762 int len,
3763 char *const output)
3764 {
3765 int inp, outp = 0;
3767 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3768 if (input[inp] == '\\'
3769 && input[inp + 1] != '\0'
3770 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3771 output[outp++] = input[++inp];
3772 } else {
3773 output[outp++] = input[inp];
3774 }
3775 }
3776 output[outp] = '\0';
3777 return inp;
3778 }
3780 /* Now just a wrapper around rrd_graph_v */
3781 int rrd_graph(
3782 int argc,
3783 char **argv,
3784 char ***prdata,
3785 int *xsize,
3786 int *ysize,
3787 FILE * stream,
3788 double *ymin,
3789 double *ymax)
3790 {
3791 int prlines = 0;
3792 rrd_info_t *grinfo = NULL;
3793 rrd_info_t *walker;
3795 grinfo = rrd_graph_v(argc, argv);
3796 if (grinfo == NULL)
3797 return -1;
3798 walker = grinfo;
3799 (*prdata) = NULL;
3800 while (walker) {
3801 if (strcmp(walker->key, "image_info") == 0) {
3802 prlines++;
3803 if (((*prdata) =
3804 (char**)rrd_realloc((*prdata),
3805 (prlines + 1) * sizeof(char *))) == NULL) {
3806 rrd_set_error("realloc prdata");
3807 return 0;
3808 }
3809 /* imginfo goes to position 0 in the prdata array */
3810 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3811 + 2) * sizeof(char));
3812 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3813 (*prdata)[prlines] = NULL;
3814 }
3815 /* skip anything else */
3816 walker = walker->next;
3817 }
3818 walker = grinfo;
3819 *xsize = 0;
3820 *ysize = 0;
3821 *ymin = 0;
3822 *ymax = 0;
3823 while (walker) {
3824 if (strcmp(walker->key, "image_width") == 0) {
3825 *xsize = walker->value.u_cnt;
3826 } else if (strcmp(walker->key, "image_height") == 0) {
3827 *ysize = walker->value.u_cnt;
3828 } else if (strcmp(walker->key, "value_min") == 0) {
3829 *ymin = walker->value.u_val;
3830 } else if (strcmp(walker->key, "value_max") == 0) {
3831 *ymax = walker->value.u_val;
3832 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3833 prlines++;
3834 if (((*prdata) =
3835 (char**)rrd_realloc((*prdata),
3836 (prlines + 1) * sizeof(char *))) == NULL) {
3837 rrd_set_error("realloc prdata");
3838 return 0;
3839 }
3840 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3841 + 2) * sizeof(char));
3842 (*prdata)[prlines] = NULL;
3843 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3844 } else if (strcmp(walker->key, "image") == 0) {
3845 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3846 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3847 rrd_set_error("writing image");
3848 return 0;
3849 }
3850 }
3851 /* skip anything else */
3852 walker = walker->next;
3853 }
3854 rrd_info_free(grinfo);
3855 return 0;
3856 }
3859 /* Some surgery done on this function, it became ridiculously big.
3860 ** Things moved:
3861 ** - initializing now in rrd_graph_init()
3862 ** - options parsing now in rrd_graph_options()
3863 ** - script parsing now in rrd_graph_script()
3864 */
3865 rrd_info_t *rrd_graph_v(
3866 int argc,
3867 char **argv)
3868 {
3869 image_desc_t im;
3870 rrd_info_t *grinfo;
3871 rrd_graph_init(&im);
3872 /* a dummy surface so that we can measure text sizes for placements */
3874 rrd_graph_options(argc, argv, &im);
3875 if (rrd_test_error()) {
3876 rrd_info_free(im.grinfo);
3877 im_free(&im);
3878 return NULL;
3879 }
3881 if (optind >= argc) {
3882 rrd_info_free(im.grinfo);
3883 im_free(&im);
3884 rrd_set_error("missing filename");
3885 return NULL;
3886 }
3888 if (strlen(argv[optind]) >= MAXPATH) {
3889 rrd_set_error("filename (including path) too long");
3890 rrd_info_free(im.grinfo);
3891 im_free(&im);
3892 return NULL;
3893 }
3895 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3896 im.graphfile[MAXPATH - 1] = '\0';
3898 if (strcmp(im.graphfile, "-") == 0) {
3899 im.graphfile[0] = '\0';
3900 }
3902 rrd_graph_script(argc, argv, &im, 1);
3903 if (rrd_test_error()) {
3904 rrd_info_free(im.grinfo);
3905 im_free(&im);
3906 return NULL;
3907 }
3909 /* Everything is now read and the actual work can start */
3911 if (graph_paint(&im) == -1) {
3912 rrd_info_free(im.grinfo);
3913 im_free(&im);
3914 return NULL;
3915 }
3918 /* The image is generated and needs to be output.
3919 ** Also, if needed, print a line with information about the image.
3920 */
3922 if (im.imginfo) {
3923 rrd_infoval_t info;
3924 char *path;
3925 char *filename;
3927 path = strdup(im.graphfile);
3928 filename = basename(path);
3929 info.u_str =
3930 sprintf_alloc(im.imginfo,
3931 filename,
3932 (long) (im.zoom *
3933 im.ximg), (long) (im.zoom * im.yimg));
3934 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3935 free(info.u_str);
3936 free(path);
3937 }
3938 if (im.rendered_image) {
3939 rrd_infoval_t img;
3941 img.u_blo.size = im.rendered_image_size;
3942 img.u_blo.ptr = im.rendered_image;
3943 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3944 }
3945 grinfo = im.grinfo;
3946 im_free(&im);
3947 return grinfo;
3948 }
3950 static void
3951 rrd_set_font_desc (
3952 image_desc_t *im,int prop,char *font, double size ){
3953 if (font){
3954 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3955 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3956 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3957 };
3958 if (size > 0){
3959 im->text_prop[prop].size = size;
3960 };
3961 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3962 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3963 };
3964 }
3966 void rrd_graph_init(
3967 image_desc_t
3968 *im)
3969 {
3970 unsigned int i;
3971 char *deffont = getenv("RRD_DEFAULT_FONT");
3972 static PangoFontMap *fontmap = NULL;
3973 PangoContext *context;
3975 #ifdef HAVE_TZSET
3976 tzset();
3977 #endif
3978 #ifdef HAVE_SETLOCALE
3979 setlocale(LC_TIME, "");
3980 #ifdef HAVE_MBSTOWCS
3981 setlocale(LC_CTYPE, "");
3982 #endif
3983 #endif
3984 im->base = 1000;
3985 im->daemon_addr = NULL;
3986 im->draw_x_grid = 1;
3987 im->draw_y_grid = 1;
3988 im->extra_flags = 0;
3989 im->font_options = cairo_font_options_create();
3990 im->forceleftspace = 0;
3991 im->gdes_c = 0;
3992 im->gdes = NULL;
3993 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3994 im->grid_dash_off = 1;
3995 im->grid_dash_on = 1;
3996 im->gridfit = 1;
3997 im->grinfo = (rrd_info_t *) NULL;
3998 im->grinfo_current = (rrd_info_t *) NULL;
3999 im->imgformat = IF_PNG;
4000 im->imginfo = NULL;
4001 im->lazy = 0;
4002 im->legenddirection = TOP_DOWN;
4003 im->legendheight = 0;
4004 im->legendposition = SOUTH;
4005 im->legendwidth = 0;
4006 im->logarithmic = 0;
4007 im->maxval = DNAN;
4008 im->minval = 0;
4009 im->minval = DNAN;
4010 im->prt_c = 0;
4011 im->rigid = 0;
4012 im->rendered_image_size = 0;
4013 im->rendered_image = NULL;
4014 im->slopemode = 0;
4015 im->step = 0;
4016 im->symbol = ' ';
4017 im->tabwidth = 40.0;
4018 im->title[0] = '\0';
4019 im->unitsexponent = 9999;
4020 im->unitslength = 6;
4021 im->viewfactor = 1.0;
4022 im->watermark[0] = '\0';
4023 im->with_markup = 0;
4024 im->ximg = 0;
4025 im->xlab_user.minsec = -1;
4026 im->xorigin = 0;
4027 im->xOriginLegend = 0;
4028 im->xOriginLegendY = 0;
4029 im->xOriginLegendY2 = 0;
4030 im->xOriginTitle = 0;
4031 im->xsize = 400;
4032 im->ygridstep = DNAN;
4033 im->yimg = 0;
4034 im->ylegend[0] = '\0';
4035 im->second_axis_scale = 0; /* 0 disables it */
4036 im->second_axis_shift = 0; /* no shift by default */
4037 im->second_axis_legend[0] = '\0';
4038 im->second_axis_format[0] = '\0';
4039 im->yorigin = 0;
4040 im->yOriginLegend = 0;
4041 im->yOriginLegendY = 0;
4042 im->yOriginLegendY2 = 0;
4043 im->yOriginTitle = 0;
4044 im->ysize = 100;
4045 im->zoom = 1;
4047 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4048 im->cr = cairo_create(im->surface);
4050 for (i = 0; i < DIM(text_prop); i++) {
4051 im->text_prop[i].size = -1;
4052 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4053 }
4055 if (fontmap == NULL){
4056 fontmap = pango_cairo_font_map_get_default();
4057 }
4059 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4061 pango_cairo_context_set_resolution(context, 100);
4063 pango_cairo_update_context(im->cr,context);
4065 im->layout = pango_layout_new(context);
4067 // im->layout = pango_cairo_create_layout(im->cr);
4070 cairo_font_options_set_hint_style
4071 (im->font_options, CAIRO_HINT_STYLE_FULL);
4072 cairo_font_options_set_hint_metrics
4073 (im->font_options, CAIRO_HINT_METRICS_ON);
4074 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4078 for (i = 0; i < DIM(graph_col); i++)
4079 im->graph_col[i] = graph_col[i];
4082 }
4085 void rrd_graph_options(
4086 int argc,
4087 char *argv[],
4088 image_desc_t
4089 *im)
4090 {
4091 int stroff;
4092 char *parsetime_error = NULL;
4093 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4094 time_t start_tmp = 0, end_tmp = 0;
4095 long long_tmp;
4096 rrd_time_value_t start_tv, end_tv;
4097 long unsigned int color;
4098 char *old_locale = "";
4100 /* defines for long options without a short equivalent. should be bytes,
4101 and may not collide with (the ASCII value of) short options */
4102 #define LONGOPT_UNITS_SI 255
4104 /* *INDENT-OFF* */
4105 struct option long_options[] = {
4106 { "start", required_argument, 0, 's'},
4107 { "end", required_argument, 0, 'e'},
4108 { "x-grid", required_argument, 0, 'x'},
4109 { "y-grid", required_argument, 0, 'y'},
4110 { "vertical-label", required_argument, 0, 'v'},
4111 { "width", required_argument, 0, 'w'},
4112 { "height", required_argument, 0, 'h'},
4113 { "full-size-mode", no_argument, 0, 'D'},
4114 { "interlaced", no_argument, 0, 'i'},
4115 { "upper-limit", required_argument, 0, 'u'},
4116 { "lower-limit", required_argument, 0, 'l'},
4117 { "rigid", no_argument, 0, 'r'},
4118 { "base", required_argument, 0, 'b'},
4119 { "logarithmic", no_argument, 0, 'o'},
4120 { "color", required_argument, 0, 'c'},
4121 { "font", required_argument, 0, 'n'},
4122 { "title", required_argument, 0, 't'},
4123 { "imginfo", required_argument, 0, 'f'},
4124 { "imgformat", required_argument, 0, 'a'},
4125 { "lazy", no_argument, 0, 'z'},
4126 { "zoom", required_argument, 0, 'm'},
4127 { "no-legend", no_argument, 0, 'g'},
4128 { "legend-position", required_argument, 0, 1005},
4129 { "legend-direction", required_argument, 0, 1006},
4130 { "force-rules-legend", no_argument, 0, 'F'},
4131 { "only-graph", no_argument, 0, 'j'},
4132 { "alt-y-grid", no_argument, 0, 'Y'},
4133 {"disable-rrdtool-tag", no_argument, 0, 1001},
4134 {"right-axis", required_argument, 0, 1002},
4135 {"right-axis-label", required_argument, 0, 1003},
4136 {"right-axis-format", required_argument, 0, 1004},
4137 { "no-minor", no_argument, 0, 'I'},
4138 { "slope-mode", no_argument, 0, 'E'},
4139 { "alt-autoscale", no_argument, 0, 'A'},
4140 { "alt-autoscale-min", no_argument, 0, 'J'},
4141 { "alt-autoscale-max", no_argument, 0, 'M'},
4142 { "no-gridfit", no_argument, 0, 'N'},
4143 { "units-exponent", required_argument, 0, 'X'},
4144 { "units-length", required_argument, 0, 'L'},
4145 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4146 { "step", required_argument, 0, 'S'},
4147 { "tabwidth", required_argument, 0, 'T'},
4148 { "font-render-mode", required_argument, 0, 'R'},
4149 { "graph-render-mode", required_argument, 0, 'G'},
4150 { "font-smoothing-threshold", required_argument, 0, 'B'},
4151 { "watermark", required_argument, 0, 'W'},
4152 { "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 */
4153 { "pango-markup", no_argument, 0, 'P'},
4154 { "daemon", required_argument, 0, 'd'},
4155 { 0, 0, 0, 0}
4156 };
4157 /* *INDENT-ON* */
4159 optind = 0;
4160 opterr = 0; /* initialize getopt */
4161 rrd_parsetime("end-24h", &start_tv);
4162 rrd_parsetime("now", &end_tv);
4163 while (1) {
4164 int option_index = 0;
4165 int opt;
4166 int col_start, col_end;
4168 opt = getopt_long(argc, argv,
4169 "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:",
4170 long_options, &option_index);
4171 if (opt == EOF)
4172 break;
4173 switch (opt) {
4174 case 'I':
4175 im->extra_flags |= NOMINOR;
4176 break;
4177 case 'Y':
4178 im->extra_flags |= ALTYGRID;
4179 break;
4180 case 'A':
4181 im->extra_flags |= ALTAUTOSCALE;
4182 break;
4183 case 'J':
4184 im->extra_flags |= ALTAUTOSCALE_MIN;
4185 break;
4186 case 'M':
4187 im->extra_flags |= ALTAUTOSCALE_MAX;
4188 break;
4189 case 'j':
4190 im->extra_flags |= ONLY_GRAPH;
4191 break;
4192 case 'g':
4193 im->extra_flags |= NOLEGEND;
4194 break;
4195 case 1005:
4196 if (strcmp(optarg, "north") == 0) {
4197 im->legendposition = NORTH;
4198 } else if (strcmp(optarg, "west") == 0) {
4199 im->legendposition = WEST;
4200 } else if (strcmp(optarg, "south") == 0) {
4201 im->legendposition = SOUTH;
4202 } else if (strcmp(optarg, "east") == 0) {
4203 im->legendposition = EAST;
4204 } else {
4205 rrd_set_error("unknown legend-position '%s'", optarg);
4206 return;
4207 }
4208 break;
4209 case 1006:
4210 if (strcmp(optarg, "topdown") == 0) {
4211 im->legenddirection = TOP_DOWN;
4212 } else if (strcmp(optarg, "bottomup") == 0) {
4213 im->legenddirection = BOTTOM_UP;
4214 } else {
4215 rrd_set_error("unknown legend-position '%s'", optarg);
4216 return;
4217 }
4218 break;
4219 case 'F':
4220 im->extra_flags |= FORCE_RULES_LEGEND;
4221 break;
4222 case 1001:
4223 im->extra_flags |= NO_RRDTOOL_TAG;
4224 break;
4225 case LONGOPT_UNITS_SI:
4226 if (im->extra_flags & FORCE_UNITS) {
4227 rrd_set_error("--units can only be used once!");
4228 setlocale(LC_NUMERIC, old_locale);
4229 return;
4230 }
4231 if (strcmp(optarg, "si") == 0)
4232 im->extra_flags |= FORCE_UNITS_SI;
4233 else {
4234 rrd_set_error("invalid argument for --units: %s", optarg);
4235 return;
4236 }
4237 break;
4238 case 'X':
4239 im->unitsexponent = atoi(optarg);
4240 break;
4241 case 'L':
4242 im->unitslength = atoi(optarg);
4243 im->forceleftspace = 1;
4244 break;
4245 case 'T':
4246 old_locale = setlocale(LC_NUMERIC, "C");
4247 im->tabwidth = atof(optarg);
4248 setlocale(LC_NUMERIC, old_locale);
4249 break;
4250 case 'S':
4251 old_locale = setlocale(LC_NUMERIC, "C");
4252 im->step = atoi(optarg);
4253 setlocale(LC_NUMERIC, old_locale);
4254 break;
4255 case 'N':
4256 im->gridfit = 0;
4257 break;
4258 case 'P':
4259 im->with_markup = 1;
4260 break;
4261 case 's':
4262 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4263 rrd_set_error("start time: %s", parsetime_error);
4264 return;
4265 }
4266 break;
4267 case 'e':
4268 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4269 rrd_set_error("end time: %s", parsetime_error);
4270 return;
4271 }
4272 break;
4273 case 'x':
4274 if (strcmp(optarg, "none") == 0) {
4275 im->draw_x_grid = 0;
4276 break;
4277 };
4278 if (sscanf(optarg,
4279 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4280 scan_gtm,
4281 &im->xlab_user.gridst,
4282 scan_mtm,
4283 &im->xlab_user.mgridst,
4284 scan_ltm,
4285 &im->xlab_user.labst,
4286 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4287 strncpy(im->xlab_form, optarg + stroff,
4288 sizeof(im->xlab_form) - 1);
4289 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4290 if ((int)
4291 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4292 rrd_set_error("unknown keyword %s", scan_gtm);
4293 return;
4294 } else if ((int)
4295 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4296 == -1) {
4297 rrd_set_error("unknown keyword %s", scan_mtm);
4298 return;
4299 } else if ((int)
4300 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4301 rrd_set_error("unknown keyword %s", scan_ltm);
4302 return;
4303 }
4304 im->xlab_user.minsec = 1;
4305 im->xlab_user.stst = im->xlab_form;
4306 } else {
4307 rrd_set_error("invalid x-grid format");
4308 return;
4309 }
4310 break;
4311 case 'y':
4313 if (strcmp(optarg, "none") == 0) {
4314 im->draw_y_grid = 0;
4315 break;
4316 };
4317 old_locale = setlocale(LC_NUMERIC, "C");
4318 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4319 setlocale(LC_NUMERIC, old_locale);
4320 if (im->ygridstep <= 0) {
4321 rrd_set_error("grid step must be > 0");
4322 return;
4323 } else if (im->ylabfact < 1) {
4324 rrd_set_error("label factor must be > 0");
4325 return;
4326 }
4327 } else {
4328 setlocale(LC_NUMERIC, old_locale);
4329 rrd_set_error("invalid y-grid format");
4330 return;
4331 }
4332 break;
4333 case 1002: /* right y axis */
4335 if(sscanf(optarg,
4336 "%lf:%lf",
4337 &im->second_axis_scale,
4338 &im->second_axis_shift) == 2) {
4339 if(im->second_axis_scale==0){
4340 rrd_set_error("the second_axis_scale must not be 0");
4341 return;
4342 }
4343 } else {
4344 rrd_set_error("invalid right-axis format expected scale:shift");
4345 return;
4346 }
4347 break;
4348 case 1003:
4349 strncpy(im->second_axis_legend,optarg,150);
4350 im->second_axis_legend[150]='\0';
4351 break;
4352 case 1004:
4353 if (bad_format(optarg)){
4354 rrd_set_error("use either %le or %lf formats");
4355 return;
4356 }
4357 strncpy(im->second_axis_format,optarg,150);
4358 im->second_axis_format[150]='\0';
4359 break;
4360 case 'v':
4361 strncpy(im->ylegend, optarg, 150);
4362 im->ylegend[150] = '\0';
4363 break;
4364 case 'u':
4365 old_locale = setlocale(LC_NUMERIC, "C");
4366 im->maxval = atof(optarg);
4367 setlocale(LC_NUMERIC, old_locale);
4368 break;
4369 case 'l':
4370 old_locale = setlocale(LC_NUMERIC, "C");
4371 im->minval = atof(optarg);
4372 setlocale(LC_NUMERIC, old_locale);
4373 break;
4374 case 'b':
4375 im->base = atol(optarg);
4376 if (im->base != 1024 && im->base != 1000) {
4377 rrd_set_error
4378 ("the only sensible value for base apart from 1000 is 1024");
4379 return;
4380 }
4381 break;
4382 case 'w':
4383 long_tmp = atol(optarg);
4384 if (long_tmp < 10) {
4385 rrd_set_error("width below 10 pixels");
4386 return;
4387 }
4388 im->xsize = long_tmp;
4389 break;
4390 case 'h':
4391 long_tmp = atol(optarg);
4392 if (long_tmp < 10) {
4393 rrd_set_error("height below 10 pixels");
4394 return;
4395 }
4396 im->ysize = long_tmp;
4397 break;
4398 case 'D':
4399 im->extra_flags |= FULL_SIZE_MODE;
4400 break;
4401 case 'i':
4402 /* interlaced png not supported at the moment */
4403 break;
4404 case 'r':
4405 im->rigid = 1;
4406 break;
4407 case 'f':
4408 im->imginfo = optarg;
4409 break;
4410 case 'a':
4411 if ((int)
4412 (im->imgformat = if_conv(optarg)) == -1) {
4413 rrd_set_error("unsupported graphics format '%s'", optarg);
4414 return;
4415 }
4416 break;
4417 case 'z':
4418 im->lazy = 1;
4419 break;
4420 case 'E':
4421 im->slopemode = 1;
4422 break;
4423 case 'o':
4424 im->logarithmic = 1;
4425 break;
4426 case 'c':
4427 if (sscanf(optarg,
4428 "%10[A-Z]#%n%8lx%n",
4429 col_nam, &col_start, &color, &col_end) == 2) {
4430 int ci;
4431 int col_len = col_end - col_start;
4433 switch (col_len) {
4434 case 3:
4435 color =
4436 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4437 0x011000) |
4438 ((color & 0x00F)
4439 * 0x001100)
4440 | 0x000000FF);
4441 break;
4442 case 4:
4443 color =
4444 (((color & 0xF000) *
4445 0x11000) | ((color & 0x0F00) *
4446 0x01100) | ((color &
4447 0x00F0) *
4448 0x00110) |
4449 ((color & 0x000F) * 0x00011)
4450 );
4451 break;
4452 case 6:
4453 color = (color << 8) + 0xff /* shift left by 8 */ ;
4454 break;
4455 case 8:
4456 break;
4457 default:
4458 rrd_set_error("the color format is #RRGGBB[AA]");
4459 return;
4460 }
4461 if ((ci = grc_conv(col_nam)) != -1) {
4462 im->graph_col[ci] = gfx_hex_to_col(color);
4463 } else {
4464 rrd_set_error("invalid color name '%s'", col_nam);
4465 return;
4466 }
4467 } else {
4468 rrd_set_error("invalid color def format");
4469 return;
4470 }
4471 break;
4472 case 'n':{
4473 char prop[15];
4474 double size = 1;
4475 int end;
4477 old_locale = setlocale(LC_NUMERIC, "C");
4478 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4479 int sindex, propidx;
4481 setlocale(LC_NUMERIC, old_locale);
4482 if ((sindex = text_prop_conv(prop)) != -1) {
4483 for (propidx = sindex;
4484 propidx < TEXT_PROP_LAST; propidx++) {
4485 if (size > 0) {
4486 rrd_set_font_desc(im,propidx,NULL,size);
4487 }
4488 if ((int) strlen(optarg) > end+2) {
4489 if (optarg[end] == ':') {
4490 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4491 } else {
4492 rrd_set_error
4493 ("expected : after font size in '%s'",
4494 optarg);
4495 return;
4496 }
4497 }
4498 /* only run the for loop for DEFAULT (0) for
4499 all others, we break here. woodo programming */
4500 if (propidx == sindex && sindex != 0)
4501 break;
4502 }
4503 } else {
4504 rrd_set_error("invalid fonttag '%s'", prop);
4505 return;
4506 }
4507 } else {
4508 setlocale(LC_NUMERIC, old_locale);
4509 rrd_set_error("invalid text property format");
4510 return;
4511 }
4512 break;
4513 }
4514 case 'm':
4515 old_locale = setlocale(LC_NUMERIC, "C");
4516 im->zoom = atof(optarg);
4517 setlocale(LC_NUMERIC, old_locale);
4518 if (im->zoom <= 0.0) {
4519 rrd_set_error("zoom factor must be > 0");
4520 return;
4521 }
4522 break;
4523 case 't':
4524 strncpy(im->title, optarg, 150);
4525 im->title[150] = '\0';
4526 break;
4527 case 'R':
4528 if (strcmp(optarg, "normal") == 0) {
4529 cairo_font_options_set_antialias
4530 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4531 cairo_font_options_set_hint_style
4532 (im->font_options, CAIRO_HINT_STYLE_FULL);
4533 } else if (strcmp(optarg, "light") == 0) {
4534 cairo_font_options_set_antialias
4535 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4536 cairo_font_options_set_hint_style
4537 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4538 } else if (strcmp(optarg, "mono") == 0) {
4539 cairo_font_options_set_antialias
4540 (im->font_options, CAIRO_ANTIALIAS_NONE);
4541 cairo_font_options_set_hint_style
4542 (im->font_options, CAIRO_HINT_STYLE_FULL);
4543 } else {
4544 rrd_set_error("unknown font-render-mode '%s'", optarg);
4545 return;
4546 }
4547 break;
4548 case 'G':
4549 if (strcmp(optarg, "normal") == 0)
4550 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4551 else if (strcmp(optarg, "mono") == 0)
4552 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4553 else {
4554 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4555 return;
4556 }
4557 break;
4558 case 'B':
4559 /* not supported curently */
4560 break;
4561 case 'W':
4562 strncpy(im->watermark, optarg, 100);
4563 im->watermark[99] = '\0';
4564 break;
4565 case 'd':
4566 {
4567 if (im->daemon_addr != NULL)
4568 {
4569 rrd_set_error ("You cannot specify --daemon "
4570 "more than once.");
4571 return;
4572 }
4574 im->daemon_addr = strdup(optarg);
4575 if (im->daemon_addr == NULL)
4576 {
4577 rrd_set_error("strdup failed");
4578 return;
4579 }
4581 break;
4582 }
4583 case '?':
4584 if (optopt != 0)
4585 rrd_set_error("unknown option '%c'", optopt);
4586 else
4587 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4588 return;
4589 }
4590 } /* while (1) */
4592 { /* try to connect to rrdcached */
4593 int status = rrdc_connect(im->daemon_addr);
4594 if (status != 0) return;
4595 }
4597 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4598 pango_layout_context_changed(im->layout);
4602 if (im->logarithmic && im->minval <= 0) {
4603 rrd_set_error
4604 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4605 return;
4606 }
4608 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4609 /* error string is set in rrd_parsetime.c */
4610 return;
4611 }
4613 if (start_tmp < 3600 * 24 * 365 * 10) {
4614 rrd_set_error
4615 ("the first entry to fetch should be after 1980 (%ld)",
4616 start_tmp);
4617 return;
4618 }
4620 if (end_tmp < start_tmp) {
4621 rrd_set_error
4622 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4623 return;
4624 }
4626 im->start = start_tmp;
4627 im->end = end_tmp;
4628 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4629 }
4631 int rrd_graph_color(
4632 image_desc_t
4633 *im,
4634 char *var,
4635 char *err,
4636 int optional)
4637 {
4638 char *color;
4639 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4641 color = strstr(var, "#");
4642 if (color == NULL) {
4643 if (optional == 0) {
4644 rrd_set_error("Found no color in %s", err);
4645 return 0;
4646 }
4647 return 0;
4648 } else {
4649 int n = 0;
4650 char *rest;
4651 long unsigned int col;
4653 rest = strstr(color, ":");
4654 if (rest != NULL)
4655 n = rest - color;
4656 else
4657 n = strlen(color);
4658 switch (n) {
4659 case 7:
4660 sscanf(color, "#%6lx%n", &col, &n);
4661 col = (col << 8) + 0xff /* shift left by 8 */ ;
4662 if (n != 7)
4663 rrd_set_error("Color problem in %s", err);
4664 break;
4665 case 9:
4666 sscanf(color, "#%8lx%n", &col, &n);
4667 if (n == 9)
4668 break;
4669 default:
4670 rrd_set_error("Color problem in %s", err);
4671 }
4672 if (rrd_test_error())
4673 return 0;
4674 gdp->col = gfx_hex_to_col(col);
4675 return n;
4676 }
4677 }
4680 int bad_format(
4681 char *fmt)
4682 {
4683 char *ptr;
4684 int n = 0;
4686 ptr = fmt;
4687 while (*ptr != '\0')
4688 if (*ptr++ == '%') {
4690 /* line cannot end with percent char */
4691 if (*ptr == '\0')
4692 return 1;
4693 /* '%s', '%S' and '%%' are allowed */
4694 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4695 ptr++;
4696 /* %c is allowed (but use only with vdef!) */
4697 else if (*ptr == 'c') {
4698 ptr++;
4699 n = 1;
4700 }
4702 /* or else '% 6.2lf' and such are allowed */
4703 else {
4704 /* optional padding character */
4705 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4706 ptr++;
4707 /* This should take care of 'm.n' with all three optional */
4708 while (*ptr >= '0' && *ptr <= '9')
4709 ptr++;
4710 if (*ptr == '.')
4711 ptr++;
4712 while (*ptr >= '0' && *ptr <= '9')
4713 ptr++;
4714 /* Either 'le', 'lf' or 'lg' must follow here */
4715 if (*ptr++ != 'l')
4716 return 1;
4717 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4718 ptr++;
4719 else
4720 return 1;
4721 n++;
4722 }
4723 }
4725 return (n != 1);
4726 }
4729 int vdef_parse(
4730 struct graph_desc_t
4731 *gdes,
4732 const char *const str)
4733 {
4734 /* A VDEF currently is either "func" or "param,func"
4735 * so the parsing is rather simple. Change if needed.
4736 */
4737 double param;
4738 char func[30];
4739 int n;
4740 char *old_locale;
4742 n = 0;
4743 old_locale = setlocale(LC_NUMERIC, "C");
4744 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4745 setlocale(LC_NUMERIC, old_locale);
4746 if (n == (int) strlen(str)) { /* matched */
4747 ;
4748 } else {
4749 n = 0;
4750 sscanf(str, "%29[A-Z]%n", func, &n);
4751 if (n == (int) strlen(str)) { /* matched */
4752 param = DNAN;
4753 } else {
4754 rrd_set_error
4755 ("Unknown function string '%s' in VDEF '%s'",
4756 str, gdes->vname);
4757 return -1;
4758 }
4759 }
4760 if (!strcmp("PERCENT", func))
4761 gdes->vf.op = VDEF_PERCENT;
4762 else if (!strcmp("PERCENTNAN", func))
4763 gdes->vf.op = VDEF_PERCENTNAN;
4764 else if (!strcmp("MAXIMUM", func))
4765 gdes->vf.op = VDEF_MAXIMUM;
4766 else if (!strcmp("AVERAGE", func))
4767 gdes->vf.op = VDEF_AVERAGE;
4768 else if (!strcmp("STDEV", func))
4769 gdes->vf.op = VDEF_STDEV;
4770 else if (!strcmp("MINIMUM", func))
4771 gdes->vf.op = VDEF_MINIMUM;
4772 else if (!strcmp("TOTAL", func))
4773 gdes->vf.op = VDEF_TOTAL;
4774 else if (!strcmp("FIRST", func))
4775 gdes->vf.op = VDEF_FIRST;
4776 else if (!strcmp("LAST", func))
4777 gdes->vf.op = VDEF_LAST;
4778 else if (!strcmp("LSLSLOPE", func))
4779 gdes->vf.op = VDEF_LSLSLOPE;
4780 else if (!strcmp("LSLINT", func))
4781 gdes->vf.op = VDEF_LSLINT;
4782 else if (!strcmp("LSLCORREL", func))
4783 gdes->vf.op = VDEF_LSLCORREL;
4784 else {
4785 rrd_set_error
4786 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4787 return -1;
4788 };
4789 switch (gdes->vf.op) {
4790 case VDEF_PERCENT:
4791 case VDEF_PERCENTNAN:
4792 if (isnan(param)) { /* no parameter given */
4793 rrd_set_error
4794 ("Function '%s' needs parameter in VDEF '%s'\n",
4795 func, gdes->vname);
4796 return -1;
4797 };
4798 if (param >= 0.0 && param <= 100.0) {
4799 gdes->vf.param = param;
4800 gdes->vf.val = DNAN; /* undefined */
4801 gdes->vf.when = 0; /* undefined */
4802 } else {
4803 rrd_set_error
4804 ("Parameter '%f' out of range in VDEF '%s'\n",
4805 param, gdes->vname);
4806 return -1;
4807 };
4808 break;
4809 case VDEF_MAXIMUM:
4810 case VDEF_AVERAGE:
4811 case VDEF_STDEV:
4812 case VDEF_MINIMUM:
4813 case VDEF_TOTAL:
4814 case VDEF_FIRST:
4815 case VDEF_LAST:
4816 case VDEF_LSLSLOPE:
4817 case VDEF_LSLINT:
4818 case VDEF_LSLCORREL:
4819 if (isnan(param)) {
4820 gdes->vf.param = DNAN;
4821 gdes->vf.val = DNAN;
4822 gdes->vf.when = 0;
4823 } else {
4824 rrd_set_error
4825 ("Function '%s' needs no parameter in VDEF '%s'\n",
4826 func, gdes->vname);
4827 return -1;
4828 };
4829 break;
4830 };
4831 return 0;
4832 }
4835 int vdef_calc(
4836 image_desc_t *im,
4837 int gdi)
4838 {
4839 graph_desc_t *src, *dst;
4840 rrd_value_t *data;
4841 long step, steps;
4843 dst = &im->gdes[gdi];
4844 src = &im->gdes[dst->vidx];
4845 data = src->data + src->ds;
4847 steps = (src->end - src->start) / src->step;
4848 #if 0
4849 printf
4850 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4851 src->start, src->end, steps);
4852 #endif
4853 switch (dst->vf.op) {
4854 case VDEF_PERCENT:{
4855 rrd_value_t *array;
4856 int field;
4857 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4858 rrd_set_error("malloc VDEV_PERCENT");
4859 return -1;
4860 }
4861 for (step = 0; step < steps; step++) {
4862 array[step] = data[step * src->ds_cnt];
4863 }
4864 qsort(array, step, sizeof(double), vdef_percent_compar);
4865 field = (steps - 1) * dst->vf.param / 100;
4866 dst->vf.val = array[field];
4867 dst->vf.when = 0; /* no time component */
4868 free(array);
4869 #if 0
4870 for (step = 0; step < steps; step++)
4871 printf("DEBUG: %3li:%10.2f %c\n",
4872 step, array[step], step == field ? '*' : ' ');
4873 #endif
4874 }
4875 break;
4876 case VDEF_PERCENTNAN:{
4877 rrd_value_t *array;
4878 int field;
4879 /* count number of "valid" values */
4880 int nancount=0;
4881 for (step = 0; step < steps; step++) {
4882 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4883 }
4884 /* and allocate it */
4885 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4886 rrd_set_error("malloc VDEV_PERCENT");
4887 return -1;
4888 }
4889 /* and fill it in */
4890 field=0;
4891 for (step = 0; step < steps; step++) {
4892 if (!isnan(data[step * src->ds_cnt])) {
4893 array[field] = data[step * src->ds_cnt];
4894 field++;
4895 }
4896 }
4897 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4898 field = (nancount - 1) * dst->vf.param / 100;
4899 dst->vf.val = array[field];
4900 dst->vf.when = 0; /* no time component */
4901 free(array);
4902 }
4903 break;
4904 case VDEF_MAXIMUM:
4905 step = 0;
4906 while (step != steps && isnan(data[step * src->ds_cnt]))
4907 step++;
4908 if (step == steps) {
4909 dst->vf.val = DNAN;
4910 dst->vf.when = 0;
4911 } else {
4912 dst->vf.val = data[step * src->ds_cnt];
4913 dst->vf.when = src->start + (step + 1) * src->step;
4914 }
4915 while (step != steps) {
4916 if (finite(data[step * src->ds_cnt])) {
4917 if (data[step * src->ds_cnt] > dst->vf.val) {
4918 dst->vf.val = data[step * src->ds_cnt];
4919 dst->vf.when = src->start + (step + 1) * src->step;
4920 }
4921 }
4922 step++;
4923 }
4924 break;
4925 case VDEF_TOTAL:
4926 case VDEF_STDEV:
4927 case VDEF_AVERAGE:{
4928 int cnt = 0;
4929 double sum = 0.0;
4930 double average = 0.0;
4932 for (step = 0; step < steps; step++) {
4933 if (finite(data[step * src->ds_cnt])) {
4934 sum += data[step * src->ds_cnt];
4935 cnt++;
4936 };
4937 }
4938 if (cnt) {
4939 if (dst->vf.op == VDEF_TOTAL) {
4940 dst->vf.val = sum * src->step;
4941 dst->vf.when = 0; /* no time component */
4942 } else if (dst->vf.op == VDEF_AVERAGE) {
4943 dst->vf.val = sum / cnt;
4944 dst->vf.when = 0; /* no time component */
4945 } else {
4946 average = sum / cnt;
4947 sum = 0.0;
4948 for (step = 0; step < steps; step++) {
4949 if (finite(data[step * src->ds_cnt])) {
4950 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4951 };
4952 }
4953 dst->vf.val = pow(sum / cnt, 0.5);
4954 dst->vf.when = 0; /* no time component */
4955 };
4956 } else {
4957 dst->vf.val = DNAN;
4958 dst->vf.when = 0;
4959 }
4960 }
4961 break;
4962 case VDEF_MINIMUM:
4963 step = 0;
4964 while (step != steps && isnan(data[step * src->ds_cnt]))
4965 step++;
4966 if (step == steps) {
4967 dst->vf.val = DNAN;
4968 dst->vf.when = 0;
4969 } else {
4970 dst->vf.val = data[step * src->ds_cnt];
4971 dst->vf.when = src->start + (step + 1) * src->step;
4972 }
4973 while (step != steps) {
4974 if (finite(data[step * src->ds_cnt])) {
4975 if (data[step * src->ds_cnt] < dst->vf.val) {
4976 dst->vf.val = data[step * src->ds_cnt];
4977 dst->vf.when = src->start + (step + 1) * src->step;
4978 }
4979 }
4980 step++;
4981 }
4982 break;
4983 case VDEF_FIRST:
4984 /* The time value returned here is one step before the
4985 * actual time value. This is the start of the first
4986 * non-NaN interval.
4987 */
4988 step = 0;
4989 while (step != steps && isnan(data[step * src->ds_cnt]))
4990 step++;
4991 if (step == steps) { /* all entries were NaN */
4992 dst->vf.val = DNAN;
4993 dst->vf.when = 0;
4994 } else {
4995 dst->vf.val = data[step * src->ds_cnt];
4996 dst->vf.when = src->start + step * src->step;
4997 }
4998 break;
4999 case VDEF_LAST:
5000 /* The time value returned here is the
5001 * actual time value. This is the end of the last
5002 * non-NaN interval.
5003 */
5004 step = steps - 1;
5005 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5006 step--;
5007 if (step < 0) { /* all entries were NaN */
5008 dst->vf.val = DNAN;
5009 dst->vf.when = 0;
5010 } else {
5011 dst->vf.val = data[step * src->ds_cnt];
5012 dst->vf.when = src->start + (step + 1) * src->step;
5013 }
5014 break;
5015 case VDEF_LSLSLOPE:
5016 case VDEF_LSLINT:
5017 case VDEF_LSLCORREL:{
5018 /* Bestfit line by linear least squares method */
5020 int cnt = 0;
5021 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5023 SUMx = 0;
5024 SUMy = 0;
5025 SUMxy = 0;
5026 SUMxx = 0;
5027 SUMyy = 0;
5028 for (step = 0; step < steps; step++) {
5029 if (finite(data[step * src->ds_cnt])) {
5030 cnt++;
5031 SUMx += step;
5032 SUMxx += step * step;
5033 SUMxy += step * data[step * src->ds_cnt];
5034 SUMy += data[step * src->ds_cnt];
5035 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5036 };
5037 }
5039 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5040 y_intercept = (SUMy - slope * SUMx) / cnt;
5041 correl =
5042 (SUMxy -
5043 (SUMx * SUMy) / cnt) /
5044 sqrt((SUMxx -
5045 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5046 if (cnt) {
5047 if (dst->vf.op == VDEF_LSLSLOPE) {
5048 dst->vf.val = slope;
5049 dst->vf.when = 0;
5050 } else if (dst->vf.op == VDEF_LSLINT) {
5051 dst->vf.val = y_intercept;
5052 dst->vf.when = 0;
5053 } else if (dst->vf.op == VDEF_LSLCORREL) {
5054 dst->vf.val = correl;
5055 dst->vf.when = 0;
5056 };
5057 } else {
5058 dst->vf.val = DNAN;
5059 dst->vf.when = 0;
5060 }
5061 }
5062 break;
5063 }
5064 return 0;
5065 }
5067 /* NaN < -INF < finite_values < INF */
5068 int vdef_percent_compar(
5069 const void
5070 *a,
5071 const void
5072 *b)
5073 {
5074 /* Equality is not returned; this doesn't hurt except
5075 * (maybe) for a little performance.
5076 */
5078 /* First catch NaN values. They are smallest */
5079 if (isnan(*(double *) a))
5080 return -1;
5081 if (isnan(*(double *) b))
5082 return 1;
5083 /* NaN doesn't reach this part so INF and -INF are extremes.
5084 * The sign from isinf() is compatible with the sign we return
5085 */
5086 if (isinf(*(double *) a))
5087 return isinf(*(double *) a);
5088 if (isinf(*(double *) b))
5089 return isinf(*(double *) b);
5090 /* If we reach this, both values must be finite */
5091 if (*(double *) a < *(double *) b)
5092 return -1;
5093 else
5094 return 1;
5095 }
5097 void grinfo_push(
5098 image_desc_t *im,
5099 char *key,
5100 rrd_info_type_t type,
5101 rrd_infoval_t value)
5102 {
5103 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5104 if (im->grinfo == NULL) {
5105 im->grinfo = im->grinfo_current;
5106 }
5107 }