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