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