0d25f99b01f601193288eb7f3acc9fff94f329d0
1 /****************************************************************************
2 * RRDtool 1.3.7 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;
3036 /* pull the data from the rrd files ... */
3037 if (data_fetch(im) == -1)
3038 return -1;
3039 /* evaluate VDEF and CDEF operations ... */
3040 if (data_calc(im) == -1)
3041 return -1;
3042 /* calculate and PRINT and GPRINT definitions. We have to do it at
3043 * this point because it will affect the length of the legends
3044 * if there are no graph elements (i==0) we stop here ...
3045 * if we are lazy, try to quit ...
3046 */
3047 i = print_calc(im);
3048 if (i < 0)
3049 return -1;
3051 if (i == 0)
3052 return 0;
3054 /**************************************************************
3055 *** Calculating sizes and locations became a bit confusing ***
3056 *** so I moved this into a separate function. ***
3057 **************************************************************/
3058 if (graph_size_location(im, i) == -1)
3059 return -1;
3061 info.u_cnt = im->xorigin;
3062 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3063 info.u_cnt = im->yorigin - im->ysize;
3064 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3065 info.u_cnt = im->xsize;
3066 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3067 info.u_cnt = im->ysize;
3068 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3069 info.u_cnt = im->ximg;
3070 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3071 info.u_cnt = im->yimg;
3072 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3073 info.u_cnt = im->start;
3074 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3075 info.u_cnt = im->end;
3076 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3078 /* if we want and can be lazy ... quit now */
3079 if (lazy)
3080 return 0;
3082 /* get actual drawing data and find min and max values */
3083 if (data_proc(im) == -1)
3084 return -1;
3085 if (!im->logarithmic) {
3086 si_unit(im);
3087 }
3089 /* identify si magnitude Kilo, Mega Giga ? */
3090 if (!im->rigid && !im->logarithmic)
3091 expand_range(im); /* make sure the upper and lower limit are
3092 sensible values */
3094 info.u_val = im->minval;
3095 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3096 info.u_val = im->maxval;
3097 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3099 if (!calc_horizontal_grid(im))
3100 return -1;
3101 /* reset precalc */
3102 ytr(im, DNAN);
3103 /* if (im->gridfit)
3104 apply_gridfit(im); */
3105 /* the actual graph is created by going through the individual
3106 graph elements and then drawing them */
3107 cairo_surface_destroy(im->surface);
3108 switch (im->imgformat) {
3109 case IF_PNG:
3110 im->surface =
3111 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3112 im->ximg * im->zoom,
3113 im->yimg * im->zoom);
3114 break;
3115 case IF_PDF:
3116 im->gridfit = 0;
3117 im->surface = strlen(im->graphfile)
3118 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3119 im->yimg * im->zoom)
3120 : cairo_pdf_surface_create_for_stream
3121 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3122 break;
3123 case IF_EPS:
3124 im->gridfit = 0;
3125 im->surface = strlen(im->graphfile)
3126 ?
3127 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3128 im->yimg * im->zoom)
3129 : cairo_ps_surface_create_for_stream
3130 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3131 break;
3132 case IF_SVG:
3133 im->gridfit = 0;
3134 im->surface = strlen(im->graphfile)
3135 ?
3136 cairo_svg_surface_create(im->
3137 graphfile,
3138 im->ximg * im->zoom, im->yimg * im->zoom)
3139 : cairo_svg_surface_create_for_stream
3140 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3141 cairo_svg_surface_restrict_to_version
3142 (im->surface, CAIRO_SVG_VERSION_1_1);
3143 break;
3144 };
3145 cairo_destroy(im->cr);
3146 im->cr = cairo_create(im->surface);
3147 cairo_set_antialias(im->cr, im->graph_antialias);
3148 cairo_scale(im->cr, im->zoom, im->zoom);
3149 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3150 gfx_new_area(im, 0, 0, 0, im->yimg,
3151 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3152 gfx_add_point(im, im->ximg, 0);
3153 gfx_close_path(im);
3154 gfx_new_area(im, im->xorigin,
3155 im->yorigin,
3156 im->xorigin +
3157 im->xsize, im->yorigin,
3158 im->xorigin +
3159 im->xsize,
3160 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3161 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3162 gfx_close_path(im);
3163 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3164 im->xsize, im->ysize + 2.0);
3165 cairo_clip(im->cr);
3166 if (im->minval > 0.0)
3167 areazero = im->minval;
3168 if (im->maxval < 0.0)
3169 areazero = im->maxval;
3170 for (i = 0; i < im->gdes_c; i++) {
3171 switch (im->gdes[i].gf) {
3172 case GF_CDEF:
3173 case GF_VDEF:
3174 case GF_DEF:
3175 case GF_PRINT:
3176 case GF_GPRINT:
3177 case GF_COMMENT:
3178 case GF_TEXTALIGN:
3179 case GF_HRULE:
3180 case GF_VRULE:
3181 case GF_XPORT:
3182 case GF_SHIFT:
3183 break;
3184 case GF_TICK:
3185 for (ii = 0; ii < im->xsize; ii++) {
3186 if (!isnan(im->gdes[i].p_data[ii])
3187 && im->gdes[i].p_data[ii] != 0.0) {
3188 if (im->gdes[i].yrule > 0) {
3189 gfx_line(im,
3190 im->xorigin + ii,
3191 im->yorigin + 1.0,
3192 im->xorigin + ii,
3193 im->yorigin -
3194 im->gdes[i].yrule *
3195 im->ysize, 1.0, im->gdes[i].col);
3196 } else if (im->gdes[i].yrule < 0) {
3197 gfx_line(im,
3198 im->xorigin + ii,
3199 im->yorigin - im->ysize - 1.0,
3200 im->xorigin + ii,
3201 im->yorigin - im->ysize -
3202 im->gdes[i].
3203 yrule *
3204 im->ysize, 1.0, im->gdes[i].col);
3205 }
3206 }
3207 }
3208 break;
3209 case GF_LINE:
3210 case GF_AREA:
3211 /* fix data points at oo and -oo */
3212 for (ii = 0; ii < im->xsize; ii++) {
3213 if (isinf(im->gdes[i].p_data[ii])) {
3214 if (im->gdes[i].p_data[ii] > 0) {
3215 im->gdes[i].p_data[ii] = im->maxval;
3216 } else {
3217 im->gdes[i].p_data[ii] = im->minval;
3218 }
3220 }
3221 } /* for */
3223 /* *******************************************************
3224 a ___. (a,t)
3225 | | ___
3226 ____| | | |
3227 | |___|
3228 -------|--t-1--t--------------------------------
3230 if we know the value at time t was a then
3231 we draw a square from t-1 to t with the value a.
3233 ********************************************************* */
3234 if (im->gdes[i].col.alpha != 0.0) {
3235 /* GF_LINE and friend */
3236 if (im->gdes[i].gf == GF_LINE) {
3237 double last_y = 0.0;
3238 int draw_on = 0;
3240 cairo_save(im->cr);
3241 cairo_new_path(im->cr);
3242 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3243 if (im->gdes[i].dash) {
3244 cairo_set_dash(im->cr,
3245 im->gdes[i].p_dashes,
3246 im->gdes[i].ndash, im->gdes[i].offset);
3247 }
3249 for (ii = 1; ii < im->xsize; ii++) {
3250 if (isnan(im->gdes[i].p_data[ii])
3251 || (im->slopemode == 1
3252 && isnan(im->gdes[i].p_data[ii - 1]))) {
3253 draw_on = 0;
3254 continue;
3255 }
3256 if (draw_on == 0) {
3257 last_y = ytr(im, im->gdes[i].p_data[ii]);
3258 if (im->slopemode == 0) {
3259 double x = ii - 1 + im->xorigin;
3260 double y = last_y;
3262 gfx_line_fit(im, &x, &y);
3263 cairo_move_to(im->cr, x, y);
3264 x = ii + im->xorigin;
3265 y = last_y;
3266 gfx_line_fit(im, &x, &y);
3267 cairo_line_to(im->cr, x, y);
3268 } else {
3269 double x = ii - 1 + im->xorigin;
3270 double y =
3271 ytr(im, im->gdes[i].p_data[ii - 1]);
3272 gfx_line_fit(im, &x, &y);
3273 cairo_move_to(im->cr, x, y);
3274 x = ii + im->xorigin;
3275 y = last_y;
3276 gfx_line_fit(im, &x, &y);
3277 cairo_line_to(im->cr, x, y);
3278 }
3279 draw_on = 1;
3280 } else {
3281 double x1 = ii + im->xorigin;
3282 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3284 if (im->slopemode == 0
3285 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3286 double x = ii - 1 + im->xorigin;
3287 double y = y1;
3289 gfx_line_fit(im, &x, &y);
3290 cairo_line_to(im->cr, x, y);
3291 };
3292 last_y = y1;
3293 gfx_line_fit(im, &x1, &y1);
3294 cairo_line_to(im->cr, x1, y1);
3295 };
3296 }
3297 cairo_set_source_rgba(im->cr,
3298 im->gdes[i].
3299 col.red,
3300 im->gdes[i].
3301 col.green,
3302 im->gdes[i].
3303 col.blue, im->gdes[i].col.alpha);
3304 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3305 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3306 cairo_stroke(im->cr);
3307 cairo_restore(im->cr);
3308 } else {
3309 int idxI = -1;
3310 double *foreY =
3311 (double *) malloc(sizeof(double) * im->xsize * 2);
3312 double *foreX =
3313 (double *) malloc(sizeof(double) * im->xsize * 2);
3314 double *backY =
3315 (double *) malloc(sizeof(double) * im->xsize * 2);
3316 double *backX =
3317 (double *) malloc(sizeof(double) * im->xsize * 2);
3318 int drawem = 0;
3320 for (ii = 0; ii <= im->xsize; ii++) {
3321 double ybase, ytop;
3323 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3324 int cntI = 1;
3325 int lastI = 0;
3327 while (cntI < idxI
3328 &&
3329 AlmostEqual2sComplement(foreY
3330 [lastI],
3331 foreY[cntI], 4)
3332 &&
3333 AlmostEqual2sComplement(foreY
3334 [lastI],
3335 foreY
3336 [cntI + 1], 4)) {
3337 cntI++;
3338 }
3339 gfx_new_area(im,
3340 backX[0], backY[0],
3341 foreX[0], foreY[0],
3342 foreX[cntI],
3343 foreY[cntI], im->gdes[i].col);
3344 while (cntI < idxI) {
3345 lastI = cntI;
3346 cntI++;
3347 while (cntI < idxI
3348 &&
3349 AlmostEqual2sComplement(foreY
3350 [lastI],
3351 foreY[cntI], 4)
3352 &&
3353 AlmostEqual2sComplement(foreY
3354 [lastI],
3355 foreY
3356 [cntI
3357 + 1], 4)) {
3358 cntI++;
3359 }
3360 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3361 }
3362 gfx_add_point(im, backX[idxI], backY[idxI]);
3363 while (idxI > 1) {
3364 lastI = idxI;
3365 idxI--;
3366 while (idxI > 1
3367 &&
3368 AlmostEqual2sComplement(backY
3369 [lastI],
3370 backY[idxI], 4)
3371 &&
3372 AlmostEqual2sComplement(backY
3373 [lastI],
3374 backY
3375 [idxI
3376 - 1], 4)) {
3377 idxI--;
3378 }
3379 gfx_add_point(im, backX[idxI], backY[idxI]);
3380 }
3381 idxI = -1;
3382 drawem = 0;
3383 gfx_close_path(im);
3384 }
3385 if (drawem != 0) {
3386 drawem = 0;
3387 idxI = -1;
3388 }
3389 if (ii == im->xsize)
3390 break;
3391 if (im->slopemode == 0 && ii == 0) {
3392 continue;
3393 }
3394 if (isnan(im->gdes[i].p_data[ii])) {
3395 drawem = 1;
3396 continue;
3397 }
3398 ytop = ytr(im, im->gdes[i].p_data[ii]);
3399 if (lastgdes && im->gdes[i].stack) {
3400 ybase = ytr(im, lastgdes->p_data[ii]);
3401 } else {
3402 ybase = ytr(im, areazero);
3403 }
3404 if (ybase == ytop) {
3405 drawem = 1;
3406 continue;
3407 }
3409 if (ybase > ytop) {
3410 double extra = ytop;
3412 ytop = ybase;
3413 ybase = extra;
3414 }
3415 if (im->slopemode == 0) {
3416 backY[++idxI] = ybase - 0.2;
3417 backX[idxI] = ii + im->xorigin - 1;
3418 foreY[idxI] = ytop + 0.2;
3419 foreX[idxI] = ii + im->xorigin - 1;
3420 }
3421 backY[++idxI] = ybase - 0.2;
3422 backX[idxI] = ii + im->xorigin;
3423 foreY[idxI] = ytop + 0.2;
3424 foreX[idxI] = ii + im->xorigin;
3425 }
3426 /* close up any remaining area */
3427 free(foreY);
3428 free(foreX);
3429 free(backY);
3430 free(backX);
3431 } /* else GF_LINE */
3432 }
3433 /* if color != 0x0 */
3434 /* make sure we do not run into trouble when stacking on NaN */
3435 for (ii = 0; ii < im->xsize; ii++) {
3436 if (isnan(im->gdes[i].p_data[ii])) {
3437 if (lastgdes && (im->gdes[i].stack)) {
3438 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3439 } else {
3440 im->gdes[i].p_data[ii] = areazero;
3441 }
3442 }
3443 }
3444 lastgdes = &(im->gdes[i]);
3445 break;
3446 case GF_STACK:
3447 rrd_set_error
3448 ("STACK should already be turned into LINE or AREA here");
3449 return -1;
3450 break;
3451 } /* switch */
3452 }
3453 cairo_reset_clip(im->cr);
3455 /* grid_paint also does the text */
3456 if (!(im->extra_flags & ONLY_GRAPH))
3457 grid_paint(im);
3458 if (!(im->extra_flags & ONLY_GRAPH))
3459 axis_paint(im);
3460 /* the RULES are the last thing to paint ... */
3461 for (i = 0; i < im->gdes_c; i++) {
3463 switch (im->gdes[i].gf) {
3464 case GF_HRULE:
3465 if (im->gdes[i].yrule >= im->minval
3466 && im->gdes[i].yrule <= im->maxval) {
3467 cairo_save(im->cr);
3468 if (im->gdes[i].dash) {
3469 cairo_set_dash(im->cr,
3470 im->gdes[i].p_dashes,
3471 im->gdes[i].ndash, im->gdes[i].offset);
3472 }
3473 gfx_line(im, im->xorigin,
3474 ytr(im, im->gdes[i].yrule),
3475 im->xorigin + im->xsize,
3476 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3477 cairo_stroke(im->cr);
3478 cairo_restore(im->cr);
3479 }
3480 break;
3481 case GF_VRULE:
3482 if (im->gdes[i].xrule >= im->start
3483 && im->gdes[i].xrule <= im->end) {
3484 cairo_save(im->cr);
3485 if (im->gdes[i].dash) {
3486 cairo_set_dash(im->cr,
3487 im->gdes[i].p_dashes,
3488 im->gdes[i].ndash, im->gdes[i].offset);
3489 }
3490 gfx_line(im,
3491 xtr(im, im->gdes[i].xrule),
3492 im->yorigin, xtr(im,
3493 im->
3494 gdes[i].
3495 xrule),
3496 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3497 cairo_stroke(im->cr);
3498 cairo_restore(im->cr);
3499 }
3500 break;
3501 default:
3502 break;
3503 }
3504 }
3507 switch (im->imgformat) {
3508 case IF_PNG:
3509 {
3510 cairo_status_t status;
3512 status = strlen(im->graphfile) ?
3513 cairo_surface_write_to_png(im->surface, im->graphfile)
3514 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3515 im);
3517 if (status != CAIRO_STATUS_SUCCESS) {
3518 rrd_set_error("Could not save png to '%s'", im->graphfile);
3519 return 1;
3520 }
3521 break;
3522 }
3523 default:
3524 if (strlen(im->graphfile)) {
3525 cairo_show_page(im->cr);
3526 } else {
3527 cairo_surface_finish(im->surface);
3528 }
3529 break;
3530 }
3532 return 0;
3533 }
3536 /*****************************************************
3537 * graph stuff
3538 *****************************************************/
3540 int gdes_alloc(
3541 image_desc_t *im)
3542 {
3544 im->gdes_c++;
3545 if ((im->gdes = (graph_desc_t *)
3546 rrd_realloc(im->gdes, (im->gdes_c)
3547 * sizeof(graph_desc_t))) == NULL) {
3548 rrd_set_error("realloc graph_descs");
3549 return -1;
3550 }
3553 im->gdes[im->gdes_c - 1].step = im->step;
3554 im->gdes[im->gdes_c - 1].step_orig = im->step;
3555 im->gdes[im->gdes_c - 1].stack = 0;
3556 im->gdes[im->gdes_c - 1].linewidth = 0;
3557 im->gdes[im->gdes_c - 1].debug = 0;
3558 im->gdes[im->gdes_c - 1].start = im->start;
3559 im->gdes[im->gdes_c - 1].start_orig = im->start;
3560 im->gdes[im->gdes_c - 1].end = im->end;
3561 im->gdes[im->gdes_c - 1].end_orig = im->end;
3562 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3563 im->gdes[im->gdes_c - 1].data = NULL;
3564 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3565 im->gdes[im->gdes_c - 1].data_first = 0;
3566 im->gdes[im->gdes_c - 1].p_data = NULL;
3567 im->gdes[im->gdes_c - 1].rpnp = NULL;
3568 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3569 im->gdes[im->gdes_c - 1].shift = 0.0;
3570 im->gdes[im->gdes_c - 1].dash = 0;
3571 im->gdes[im->gdes_c - 1].ndash = 0;
3572 im->gdes[im->gdes_c - 1].offset = 0;
3573 im->gdes[im->gdes_c - 1].col.red = 0.0;
3574 im->gdes[im->gdes_c - 1].col.green = 0.0;
3575 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3576 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3577 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3578 im->gdes[im->gdes_c - 1].format[0] = '\0';
3579 im->gdes[im->gdes_c - 1].strftm = 0;
3580 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3581 im->gdes[im->gdes_c - 1].ds = -1;
3582 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3583 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3584 im->gdes[im->gdes_c - 1].yrule = DNAN;
3585 im->gdes[im->gdes_c - 1].xrule = 0;
3586 return 0;
3587 }
3589 /* copies input untill the first unescaped colon is found
3590 or until input ends. backslashes have to be escaped as well */
3591 int scan_for_col(
3592 const char *const input,
3593 int len,
3594 char *const output)
3595 {
3596 int inp, outp = 0;
3598 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3599 if (input[inp] == '\\'
3600 && input[inp + 1] != '\0'
3601 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3602 output[outp++] = input[++inp];
3603 } else {
3604 output[outp++] = input[inp];
3605 }
3606 }
3607 output[outp] = '\0';
3608 return inp;
3609 }
3611 /* Now just a wrapper around rrd_graph_v */
3612 int rrd_graph(
3613 int argc,
3614 char **argv,
3615 char ***prdata,
3616 int *xsize,
3617 int *ysize,
3618 FILE * stream,
3619 double *ymin,
3620 double *ymax)
3621 {
3622 int prlines = 0;
3623 rrd_info_t *grinfo = NULL;
3624 rrd_info_t *walker;
3626 grinfo = rrd_graph_v(argc, argv);
3627 if (grinfo == NULL)
3628 return -1;
3629 walker = grinfo;
3630 (*prdata) = NULL;
3631 while (walker) {
3632 if (strcmp(walker->key, "image_info") == 0) {
3633 prlines++;
3634 if (((*prdata) =
3635 (char**)(rrd_realloc((*prdata),
3636 (prlines + 1) * sizeof(char *)))) == NULL) {
3637 rrd_set_error("realloc prdata");
3638 return 0;
3639 }
3640 /* imginfo goes to position 0 in the prdata array */
3641 (*prdata)[prlines - 1] = (char*)(malloc((strlen(walker->value.u_str)
3642 + 2) * sizeof(char)));
3643 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3644 (*prdata)[prlines] = NULL;
3645 }
3646 /* skip anything else */
3647 walker = walker->next;
3648 }
3649 walker = grinfo;
3650 *xsize = 0;
3651 *ysize = 0;
3652 *ymin = 0;
3653 *ymax = 0;
3654 while (walker) {
3655 if (strcmp(walker->key, "image_width") == 0) {
3656 *xsize = walker->value.u_cnt;
3657 } else if (strcmp(walker->key, "image_height") == 0) {
3658 *ysize = walker->value.u_cnt;
3659 } else if (strcmp(walker->key, "value_min") == 0) {
3660 *ymin = walker->value.u_val;
3661 } else if (strcmp(walker->key, "value_max") == 0) {
3662 *ymax = walker->value.u_val;
3663 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3664 prlines++;
3665 if (((*prdata) =
3666 (char**)(rrd_realloc((*prdata),
3667 (prlines + 1) * sizeof(char *)))) == NULL) {
3668 rrd_set_error("realloc prdata");
3669 return 0;
3670 }
3671 (*prdata)[prlines - 1] = (char*)(malloc((strlen(walker->value.u_str)
3672 + 2) * sizeof(char)));
3673 (*prdata)[prlines] = NULL;
3674 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3675 } else if (strcmp(walker->key, "image") == 0) {
3676 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3677 (stream ? stream : stdout));
3678 }
3679 /* skip anything else */
3680 walker = walker->next;
3681 }
3682 rrd_info_free(grinfo);
3683 return 0;
3684 }
3687 /* Some surgery done on this function, it became ridiculously big.
3688 ** Things moved:
3689 ** - initializing now in rrd_graph_init()
3690 ** - options parsing now in rrd_graph_options()
3691 ** - script parsing now in rrd_graph_script()
3692 */
3693 rrd_info_t *rrd_graph_v(
3694 int argc,
3695 char **argv)
3696 {
3697 image_desc_t im;
3698 rrd_info_t *grinfo;
3699 rrd_graph_init(&im);
3700 /* a dummy surface so that we can measure text sizes for placements */
3702 rrd_graph_options(argc, argv, &im);
3703 if (rrd_test_error()) {
3704 rrd_info_free(im.grinfo);
3705 im_free(&im);
3706 return NULL;
3707 }
3709 if (optind >= argc) {
3710 rrd_info_free(im.grinfo);
3711 im_free(&im);
3712 rrd_set_error("missing filename");
3713 return NULL;
3714 }
3716 if (strlen(argv[optind]) >= MAXPATH) {
3717 rrd_set_error("filename (including path) too long");
3718 rrd_info_free(im.grinfo);
3719 im_free(&im);
3720 return NULL;
3721 }
3723 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3724 im.graphfile[MAXPATH - 1] = '\0';
3726 if (strcmp(im.graphfile, "-") == 0) {
3727 im.graphfile[0] = '\0';
3728 }
3730 rrd_graph_script(argc, argv, &im, 1);
3731 if (rrd_test_error()) {
3732 rrd_info_free(im.grinfo);
3733 im_free(&im);
3734 return NULL;
3735 }
3737 /* Everything is now read and the actual work can start */
3739 if (graph_paint(&im) == -1) {
3740 rrd_info_free(im.grinfo);
3741 im_free(&im);
3742 return NULL;
3743 }
3746 /* The image is generated and needs to be output.
3747 ** Also, if needed, print a line with information about the image.
3748 */
3750 if (im.imginfo) {
3751 rrd_infoval_t info;
3752 char *path;
3753 char *filename;
3755 path = strdup(im.graphfile);
3756 filename = basename(path);
3757 info.u_str =
3758 sprintf_alloc(im.imginfo,
3759 filename,
3760 (long) (im.zoom *
3761 im.ximg), (long) (im.zoom * im.yimg));
3762 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3763 free(info.u_str);
3764 free(path);
3765 }
3766 if (im.rendered_image) {
3767 rrd_infoval_t img;
3769 img.u_blo.size = im.rendered_image_size;
3770 img.u_blo.ptr = im.rendered_image;
3771 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3772 }
3773 grinfo = im.grinfo;
3774 im_free(&im);
3775 return grinfo;
3776 }
3778 static void
3779 rrd_set_font_desc (
3780 image_desc_t *im,int prop,char *font, double size ){
3781 if (font){
3782 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3783 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3784 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3785 };
3786 if (size > 0){
3787 im->text_prop[prop].size = size;
3788 };
3789 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3790 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3791 };
3792 }
3794 void rrd_graph_init(
3795 image_desc_t
3796 *im)
3797 {
3798 unsigned int i;
3799 char *deffont = getenv("RRD_DEFAULT_FONT");
3800 static PangoFontMap *fontmap = NULL;
3801 PangoContext *context;
3803 #ifdef HAVE_TZSET
3804 tzset();
3805 #endif
3806 #ifdef HAVE_SETLOCALE
3807 setlocale(LC_TIME, "");
3808 #ifdef HAVE_MBSTOWCS
3809 setlocale(LC_CTYPE, "");
3810 #endif
3811 #endif
3812 im->base = 1000;
3813 im->draw_x_grid = 1;
3814 im->draw_y_grid = 1;
3815 im->extra_flags = 0;
3816 im->font_options = cairo_font_options_create();
3817 im->forceleftspace = 0;
3818 im->gdes_c = 0;
3819 im->gdes = NULL;
3820 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3821 im->grid_dash_off = 1;
3822 im->grid_dash_on = 1;
3823 im->gridfit = 1;
3824 im->grinfo = (rrd_info_t *) NULL;
3825 im->grinfo_current = (rrd_info_t *) NULL;
3826 im->imgformat = IF_PNG;
3827 im->imginfo = NULL;
3828 im->lazy = 0;
3829 im->logarithmic = 0;
3830 im->maxval = DNAN;
3831 im->minval = 0;
3832 im->minval = DNAN;
3833 im->prt_c = 0;
3834 im->rigid = 0;
3835 im->rendered_image_size = 0;
3836 im->rendered_image = NULL;
3837 im->slopemode = 0;
3838 im->step = 0;
3839 im->symbol = ' ';
3840 im->tabwidth = 40.0;
3841 im->title[0] = '\0';
3842 im->unitsexponent = 9999;
3843 im->unitslength = 6;
3844 im->viewfactor = 1.0;
3845 im->watermark[0] = '\0';
3846 im->with_markup = 0;
3847 im->ximg = 0;
3848 im->xlab_user.minsec = -1;
3849 im->xorigin = 0;
3850 im->xsize = 400;
3851 im->ygridstep = DNAN;
3852 im->yimg = 0;
3853 im->ylegend[0] = '\0';
3854 im->second_axis_scale = 0; /* 0 disables it */
3855 im->second_axis_shift = 0; /* no shift by default */
3856 im->second_axis_legend[0] = '\0';
3857 im->second_axis_format[0] = '\0';
3858 im->yorigin = 0;
3859 im->ysize = 100;
3860 im->zoom = 1;
3862 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3863 im->cr = cairo_create(im->surface);
3865 for (i = 0; i < DIM(text_prop); i++) {
3866 im->text_prop[i].size = -1;
3867 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3868 }
3870 if (fontmap == NULL){
3871 fontmap = pango_cairo_font_map_get_default();
3872 }
3874 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3876 pango_cairo_context_set_resolution(context, 100);
3878 pango_cairo_update_context(im->cr,context);
3880 im->layout = pango_layout_new(context);
3882 // im->layout = pango_cairo_create_layout(im->cr);
3885 cairo_font_options_set_hint_style
3886 (im->font_options, CAIRO_HINT_STYLE_FULL);
3887 cairo_font_options_set_hint_metrics
3888 (im->font_options, CAIRO_HINT_METRICS_ON);
3889 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3893 for (i = 0; i < DIM(graph_col); i++)
3894 im->graph_col[i] = graph_col[i];
3897 }
3900 void rrd_graph_options(
3901 int argc,
3902 char *argv[],
3903 image_desc_t
3904 *im)
3905 {
3906 int stroff;
3907 char *parsetime_error = NULL;
3908 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3909 time_t start_tmp = 0, end_tmp = 0;
3910 long long_tmp;
3911 rrd_time_value_t start_tv, end_tv;
3912 long unsigned int color;
3913 char *old_locale = "";
3915 /* defines for long options without a short equivalent. should be bytes,
3916 and may not collide with (the ASCII value of) short options */
3917 #define LONGOPT_UNITS_SI 255
3919 /* *INDENT-OFF* */
3920 struct option long_options[] = {
3921 { "start", required_argument, 0, 's'},
3922 { "end", required_argument, 0, 'e'},
3923 { "x-grid", required_argument, 0, 'x'},
3924 { "y-grid", required_argument, 0, 'y'},
3925 { "vertical-label", required_argument, 0, 'v'},
3926 { "width", required_argument, 0, 'w'},
3927 { "height", required_argument, 0, 'h'},
3928 { "full-size-mode", no_argument, 0, 'D'},
3929 { "interlaced", no_argument, 0, 'i'},
3930 { "upper-limit", required_argument, 0, 'u'},
3931 { "lower-limit", required_argument, 0, 'l'},
3932 { "rigid", no_argument, 0, 'r'},
3933 { "base", required_argument, 0, 'b'},
3934 { "logarithmic", no_argument, 0, 'o'},
3935 { "color", required_argument, 0, 'c'},
3936 { "font", required_argument, 0, 'n'},
3937 { "title", required_argument, 0, 't'},
3938 { "imginfo", required_argument, 0, 'f'},
3939 { "imgformat", required_argument, 0, 'a'},
3940 { "lazy", no_argument, 0, 'z'},
3941 { "zoom", required_argument, 0, 'm'},
3942 { "no-legend", no_argument, 0, 'g'},
3943 { "force-rules-legend", no_argument, 0, 'F'},
3944 { "only-graph", no_argument, 0, 'j'},
3945 { "alt-y-grid", no_argument, 0, 'Y'},
3946 {"disable-rrdtool-tag", no_argument, 0, 1001},
3947 {"right-axis", required_argument, 0, 1002},
3948 {"right-axis-label", required_argument, 0, 1003},
3949 {"right-axis-format", required_argument, 0, 1004},
3950 { "no-minor", no_argument, 0, 'I'},
3951 { "slope-mode", no_argument, 0, 'E'},
3952 { "alt-autoscale", no_argument, 0, 'A'},
3953 { "alt-autoscale-min", no_argument, 0, 'J'},
3954 { "alt-autoscale-max", no_argument, 0, 'M'},
3955 { "no-gridfit", no_argument, 0, 'N'},
3956 { "units-exponent", required_argument, 0, 'X'},
3957 { "units-length", required_argument, 0, 'L'},
3958 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3959 { "step", required_argument, 0, 'S'},
3960 { "tabwidth", required_argument, 0, 'T'},
3961 { "font-render-mode", required_argument, 0, 'R'},
3962 { "graph-render-mode", required_argument, 0, 'G'},
3963 { "font-smoothing-threshold", required_argument, 0, 'B'},
3964 { "watermark", required_argument, 0, 'W'},
3965 { "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 */
3966 { "pango-markup", no_argument, 0, 'P'},
3967 { 0, 0, 0, 0}
3968 };
3969 /* *INDENT-ON* */
3971 optind = 0;
3972 opterr = 0; /* initialize getopt */
3973 rrd_parsetime("end-24h", &start_tv);
3974 rrd_parsetime("now", &end_tv);
3975 while (1) {
3976 int option_index = 0;
3977 int opt;
3978 int col_start, col_end;
3980 opt = getopt_long(argc, argv,
3981 "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",
3982 long_options, &option_index);
3983 if (opt == EOF)
3984 break;
3985 switch (opt) {
3986 case 'I':
3987 im->extra_flags |= NOMINOR;
3988 break;
3989 case 'Y':
3990 im->extra_flags |= ALTYGRID;
3991 break;
3992 case 'A':
3993 im->extra_flags |= ALTAUTOSCALE;
3994 break;
3995 case 'J':
3996 im->extra_flags |= ALTAUTOSCALE_MIN;
3997 break;
3998 case 'M':
3999 im->extra_flags |= ALTAUTOSCALE_MAX;
4000 break;
4001 case 'j':
4002 im->extra_flags |= ONLY_GRAPH;
4003 break;
4004 case 'g':
4005 im->extra_flags |= NOLEGEND;
4006 break;
4007 case 'F':
4008 im->extra_flags |= FORCE_RULES_LEGEND;
4009 break;
4010 case 1001:
4011 im->extra_flags |= NO_RRDTOOL_TAG;
4012 break;
4013 case LONGOPT_UNITS_SI:
4014 if (im->extra_flags & FORCE_UNITS) {
4015 rrd_set_error("--units can only be used once!");
4016 setlocale(LC_NUMERIC, old_locale);
4017 return;
4018 }
4019 if (strcmp(optarg, "si") == 0)
4020 im->extra_flags |= FORCE_UNITS_SI;
4021 else {
4022 rrd_set_error("invalid argument for --units: %s", optarg);
4023 return;
4024 }
4025 break;
4026 case 'X':
4027 im->unitsexponent = atoi(optarg);
4028 break;
4029 case 'L':
4030 im->unitslength = atoi(optarg);
4031 im->forceleftspace = 1;
4032 break;
4033 case 'T':
4034 old_locale = setlocale(LC_NUMERIC, "C");
4035 im->tabwidth = atof(optarg);
4036 setlocale(LC_NUMERIC, old_locale);
4037 break;
4038 case 'S':
4039 old_locale = setlocale(LC_NUMERIC, "C");
4040 im->step = atoi(optarg);
4041 setlocale(LC_NUMERIC, old_locale);
4042 break;
4043 case 'N':
4044 im->gridfit = 0;
4045 break;
4046 case 'P':
4047 im->with_markup = 1;
4048 break;
4049 case 's':
4050 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4051 rrd_set_error("start time: %s", parsetime_error);
4052 return;
4053 }
4054 break;
4055 case 'e':
4056 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4057 rrd_set_error("end time: %s", parsetime_error);
4058 return;
4059 }
4060 break;
4061 case 'x':
4062 if (strcmp(optarg, "none") == 0) {
4063 im->draw_x_grid = 0;
4064 break;
4065 };
4066 if (sscanf(optarg,
4067 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4068 scan_gtm,
4069 &im->xlab_user.gridst,
4070 scan_mtm,
4071 &im->xlab_user.mgridst,
4072 scan_ltm,
4073 &im->xlab_user.labst,
4074 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4075 strncpy(im->xlab_form, optarg + stroff,
4076 sizeof(im->xlab_form) - 1);
4077 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4078 if ((int)
4079 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4080 rrd_set_error("unknown keyword %s", scan_gtm);
4081 return;
4082 } else if ((int)
4083 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4084 == -1) {
4085 rrd_set_error("unknown keyword %s", scan_mtm);
4086 return;
4087 } else if ((int)
4088 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4089 rrd_set_error("unknown keyword %s", scan_ltm);
4090 return;
4091 }
4092 im->xlab_user.minsec = 1;
4093 im->xlab_user.stst = im->xlab_form;
4094 } else {
4095 rrd_set_error("invalid x-grid format");
4096 return;
4097 }
4098 break;
4099 case 'y':
4101 if (strcmp(optarg, "none") == 0) {
4102 im->draw_y_grid = 0;
4103 break;
4104 };
4105 old_locale = setlocale(LC_NUMERIC, "C");
4106 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4107 setlocale(LC_NUMERIC, old_locale);
4108 if (im->ygridstep <= 0) {
4109 rrd_set_error("grid step must be > 0");
4110 return;
4111 } else if (im->ylabfact < 1) {
4112 rrd_set_error("label factor must be > 0");
4113 return;
4114 }
4115 } else {
4116 setlocale(LC_NUMERIC, old_locale);
4117 rrd_set_error("invalid y-grid format");
4118 return;
4119 }
4120 break;
4121 case 1002: /* right y axis */
4123 if(sscanf(optarg,
4124 "%lf:%lf",
4125 &im->second_axis_scale,
4126 &im->second_axis_shift) == 2) {
4127 if(im->second_axis_scale==0){
4128 rrd_set_error("the second_axis_scale must not be 0");
4129 return;
4130 }
4131 } else {
4132 rrd_set_error("invalid right-axis format expected scale:shift");
4133 return;
4134 }
4135 break;
4136 case 1003:
4137 strncpy(im->second_axis_legend,optarg,150);
4138 im->second_axis_legend[150]='\0';
4139 break;
4140 case 1004:
4141 if (bad_format(optarg)){
4142 rrd_set_error("use either %le or %lf formats");
4143 return;
4144 }
4145 strncpy(im->second_axis_format,optarg,150);
4146 im->second_axis_format[150]='\0';
4147 break;
4148 case 'v':
4149 strncpy(im->ylegend, optarg, 150);
4150 im->ylegend[150] = '\0';
4151 break;
4152 case 'u':
4153 old_locale = setlocale(LC_NUMERIC, "C");
4154 im->maxval = atof(optarg);
4155 setlocale(LC_NUMERIC, old_locale);
4156 break;
4157 case 'l':
4158 old_locale = setlocale(LC_NUMERIC, "C");
4159 im->minval = atof(optarg);
4160 setlocale(LC_NUMERIC, old_locale);
4161 break;
4162 case 'b':
4163 im->base = atol(optarg);
4164 if (im->base != 1024 && im->base != 1000) {
4165 rrd_set_error
4166 ("the only sensible value for base apart from 1000 is 1024");
4167 return;
4168 }
4169 break;
4170 case 'w':
4171 long_tmp = atol(optarg);
4172 if (long_tmp < 10) {
4173 rrd_set_error("width below 10 pixels");
4174 return;
4175 }
4176 im->xsize = long_tmp;
4177 break;
4178 case 'h':
4179 long_tmp = atol(optarg);
4180 if (long_tmp < 10) {
4181 rrd_set_error("height below 10 pixels");
4182 return;
4183 }
4184 im->ysize = long_tmp;
4185 break;
4186 case 'D':
4187 im->extra_flags |= FULL_SIZE_MODE;
4188 break;
4189 case 'i':
4190 /* interlaced png not supported at the moment */
4191 break;
4192 case 'r':
4193 im->rigid = 1;
4194 break;
4195 case 'f':
4196 im->imginfo = optarg;
4197 break;
4198 case 'a':
4199 if ((int)
4200 (im->imgformat = if_conv(optarg)) == -1) {
4201 rrd_set_error("unsupported graphics format '%s'", optarg);
4202 return;
4203 }
4204 break;
4205 case 'z':
4206 im->lazy = 1;
4207 break;
4208 case 'E':
4209 im->slopemode = 1;
4210 break;
4211 case 'o':
4212 im->logarithmic = 1;
4213 break;
4214 case 'c':
4215 if (sscanf(optarg,
4216 "%10[A-Z]#%n%8lx%n",
4217 col_nam, &col_start, &color, &col_end) == 2) {
4218 int ci;
4219 int col_len = col_end - col_start;
4221 switch (col_len) {
4222 case 3:
4223 color =
4224 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4225 0x011000) |
4226 ((color & 0x00F)
4227 * 0x001100)
4228 | 0x000000FF);
4229 break;
4230 case 4:
4231 color =
4232 (((color & 0xF000) *
4233 0x11000) | ((color & 0x0F00) *
4234 0x01100) | ((color &
4235 0x00F0) *
4236 0x00110) |
4237 ((color & 0x000F) * 0x00011)
4238 );
4239 break;
4240 case 6:
4241 color = (color << 8) + 0xff /* shift left by 8 */ ;
4242 break;
4243 case 8:
4244 break;
4245 default:
4246 rrd_set_error("the color format is #RRGGBB[AA]");
4247 return;
4248 }
4249 if ((ci = grc_conv(col_nam)) != -1) {
4250 im->graph_col[ci] = gfx_hex_to_col(color);
4251 } else {
4252 rrd_set_error("invalid color name '%s'", col_nam);
4253 return;
4254 }
4255 } else {
4256 rrd_set_error("invalid color def format");
4257 return;
4258 }
4259 break;
4260 case 'n':{
4261 char prop[15];
4262 double size = 1;
4263 int end;
4265 old_locale = setlocale(LC_NUMERIC, "C");
4266 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4267 int sindex, propidx;
4269 setlocale(LC_NUMERIC, old_locale);
4270 if ((sindex = text_prop_conv(prop)) != -1) {
4271 for (propidx = sindex;
4272 propidx < TEXT_PROP_LAST; propidx++) {
4273 if (size > 0) {
4274 rrd_set_font_desc(im,propidx,NULL,size);
4275 }
4276 if ((int) strlen(optarg) > end+2) {
4277 if (optarg[end] == ':') {
4278 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4279 } else {
4280 rrd_set_error
4281 ("expected : after font size in '%s'",
4282 optarg);
4283 return;
4284 }
4285 }
4286 /* only run the for loop for DEFAULT (0) for
4287 all others, we break here. woodo programming */
4288 if (propidx == sindex && sindex != 0)
4289 break;
4290 }
4291 } else {
4292 rrd_set_error("invalid fonttag '%s'", prop);
4293 return;
4294 }
4295 } else {
4296 setlocale(LC_NUMERIC, old_locale);
4297 rrd_set_error("invalid text property format");
4298 return;
4299 }
4300 break;
4301 }
4302 case 'm':
4303 old_locale = setlocale(LC_NUMERIC, "C");
4304 im->zoom = atof(optarg);
4305 setlocale(LC_NUMERIC, old_locale);
4306 if (im->zoom <= 0.0) {
4307 rrd_set_error("zoom factor must be > 0");
4308 return;
4309 }
4310 break;
4311 case 't':
4312 strncpy(im->title, optarg, 150);
4313 im->title[150] = '\0';
4314 break;
4315 case 'R':
4316 if (strcmp(optarg, "normal") == 0) {
4317 cairo_font_options_set_antialias
4318 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4319 cairo_font_options_set_hint_style
4320 (im->font_options, CAIRO_HINT_STYLE_FULL);
4321 } else if (strcmp(optarg, "light") == 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_SLIGHT);
4326 } else if (strcmp(optarg, "mono") == 0) {
4327 cairo_font_options_set_antialias
4328 (im->font_options, CAIRO_ANTIALIAS_NONE);
4329 cairo_font_options_set_hint_style
4330 (im->font_options, CAIRO_HINT_STYLE_FULL);
4331 } else {
4332 rrd_set_error("unknown font-render-mode '%s'", optarg);
4333 return;
4334 }
4335 break;
4336 case 'G':
4337 if (strcmp(optarg, "normal") == 0)
4338 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4339 else if (strcmp(optarg, "mono") == 0)
4340 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4341 else {
4342 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4343 return;
4344 }
4345 break;
4346 case 'B':
4347 /* not supported curently */
4348 break;
4349 case 'W':
4350 strncpy(im->watermark, optarg, 100);
4351 im->watermark[99] = '\0';
4352 break;
4353 case '?':
4354 if (optopt != 0)
4355 rrd_set_error("unknown option '%c'", optopt);
4356 else
4357 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4358 return;
4359 }
4360 }
4362 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4363 pango_layout_context_changed(im->layout);
4367 if (im->logarithmic && im->minval <= 0) {
4368 rrd_set_error
4369 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4370 return;
4371 }
4373 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4374 /* error string is set in rrd_parsetime.c */
4375 return;
4376 }
4378 if (start_tmp < 3600 * 24 * 365 * 10) {
4379 rrd_set_error
4380 ("the first entry to fetch should be after 1980 (%ld)",
4381 start_tmp);
4382 return;
4383 }
4385 if (end_tmp < start_tmp) {
4386 rrd_set_error
4387 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4388 return;
4389 }
4391 im->start = start_tmp;
4392 im->end = end_tmp;
4393 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4394 }
4396 int rrd_graph_color(
4397 image_desc_t
4398 *im,
4399 char *var,
4400 char *err,
4401 int optional)
4402 {
4403 char *color;
4404 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4406 color = strstr(var, "#");
4407 if (color == NULL) {
4408 if (optional == 0) {
4409 rrd_set_error("Found no color in %s", err);
4410 return 0;
4411 }
4412 return 0;
4413 } else {
4414 int n = 0;
4415 char *rest;
4416 long unsigned int col;
4418 rest = strstr(color, ":");
4419 if (rest != NULL)
4420 n = rest - color;
4421 else
4422 n = strlen(color);
4423 switch (n) {
4424 case 7:
4425 sscanf(color, "#%6lx%n", &col, &n);
4426 col = (col << 8) + 0xff /* shift left by 8 */ ;
4427 if (n != 7)
4428 rrd_set_error("Color problem in %s", err);
4429 break;
4430 case 9:
4431 sscanf(color, "#%8lx%n", &col, &n);
4432 if (n == 9)
4433 break;
4434 default:
4435 rrd_set_error("Color problem in %s", err);
4436 }
4437 if (rrd_test_error())
4438 return 0;
4439 gdp->col = gfx_hex_to_col(col);
4440 return n;
4441 }
4442 }
4445 int bad_format(
4446 char *fmt)
4447 {
4448 char *ptr;
4449 int n = 0;
4451 ptr = fmt;
4452 while (*ptr != '\0')
4453 if (*ptr++ == '%') {
4455 /* line cannot end with percent char */
4456 if (*ptr == '\0')
4457 return 1;
4458 /* '%s', '%S' and '%%' are allowed */
4459 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4460 ptr++;
4461 /* %c is allowed (but use only with vdef!) */
4462 else if (*ptr == 'c') {
4463 ptr++;
4464 n = 1;
4465 }
4467 /* or else '% 6.2lf' and such are allowed */
4468 else {
4469 /* optional padding character */
4470 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4471 ptr++;
4472 /* This should take care of 'm.n' with all three optional */
4473 while (*ptr >= '0' && *ptr <= '9')
4474 ptr++;
4475 if (*ptr == '.')
4476 ptr++;
4477 while (*ptr >= '0' && *ptr <= '9')
4478 ptr++;
4479 /* Either 'le', 'lf' or 'lg' must follow here */
4480 if (*ptr++ != 'l')
4481 return 1;
4482 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4483 ptr++;
4484 else
4485 return 1;
4486 n++;
4487 }
4488 }
4490 return (n != 1);
4491 }
4494 int vdef_parse(
4495 struct graph_desc_t
4496 *gdes,
4497 const char *const str)
4498 {
4499 /* A VDEF currently is either "func" or "param,func"
4500 * so the parsing is rather simple. Change if needed.
4501 */
4502 double param;
4503 char func[30];
4504 int n;
4505 char *old_locale;
4507 n = 0;
4508 old_locale = setlocale(LC_NUMERIC, "C");
4509 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4510 setlocale(LC_NUMERIC, old_locale);
4511 if (n == (int) strlen(str)) { /* matched */
4512 ;
4513 } else {
4514 n = 0;
4515 sscanf(str, "%29[A-Z]%n", func, &n);
4516 if (n == (int) strlen(str)) { /* matched */
4517 param = DNAN;
4518 } else {
4519 rrd_set_error
4520 ("Unknown function string '%s' in VDEF '%s'",
4521 str, gdes->vname);
4522 return -1;
4523 }
4524 }
4525 if (!strcmp("PERCENT", func))
4526 gdes->vf.op = VDEF_PERCENT;
4527 else if (!strcmp("MAXIMUM", func))
4528 gdes->vf.op = VDEF_MAXIMUM;
4529 else if (!strcmp("AVERAGE", func))
4530 gdes->vf.op = VDEF_AVERAGE;
4531 else if (!strcmp("STDEV", func))
4532 gdes->vf.op = VDEF_STDEV;
4533 else if (!strcmp("MINIMUM", func))
4534 gdes->vf.op = VDEF_MINIMUM;
4535 else if (!strcmp("TOTAL", func))
4536 gdes->vf.op = VDEF_TOTAL;
4537 else if (!strcmp("FIRST", func))
4538 gdes->vf.op = VDEF_FIRST;
4539 else if (!strcmp("LAST", func))
4540 gdes->vf.op = VDEF_LAST;
4541 else if (!strcmp("LSLSLOPE", func))
4542 gdes->vf.op = VDEF_LSLSLOPE;
4543 else if (!strcmp("LSLINT", func))
4544 gdes->vf.op = VDEF_LSLINT;
4545 else if (!strcmp("LSLCORREL", func))
4546 gdes->vf.op = VDEF_LSLCORREL;
4547 else {
4548 rrd_set_error
4549 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4550 return -1;
4551 };
4552 switch (gdes->vf.op) {
4553 case VDEF_PERCENT:
4554 if (isnan(param)) { /* no parameter given */
4555 rrd_set_error
4556 ("Function '%s' needs parameter in VDEF '%s'\n",
4557 func, gdes->vname);
4558 return -1;
4559 };
4560 if (param >= 0.0 && param <= 100.0) {
4561 gdes->vf.param = param;
4562 gdes->vf.val = DNAN; /* undefined */
4563 gdes->vf.when = 0; /* undefined */
4564 } else {
4565 rrd_set_error
4566 ("Parameter '%f' out of range in VDEF '%s'\n",
4567 param, gdes->vname);
4568 return -1;
4569 };
4570 break;
4571 case VDEF_MAXIMUM:
4572 case VDEF_AVERAGE:
4573 case VDEF_STDEV:
4574 case VDEF_MINIMUM:
4575 case VDEF_TOTAL:
4576 case VDEF_FIRST:
4577 case VDEF_LAST:
4578 case VDEF_LSLSLOPE:
4579 case VDEF_LSLINT:
4580 case VDEF_LSLCORREL:
4581 if (isnan(param)) {
4582 gdes->vf.param = DNAN;
4583 gdes->vf.val = DNAN;
4584 gdes->vf.when = 0;
4585 } else {
4586 rrd_set_error
4587 ("Function '%s' needs no parameter in VDEF '%s'\n",
4588 func, gdes->vname);
4589 return -1;
4590 };
4591 break;
4592 };
4593 return 0;
4594 }
4597 int vdef_calc(
4598 image_desc_t *im,
4599 int gdi)
4600 {
4601 graph_desc_t *src, *dst;
4602 rrd_value_t *data;
4603 long step, steps;
4605 dst = &im->gdes[gdi];
4606 src = &im->gdes[dst->vidx];
4607 data = src->data + src->ds;
4609 steps = (src->end - src->start) / src->step;
4610 #if 0
4611 printf
4612 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4613 src->start, src->end, steps);
4614 #endif
4615 switch (dst->vf.op) {
4616 case VDEF_PERCENT:{
4617 rrd_value_t *array;
4618 int field;
4619 if ((array = (rrd_value_t*)(malloc(steps * sizeof(double)))) == NULL) {
4620 rrd_set_error("malloc VDEV_PERCENT");
4621 return -1;
4622 }
4623 for (step = 0; step < steps; step++) {
4624 array[step] = data[step * src->ds_cnt];
4625 }
4626 qsort(array, step, sizeof(double), vdef_percent_compar);
4627 field = (steps - 1) * dst->vf.param / 100;
4628 dst->vf.val = array[field];
4629 dst->vf.when = 0; /* no time component */
4630 free(array);
4631 #if 0
4632 for (step = 0; step < steps; step++)
4633 printf("DEBUG: %3li:%10.2f %c\n",
4634 step, array[step], step == field ? '*' : ' ');
4635 #endif
4636 }
4637 break;
4638 case VDEF_MAXIMUM:
4639 step = 0;
4640 while (step != steps && isnan(data[step * src->ds_cnt]))
4641 step++;
4642 if (step == steps) {
4643 dst->vf.val = DNAN;
4644 dst->vf.when = 0;
4645 } else {
4646 dst->vf.val = data[step * src->ds_cnt];
4647 dst->vf.when = src->start + (step + 1) * src->step;
4648 }
4649 while (step != steps) {
4650 if (finite(data[step * src->ds_cnt])) {
4651 if (data[step * src->ds_cnt] > dst->vf.val) {
4652 dst->vf.val = data[step * src->ds_cnt];
4653 dst->vf.when = src->start + (step + 1) * src->step;
4654 }
4655 }
4656 step++;
4657 }
4658 break;
4659 case VDEF_TOTAL:
4660 case VDEF_STDEV:
4661 case VDEF_AVERAGE:{
4662 int cnt = 0;
4663 double sum = 0.0;
4664 double average = 0.0;
4666 for (step = 0; step < steps; step++) {
4667 if (finite(data[step * src->ds_cnt])) {
4668 sum += data[step * src->ds_cnt];
4669 cnt++;
4670 };
4671 }
4672 if (cnt) {
4673 if (dst->vf.op == VDEF_TOTAL) {
4674 dst->vf.val = sum * src->step;
4675 dst->vf.when = 0; /* no time component */
4676 } else if (dst->vf.op == VDEF_AVERAGE) {
4677 dst->vf.val = sum / cnt;
4678 dst->vf.when = 0; /* no time component */
4679 } else {
4680 average = sum / cnt;
4681 sum = 0.0;
4682 for (step = 0; step < steps; step++) {
4683 if (finite(data[step * src->ds_cnt])) {
4684 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4685 };
4686 }
4687 dst->vf.val = pow(sum / cnt, 0.5);
4688 dst->vf.when = 0; /* no time component */
4689 };
4690 } else {
4691 dst->vf.val = DNAN;
4692 dst->vf.when = 0;
4693 }
4694 }
4695 break;
4696 case VDEF_MINIMUM:
4697 step = 0;
4698 while (step != steps && isnan(data[step * src->ds_cnt]))
4699 step++;
4700 if (step == steps) {
4701 dst->vf.val = DNAN;
4702 dst->vf.when = 0;
4703 } else {
4704 dst->vf.val = data[step * src->ds_cnt];
4705 dst->vf.when = src->start + (step + 1) * src->step;
4706 }
4707 while (step != steps) {
4708 if (finite(data[step * src->ds_cnt])) {
4709 if (data[step * src->ds_cnt] < dst->vf.val) {
4710 dst->vf.val = data[step * src->ds_cnt];
4711 dst->vf.when = src->start + (step + 1) * src->step;
4712 }
4713 }
4714 step++;
4715 }
4716 break;
4717 case VDEF_FIRST:
4718 /* The time value returned here is one step before the
4719 * actual time value. This is the start of the first
4720 * non-NaN interval.
4721 */
4722 step = 0;
4723 while (step != steps && isnan(data[step * src->ds_cnt]))
4724 step++;
4725 if (step == steps) { /* all entries were NaN */
4726 dst->vf.val = DNAN;
4727 dst->vf.when = 0;
4728 } else {
4729 dst->vf.val = data[step * src->ds_cnt];
4730 dst->vf.when = src->start + step * src->step;
4731 }
4732 break;
4733 case VDEF_LAST:
4734 /* The time value returned here is the
4735 * actual time value. This is the end of the last
4736 * non-NaN interval.
4737 */
4738 step = steps - 1;
4739 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4740 step--;
4741 if (step < 0) { /* all entries were NaN */
4742 dst->vf.val = DNAN;
4743 dst->vf.when = 0;
4744 } else {
4745 dst->vf.val = data[step * src->ds_cnt];
4746 dst->vf.when = src->start + (step + 1) * src->step;
4747 }
4748 break;
4749 case VDEF_LSLSLOPE:
4750 case VDEF_LSLINT:
4751 case VDEF_LSLCORREL:{
4752 /* Bestfit line by linear least squares method */
4754 int cnt = 0;
4755 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4757 SUMx = 0;
4758 SUMy = 0;
4759 SUMxy = 0;
4760 SUMxx = 0;
4761 SUMyy = 0;
4762 for (step = 0; step < steps; step++) {
4763 if (finite(data[step * src->ds_cnt])) {
4764 cnt++;
4765 SUMx += step;
4766 SUMxx += step * step;
4767 SUMxy += step * data[step * src->ds_cnt];
4768 SUMy += data[step * src->ds_cnt];
4769 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4770 };
4771 }
4773 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4774 y_intercept = (SUMy - slope * SUMx) / cnt;
4775 correl =
4776 (SUMxy -
4777 (SUMx * SUMy) / cnt) /
4778 sqrt((SUMxx -
4779 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4780 if (cnt) {
4781 if (dst->vf.op == VDEF_LSLSLOPE) {
4782 dst->vf.val = slope;
4783 dst->vf.when = 0;
4784 } else if (dst->vf.op == VDEF_LSLINT) {
4785 dst->vf.val = y_intercept;
4786 dst->vf.when = 0;
4787 } else if (dst->vf.op == VDEF_LSLCORREL) {
4788 dst->vf.val = correl;
4789 dst->vf.when = 0;
4790 };
4791 } else {
4792 dst->vf.val = DNAN;
4793 dst->vf.when = 0;
4794 }
4795 }
4796 break;
4797 }
4798 return 0;
4799 }
4801 /* NaN < -INF < finite_values < INF */
4802 int vdef_percent_compar(
4803 const void
4804 *a,
4805 const void
4806 *b)
4807 {
4808 /* Equality is not returned; this doesn't hurt except
4809 * (maybe) for a little performance.
4810 */
4812 /* First catch NaN values. They are smallest */
4813 if (isnan(*(double *) a))
4814 return -1;
4815 if (isnan(*(double *) b))
4816 return 1;
4817 /* NaN doesn't reach this part so INF and -INF are extremes.
4818 * The sign from isinf() is compatible with the sign we return
4819 */
4820 if (isinf(*(double *) a))
4821 return isinf(*(double *) a);
4822 if (isinf(*(double *) b))
4823 return isinf(*(double *) b);
4824 /* If we reach this, both values must be finite */
4825 if (*(double *) a < *(double *) b)
4826 return -1;
4827 else
4828 return 1;
4829 }
4831 void grinfo_push(
4832 image_desc_t *im,
4833 char *key,
4834 rrd_info_type_t type,
4835 rrd_infoval_t value)
4836 {
4837 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4838 if (im->grinfo == NULL) {
4839 im->grinfo = im->grinfo_current;
4840 }
4841 }