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