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