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