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