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