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