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