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