1 /****************************************************************************
2 * RRDtool 1.3.5 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 (enum gf_en)(-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 (enum gfx_if_en)(-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 (enum tmt_en)(-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 (enum grc_en)(-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 (enum text_prop_en)(-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 = CAIRO_STATUS_SUCCESS;
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((double)(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 (long*)(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 = (rrd_value_t*)(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 = (rrd_value_t*)(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 = (char*)(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 double leg_x = border;
1625 int leg_y = im->yimg;
1626 int leg_y_prev = im->yimg;
1627 int leg_cc;
1628 double glue = 0;
1629 int i, ii, mark = 0;
1630 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1631 int *legspace;
1632 char *tab;
1634 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1635 if ((legspace = (int*)(malloc(im->gdes_c * sizeof(int)))) == NULL) {
1636 rrd_set_error("malloc for legspace");
1637 return -1;
1638 }
1640 for (i = 0; i < im->gdes_c; i++) {
1641 char prt_fctn; /*special printfunctions */
1642 fill_last = fill;
1643 /* hide legends for rules which are not displayed */
1644 if (im->gdes[i].gf == GF_TEXTALIGN) {
1645 default_txtalign = im->gdes[i].txtalign;
1646 }
1648 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1649 if (im->gdes[i].gf == GF_HRULE
1650 && (im->gdes[i].yrule <
1651 im->minval || im->gdes[i].yrule > im->maxval))
1652 im->gdes[i].legend[0] = '\0';
1653 if (im->gdes[i].gf == GF_VRULE
1654 && (im->gdes[i].xrule <
1655 im->start || im->gdes[i].xrule > im->end))
1656 im->gdes[i].legend[0] = '\0';
1657 }
1659 /* turn \\t into tab */
1660 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1661 memmove(tab, tab + 1, strlen(tab));
1662 tab[0] = (char) 9;
1663 }
1664 leg_cc = strlen(im->gdes[i].legend);
1665 /* is there a controle code at the end of the legend string ? */
1666 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1667 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1668 leg_cc -= 2;
1669 im->gdes[i].legend[leg_cc] = '\0';
1670 } else {
1671 prt_fctn = '\0';
1672 }
1673 /* only valid control codes */
1674 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1675 prt_fctn != 'r' &&
1676 prt_fctn != 'j' &&
1677 prt_fctn != 'c' &&
1678 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1679 free(legspace);
1680 rrd_set_error
1681 ("Unknown control code at the end of '%s\\%c'",
1682 im->gdes[i].legend, prt_fctn);
1683 return -1;
1684 }
1685 /* \n -> \l */
1686 if (prt_fctn == 'n') {
1687 prt_fctn = 'l';
1688 }
1690 /* remove exess space from the end of the legend for \g */
1691 while (prt_fctn == 'g' &&
1692 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1693 leg_cc--;
1694 im->gdes[i].legend[leg_cc] = '\0';
1695 }
1697 if (leg_cc != 0) {
1699 /* no interleg space if string ends in \g */
1700 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1701 if (fill > 0) {
1702 fill += legspace[i];
1703 }
1704 fill +=
1705 gfx_get_text_width(im,
1706 fill + border,
1707 im->
1708 text_prop
1709 [TEXT_PROP_LEGEND].
1710 font_desc,
1711 im->tabwidth, im->gdes[i].legend);
1712 leg_c++;
1713 } else {
1714 legspace[i] = 0;
1715 }
1716 /* who said there was a special tag ... ? */
1717 if (prt_fctn == 'g') {
1718 prt_fctn = '\0';
1719 }
1721 if (prt_fctn == '\0') {
1722 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1723 /* just one legend item is left right or center */
1724 switch (default_txtalign) {
1725 case TXA_RIGHT:
1726 prt_fctn = 'r';
1727 break;
1728 case TXA_CENTER:
1729 prt_fctn = 'c';
1730 break;
1731 case TXA_JUSTIFIED:
1732 prt_fctn = 'j';
1733 break;
1734 default:
1735 prt_fctn = 'l';
1736 break;
1737 }
1738 }
1739 /* is it time to place the legends ? */
1740 if (fill > im->ximg - 2 * border) {
1741 if (leg_c > 1) {
1742 /* go back one */
1743 i--;
1744 fill = fill_last;
1745 leg_c--;
1746 }
1747 }
1748 if (leg_c == 1 && prt_fctn == 'j') {
1749 prt_fctn = 'l';
1750 }
1751 }
1754 if (prt_fctn != '\0') {
1755 leg_x = border;
1756 if (leg_c >= 2 && prt_fctn == 'j') {
1757 glue = (double)(im->ximg - fill - 2 * border) / (double)(leg_c - 1);
1758 } else {
1759 glue = 0;
1760 }
1761 if (prt_fctn == 'c')
1762 leg_x = (double)(im->ximg - fill) / 2.0;
1763 if (prt_fctn == 'r')
1764 leg_x = im->ximg - fill - border;
1765 for (ii = mark; ii <= i; ii++) {
1766 if (im->gdes[ii].legend[0] == '\0')
1767 continue; /* skip empty legends */
1768 im->gdes[ii].leg_x = leg_x;
1769 im->gdes[ii].leg_y = leg_y;
1770 leg_x +=
1771 (double)gfx_get_text_width(im, leg_x,
1772 im->
1773 text_prop
1774 [TEXT_PROP_LEGEND].
1775 font_desc,
1776 im->tabwidth, im->gdes[ii].legend)
1777 +(double)legspace[ii]
1778 + glue;
1779 }
1780 leg_y_prev = leg_y;
1781 if (leg_x > border || prt_fctn == 's')
1782 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1783 if (prt_fctn == 's')
1784 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1785 fill = 0;
1786 leg_c = 0;
1787 mark = ii;
1788 }
1789 }
1791 if (im->extra_flags & FULL_SIZE_MODE) {
1792 /* now for some backpaddeling. We have to shift up all the
1793 legend items into the graph and tell the caller about the
1794 space we used up. */
1795 long shift_up = leg_y - im->yimg - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 + border * 0.7;
1796 for (i = 0; i < im->gdes_c; i++) {
1797 im->gdes[i].leg_y -= shift_up;
1798 }
1799 im->yorigin = im->yorigin - leg_y + im->yimg - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 - border;
1800 *gY = im->yorigin;
1801 } else {
1802 im->yimg =
1803 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1804 border * 0.6;
1805 }
1806 free(legspace);
1807 }
1808 return 0;
1809 }
1811 /* create a grid on the graph. it determines what to do
1812 from the values of xsize, start and end */
1814 /* the xaxis labels are determined from the number of seconds per pixel
1815 in the requested graph */
1817 int calc_horizontal_grid(
1818 image_desc_t
1819 *im)
1820 {
1821 double range;
1822 double scaledrange;
1823 int pixel, i;
1824 int gridind = 0;
1825 int decimals, fractionals;
1827 im->ygrid_scale.labfact = 2;
1828 range = im->maxval - im->minval;
1829 scaledrange = range / im->magfact;
1830 /* does the scale of this graph make it impossible to put lines
1831 on it? If so, give up. */
1832 if (isnan(scaledrange)) {
1833 return 0;
1834 }
1836 /* find grid spaceing */
1837 pixel = 1;
1838 if (isnan(im->ygridstep)) {
1839 if (im->extra_flags & ALTYGRID) {
1840 /* find the value with max number of digits. Get number of digits */
1841 decimals =
1842 ceil(log10
1843 (max(fabs(im->maxval), fabs(im->minval)) *
1844 im->viewfactor / im->magfact));
1845 if (decimals <= 0) /* everything is small. make place for zero */
1846 decimals = 1;
1847 im->ygrid_scale.gridstep =
1848 pow((double) 10,
1849 floor(log10(range * im->viewfactor / im->magfact))) /
1850 im->viewfactor * im->magfact;
1851 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1852 im->ygrid_scale.gridstep = 0.1;
1853 /* should have at least 5 lines but no more then 15 */
1854 if (range / im->ygrid_scale.gridstep < 5
1855 && im->ygrid_scale.gridstep >= 30)
1856 im->ygrid_scale.gridstep /= 10;
1857 if (range / im->ygrid_scale.gridstep > 15)
1858 im->ygrid_scale.gridstep *= 10;
1859 if (range / im->ygrid_scale.gridstep > 5) {
1860 im->ygrid_scale.labfact = 1;
1861 if (range / im->ygrid_scale.gridstep > 8
1862 || im->ygrid_scale.gridstep <
1863 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1864 im->ygrid_scale.labfact = 2;
1865 } else {
1866 im->ygrid_scale.gridstep /= 5;
1867 im->ygrid_scale.labfact = 5;
1868 }
1869 fractionals =
1870 floor(log10
1871 (im->ygrid_scale.gridstep *
1872 (double) im->ygrid_scale.labfact * im->viewfactor /
1873 im->magfact));
1874 if (fractionals < 0) { /* small amplitude. */
1875 int len = decimals - fractionals + 1;
1877 if (im->unitslength < len + 2)
1878 im->unitslength = len + 2;
1879 sprintf(im->ygrid_scale.labfmt,
1880 "%%%d.%df%s", len,
1881 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1882 } else {
1883 int len = decimals + 1;
1885 if (im->unitslength < len + 2)
1886 im->unitslength = len + 2;
1887 sprintf(im->ygrid_scale.labfmt,
1888 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1889 }
1890 } else { /* classic rrd grid */
1891 for (i = 0; ylab[i].grid > 0; i++) {
1892 pixel = im->ysize / (scaledrange / ylab[i].grid);
1893 gridind = i;
1894 if (pixel >= 5)
1895 break;
1896 }
1898 for (i = 0; i < 4; i++) {
1899 if (pixel * ylab[gridind].lfac[i] >=
1900 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1901 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1902 break;
1903 }
1904 }
1906 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1907 }
1908 } else {
1909 im->ygrid_scale.gridstep = im->ygridstep;
1910 im->ygrid_scale.labfact = im->ylabfact;
1911 }
1912 return 1;
1913 }
1915 int draw_horizontal_grid(
1916 image_desc_t
1917 *im)
1918 {
1919 int i;
1920 double scaledstep;
1921 char graph_label[100];
1922 int nlabels = 0;
1923 double X0 = im->xorigin;
1924 double X1 = im->xorigin + im->xsize;
1925 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1926 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1927 double MaxY;
1928 double second_axis_magfact = 0;
1929 char *second_axis_symb = "";
1931 scaledstep =
1932 im->ygrid_scale.gridstep /
1933 (double) im->magfact * (double) im->viewfactor;
1934 MaxY = scaledstep * (double) egrid;
1935 for (i = sgrid; i <= egrid; i++) {
1936 double Y0 = ytr(im,
1937 im->ygrid_scale.gridstep * i);
1938 double YN = ytr(im,
1939 im->ygrid_scale.gridstep * (i + 1));
1941 if (floor(Y0 + 0.5) >=
1942 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1943 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1944 with the chosen settings. Add a label if required by settings, or if
1945 there is only one label so far and the next grid line is out of bounds. */
1946 if (i % im->ygrid_scale.labfact == 0
1947 || (nlabels == 1
1948 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1949 if (im->symbol == ' ') {
1950 if (im->extra_flags & ALTYGRID) {
1951 sprintf(graph_label,
1952 im->ygrid_scale.labfmt,
1953 scaledstep * (double) i);
1954 } else {
1955 if (MaxY < 10) {
1956 sprintf(graph_label, "%4.1f",
1957 scaledstep * (double) i);
1958 } else {
1959 sprintf(graph_label, "%4.0f",
1960 scaledstep * (double) i);
1961 }
1962 }
1963 } else {
1964 char sisym = (i == 0 ? ' ' : im->symbol);
1966 if (im->extra_flags & ALTYGRID) {
1967 sprintf(graph_label,
1968 im->ygrid_scale.labfmt,
1969 scaledstep * (double) i, sisym);
1970 } else {
1971 if (MaxY < 10) {
1972 sprintf(graph_label, "%4.1f %c",
1973 scaledstep * (double) i, sisym);
1974 } else {
1975 sprintf(graph_label, "%4.0f %c",
1976 scaledstep * (double) i, sisym);
1977 }
1978 }
1979 }
1980 nlabels++;
1981 if (im->second_axis_scale != 0){
1982 char graph_label_right[100];
1983 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
1984 if (im->second_axis_format[0] == '\0'){
1985 if (!second_axis_magfact){
1986 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
1987 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
1988 }
1989 sval /= second_axis_magfact;
1991 if(MaxY < 10) {
1992 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
1993 } else {
1994 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
1995 }
1996 }
1997 else {
1998 sprintf(graph_label_right,im->second_axis_format,sval);
1999 }
2000 gfx_text ( im,
2001 X1+7, Y0,
2002 im->graph_col[GRC_FONT],
2003 im->text_prop[TEXT_PROP_AXIS].font_desc,
2004 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2005 graph_label_right );
2006 }
2008 gfx_text(im,
2009 X0 -
2010 im->
2011 text_prop[TEXT_PROP_AXIS].
2012 size, Y0,
2013 im->graph_col[GRC_FONT],
2014 im->
2015 text_prop[TEXT_PROP_AXIS].
2016 font_desc,
2017 im->tabwidth, 0.0,
2018 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2019 gfx_line(im, X0 - 2, Y0, X0, Y0,
2020 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2021 gfx_line(im, X1, Y0, X1 + 2, Y0,
2022 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2023 gfx_dashed_line(im, X0 - 2, Y0,
2024 X1 + 2, Y0,
2025 MGRIDWIDTH,
2026 im->
2027 graph_col
2028 [GRC_MGRID],
2029 im->grid_dash_on, im->grid_dash_off);
2030 } else if (!(im->extra_flags & NOMINOR)) {
2031 gfx_line(im,
2032 X0 - 2, Y0,
2033 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2034 gfx_line(im, X1, Y0, X1 + 2, Y0,
2035 GRIDWIDTH, im->graph_col[GRC_GRID]);
2036 gfx_dashed_line(im, X0 - 1, Y0,
2037 X1 + 1, Y0,
2038 GRIDWIDTH,
2039 im->
2040 graph_col[GRC_GRID],
2041 im->grid_dash_on, im->grid_dash_off);
2042 }
2043 }
2044 }
2045 return 1;
2046 }
2048 /* this is frexp for base 10 */
2049 double frexp10(
2050 double,
2051 double *);
2052 double frexp10(
2053 double x,
2054 double *e)
2055 {
2056 double mnt;
2057 int iexp;
2059 iexp = floor(log((double)(fabs(x))) / log(10.0));
2060 mnt = x / pow(10.0, iexp);
2061 if (mnt >= 10.0) {
2062 iexp++;
2063 mnt = x / pow(10.0, iexp);
2064 }
2065 *e = iexp;
2066 return mnt;
2067 }
2070 /* logaritmic horizontal grid */
2071 int horizontal_log_grid(
2072 image_desc_t
2073 *im)
2074 {
2075 double yloglab[][10] = {
2076 {
2077 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2078 0.0, 0.0, 0.0}, {
2079 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2080 0.0, 0.0, 0.0}, {
2081 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2082 0.0, 0.0, 0.0}, {
2083 1.0, 2.0, 4.0,
2084 6.0, 8.0, 10.,
2085 0.0,
2086 0.0, 0.0, 0.0}, {
2087 1.0,
2088 2.0,
2089 3.0,
2090 4.0,
2091 5.0,
2092 6.0,
2093 7.0,
2094 8.0,
2095 9.0,
2096 10.},
2097 {
2098 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2099 };
2100 int i, j, val_exp, min_exp;
2101 double nex; /* number of decades in data */
2102 double logscale; /* scale in logarithmic space */
2103 int exfrac = 1; /* decade spacing */
2104 int mid = -1; /* row in yloglab for major grid */
2105 double mspac; /* smallest major grid spacing (pixels) */
2106 int flab; /* first value in yloglab to use */
2107 double value, tmp, pre_value;
2108 double X0, X1, Y0;
2109 char graph_label[100];
2111 nex = log10(im->maxval / im->minval);
2112 logscale = im->ysize / nex;
2113 /* major spacing for data with high dynamic range */
2114 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2115 if (exfrac == 1)
2116 exfrac = 3;
2117 else
2118 exfrac += 3;
2119 }
2121 /* major spacing for less dynamic data */
2122 do {
2123 /* search best row in yloglab */
2124 mid++;
2125 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2126 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2127 }
2128 while (mspac >
2129 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2130 if (mid)
2131 mid--;
2132 /* find first value in yloglab */
2133 for (flab = 0;
2134 yloglab[mid][flab] < 10
2135 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2136 if (yloglab[mid][flab] == 10.0) {
2137 tmp += 1.0;
2138 flab = 0;
2139 }
2140 val_exp = tmp;
2141 if (val_exp % exfrac)
2142 val_exp += abs(-val_exp % exfrac);
2143 X0 = im->xorigin;
2144 X1 = im->xorigin + im->xsize;
2145 /* draw grid */
2146 pre_value = DNAN;
2147 while (1) {
2149 value = yloglab[mid][flab] * pow(10.0, val_exp);
2150 if (AlmostEqual2sComplement(value, pre_value, 4))
2151 break; /* it seems we are not converging */
2152 pre_value = value;
2153 Y0 = ytr(im, value);
2154 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2155 break;
2156 /* major grid line */
2157 gfx_line(im,
2158 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2159 gfx_line(im, X1, Y0, X1 + 2, Y0,
2160 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2161 gfx_dashed_line(im, X0 - 2, Y0,
2162 X1 + 2, Y0,
2163 MGRIDWIDTH,
2164 im->
2165 graph_col
2166 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2167 /* label */
2168 if (im->extra_flags & FORCE_UNITS_SI) {
2169 int scale;
2170 double pvalue;
2171 char symbol;
2173 scale = floor(val_exp / 3.0);
2174 if (value >= 1.0)
2175 pvalue = pow(10.0, val_exp % 3);
2176 else
2177 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2178 pvalue *= yloglab[mid][flab];
2179 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2180 && ((scale + si_symbcenter) >= 0))
2181 symbol = si_symbol[scale + si_symbcenter];
2182 else
2183 symbol = '?';
2184 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2185 } else {
2186 sprintf(graph_label, "%3.0e", value);
2187 }
2188 if (im->second_axis_scale != 0){
2189 char graph_label_right[100];
2190 double sval = value*im->second_axis_scale+im->second_axis_shift;
2191 if (im->second_axis_format[0] == '\0'){
2192 if (im->extra_flags & FORCE_UNITS_SI) {
2193 double mfac = 1;
2194 char *symb = "";
2195 auto_scale(im,&sval,&symb,&mfac);
2196 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2197 }
2198 else {
2199 sprintf(graph_label_right,"%3.0e", sval);
2200 }
2201 }
2202 else {
2203 sprintf(graph_label_right,im->second_axis_format,sval);
2204 }
2206 gfx_text ( im,
2207 X1+7, Y0,
2208 im->graph_col[GRC_FONT],
2209 im->text_prop[TEXT_PROP_AXIS].font_desc,
2210 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2211 graph_label_right );
2212 }
2214 gfx_text(im,
2215 X0 -
2216 im->
2217 text_prop[TEXT_PROP_AXIS].
2218 size, Y0,
2219 im->graph_col[GRC_FONT],
2220 im->
2221 text_prop[TEXT_PROP_AXIS].
2222 font_desc,
2223 im->tabwidth, 0.0,
2224 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2225 /* minor grid */
2226 if (mid < 4 && exfrac == 1) {
2227 /* find first and last minor line behind current major line
2228 * i is the first line and j tha last */
2229 if (flab == 0) {
2230 min_exp = val_exp - 1;
2231 for (i = 1; yloglab[mid][i] < 10.0; i++);
2232 i = yloglab[mid][i - 1] + 1;
2233 j = 10;
2234 } else {
2235 min_exp = val_exp;
2236 i = yloglab[mid][flab - 1] + 1;
2237 j = yloglab[mid][flab];
2238 }
2240 /* draw minor lines below current major line */
2241 for (; i < j; i++) {
2243 value = i * pow(10.0, min_exp);
2244 if (value < im->minval)
2245 continue;
2246 Y0 = ytr(im, value);
2247 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2248 break;
2249 /* draw lines */
2250 gfx_line(im,
2251 X0 - 2, Y0,
2252 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2253 gfx_line(im, X1, Y0, X1 + 2, Y0,
2254 GRIDWIDTH, im->graph_col[GRC_GRID]);
2255 gfx_dashed_line(im, X0 - 1, Y0,
2256 X1 + 1, Y0,
2257 GRIDWIDTH,
2258 im->
2259 graph_col[GRC_GRID],
2260 im->grid_dash_on, im->grid_dash_off);
2261 }
2262 } else if (exfrac > 1) {
2263 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2264 value = pow(10.0, i);
2265 if (value < im->minval)
2266 continue;
2267 Y0 = ytr(im, value);
2268 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2269 break;
2270 /* draw lines */
2271 gfx_line(im,
2272 X0 - 2, Y0,
2273 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2274 gfx_line(im, X1, Y0, X1 + 2, Y0,
2275 GRIDWIDTH, im->graph_col[GRC_GRID]);
2276 gfx_dashed_line(im, X0 - 1, Y0,
2277 X1 + 1, Y0,
2278 GRIDWIDTH,
2279 im->
2280 graph_col[GRC_GRID],
2281 im->grid_dash_on, im->grid_dash_off);
2282 }
2283 }
2285 /* next decade */
2286 if (yloglab[mid][++flab] == 10.0) {
2287 flab = 0;
2288 val_exp += exfrac;
2289 }
2290 }
2292 /* draw minor lines after highest major line */
2293 if (mid < 4 && exfrac == 1) {
2294 /* find first and last minor line below current major line
2295 * i is the first line and j tha last */
2296 if (flab == 0) {
2297 min_exp = val_exp - 1;
2298 for (i = 1; yloglab[mid][i] < 10.0; i++);
2299 i = yloglab[mid][i - 1] + 1;
2300 j = 10;
2301 } else {
2302 min_exp = val_exp;
2303 i = yloglab[mid][flab - 1] + 1;
2304 j = yloglab[mid][flab];
2305 }
2307 /* draw minor lines below current major line */
2308 for (; i < j; i++) {
2310 value = i * pow(10.0, min_exp);
2311 if (value < im->minval)
2312 continue;
2313 Y0 = ytr(im, value);
2314 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2315 break;
2316 /* draw lines */
2317 gfx_line(im,
2318 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2319 gfx_line(im, X1, Y0, X1 + 2, Y0,
2320 GRIDWIDTH, im->graph_col[GRC_GRID]);
2321 gfx_dashed_line(im, X0 - 1, Y0,
2322 X1 + 1, Y0,
2323 GRIDWIDTH,
2324 im->
2325 graph_col[GRC_GRID],
2326 im->grid_dash_on, im->grid_dash_off);
2327 }
2328 }
2329 /* fancy minor gridlines */
2330 else if (exfrac > 1) {
2331 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2332 value = pow(10.0, i);
2333 if (value < im->minval)
2334 continue;
2335 Y0 = ytr(im, value);
2336 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2337 break;
2338 /* draw lines */
2339 gfx_line(im,
2340 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2341 gfx_line(im, X1, Y0, X1 + 2, Y0,
2342 GRIDWIDTH, im->graph_col[GRC_GRID]);
2343 gfx_dashed_line(im, X0 - 1, Y0,
2344 X1 + 1, Y0,
2345 GRIDWIDTH,
2346 im->
2347 graph_col[GRC_GRID],
2348 im->grid_dash_on, im->grid_dash_off);
2349 }
2350 }
2352 return 1;
2353 }
2356 void vertical_grid(
2357 image_desc_t *im)
2358 {
2359 int xlab_sel; /* which sort of label and grid ? */
2360 time_t ti, tilab, timajor;
2361 long factor;
2362 char graph_label[100];
2363 double X0, Y0, Y1; /* points for filled graph and more */
2364 struct tm tm;
2366 /* the type of time grid is determined by finding
2367 the number of seconds per pixel in the graph */
2368 if (im->xlab_user.minsec == -1) {
2369 factor = (im->end - im->start) / im->xsize;
2370 xlab_sel = 0;
2371 while (xlab[xlab_sel + 1].minsec !=
2372 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2373 xlab_sel++;
2374 } /* pick the last one */
2375 while (xlab[xlab_sel - 1].minsec ==
2376 xlab[xlab_sel].minsec
2377 && xlab[xlab_sel].length > (im->end - im->start)) {
2378 xlab_sel--;
2379 } /* go back to the smallest size */
2380 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2381 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2382 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2383 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2384 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2385 im->xlab_user.labst = xlab[xlab_sel].labst;
2386 im->xlab_user.precis = xlab[xlab_sel].precis;
2387 im->xlab_user.stst = xlab[xlab_sel].stst;
2388 }
2390 /* y coords are the same for every line ... */
2391 Y0 = im->yorigin;
2392 Y1 = im->yorigin - im->ysize;
2393 /* paint the minor grid */
2394 if (!(im->extra_flags & NOMINOR)) {
2395 for (ti = find_first_time(im->start,
2396 im->
2397 xlab_user.
2398 gridtm,
2399 im->
2400 xlab_user.
2401 gridst),
2402 timajor =
2403 find_first_time(im->start,
2404 im->xlab_user.
2405 mgridtm,
2406 im->xlab_user.
2407 mgridst);
2408 ti < im->end;
2409 ti =
2410 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2411 ) {
2412 /* are we inside the graph ? */
2413 if (ti < im->start || ti > im->end)
2414 continue;
2415 while (timajor < ti) {
2416 timajor = find_next_time(timajor,
2417 im->
2418 xlab_user.
2419 mgridtm, im->xlab_user.mgridst);
2420 }
2421 if (ti == timajor)
2422 continue; /* skip as falls on major grid line */
2423 X0 = xtr(im, ti);
2424 gfx_line(im, X0, Y1 - 2, X0, Y1,
2425 GRIDWIDTH, im->graph_col[GRC_GRID]);
2426 gfx_line(im, X0, Y0, X0, Y0 + 2,
2427 GRIDWIDTH, im->graph_col[GRC_GRID]);
2428 gfx_dashed_line(im, X0, Y0 + 1, X0,
2429 Y1 - 1, GRIDWIDTH,
2430 im->
2431 graph_col[GRC_GRID],
2432 im->grid_dash_on, im->grid_dash_off);
2433 }
2434 }
2436 /* paint the major grid */
2437 for (ti = find_first_time(im->start,
2438 im->
2439 xlab_user.
2440 mgridtm,
2441 im->
2442 xlab_user.
2443 mgridst);
2444 ti < im->end;
2445 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2446 ) {
2447 /* are we inside the graph ? */
2448 if (ti < im->start || ti > im->end)
2449 continue;
2450 X0 = xtr(im, ti);
2451 gfx_line(im, X0, Y1 - 2, X0, Y1,
2452 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2453 gfx_line(im, X0, Y0, X0, Y0 + 3,
2454 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2455 gfx_dashed_line(im, X0, Y0 + 3, X0,
2456 Y1 - 2, MGRIDWIDTH,
2457 im->
2458 graph_col
2459 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2460 }
2461 /* paint the labels below the graph */
2462 for (ti =
2463 find_first_time(im->start -
2464 im->xlab_user.
2465 precis / 2,
2466 im->xlab_user.
2467 labtm,
2468 im->xlab_user.
2469 labst);
2470 ti <=
2471 im->end -
2472 im->xlab_user.precis / 2;
2473 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2474 ) {
2475 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2476 /* are we inside the graph ? */
2477 if (tilab < im->start || tilab > im->end)
2478 continue;
2479 #if HAVE_STRFTIME
2480 localtime_r(&tilab, &tm);
2481 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2482 #else
2483 # error "your libc has no strftime I guess we'll abort the exercise here."
2484 #endif
2485 gfx_text(im,
2486 xtr(im, tilab),
2487 Y0 + 3,
2488 im->graph_col[GRC_FONT],
2489 im->
2490 text_prop[TEXT_PROP_AXIS].
2491 font_desc,
2492 im->tabwidth, 0.0,
2493 GFX_H_CENTER, GFX_V_TOP, graph_label);
2494 }
2496 }
2499 void axis_paint(
2500 image_desc_t *im)
2501 {
2502 /* draw x and y axis */
2503 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2504 im->xorigin+im->xsize,im->yorigin-im->ysize,
2505 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2507 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2508 im->xorigin+im->xsize,im->yorigin-im->ysize,
2509 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2511 gfx_line(im, im->xorigin - 4,
2512 im->yorigin,
2513 im->xorigin + im->xsize +
2514 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2515 gfx_line(im, im->xorigin,
2516 im->yorigin + 4,
2517 im->xorigin,
2518 im->yorigin - im->ysize -
2519 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2520 /* arrow for X and Y axis direction */
2521 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 */
2522 im->graph_col[GRC_ARROW]);
2523 gfx_close_path(im);
2524 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 */
2525 im->graph_col[GRC_ARROW]);
2526 gfx_close_path(im);
2527 if (im->second_axis_scale != 0){
2528 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2529 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2530 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2531 gfx_new_area ( im,
2532 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2533 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2534 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2535 im->graph_col[GRC_ARROW]);
2536 gfx_close_path(im);
2537 }
2539 }
2541 void grid_paint(
2542 image_desc_t *im)
2543 {
2544 long i;
2545 int res = 0;
2546 double X0, Y0; /* points for filled graph and more */
2547 struct gfx_color_t water_color;
2549 /* draw 3d border */
2550 gfx_new_area(im, 0, im->yimg,
2551 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2552 gfx_add_point(im, im->ximg - 2, 2);
2553 gfx_add_point(im, im->ximg, 0);
2554 gfx_add_point(im, 0, 0);
2555 gfx_close_path(im);
2556 gfx_new_area(im, 2, im->yimg - 2,
2557 im->ximg - 2,
2558 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2559 gfx_add_point(im, im->ximg, 0);
2560 gfx_add_point(im, im->ximg, im->yimg);
2561 gfx_add_point(im, 0, im->yimg);
2562 gfx_close_path(im);
2563 if (im->draw_x_grid == 1)
2564 vertical_grid(im);
2565 if (im->draw_y_grid == 1) {
2566 if (im->logarithmic) {
2567 res = horizontal_log_grid(im);
2568 } else {
2569 res = draw_horizontal_grid(im);
2570 }
2572 /* dont draw horizontal grid if there is no min and max val */
2573 if (!res) {
2574 char *nodata = "No Data found";
2576 gfx_text(im, im->ximg / 2,
2577 (2 * im->yorigin -
2578 im->ysize) / 2,
2579 im->graph_col[GRC_FONT],
2580 im->
2581 text_prop[TEXT_PROP_AXIS].
2582 font_desc,
2583 im->tabwidth, 0.0,
2584 GFX_H_CENTER, GFX_V_CENTER, nodata);
2585 }
2586 }
2588 /* yaxis unit description */
2589 if (im->ylegend[0] != '\0'){
2590 gfx_text(im,
2591 10,
2592 (im->yorigin -
2593 im->ysize / 2),
2594 im->graph_col[GRC_FONT],
2595 im->
2596 text_prop[TEXT_PROP_UNIT].
2597 font_desc,
2598 im->tabwidth,
2599 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2600 }
2601 if (im->second_axis_legend[0] != '\0'){
2602 double Xylabel=gfx_get_text_width(im, 0,
2603 im->text_prop[TEXT_PROP_AXIS].font_desc,
2604 im->tabwidth,
2605 "0") * im->unitslength
2606 + im->text_prop[TEXT_PROP_UNIT].size *2;
2607 gfx_text( im,
2608 im->xorigin+im->xsize+Xylabel+8, (im->yorigin - im->ysize/2),
2609 im->graph_col[GRC_FONT],
2610 im->text_prop[TEXT_PROP_UNIT].font_desc,
2611 im->tabwidth,
2612 RRDGRAPH_YLEGEND_ANGLE,
2613 GFX_H_CENTER, GFX_V_CENTER,
2614 im->second_axis_legend);
2615 }
2617 /* graph title */
2618 gfx_text(im,
2619 im->ximg / 2, 6,
2620 im->graph_col[GRC_FONT],
2621 im->
2622 text_prop[TEXT_PROP_TITLE].
2623 font_desc,
2624 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2625 /* rrdtool 'logo' */
2626 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2627 water_color = im->graph_col[GRC_FONT];
2628 water_color.alpha = 0.3;
2629 gfx_text(im, im->ximg - 4, 5,
2630 water_color,
2631 im->
2632 text_prop[TEXT_PROP_WATERMARK].
2633 font_desc, im->tabwidth,
2634 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2635 }
2636 /* graph watermark */
2637 if (im->watermark[0] != '\0') {
2638 gfx_text(im,
2639 im->ximg / 2, im->yimg - 6,
2640 water_color,
2641 im->
2642 text_prop[TEXT_PROP_WATERMARK].
2643 font_desc, im->tabwidth, 0,
2644 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2645 }
2647 /* graph labels */
2648 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2649 for (i = 0; i < im->gdes_c; i++) {
2650 if (im->gdes[i].legend[0] == '\0')
2651 continue;
2652 /* im->gdes[i].leg_y is the bottom of the legend */
2653 X0 = im->gdes[i].leg_x;
2654 Y0 = im->gdes[i].leg_y;
2655 gfx_text(im, X0, Y0,
2656 im->graph_col[GRC_FONT],
2657 im->
2658 text_prop
2659 [TEXT_PROP_LEGEND].font_desc,
2660 im->tabwidth, 0.0,
2661 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2662 /* The legend for GRAPH items starts with "M " to have
2663 enough space for the box */
2664 if (im->gdes[i].gf != GF_PRINT &&
2665 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2666 double boxH, boxV;
2667 double X1, Y1;
2669 boxH = gfx_get_text_width(im, 0,
2670 im->
2671 text_prop
2672 [TEXT_PROP_LEGEND].
2673 font_desc,
2674 im->tabwidth, "o") * 1.2;
2675 boxV = boxH;
2676 /* shift the box up a bit */
2677 Y0 -= boxV * 0.4;
2678 /* make sure transparent colors show up the same way as in the graph */
2679 gfx_new_area(im,
2680 X0, Y0 - boxV,
2681 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2682 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2683 gfx_close_path(im);
2684 gfx_new_area(im, X0, Y0 - boxV, X0,
2685 Y0, X0 + boxH, Y0, im->gdes[i].col);
2686 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2687 gfx_close_path(im);
2688 cairo_save(im->cr);
2689 cairo_new_path(im->cr);
2690 cairo_set_line_width(im->cr, 1.0);
2691 X1 = X0 + boxH;
2692 Y1 = Y0 - boxV;
2693 gfx_line_fit(im, &X0, &Y0);
2694 gfx_line_fit(im, &X1, &Y1);
2695 cairo_move_to(im->cr, X0, Y0);
2696 cairo_line_to(im->cr, X1, Y0);
2697 cairo_line_to(im->cr, X1, Y1);
2698 cairo_line_to(im->cr, X0, Y1);
2699 cairo_close_path(im->cr);
2700 cairo_set_source_rgba(im->cr,
2701 im->
2702 graph_col
2703 [GRC_FRAME].
2704 red,
2705 im->
2706 graph_col
2707 [GRC_FRAME].
2708 green,
2709 im->
2710 graph_col
2711 [GRC_FRAME].
2712 blue, im->graph_col[GRC_FRAME].alpha);
2713 if (im->gdes[i].dash) {
2714 /* make box borders in legend dashed if the graph is dashed */
2715 double dashes[] = {
2716 3.0
2717 };
2718 cairo_set_dash(im->cr, dashes, 1, 0.0);
2719 }
2720 cairo_stroke(im->cr);
2721 cairo_restore(im->cr);
2722 }
2723 }
2724 }
2725 }
2728 /*****************************************************
2729 * lazy check make sure we rely need to create this graph
2730 *****************************************************/
2732 int lazy_check(
2733 image_desc_t *im)
2734 {
2735 FILE *fd = NULL;
2736 int size = 1;
2737 struct stat imgstat;
2739 if (im->lazy == 0)
2740 return 0; /* no lazy option */
2741 if (strlen(im->graphfile) == 0)
2742 return 0; /* inmemory option */
2743 if (stat(im->graphfile, &imgstat) != 0)
2744 return 0; /* can't stat */
2745 /* one pixel in the existing graph is more then what we would
2746 change here ... */
2747 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2748 return 0;
2749 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2750 return 0; /* the file does not exist */
2751 switch (im->imgformat) {
2752 case IF_PNG:
2753 size = PngSize(fd, &(im->ximg), &(im->yimg));
2754 break;
2755 default:
2756 size = 1;
2757 }
2758 fclose(fd);
2759 return size;
2760 }
2763 int graph_size_location(
2764 image_desc_t
2765 *im,
2766 int elements)
2767 {
2768 /* The actual size of the image to draw is determined from
2769 ** several sources. The size given on the command line is
2770 ** the graph area but we need more as we have to draw labels
2771 ** and other things outside the graph area
2772 */
2774 int Xvertical = 0, Ytitle =
2775 0, Xylabel = 0, Xmain = 0, Ymain =
2776 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2778 if (im->extra_flags & ONLY_GRAPH) {
2779 im->xorigin = 0;
2780 im->ximg = im->xsize;
2781 im->yimg = im->ysize;
2782 im->yorigin = im->ysize;
2783 ytr(im, DNAN);
2784 return 0;
2785 }
2787 /** +---+-----------------------------------+
2788 ** | y |...............graph title.........|
2789 ** | +---+-------------------------------+
2790 ** | a | y | |
2791 ** | x | | |
2792 ** | i | a | |
2793 ** | s | x | main graph area |
2794 ** | | i | |
2795 ** | t | s | |
2796 ** | i | | |
2797 ** | t | l | |
2798 ** | l | b +-------------------------------+
2799 ** | e | l | x axis labels |
2800 ** +---+---+-------------------------------+
2801 ** |....................legends............|
2802 ** +---------------------------------------+
2803 ** | watermark |
2804 ** +---------------------------------------+
2805 */
2807 if (im->ylegend[0] != '\0') {
2808 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2809 }
2811 if (im->title[0] != '\0') {
2812 /* The title is placed "inbetween" two text lines so it
2813 ** automatically has some vertical spacing. The horizontal
2814 ** spacing is added here, on each side.
2815 */
2816 /* if necessary, reduce the font size of the title until it fits the image width */
2817 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2818 }
2820 if (elements) {
2821 if (im->draw_x_grid) {
2822 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2823 }
2824 if (im->draw_y_grid || im->forceleftspace) {
2825 Xylabel =
2826 gfx_get_text_width(im, 0,
2827 im->
2828 text_prop
2829 [TEXT_PROP_AXIS].
2830 font_desc,
2831 im->tabwidth, "0") * im->unitslength;
2832 }
2833 }
2835 if (im->extra_flags & FULL_SIZE_MODE) {
2836 /* The actual size of the image to draw has been determined by the user.
2837 ** The graph area is the space remaining after accounting for the legend,
2838 ** the watermark, the axis labels, and the title.
2839 */
2840 im->xorigin = 0;
2841 im->ximg = im->xsize;
2842 im->yimg = im->ysize;
2843 im->yorigin = im->ysize;
2844 Xmain = im->ximg;
2845 Ymain = im->yimg;
2846 /* Now calculate the total size. Insert some spacing where
2847 desired. im->xorigin and im->yorigin need to correspond
2848 with the lower left corner of the main graph area or, if
2849 this one is not set, the imaginary box surrounding the
2850 pie chart area. */
2851 /* Initial size calculation for the main graph area */
2852 Xmain = im->ximg - Xylabel - 3 * Xspacing;
2854 im->xorigin = Xspacing + Xylabel;
2856 if (Xvertical) { /* unit description */
2857 Xmain -= Xvertical;
2858 im->xorigin += Xvertical;
2859 }
2861 /* adjust space for second axis */
2862 if (im->second_axis_scale != 0){
2863 Xmain -= Xylabel + Xspacing;
2864 }
2865 if (im->extra_flags & NO_RRDTOOL_TAG){
2866 Xmain += Xspacing;
2867 }
2868 if (im->second_axis_legend[0] != '\0' ) {
2869 Xmain -= im->text_prop[TEXT_PROP_UNIT].size * 1.5;
2870 }
2872 im->xsize = Xmain;
2874 xtr(im, 0);
2875 /* The vertical size of the image is known in advance. The main graph area
2876 ** (Ymain) and im->yorigin must be set according to the space requirements
2877 ** of the legend and the axis labels.
2878 */
2879 if (im->extra_flags & NOLEGEND) {
2880 im->yorigin = im->yimg -
2881 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2882 Ymain = im->yorigin;
2883 }
2884 else {
2885 /* Determine where to place the legends onto the image.
2886 ** Set Ymain and adjust im->yorigin to match the space requirements.
2887 */
2888 if (leg_place(im, &Ymain) == -1)
2889 return -1;
2890 }
2893 /* remove title space *or* some padding above the graph from the main graph area */
2894 if (Ytitle) {
2895 Ymain -= Ytitle;
2896 } else {
2897 Ymain -= 1.5 * Yspacing;
2898 }
2900 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2901 if (im->watermark[0] != '\0') {
2902 Ymain -= Ywatermark;
2903 }
2905 im->ysize = Ymain;
2906 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2908 /* The actual size of the image to draw is determined from
2909 ** several sources. The size given on the command line is
2910 ** the graph area but we need more as we have to draw labels
2911 ** and other things outside the graph area.
2912 */
2914 if (im->ylegend[0] != '\0') {
2915 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2916 }
2919 if (im->title[0] != '\0') {
2920 /* The title is placed "inbetween" two text lines so it
2921 ** automatically has some vertical spacing. The horizontal
2922 ** spacing is added here, on each side.
2923 */
2924 /* don't care for the with of the title
2925 Xtitle = gfx_get_text_width(im->canvas, 0,
2926 im->text_prop[TEXT_PROP_TITLE].font_desc,
2927 im->tabwidth,
2928 im->title, 0) + 2*Xspacing; */
2929 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2930 }
2932 if (elements) {
2933 Xmain = im->xsize;
2934 Ymain = im->ysize;
2935 }
2936 /* Now calculate the total size. Insert some spacing where
2937 desired. im->xorigin and im->yorigin need to correspond
2938 with the lower left corner of the main graph area or, if
2939 this one is not set, the imaginary box surrounding the
2940 pie chart area. */
2942 /* The legend width cannot yet be determined, as a result we
2943 ** have problems adjusting the image to it. For now, we just
2944 ** forget about it at all; the legend will have to fit in the
2945 ** size already allocated.
2946 */
2947 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2949 if (im->second_axis_scale != 0){
2950 im->ximg += Xylabel + Xspacing;
2951 }
2952 if (im->extra_flags & NO_RRDTOOL_TAG){
2953 im->ximg -= Xspacing;
2954 }
2956 if (Xmain)
2957 im->ximg += Xspacing;
2958 im->xorigin = Xspacing + Xylabel;
2959 /* the length of the title should not influence with width of the graph
2960 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2961 if (Xvertical) { /* unit description */
2962 im->ximg += Xvertical;
2963 im->xorigin += Xvertical;
2964 }
2965 if (im->second_axis_legend[0] != '\0' ) {
2966 im->ximg += Xvertical;
2967 }
2969 xtr(im, 0);
2970 /* The vertical size is interesting... we need to compare
2971 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2972 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2973 ** in order to start even thinking about Ylegend or Ywatermark.
2974 **
2975 ** Do it in three portions: First calculate the inner part,
2976 ** then do the legend, then adjust the total height of the img,
2977 ** adding space for a watermark if one exists;
2978 */
2979 /* reserve space for main and/or pie */
2980 im->yimg = Ymain + Yxlabel;
2981 im->yorigin = im->yimg - Yxlabel;
2982 /* reserve space for the title *or* some padding above the graph */
2983 if (Ytitle) {
2984 im->yimg += Ytitle;
2985 im->yorigin += Ytitle;
2986 } else {
2987 im->yimg += 1.5 * Yspacing;
2988 im->yorigin += 1.5 * Yspacing;
2989 }
2990 /* reserve space for padding below the graph */
2991 im->yimg += Yspacing;
2992 /* Determine where to place the legends onto the image.
2993 ** Adjust im->yimg to match the space requirements.
2994 */
2995 if (leg_place(im, 0) == -1)
2996 return -1;
2997 if (im->watermark[0] != '\0') {
2998 im->yimg += Ywatermark;
2999 }
3000 }
3002 ytr(im, DNAN);
3003 return 0;
3004 }
3006 static cairo_status_t cairo_output(
3007 void *closure,
3008 const unsigned char
3009 *data,
3010 unsigned int length)
3011 {
3012 image_desc_t *im = (image_desc_t*)(closure);
3014 im->rendered_image =
3015 (unsigned char*)(realloc(im->rendered_image, im->rendered_image_size + length));
3016 if (im->rendered_image == NULL)
3017 return CAIRO_STATUS_WRITE_ERROR;
3018 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3019 im->rendered_image_size += length;
3020 return CAIRO_STATUS_SUCCESS;
3021 }
3023 /* draw that picture thing ... */
3024 int graph_paint(
3025 image_desc_t *im)
3026 {
3027 int i, ii;
3028 int lazy = lazy_check(im);
3029 double areazero = 0.0;
3030 graph_desc_t *lastgdes = NULL;
3031 rrd_infoval_t info;
3033 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3035 /* if we want and can be lazy ... quit now */
3036 if (lazy) {
3037 info.u_cnt = im->ximg;
3038 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3039 info.u_cnt = im->yimg;
3040 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3041 return 0;
3042 }
3043 /* pull the data from the rrd files ... */
3044 if (data_fetch(im) == -1)
3045 return -1;
3046 /* evaluate VDEF and CDEF operations ... */
3047 if (data_calc(im) == -1)
3048 return -1;
3049 /* calculate and PRINT and GPRINT definitions. We have to do it at
3050 * this point because it will affect the length of the legends
3051 * if there are no graph elements (i==0) we stop here ...
3052 * if we are lazy, try to quit ...
3053 */
3054 i = print_calc(im);
3055 if (i < 0)
3056 return -1;
3058 if ((i == 0) || lazy)
3059 return 0;
3061 /**************************************************************
3062 *** Calculating sizes and locations became a bit confusing ***
3063 *** so I moved this into a separate function. ***
3064 **************************************************************/
3065 if (graph_size_location(im, i) == -1)
3066 return -1;
3068 info.u_cnt = im->xorigin;
3069 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3070 info.u_cnt = im->yorigin - im->ysize;
3071 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3072 info.u_cnt = im->xsize;
3073 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3074 info.u_cnt = im->ysize;
3075 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3076 info.u_cnt = im->ximg;
3077 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3078 info.u_cnt = im->yimg;
3079 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3081 /* get actual drawing data and find min and max values */
3082 if (data_proc(im) == -1)
3083 return -1;
3084 if (!im->logarithmic) {
3085 si_unit(im);
3086 }
3088 /* identify si magnitude Kilo, Mega Giga ? */
3089 if (!im->rigid && !im->logarithmic)
3090 expand_range(im); /* make sure the upper and lower limit are
3091 sensible values */
3093 info.u_val = im->minval;
3094 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3095 info.u_val = im->maxval;
3096 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3098 if (!calc_horizontal_grid(im))
3099 return -1;
3100 /* reset precalc */
3101 ytr(im, DNAN);
3102 /* if (im->gridfit)
3103 apply_gridfit(im); */
3104 /* the actual graph is created by going through the individual
3105 graph elements and then drawing them */
3106 cairo_surface_destroy(im->surface);
3107 switch (im->imgformat) {
3108 case IF_PNG:
3109 im->surface =
3110 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3111 im->ximg * im->zoom,
3112 im->yimg * im->zoom);
3113 break;
3114 case IF_PDF:
3115 im->gridfit = 0;
3116 im->surface = strlen(im->graphfile)
3117 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3118 im->yimg * im->zoom)
3119 : cairo_pdf_surface_create_for_stream
3120 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3121 break;
3122 case IF_EPS:
3123 im->gridfit = 0;
3124 im->surface = strlen(im->graphfile)
3125 ?
3126 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3127 im->yimg * im->zoom)
3128 : cairo_ps_surface_create_for_stream
3129 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3130 break;
3131 case IF_SVG:
3132 im->gridfit = 0;
3133 im->surface = strlen(im->graphfile)
3134 ?
3135 cairo_svg_surface_create(im->
3136 graphfile,
3137 im->ximg * im->zoom, im->yimg * im->zoom)
3138 : cairo_svg_surface_create_for_stream
3139 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3140 cairo_svg_surface_restrict_to_version
3141 (im->surface, CAIRO_SVG_VERSION_1_1);
3142 break;
3143 };
3144 cairo_destroy(im->cr);
3145 im->cr = cairo_create(im->surface);
3146 cairo_set_antialias(im->cr, im->graph_antialias);
3147 cairo_scale(im->cr, im->zoom, im->zoom);
3148 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3149 gfx_new_area(im, 0, 0, 0, im->yimg,
3150 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3151 gfx_add_point(im, im->ximg, 0);
3152 gfx_close_path(im);
3153 gfx_new_area(im, im->xorigin,
3154 im->yorigin,
3155 im->xorigin +
3156 im->xsize, im->yorigin,
3157 im->xorigin +
3158 im->xsize,
3159 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3160 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3161 gfx_close_path(im);
3162 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3163 im->xsize, im->ysize + 2.0);
3164 cairo_clip(im->cr);
3165 if (im->minval > 0.0)
3166 areazero = im->minval;
3167 if (im->maxval < 0.0)
3168 areazero = im->maxval;
3169 for (i = 0; i < im->gdes_c; i++) {
3170 switch (im->gdes[i].gf) {
3171 case GF_CDEF:
3172 case GF_VDEF:
3173 case GF_DEF:
3174 case GF_PRINT:
3175 case GF_GPRINT:
3176 case GF_COMMENT:
3177 case GF_TEXTALIGN:
3178 case GF_HRULE:
3179 case GF_VRULE:
3180 case GF_XPORT:
3181 case GF_SHIFT:
3182 break;
3183 case GF_TICK:
3184 for (ii = 0; ii < im->xsize; ii++) {
3185 if (!isnan(im->gdes[i].p_data[ii])
3186 && im->gdes[i].p_data[ii] != 0.0) {
3187 if (im->gdes[i].yrule > 0) {
3188 gfx_line(im,
3189 im->xorigin + ii,
3190 im->yorigin,
3191 im->xorigin + ii,
3192 im->yorigin -
3193 im->gdes[i].yrule *
3194 im->ysize, 1.0, im->gdes[i].col);
3195 } else if (im->gdes[i].yrule < 0) {
3196 gfx_line(im,
3197 im->xorigin + ii,
3198 im->yorigin - im->ysize,
3199 im->xorigin + ii,
3200 im->yorigin - (1 -
3201 im->gdes[i].
3202 yrule) *
3203 im->ysize, 1.0, im->gdes[i].col);
3204 }
3205 }
3206 }
3207 break;
3208 case GF_LINE:
3209 case GF_AREA:
3210 /* fix data points at oo and -oo */
3211 for (ii = 0; ii < im->xsize; ii++) {
3212 if (isinf(im->gdes[i].p_data[ii])) {
3213 if (im->gdes[i].p_data[ii] > 0) {
3214 im->gdes[i].p_data[ii] = im->maxval;
3215 } else {
3216 im->gdes[i].p_data[ii] = im->minval;
3217 }
3219 }
3220 } /* for */
3222 /* *******************************************************
3223 a ___. (a,t)
3224 | | ___
3225 ____| | | |
3226 | |___|
3227 -------|--t-1--t--------------------------------
3229 if we know the value at time t was a then
3230 we draw a square from t-1 to t with the value a.
3232 ********************************************************* */
3233 if (im->gdes[i].col.alpha != 0.0) {
3234 /* GF_LINE and friend */
3235 if (im->gdes[i].gf == GF_LINE) {
3236 double last_y = 0.0;
3237 int draw_on = 0;
3239 cairo_save(im->cr);
3240 cairo_new_path(im->cr);
3241 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3242 if (im->gdes[i].dash) {
3243 cairo_set_dash(im->cr,
3244 im->gdes[i].p_dashes,
3245 im->gdes[i].ndash, im->gdes[i].offset);
3246 }
3248 for (ii = 1; ii < im->xsize; ii++) {
3249 if (isnan(im->gdes[i].p_data[ii])
3250 || (im->slopemode == 1
3251 && isnan(im->gdes[i].p_data[ii - 1]))) {
3252 draw_on = 0;
3253 continue;
3254 }
3255 if (draw_on == 0) {
3256 last_y = ytr(im, im->gdes[i].p_data[ii]);
3257 if (im->slopemode == 0) {
3258 double x = ii - 1 + im->xorigin;
3259 double y = last_y;
3261 gfx_line_fit(im, &x, &y);
3262 cairo_move_to(im->cr, x, y);
3263 x = ii + im->xorigin;
3264 y = last_y;
3265 gfx_line_fit(im, &x, &y);
3266 cairo_line_to(im->cr, x, y);
3267 } else {
3268 double x = ii - 1 + im->xorigin;
3269 double y =
3270 ytr(im, im->gdes[i].p_data[ii - 1]);
3271 gfx_line_fit(im, &x, &y);
3272 cairo_move_to(im->cr, x, y);
3273 x = ii + im->xorigin;
3274 y = last_y;
3275 gfx_line_fit(im, &x, &y);
3276 cairo_line_to(im->cr, x, y);
3277 }
3278 draw_on = 1;
3279 } else {
3280 double x1 = ii + im->xorigin;
3281 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3283 if (im->slopemode == 0
3284 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3285 double x = ii - 1 + im->xorigin;
3286 double y = y1;
3288 gfx_line_fit(im, &x, &y);
3289 cairo_line_to(im->cr, x, y);
3290 };
3291 last_y = y1;
3292 gfx_line_fit(im, &x1, &y1);
3293 cairo_line_to(im->cr, x1, y1);
3294 };
3295 }
3296 cairo_set_source_rgba(im->cr,
3297 im->gdes[i].
3298 col.red,
3299 im->gdes[i].
3300 col.green,
3301 im->gdes[i].
3302 col.blue, im->gdes[i].col.alpha);
3303 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3304 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3305 cairo_stroke(im->cr);
3306 cairo_restore(im->cr);
3307 } else {
3308 int idxI = -1;
3309 double *foreY =
3310 (double *) malloc(sizeof(double) * im->xsize * 2);
3311 double *foreX =
3312 (double *) malloc(sizeof(double) * im->xsize * 2);
3313 double *backY =
3314 (double *) malloc(sizeof(double) * im->xsize * 2);
3315 double *backX =
3316 (double *) malloc(sizeof(double) * im->xsize * 2);
3317 int drawem = 0;
3319 for (ii = 0; ii <= im->xsize; ii++) {
3320 double ybase, ytop;
3322 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3323 int cntI = 1;
3324 int lastI = 0;
3326 while (cntI < idxI
3327 &&
3328 AlmostEqual2sComplement(foreY
3329 [lastI],
3330 foreY[cntI], 4)
3331 &&
3332 AlmostEqual2sComplement(foreY
3333 [lastI],
3334 foreY
3335 [cntI + 1], 4)) {
3336 cntI++;
3337 }
3338 gfx_new_area(im,
3339 backX[0], backY[0],
3340 foreX[0], foreY[0],
3341 foreX[cntI],
3342 foreY[cntI], im->gdes[i].col);
3343 while (cntI < idxI) {
3344 lastI = cntI;
3345 cntI++;
3346 while (cntI < idxI
3347 &&
3348 AlmostEqual2sComplement(foreY
3349 [lastI],
3350 foreY[cntI], 4)
3351 &&
3352 AlmostEqual2sComplement(foreY
3353 [lastI],
3354 foreY
3355 [cntI
3356 + 1], 4)) {
3357 cntI++;
3358 }
3359 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3360 }
3361 gfx_add_point(im, backX[idxI], backY[idxI]);
3362 while (idxI > 1) {
3363 lastI = idxI;
3364 idxI--;
3365 while (idxI > 1
3366 &&
3367 AlmostEqual2sComplement(backY
3368 [lastI],
3369 backY[idxI], 4)
3370 &&
3371 AlmostEqual2sComplement(backY
3372 [lastI],
3373 backY
3374 [idxI
3375 - 1], 4)) {
3376 idxI--;
3377 }
3378 gfx_add_point(im, backX[idxI], backY[idxI]);
3379 }
3380 idxI = -1;
3381 drawem = 0;
3382 gfx_close_path(im);
3383 }
3384 if (drawem != 0) {
3385 drawem = 0;
3386 idxI = -1;
3387 }
3388 if (ii == im->xsize)
3389 break;
3390 if (im->slopemode == 0 && ii == 0) {
3391 continue;
3392 }
3393 if (isnan(im->gdes[i].p_data[ii])) {
3394 drawem = 1;
3395 continue;
3396 }
3397 ytop = ytr(im, im->gdes[i].p_data[ii]);
3398 if (lastgdes && im->gdes[i].stack) {
3399 ybase = ytr(im, lastgdes->p_data[ii]);
3400 } else {
3401 ybase = ytr(im, areazero);
3402 }
3403 if (ybase == ytop) {
3404 drawem = 1;
3405 continue;
3406 }
3408 if (ybase > ytop) {
3409 double extra = ytop;
3411 ytop = ybase;
3412 ybase = extra;
3413 }
3414 if (im->slopemode == 0) {
3415 backY[++idxI] = ybase - 0.2;
3416 backX[idxI] = ii + im->xorigin - 1;
3417 foreY[idxI] = ytop + 0.2;
3418 foreX[idxI] = ii + im->xorigin - 1;
3419 }
3420 backY[++idxI] = ybase - 0.2;
3421 backX[idxI] = ii + im->xorigin;
3422 foreY[idxI] = ytop + 0.2;
3423 foreX[idxI] = ii + im->xorigin;
3424 }
3425 /* close up any remaining area */
3426 free(foreY);
3427 free(foreX);
3428 free(backY);
3429 free(backX);
3430 } /* else GF_LINE */
3431 }
3432 /* if color != 0x0 */
3433 /* make sure we do not run into trouble when stacking on NaN */
3434 for (ii = 0; ii < im->xsize; ii++) {
3435 if (isnan(im->gdes[i].p_data[ii])) {
3436 if (lastgdes && (im->gdes[i].stack)) {
3437 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3438 } else {
3439 im->gdes[i].p_data[ii] = areazero;
3440 }
3441 }
3442 }
3443 lastgdes = &(im->gdes[i]);
3444 break;
3445 case GF_STACK:
3446 rrd_set_error
3447 ("STACK should already be turned into LINE or AREA here");
3448 return -1;
3449 break;
3450 } /* switch */
3451 }
3452 cairo_reset_clip(im->cr);
3454 /* grid_paint also does the text */
3455 if (!(im->extra_flags & ONLY_GRAPH))
3456 grid_paint(im);
3457 if (!(im->extra_flags & ONLY_GRAPH))
3458 axis_paint(im);
3459 /* the RULES are the last thing to paint ... */
3460 for (i = 0; i < im->gdes_c; i++) {
3462 switch (im->gdes[i].gf) {
3463 case GF_HRULE:
3464 if (im->gdes[i].yrule >= im->minval
3465 && im->gdes[i].yrule <= im->maxval) {
3466 cairo_save(im->cr);
3467 if (im->gdes[i].dash) {
3468 cairo_set_dash(im->cr,
3469 im->gdes[i].p_dashes,
3470 im->gdes[i].ndash, im->gdes[i].offset);
3471 }
3472 gfx_line(im, im->xorigin,
3473 ytr(im, im->gdes[i].yrule),
3474 im->xorigin + im->xsize,
3475 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3476 cairo_stroke(im->cr);
3477 cairo_restore(im->cr);
3478 }
3479 break;
3480 case GF_VRULE:
3481 if (im->gdes[i].xrule >= im->start
3482 && im->gdes[i].xrule <= im->end) {
3483 cairo_save(im->cr);
3484 if (im->gdes[i].dash) {
3485 cairo_set_dash(im->cr,
3486 im->gdes[i].p_dashes,
3487 im->gdes[i].ndash, im->gdes[i].offset);
3488 }
3489 gfx_line(im,
3490 xtr(im, im->gdes[i].xrule),
3491 im->yorigin, xtr(im,
3492 im->
3493 gdes[i].
3494 xrule),
3495 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3496 cairo_stroke(im->cr);
3497 cairo_restore(im->cr);
3498 }
3499 break;
3500 default:
3501 break;
3502 }
3503 }
3506 switch (im->imgformat) {
3507 case IF_PNG:
3508 {
3509 cairo_status_t status;
3511 status = strlen(im->graphfile) ?
3512 cairo_surface_write_to_png(im->surface, im->graphfile)
3513 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3514 im);
3516 if (status != CAIRO_STATUS_SUCCESS) {
3517 rrd_set_error("Could not save png to '%s'", im->graphfile);
3518 return 1;
3519 }
3520 break;
3521 }
3522 default:
3523 if (strlen(im->graphfile)) {
3524 cairo_show_page(im->cr);
3525 } else {
3526 cairo_surface_finish(im->surface);
3527 }
3528 break;
3529 }
3531 return 0;
3532 }
3535 /*****************************************************
3536 * graph stuff
3537 *****************************************************/
3539 int gdes_alloc(
3540 image_desc_t *im)
3541 {
3543 im->gdes_c++;
3544 if ((im->gdes = (graph_desc_t *)
3545 rrd_realloc(im->gdes, (im->gdes_c)
3546 * sizeof(graph_desc_t))) == NULL) {
3547 rrd_set_error("realloc graph_descs");
3548 return -1;
3549 }
3552 im->gdes[im->gdes_c - 1].step = im->step;
3553 im->gdes[im->gdes_c - 1].step_orig = im->step;
3554 im->gdes[im->gdes_c - 1].stack = 0;
3555 im->gdes[im->gdes_c - 1].linewidth = 0;
3556 im->gdes[im->gdes_c - 1].debug = 0;
3557 im->gdes[im->gdes_c - 1].start = im->start;
3558 im->gdes[im->gdes_c - 1].start_orig = im->start;
3559 im->gdes[im->gdes_c - 1].end = im->end;
3560 im->gdes[im->gdes_c - 1].end_orig = im->end;
3561 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3562 im->gdes[im->gdes_c - 1].data = NULL;
3563 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3564 im->gdes[im->gdes_c - 1].data_first = 0;
3565 im->gdes[im->gdes_c - 1].p_data = NULL;
3566 im->gdes[im->gdes_c - 1].rpnp = NULL;
3567 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3568 im->gdes[im->gdes_c - 1].shift = 0.0;
3569 im->gdes[im->gdes_c - 1].dash = 0;
3570 im->gdes[im->gdes_c - 1].ndash = 0;
3571 im->gdes[im->gdes_c - 1].offset = 0;
3572 im->gdes[im->gdes_c - 1].col.red = 0.0;
3573 im->gdes[im->gdes_c - 1].col.green = 0.0;
3574 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3575 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3576 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3577 im->gdes[im->gdes_c - 1].format[0] = '\0';
3578 im->gdes[im->gdes_c - 1].strftm = 0;
3579 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3580 im->gdes[im->gdes_c - 1].ds = -1;
3581 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3582 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3583 im->gdes[im->gdes_c - 1].yrule = DNAN;
3584 im->gdes[im->gdes_c - 1].xrule = 0;
3585 return 0;
3586 }
3588 /* copies input untill the first unescaped colon is found
3589 or until input ends. backslashes have to be escaped as well */
3590 int scan_for_col(
3591 const char *const input,
3592 int len,
3593 char *const output)
3594 {
3595 int inp, outp = 0;
3597 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3598 if (input[inp] == '\\'
3599 && input[inp + 1] != '\0'
3600 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3601 output[outp++] = input[++inp];
3602 } else {
3603 output[outp++] = input[inp];
3604 }
3605 }
3606 output[outp] = '\0';
3607 return inp;
3608 }
3610 /* Now just a wrapper around rrd_graph_v */
3611 int rrd_graph(
3612 int argc,
3613 char **argv,
3614 char ***prdata,
3615 int *xsize,
3616 int *ysize,
3617 FILE * stream,
3618 double *ymin,
3619 double *ymax)
3620 {
3621 int prlines = 0;
3622 rrd_info_t *grinfo = NULL;
3623 rrd_info_t *walker;
3625 grinfo = rrd_graph_v(argc, argv);
3626 if (grinfo == NULL)
3627 return -1;
3628 walker = grinfo;
3629 (*prdata) = NULL;
3630 while (walker) {
3631 if (strcmp(walker->key, "image_info") == 0) {
3632 prlines++;
3633 if (((*prdata) =
3634 (char**)(rrd_realloc((*prdata),
3635 (prlines + 1) * sizeof(char *)))) == NULL) {
3636 rrd_set_error("realloc prdata");
3637 return 0;
3638 }
3639 /* imginfo goes to position 0 in the prdata array */
3640 (*prdata)[prlines - 1] = (char*)(malloc((strlen(walker->value.u_str)
3641 + 2) * sizeof(char)));
3642 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3643 (*prdata)[prlines] = NULL;
3644 }
3645 /* skip anything else */
3646 walker = walker->next;
3647 }
3648 walker = grinfo;
3649 *xsize = 0;
3650 *ysize = 0;
3651 *ymin = 0;
3652 *ymax = 0;
3653 while (walker) {
3654 if (strcmp(walker->key, "image_width") == 0) {
3655 *xsize = walker->value.u_int;
3656 } else if (strcmp(walker->key, "image_height") == 0) {
3657 *ysize = walker->value.u_int;
3658 } else if (strcmp(walker->key, "value_min") == 0) {
3659 *ymin = walker->value.u_val;
3660 } else if (strcmp(walker->key, "value_max") == 0) {
3661 *ymax = walker->value.u_val;
3662 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3663 prlines++;
3664 if (((*prdata) =
3665 (char**)(rrd_realloc((*prdata),
3666 (prlines + 1) * sizeof(char *)))) == NULL) {
3667 rrd_set_error("realloc prdata");
3668 return 0;
3669 }
3670 (*prdata)[prlines - 1] = (char*)(malloc((strlen(walker->value.u_str)
3671 + 2) * sizeof(char)));
3672 (*prdata)[prlines] = NULL;
3673 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3674 } else if (strcmp(walker->key, "image") == 0) {
3675 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3676 (stream ? stream : stdout));
3677 }
3678 /* skip anything else */
3679 walker = walker->next;
3680 }
3681 rrd_info_free(grinfo);
3682 return 0;
3683 }
3686 /* Some surgery done on this function, it became ridiculously big.
3687 ** Things moved:
3688 ** - initializing now in rrd_graph_init()
3689 ** - options parsing now in rrd_graph_options()
3690 ** - script parsing now in rrd_graph_script()
3691 */
3692 rrd_info_t *rrd_graph_v(
3693 int argc,
3694 char **argv)
3695 {
3696 image_desc_t im;
3697 rrd_info_t *grinfo;
3698 rrd_graph_init(&im);
3699 /* a dummy surface so that we can measure text sizes for placements */
3701 rrd_graph_options(argc, argv, &im);
3702 if (rrd_test_error()) {
3703 rrd_info_free(im.grinfo);
3704 im_free(&im);
3705 return NULL;
3706 }
3708 if (optind >= argc) {
3709 rrd_info_free(im.grinfo);
3710 im_free(&im);
3711 rrd_set_error("missing filename");
3712 return NULL;
3713 }
3715 if (strlen(argv[optind]) >= MAXPATH) {
3716 rrd_set_error("filename (including path) too long");
3717 rrd_info_free(im.grinfo);
3718 im_free(&im);
3719 return NULL;
3720 }
3722 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3723 im.graphfile[MAXPATH - 1] = '\0';
3725 if (strcmp(im.graphfile, "-") == 0) {
3726 im.graphfile[0] = '\0';
3727 }
3729 rrd_graph_script(argc, argv, &im, 1);
3730 if (rrd_test_error()) {
3731 rrd_info_free(im.grinfo);
3732 im_free(&im);
3733 return NULL;
3734 }
3736 /* Everything is now read and the actual work can start */
3738 if (graph_paint(&im) == -1) {
3739 rrd_info_free(im.grinfo);
3740 im_free(&im);
3741 return NULL;
3742 }
3745 /* The image is generated and needs to be output.
3746 ** Also, if needed, print a line with information about the image.
3747 */
3749 if (im.imginfo) {
3750 rrd_infoval_t info;
3751 char *path;
3752 char *filename;
3754 path = strdup(im.graphfile);
3755 filename = basename(path);
3756 info.u_str =
3757 sprintf_alloc(im.imginfo,
3758 filename,
3759 (long) (im.zoom *
3760 im.ximg), (long) (im.zoom * im.yimg));
3761 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3762 free(info.u_str);
3763 free(path);
3764 }
3765 if (im.rendered_image) {
3766 rrd_infoval_t img;
3768 img.u_blo.size = im.rendered_image_size;
3769 img.u_blo.ptr = im.rendered_image;
3770 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3771 }
3772 grinfo = im.grinfo;
3773 im_free(&im);
3774 return grinfo;
3775 }
3777 static void
3778 rrd_set_font_desc (
3779 image_desc_t *im,int prop,char *font, double size ){
3780 if (font){
3781 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3782 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3783 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3784 };
3785 if (size > 0){
3786 im->text_prop[prop].size = size;
3787 };
3788 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3789 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3790 };
3791 }
3793 void rrd_graph_init(
3794 image_desc_t
3795 *im)
3796 {
3797 unsigned int i;
3798 char *deffont = getenv("RRD_DEFAULT_FONT");
3799 static PangoFontMap *fontmap = NULL;
3800 PangoContext *context;
3802 #ifdef HAVE_TZSET
3803 tzset();
3804 #endif
3805 #ifdef HAVE_SETLOCALE
3806 setlocale(LC_TIME, "");
3807 #ifdef HAVE_MBSTOWCS
3808 setlocale(LC_CTYPE, "");
3809 #endif
3810 #endif
3811 im->base = 1000;
3812 im->draw_x_grid = 1;
3813 im->draw_y_grid = 1;
3814 im->extra_flags = 0;
3815 im->font_options = cairo_font_options_create();
3816 im->forceleftspace = 0;
3817 im->gdes_c = 0;
3818 im->gdes = NULL;
3819 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3820 im->grid_dash_off = 1;
3821 im->grid_dash_on = 1;
3822 im->gridfit = 1;
3823 im->grinfo = (rrd_info_t *) NULL;
3824 im->grinfo_current = (rrd_info_t *) NULL;
3825 im->imgformat = IF_PNG;
3826 im->imginfo = NULL;
3827 im->lazy = 0;
3828 im->logarithmic = 0;
3829 im->maxval = DNAN;
3830 im->minval = 0;
3831 im->minval = DNAN;
3832 im->prt_c = 0;
3833 im->rigid = 0;
3834 im->rendered_image_size = 0;
3835 im->rendered_image = NULL;
3836 im->slopemode = 0;
3837 im->step = 0;
3838 im->symbol = ' ';
3839 im->tabwidth = 40.0;
3840 im->title[0] = '\0';
3841 im->unitsexponent = 9999;
3842 im->unitslength = 6;
3843 im->viewfactor = 1.0;
3844 im->watermark[0] = '\0';
3845 im->with_markup = 0;
3846 im->ximg = 0;
3847 im->xlab_user.minsec = -1;
3848 im->xorigin = 0;
3849 im->xsize = 400;
3850 im->ygridstep = DNAN;
3851 im->yimg = 0;
3852 im->ylegend[0] = '\0';
3853 im->second_axis_scale = 0; /* 0 disables it */
3854 im->second_axis_shift = 0; /* no shift by default */
3855 im->second_axis_legend[0] = '\0';
3856 im->second_axis_format[0] = '\0';
3857 im->yorigin = 0;
3858 im->ysize = 100;
3859 im->zoom = 1;
3861 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3862 im->cr = cairo_create(im->surface);
3864 for (i = 0; i < DIM(text_prop); i++) {
3865 im->text_prop[i].size = -1;
3866 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3867 }
3869 if (fontmap == NULL){
3870 fontmap = pango_cairo_font_map_get_default();
3871 }
3873 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3875 pango_cairo_context_set_resolution(context, 100);
3877 pango_cairo_update_context(im->cr,context);
3879 im->layout = pango_layout_new(context);
3881 // im->layout = pango_cairo_create_layout(im->cr);
3884 cairo_font_options_set_hint_style
3885 (im->font_options, CAIRO_HINT_STYLE_FULL);
3886 cairo_font_options_set_hint_metrics
3887 (im->font_options, CAIRO_HINT_METRICS_ON);
3888 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3892 for (i = 0; i < DIM(graph_col); i++)
3893 im->graph_col[i] = graph_col[i];
3896 }
3899 void rrd_graph_options(
3900 int argc,
3901 char *argv[],
3902 image_desc_t
3903 *im)
3904 {
3905 int stroff;
3906 char *parsetime_error = NULL;
3907 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3908 time_t start_tmp = 0, end_tmp = 0;
3909 long long_tmp;
3910 rrd_time_value_t start_tv, end_tv;
3911 long unsigned int color;
3912 char *old_locale = "";
3914 /* defines for long options without a short equivalent. should be bytes,
3915 and may not collide with (the ASCII value of) short options */
3916 #define LONGOPT_UNITS_SI 255
3918 /* *INDENT-OFF* */
3919 struct option long_options[] = {
3920 { "start", required_argument, 0, 's'},
3921 { "end", required_argument, 0, 'e'},
3922 { "x-grid", required_argument, 0, 'x'},
3923 { "y-grid", required_argument, 0, 'y'},
3924 { "vertical-label", required_argument, 0, 'v'},
3925 { "width", required_argument, 0, 'w'},
3926 { "height", required_argument, 0, 'h'},
3927 { "full-size-mode", no_argument, 0, 'D'},
3928 { "interlaced", no_argument, 0, 'i'},
3929 { "upper-limit", required_argument, 0, 'u'},
3930 { "lower-limit", required_argument, 0, 'l'},
3931 { "rigid", no_argument, 0, 'r'},
3932 { "base", required_argument, 0, 'b'},
3933 { "logarithmic", no_argument, 0, 'o'},
3934 { "color", required_argument, 0, 'c'},
3935 { "font", required_argument, 0, 'n'},
3936 { "title", required_argument, 0, 't'},
3937 { "imginfo", required_argument, 0, 'f'},
3938 { "imgformat", required_argument, 0, 'a'},
3939 { "lazy", no_argument, 0, 'z'},
3940 { "zoom", required_argument, 0, 'm'},
3941 { "no-legend", no_argument, 0, 'g'},
3942 { "force-rules-legend", no_argument, 0, 'F'},
3943 { "only-graph", no_argument, 0, 'j'},
3944 { "alt-y-grid", no_argument, 0, 'Y'},
3945 {"disable-rrdtool-tag", no_argument, 0, 1001},
3946 {"right-axis", required_argument, 0, 1002},
3947 {"right-axis-label", required_argument, 0, 1003},
3948 {"right-axis-format", required_argument, 0, 1004},
3949 { "no-minor", no_argument, 0, 'I'},
3950 { "slope-mode", no_argument, 0, 'E'},
3951 { "alt-autoscale", no_argument, 0, 'A'},
3952 { "alt-autoscale-min", no_argument, 0, 'J'},
3953 { "alt-autoscale-max", no_argument, 0, 'M'},
3954 { "no-gridfit", no_argument, 0, 'N'},
3955 { "units-exponent", required_argument, 0, 'X'},
3956 { "units-length", required_argument, 0, 'L'},
3957 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3958 { "step", required_argument, 0, 'S'},
3959 { "tabwidth", required_argument, 0, 'T'},
3960 { "font-render-mode", required_argument, 0, 'R'},
3961 { "graph-render-mode", required_argument, 0, 'G'},
3962 { "font-smoothing-threshold", required_argument, 0, 'B'},
3963 { "watermark", required_argument, 0, 'W'},
3964 { "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 */
3965 { "pango-markup", no_argument, 0, 'P'},
3966 { 0, 0, 0, 0}
3967 };
3968 /* *INDENT-ON* */
3970 optind = 0;
3971 opterr = 0; /* initialize getopt */
3972 rrd_parsetime("end-24h", &start_tv);
3973 rrd_parsetime("now", &end_tv);
3974 while (1) {
3975 int option_index = 0;
3976 int opt;
3977 int col_start, col_end;
3979 opt = getopt_long(argc, argv,
3980 "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",
3981 long_options, &option_index);
3982 if (opt == EOF)
3983 break;
3984 switch (opt) {
3985 case 'I':
3986 im->extra_flags |= NOMINOR;
3987 break;
3988 case 'Y':
3989 im->extra_flags |= ALTYGRID;
3990 break;
3991 case 'A':
3992 im->extra_flags |= ALTAUTOSCALE;
3993 break;
3994 case 'J':
3995 im->extra_flags |= ALTAUTOSCALE_MIN;
3996 break;
3997 case 'M':
3998 im->extra_flags |= ALTAUTOSCALE_MAX;
3999 break;
4000 case 'j':
4001 im->extra_flags |= ONLY_GRAPH;
4002 break;
4003 case 'g':
4004 im->extra_flags |= NOLEGEND;
4005 break;
4006 case 'F':
4007 im->extra_flags |= FORCE_RULES_LEGEND;
4008 break;
4009 case 1001:
4010 im->extra_flags |= NO_RRDTOOL_TAG;
4011 break;
4012 case LONGOPT_UNITS_SI:
4013 if (im->extra_flags & FORCE_UNITS) {
4014 rrd_set_error("--units can only be used once!");
4015 setlocale(LC_NUMERIC, old_locale);
4016 return;
4017 }
4018 if (strcmp(optarg, "si") == 0)
4019 im->extra_flags |= FORCE_UNITS_SI;
4020 else {
4021 rrd_set_error("invalid argument for --units: %s", optarg);
4022 return;
4023 }
4024 break;
4025 case 'X':
4026 im->unitsexponent = atoi(optarg);
4027 break;
4028 case 'L':
4029 im->unitslength = atoi(optarg);
4030 im->forceleftspace = 1;
4031 break;
4032 case 'T':
4033 old_locale = setlocale(LC_NUMERIC, "C");
4034 im->tabwidth = atof(optarg);
4035 setlocale(LC_NUMERIC, old_locale);
4036 break;
4037 case 'S':
4038 old_locale = setlocale(LC_NUMERIC, "C");
4039 im->step = atoi(optarg);
4040 setlocale(LC_NUMERIC, old_locale);
4041 break;
4042 case 'N':
4043 im->gridfit = 0;
4044 break;
4045 case 'P':
4046 im->with_markup = 1;
4047 break;
4048 case 's':
4049 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4050 rrd_set_error("start time: %s", parsetime_error);
4051 return;
4052 }
4053 break;
4054 case 'e':
4055 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4056 rrd_set_error("end time: %s", parsetime_error);
4057 return;
4058 }
4059 break;
4060 case 'x':
4061 if (strcmp(optarg, "none") == 0) {
4062 im->draw_x_grid = 0;
4063 break;
4064 };
4065 if (sscanf(optarg,
4066 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4067 scan_gtm,
4068 &im->xlab_user.gridst,
4069 scan_mtm,
4070 &im->xlab_user.mgridst,
4071 scan_ltm,
4072 &im->xlab_user.labst,
4073 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4074 strncpy(im->xlab_form, optarg + stroff,
4075 sizeof(im->xlab_form) - 1);
4076 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4077 if ((int)
4078 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4079 rrd_set_error("unknown keyword %s", scan_gtm);
4080 return;
4081 } else if ((int)
4082 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4083 == -1) {
4084 rrd_set_error("unknown keyword %s", scan_mtm);
4085 return;
4086 } else if ((int)
4087 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4088 rrd_set_error("unknown keyword %s", scan_ltm);
4089 return;
4090 }
4091 im->xlab_user.minsec = 1;
4092 im->xlab_user.stst = im->xlab_form;
4093 } else {
4094 rrd_set_error("invalid x-grid format");
4095 return;
4096 }
4097 break;
4098 case 'y':
4100 if (strcmp(optarg, "none") == 0) {
4101 im->draw_y_grid = 0;
4102 break;
4103 };
4104 old_locale = setlocale(LC_NUMERIC, "C");
4105 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4106 setlocale(LC_NUMERIC, old_locale);
4107 if (im->ygridstep <= 0) {
4108 rrd_set_error("grid step must be > 0");
4109 return;
4110 } else if (im->ylabfact < 1) {
4111 rrd_set_error("label factor must be > 0");
4112 return;
4113 }
4114 } else {
4115 setlocale(LC_NUMERIC, old_locale);
4116 rrd_set_error("invalid y-grid format");
4117 return;
4118 }
4119 break;
4120 case 1002: /* right y axis */
4122 if(sscanf(optarg,
4123 "%lf:%lf",
4124 &im->second_axis_scale,
4125 &im->second_axis_shift) == 2) {
4126 if(im->second_axis_scale==0){
4127 rrd_set_error("the second_axis_scale must not be 0");
4128 return;
4129 }
4130 } else {
4131 rrd_set_error("invalid right-axis format expected scale:shift");
4132 return;
4133 }
4134 break;
4135 case 1003:
4136 strncpy(im->second_axis_legend,optarg,150);
4137 im->second_axis_legend[150]='\0';
4138 break;
4139 case 1004:
4140 if (bad_format(optarg)){
4141 rrd_set_error("use either %le or %lf formats");
4142 return;
4143 }
4144 strncpy(im->second_axis_format,optarg,150);
4145 im->second_axis_format[150]='\0';
4146 break;
4147 case 'v':
4148 strncpy(im->ylegend, optarg, 150);
4149 im->ylegend[150] = '\0';
4150 break;
4151 case 'u':
4152 old_locale = setlocale(LC_NUMERIC, "C");
4153 im->maxval = atof(optarg);
4154 setlocale(LC_NUMERIC, old_locale);
4155 break;
4156 case 'l':
4157 old_locale = setlocale(LC_NUMERIC, "C");
4158 im->minval = atof(optarg);
4159 setlocale(LC_NUMERIC, old_locale);
4160 break;
4161 case 'b':
4162 im->base = atol(optarg);
4163 if (im->base != 1024 && im->base != 1000) {
4164 rrd_set_error
4165 ("the only sensible value for base apart from 1000 is 1024");
4166 return;
4167 }
4168 break;
4169 case 'w':
4170 long_tmp = atol(optarg);
4171 if (long_tmp < 10) {
4172 rrd_set_error("width below 10 pixels");
4173 return;
4174 }
4175 im->xsize = long_tmp;
4176 break;
4177 case 'h':
4178 long_tmp = atol(optarg);
4179 if (long_tmp < 10) {
4180 rrd_set_error("height below 10 pixels");
4181 return;
4182 }
4183 im->ysize = long_tmp;
4184 break;
4185 case 'D':
4186 im->extra_flags |= FULL_SIZE_MODE;
4187 break;
4188 case 'i':
4189 /* interlaced png not supported at the moment */
4190 break;
4191 case 'r':
4192 im->rigid = 1;
4193 break;
4194 case 'f':
4195 im->imginfo = optarg;
4196 break;
4197 case 'a':
4198 if ((int)
4199 (im->imgformat = if_conv(optarg)) == -1) {
4200 rrd_set_error("unsupported graphics format '%s'", optarg);
4201 return;
4202 }
4203 break;
4204 case 'z':
4205 im->lazy = 1;
4206 break;
4207 case 'E':
4208 im->slopemode = 1;
4209 break;
4210 case 'o':
4211 im->logarithmic = 1;
4212 break;
4213 case 'c':
4214 if (sscanf(optarg,
4215 "%10[A-Z]#%n%8lx%n",
4216 col_nam, &col_start, &color, &col_end) == 2) {
4217 int ci;
4218 int col_len = col_end - col_start;
4220 switch (col_len) {
4221 case 3:
4222 color =
4223 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4224 0x011000) |
4225 ((color & 0x00F)
4226 * 0x001100)
4227 | 0x000000FF);
4228 break;
4229 case 4:
4230 color =
4231 (((color & 0xF000) *
4232 0x11000) | ((color & 0x0F00) *
4233 0x01100) | ((color &
4234 0x00F0) *
4235 0x00110) |
4236 ((color & 0x000F) * 0x00011)
4237 );
4238 break;
4239 case 6:
4240 color = (color << 8) + 0xff /* shift left by 8 */ ;
4241 break;
4242 case 8:
4243 break;
4244 default:
4245 rrd_set_error("the color format is #RRGGBB[AA]");
4246 return;
4247 }
4248 if ((ci = grc_conv(col_nam)) != -1) {
4249 im->graph_col[ci] = gfx_hex_to_col(color);
4250 } else {
4251 rrd_set_error("invalid color name '%s'", col_nam);
4252 return;
4253 }
4254 } else {
4255 rrd_set_error("invalid color def format");
4256 return;
4257 }
4258 break;
4259 case 'n':{
4260 char prop[15];
4261 double size = 1;
4262 int end;
4264 old_locale = setlocale(LC_NUMERIC, "C");
4265 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4266 int sindex, propidx;
4268 setlocale(LC_NUMERIC, old_locale);
4269 if ((sindex = text_prop_conv(prop)) != -1) {
4270 for (propidx = sindex;
4271 propidx < TEXT_PROP_LAST; propidx++) {
4272 if (size > 0) {
4273 rrd_set_font_desc(im,propidx,NULL,size);
4274 }
4275 if ((int) strlen(optarg) > end+2) {
4276 if (optarg[end] == ':') {
4277 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4278 } else {
4279 rrd_set_error
4280 ("expected : after font size in '%s'",
4281 optarg);
4282 return;
4283 }
4284 }
4285 /* only run the for loop for DEFAULT (0) for
4286 all others, we break here. woodo programming */
4287 if (propidx == sindex && sindex != 0)
4288 break;
4289 }
4290 } else {
4291 rrd_set_error("invalid fonttag '%s'", prop);
4292 return;
4293 }
4294 } else {
4295 setlocale(LC_NUMERIC, old_locale);
4296 rrd_set_error("invalid text property format");
4297 return;
4298 }
4299 break;
4300 }
4301 case 'm':
4302 old_locale = setlocale(LC_NUMERIC, "C");
4303 im->zoom = atof(optarg);
4304 setlocale(LC_NUMERIC, old_locale);
4305 if (im->zoom <= 0.0) {
4306 rrd_set_error("zoom factor must be > 0");
4307 return;
4308 }
4309 break;
4310 case 't':
4311 strncpy(im->title, optarg, 150);
4312 im->title[150] = '\0';
4313 break;
4314 case 'R':
4315 if (strcmp(optarg, "normal") == 0) {
4316 cairo_font_options_set_antialias
4317 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4318 cairo_font_options_set_hint_style
4319 (im->font_options, CAIRO_HINT_STYLE_FULL);
4320 } else if (strcmp(optarg, "light") == 0) {
4321 cairo_font_options_set_antialias
4322 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4323 cairo_font_options_set_hint_style
4324 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4325 } else if (strcmp(optarg, "mono") == 0) {
4326 cairo_font_options_set_antialias
4327 (im->font_options, CAIRO_ANTIALIAS_NONE);
4328 cairo_font_options_set_hint_style
4329 (im->font_options, CAIRO_HINT_STYLE_FULL);
4330 } else {
4331 rrd_set_error("unknown font-render-mode '%s'", optarg);
4332 return;
4333 }
4334 break;
4335 case 'G':
4336 if (strcmp(optarg, "normal") == 0)
4337 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4338 else if (strcmp(optarg, "mono") == 0)
4339 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4340 else {
4341 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4342 return;
4343 }
4344 break;
4345 case 'B':
4346 /* not supported curently */
4347 break;
4348 case 'W':
4349 strncpy(im->watermark, optarg, 100);
4350 im->watermark[99] = '\0';
4351 break;
4352 case '?':
4353 if (optopt != 0)
4354 rrd_set_error("unknown option '%c'", optopt);
4355 else
4356 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4357 return;
4358 }
4359 }
4361 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4362 pango_layout_context_changed(im->layout);
4366 if (im->logarithmic && im->minval <= 0) {
4367 rrd_set_error
4368 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4369 return;
4370 }
4372 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4373 /* error string is set in rrd_parsetime.c */
4374 return;
4375 }
4377 if (start_tmp < 3600 * 24 * 365 * 10) {
4378 rrd_set_error
4379 ("the first entry to fetch should be after 1980 (%ld)",
4380 start_tmp);
4381 return;
4382 }
4384 if (end_tmp < start_tmp) {
4385 rrd_set_error
4386 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4387 return;
4388 }
4390 im->start = start_tmp;
4391 im->end = end_tmp;
4392 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4393 }
4395 int rrd_graph_color(
4396 image_desc_t
4397 *im,
4398 char *var,
4399 char *err,
4400 int optional)
4401 {
4402 char *color;
4403 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4405 color = strstr(var, "#");
4406 if (color == NULL) {
4407 if (optional == 0) {
4408 rrd_set_error("Found no color in %s", err);
4409 return 0;
4410 }
4411 return 0;
4412 } else {
4413 int n = 0;
4414 char *rest;
4415 long unsigned int col;
4417 rest = strstr(color, ":");
4418 if (rest != NULL)
4419 n = rest - color;
4420 else
4421 n = strlen(color);
4422 switch (n) {
4423 case 7:
4424 sscanf(color, "#%6lx%n", &col, &n);
4425 col = (col << 8) + 0xff /* shift left by 8 */ ;
4426 if (n != 7)
4427 rrd_set_error("Color problem in %s", err);
4428 break;
4429 case 9:
4430 sscanf(color, "#%8lx%n", &col, &n);
4431 if (n == 9)
4432 break;
4433 default:
4434 rrd_set_error("Color problem in %s", err);
4435 }
4436 if (rrd_test_error())
4437 return 0;
4438 gdp->col = gfx_hex_to_col(col);
4439 return n;
4440 }
4441 }
4444 int bad_format(
4445 char *fmt)
4446 {
4447 char *ptr;
4448 int n = 0;
4450 ptr = fmt;
4451 while (*ptr != '\0')
4452 if (*ptr++ == '%') {
4454 /* line cannot end with percent char */
4455 if (*ptr == '\0')
4456 return 1;
4457 /* '%s', '%S' and '%%' are allowed */
4458 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4459 ptr++;
4460 /* %c is allowed (but use only with vdef!) */
4461 else if (*ptr == 'c') {
4462 ptr++;
4463 n = 1;
4464 }
4466 /* or else '% 6.2lf' and such are allowed */
4467 else {
4468 /* optional padding character */
4469 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4470 ptr++;
4471 /* This should take care of 'm.n' with all three optional */
4472 while (*ptr >= '0' && *ptr <= '9')
4473 ptr++;
4474 if (*ptr == '.')
4475 ptr++;
4476 while (*ptr >= '0' && *ptr <= '9')
4477 ptr++;
4478 /* Either 'le', 'lf' or 'lg' must follow here */
4479 if (*ptr++ != 'l')
4480 return 1;
4481 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4482 ptr++;
4483 else
4484 return 1;
4485 n++;
4486 }
4487 }
4489 return (n != 1);
4490 }
4493 int vdef_parse(
4494 struct graph_desc_t
4495 *gdes,
4496 const char *const str)
4497 {
4498 /* A VDEF currently is either "func" or "param,func"
4499 * so the parsing is rather simple. Change if needed.
4500 */
4501 double param;
4502 char func[30];
4503 int n;
4504 char *old_locale;
4506 n = 0;
4507 old_locale = setlocale(LC_NUMERIC, "C");
4508 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4509 setlocale(LC_NUMERIC, old_locale);
4510 if (n == (int) strlen(str)) { /* matched */
4511 ;
4512 } else {
4513 n = 0;
4514 sscanf(str, "%29[A-Z]%n", func, &n);
4515 if (n == (int) strlen(str)) { /* matched */
4516 param = DNAN;
4517 } else {
4518 rrd_set_error
4519 ("Unknown function string '%s' in VDEF '%s'",
4520 str, gdes->vname);
4521 return -1;
4522 }
4523 }
4524 if (!strcmp("PERCENT", func))
4525 gdes->vf.op = VDEF_PERCENT;
4526 else if (!strcmp("MAXIMUM", func))
4527 gdes->vf.op = VDEF_MAXIMUM;
4528 else if (!strcmp("AVERAGE", func))
4529 gdes->vf.op = VDEF_AVERAGE;
4530 else if (!strcmp("STDEV", func))
4531 gdes->vf.op = VDEF_STDEV;
4532 else if (!strcmp("MINIMUM", func))
4533 gdes->vf.op = VDEF_MINIMUM;
4534 else if (!strcmp("TOTAL", func))
4535 gdes->vf.op = VDEF_TOTAL;
4536 else if (!strcmp("FIRST", func))
4537 gdes->vf.op = VDEF_FIRST;
4538 else if (!strcmp("LAST", func))
4539 gdes->vf.op = VDEF_LAST;
4540 else if (!strcmp("LSLSLOPE", func))
4541 gdes->vf.op = VDEF_LSLSLOPE;
4542 else if (!strcmp("LSLINT", func))
4543 gdes->vf.op = VDEF_LSLINT;
4544 else if (!strcmp("LSLCORREL", func))
4545 gdes->vf.op = VDEF_LSLCORREL;
4546 else {
4547 rrd_set_error
4548 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4549 return -1;
4550 };
4551 switch (gdes->vf.op) {
4552 case VDEF_PERCENT:
4553 if (isnan(param)) { /* no parameter given */
4554 rrd_set_error
4555 ("Function '%s' needs parameter in VDEF '%s'\n",
4556 func, gdes->vname);
4557 return -1;
4558 };
4559 if (param >= 0.0 && param <= 100.0) {
4560 gdes->vf.param = param;
4561 gdes->vf.val = DNAN; /* undefined */
4562 gdes->vf.when = 0; /* undefined */
4563 } else {
4564 rrd_set_error
4565 ("Parameter '%f' out of range in VDEF '%s'\n",
4566 param, gdes->vname);
4567 return -1;
4568 };
4569 break;
4570 case VDEF_MAXIMUM:
4571 case VDEF_AVERAGE:
4572 case VDEF_STDEV:
4573 case VDEF_MINIMUM:
4574 case VDEF_TOTAL:
4575 case VDEF_FIRST:
4576 case VDEF_LAST:
4577 case VDEF_LSLSLOPE:
4578 case VDEF_LSLINT:
4579 case VDEF_LSLCORREL:
4580 if (isnan(param)) {
4581 gdes->vf.param = DNAN;
4582 gdes->vf.val = DNAN;
4583 gdes->vf.when = 0;
4584 } else {
4585 rrd_set_error
4586 ("Function '%s' needs no parameter in VDEF '%s'\n",
4587 func, gdes->vname);
4588 return -1;
4589 };
4590 break;
4591 };
4592 return 0;
4593 }
4596 int vdef_calc(
4597 image_desc_t *im,
4598 int gdi)
4599 {
4600 graph_desc_t *src, *dst;
4601 rrd_value_t *data;
4602 long step, steps;
4604 dst = &im->gdes[gdi];
4605 src = &im->gdes[dst->vidx];
4606 data = src->data + src->ds;
4608 steps = (src->end - src->start) / src->step;
4609 #if 0
4610 printf
4611 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4612 src->start, src->end, steps);
4613 #endif
4614 switch (dst->vf.op) {
4615 case VDEF_PERCENT:{
4616 rrd_value_t *array;
4617 int field;
4618 if ((array = (rrd_value_t*)(malloc(steps * sizeof(double)))) == NULL) {
4619 rrd_set_error("malloc VDEV_PERCENT");
4620 return -1;
4621 }
4622 for (step = 0; step < steps; step++) {
4623 array[step] = data[step * src->ds_cnt];
4624 }
4625 qsort(array, step, sizeof(double), vdef_percent_compar);
4626 field = (steps - 1) * dst->vf.param / 100;
4627 dst->vf.val = array[field];
4628 dst->vf.when = 0; /* no time component */
4629 free(array);
4630 #if 0
4631 for (step = 0; step < steps; step++)
4632 printf("DEBUG: %3li:%10.2f %c\n",
4633 step, array[step], step == field ? '*' : ' ');
4634 #endif
4635 }
4636 break;
4637 case VDEF_MAXIMUM:
4638 step = 0;
4639 while (step != steps && isnan(data[step * src->ds_cnt]))
4640 step++;
4641 if (step == steps) {
4642 dst->vf.val = DNAN;
4643 dst->vf.when = 0;
4644 } else {
4645 dst->vf.val = data[step * src->ds_cnt];
4646 dst->vf.when = src->start + (step + 1) * src->step;
4647 }
4648 while (step != steps) {
4649 if (finite(data[step * src->ds_cnt])) {
4650 if (data[step * src->ds_cnt] > dst->vf.val) {
4651 dst->vf.val = data[step * src->ds_cnt];
4652 dst->vf.when = src->start + (step + 1) * src->step;
4653 }
4654 }
4655 step++;
4656 }
4657 break;
4658 case VDEF_TOTAL:
4659 case VDEF_STDEV:
4660 case VDEF_AVERAGE:{
4661 int cnt = 0;
4662 double sum = 0.0;
4663 double average = 0.0;
4665 for (step = 0; step < steps; step++) {
4666 if (finite(data[step * src->ds_cnt])) {
4667 sum += data[step * src->ds_cnt];
4668 cnt++;
4669 };
4670 }
4671 if (cnt) {
4672 if (dst->vf.op == VDEF_TOTAL) {
4673 dst->vf.val = sum * src->step;
4674 dst->vf.when = 0; /* no time component */
4675 } else if (dst->vf.op == VDEF_AVERAGE) {
4676 dst->vf.val = sum / cnt;
4677 dst->vf.when = 0; /* no time component */
4678 } else {
4679 average = sum / cnt;
4680 sum = 0.0;
4681 for (step = 0; step < steps; step++) {
4682 if (finite(data[step * src->ds_cnt])) {
4683 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4684 };
4685 }
4686 dst->vf.val = pow(sum / cnt, 0.5);
4687 dst->vf.when = 0; /* no time component */
4688 };
4689 } else {
4690 dst->vf.val = DNAN;
4691 dst->vf.when = 0;
4692 }
4693 }
4694 break;
4695 case VDEF_MINIMUM:
4696 step = 0;
4697 while (step != steps && isnan(data[step * src->ds_cnt]))
4698 step++;
4699 if (step == steps) {
4700 dst->vf.val = DNAN;
4701 dst->vf.when = 0;
4702 } else {
4703 dst->vf.val = data[step * src->ds_cnt];
4704 dst->vf.when = src->start + (step + 1) * src->step;
4705 }
4706 while (step != steps) {
4707 if (finite(data[step * src->ds_cnt])) {
4708 if (data[step * src->ds_cnt] < dst->vf.val) {
4709 dst->vf.val = data[step * src->ds_cnt];
4710 dst->vf.when = src->start + (step + 1) * src->step;
4711 }
4712 }
4713 step++;
4714 }
4715 break;
4716 case VDEF_FIRST:
4717 /* The time value returned here is one step before the
4718 * actual time value. This is the start of the first
4719 * non-NaN interval.
4720 */
4721 step = 0;
4722 while (step != steps && isnan(data[step * src->ds_cnt]))
4723 step++;
4724 if (step == steps) { /* all entries were NaN */
4725 dst->vf.val = DNAN;
4726 dst->vf.when = 0;
4727 } else {
4728 dst->vf.val = data[step * src->ds_cnt];
4729 dst->vf.when = src->start + step * src->step;
4730 }
4731 break;
4732 case VDEF_LAST:
4733 /* The time value returned here is the
4734 * actual time value. This is the end of the last
4735 * non-NaN interval.
4736 */
4737 step = steps - 1;
4738 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4739 step--;
4740 if (step < 0) { /* all entries were NaN */
4741 dst->vf.val = DNAN;
4742 dst->vf.when = 0;
4743 } else {
4744 dst->vf.val = data[step * src->ds_cnt];
4745 dst->vf.when = src->start + (step + 1) * src->step;
4746 }
4747 break;
4748 case VDEF_LSLSLOPE:
4749 case VDEF_LSLINT:
4750 case VDEF_LSLCORREL:{
4751 /* Bestfit line by linear least squares method */
4753 int cnt = 0;
4754 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4756 SUMx = 0;
4757 SUMy = 0;
4758 SUMxy = 0;
4759 SUMxx = 0;
4760 SUMyy = 0;
4761 for (step = 0; step < steps; step++) {
4762 if (finite(data[step * src->ds_cnt])) {
4763 cnt++;
4764 SUMx += step;
4765 SUMxx += step * step;
4766 SUMxy += step * data[step * src->ds_cnt];
4767 SUMy += data[step * src->ds_cnt];
4768 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4769 };
4770 }
4772 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4773 y_intercept = (SUMy - slope * SUMx) / cnt;
4774 correl =
4775 (SUMxy -
4776 (SUMx * SUMy) / cnt) /
4777 sqrt((SUMxx -
4778 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4779 if (cnt) {
4780 if (dst->vf.op == VDEF_LSLSLOPE) {
4781 dst->vf.val = slope;
4782 dst->vf.when = 0;
4783 } else if (dst->vf.op == VDEF_LSLINT) {
4784 dst->vf.val = y_intercept;
4785 dst->vf.when = 0;
4786 } else if (dst->vf.op == VDEF_LSLCORREL) {
4787 dst->vf.val = correl;
4788 dst->vf.when = 0;
4789 };
4790 } else {
4791 dst->vf.val = DNAN;
4792 dst->vf.when = 0;
4793 }
4794 }
4795 break;
4796 }
4797 return 0;
4798 }
4800 /* NaN < -INF < finite_values < INF */
4801 int vdef_percent_compar(
4802 const void
4803 *a,
4804 const void
4805 *b)
4806 {
4807 /* Equality is not returned; this doesn't hurt except
4808 * (maybe) for a little performance.
4809 */
4811 /* First catch NaN values. They are smallest */
4812 if (isnan(*(double *) a))
4813 return -1;
4814 if (isnan(*(double *) b))
4815 return 1;
4816 /* NaN doesn't reach this part so INF and -INF are extremes.
4817 * The sign from isinf() is compatible with the sign we return
4818 */
4819 if (isinf(*(double *) a))
4820 return isinf(*(double *) a);
4821 if (isinf(*(double *) b))
4822 return isinf(*(double *) b);
4823 /* If we reach this, both values must be finite */
4824 if (*(double *) a < *(double *) b)
4825 return -1;
4826 else
4827 return 1;
4828 }
4830 void grinfo_push(
4831 image_desc_t *im,
4832 char *key,
4833 rrd_info_type_t type,
4834 rrd_infoval_t value)
4835 {
4836 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4837 if (im->grinfo == NULL) {
4838 im->grinfo = im->grinfo_current;
4839 }
4840 }