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