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