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