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