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