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