8aa71560b8c60ef554c00c50cc28d0bad2ca5ff8
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 greatest common divisor 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 double leg_x = border;
1660 int leg_y = im->yimg;
1661 int leg_y_prev = im->yimg;
1662 int leg_cc;
1663 double 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 = (double)(im->ximg - fill - 2 * border) / (double)(leg_c - 1);
1796 } else {
1797 glue = 0;
1798 }
1799 if (prt_fctn == 'c')
1800 leg_x = (double)(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 (double)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 +(double)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 *path;
3686 char *filename;
3688 path = strdup(im.graphfile);
3689 filename = basename(path);
3690 info.u_str =
3691 sprintf_alloc(im.imginfo,
3692 filename,
3693 (long) (im.zoom *
3694 im.ximg), (long) (im.zoom * im.yimg));
3695 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3696 free(info.u_str);
3697 free(path);
3698 }
3699 if (im.rendered_image) {
3700 rrd_infoval_t img;
3702 img.u_blo.size = im.rendered_image_size;
3703 img.u_blo.ptr = im.rendered_image;
3704 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3705 }
3706 grinfo = im.grinfo;
3707 im_free(&im);
3708 return grinfo;
3709 }
3711 static void
3712 rrd_set_font_desc (
3713 image_desc_t *im,int prop,char *font, double size ){
3714 if (font){
3715 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
3716 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
3717 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
3718 };
3719 if (size > 0){
3720 im->text_prop[prop].size = size;
3721 };
3722 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3723 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3724 };
3725 }
3727 void rrd_graph_init(
3728 image_desc_t
3729 *im)
3730 {
3731 unsigned int i;
3732 char *deffont = getenv("RRD_DEFAULT_FONT");
3733 static PangoFontMap *fontmap = NULL;
3734 PangoContext *context;
3736 #ifdef HAVE_TZSET
3737 tzset();
3738 #endif
3739 #ifdef HAVE_SETLOCALE
3740 setlocale(LC_TIME, "");
3741 #ifdef HAVE_MBSTOWCS
3742 setlocale(LC_CTYPE, "");
3743 #endif
3744 #endif
3745 im->base = 1000;
3746 im->daemon_addr = NULL;
3747 im->draw_x_grid = 1;
3748 im->draw_y_grid = 1;
3749 im->extra_flags = 0;
3750 im->font_options = cairo_font_options_create();
3751 im->forceleftspace = 0;
3752 im->gdes_c = 0;
3753 im->gdes = NULL;
3754 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3755 im->grid_dash_off = 1;
3756 im->grid_dash_on = 1;
3757 im->gridfit = 1;
3758 im->grinfo = (rrd_info_t *) NULL;
3759 im->grinfo_current = (rrd_info_t *) NULL;
3760 im->imgformat = IF_PNG;
3761 im->imginfo = NULL;
3762 im->lazy = 0;
3763 im->logarithmic = 0;
3764 im->maxval = DNAN;
3765 im->minval = 0;
3766 im->minval = DNAN;
3767 im->prt_c = 0;
3768 im->rigid = 0;
3769 im->rendered_image_size = 0;
3770 im->rendered_image = NULL;
3771 im->slopemode = 0;
3772 im->step = 0;
3773 im->symbol = ' ';
3774 im->tabwidth = 40.0;
3775 im->title[0] = '\0';
3776 im->unitsexponent = 9999;
3777 im->unitslength = 6;
3778 im->viewfactor = 1.0;
3779 im->watermark[0] = '\0';
3780 im->with_markup = 0;
3781 im->ximg = 0;
3782 im->xlab_user.minsec = -1;
3783 im->xorigin = 0;
3784 im->xsize = 400;
3785 im->ygridstep = DNAN;
3786 im->yimg = 0;
3787 im->ylegend[0] = '\0';
3788 im->yorigin = 0;
3789 im->ysize = 100;
3790 im->zoom = 1;
3792 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3793 im->cr = cairo_create(im->surface);
3795 for (i = 0; i < DIM(text_prop); i++) {
3796 im->text_prop[i].size = -1;
3797 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3798 }
3800 if (fontmap == NULL){
3801 fontmap = pango_cairo_font_map_get_default();
3802 }
3804 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3806 pango_cairo_context_set_resolution(context, 100);
3808 pango_cairo_update_context(im->cr,context);
3810 im->layout = pango_layout_new(context);
3812 // im->layout = pango_cairo_create_layout(im->cr);
3815 cairo_font_options_set_hint_style
3816 (im->font_options, CAIRO_HINT_STYLE_FULL);
3817 cairo_font_options_set_hint_metrics
3818 (im->font_options, CAIRO_HINT_METRICS_ON);
3819 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3823 for (i = 0; i < DIM(graph_col); i++)
3824 im->graph_col[i] = graph_col[i];
3827 }
3830 void rrd_graph_options(
3831 int argc,
3832 char *argv[],
3833 image_desc_t
3834 *im)
3835 {
3836 int stroff;
3837 char *parsetime_error = NULL;
3838 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3839 time_t start_tmp = 0, end_tmp = 0;
3840 long long_tmp;
3841 rrd_time_value_t start_tv, end_tv;
3842 long unsigned int color;
3843 char *old_locale = "";
3845 /* defines for long options without a short equivalent. should be bytes,
3846 and may not collide with (the ASCII value of) short options */
3847 #define LONGOPT_UNITS_SI 255
3849 /* *INDENT-OFF* */
3850 struct option long_options[] = {
3851 { "start", required_argument, 0, 's'},
3852 { "end", required_argument, 0, 'e'},
3853 { "x-grid", required_argument, 0, 'x'},
3854 { "y-grid", required_argument, 0, 'y'},
3855 { "vertical-label", required_argument, 0, 'v'},
3856 { "width", required_argument, 0, 'w'},
3857 { "height", required_argument, 0, 'h'},
3858 { "full-size-mode", no_argument, 0, 'D'},
3859 { "interlaced", no_argument, 0, 'i'},
3860 { "upper-limit", required_argument, 0, 'u'},
3861 { "lower-limit", required_argument, 0, 'l'},
3862 { "rigid", no_argument, 0, 'r'},
3863 { "base", required_argument, 0, 'b'},
3864 { "logarithmic", no_argument, 0, 'o'},
3865 { "color", required_argument, 0, 'c'},
3866 { "font", required_argument, 0, 'n'},
3867 { "title", required_argument, 0, 't'},
3868 { "imginfo", required_argument, 0, 'f'},
3869 { "imgformat", required_argument, 0, 'a'},
3870 { "lazy", no_argument, 0, 'z'},
3871 { "zoom", required_argument, 0, 'm'},
3872 { "no-legend", no_argument, 0, 'g'},
3873 { "force-rules-legend", no_argument, 0, 'F'},
3874 { "only-graph", no_argument, 0, 'j'},
3875 { "alt-y-grid", no_argument, 0, 'Y'},
3876 { "no-minor", no_argument, 0, 'I'},
3877 { "slope-mode", no_argument, 0, 'E'},
3878 { "alt-autoscale", no_argument, 0, 'A'},
3879 { "alt-autoscale-min", no_argument, 0, 'J'},
3880 { "alt-autoscale-max", no_argument, 0, 'M'},
3881 { "no-gridfit", no_argument, 0, 'N'},
3882 { "units-exponent", required_argument, 0, 'X'},
3883 { "units-length", required_argument, 0, 'L'},
3884 { "units", required_argument, 0, LONGOPT_UNITS_SI},
3885 { "step", required_argument, 0, 'S'},
3886 { "tabwidth", required_argument, 0, 'T'},
3887 { "font-render-mode", required_argument, 0, 'R'},
3888 { "graph-render-mode", required_argument, 0, 'G'},
3889 { "font-smoothing-threshold", required_argument, 0, 'B'},
3890 { "watermark", required_argument, 0, 'W'},
3891 { "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 */
3892 { "pango-markup", no_argument, 0, 'P'},
3893 { "daemon", required_argument, 0, 'd'},
3894 { 0, 0, 0, 0}
3895 };
3896 /* *INDENT-ON* */
3898 optind = 0;
3899 opterr = 0; /* initialize getopt */
3900 rrd_parsetime("end-24h", &start_tv);
3901 rrd_parsetime("now", &end_tv);
3902 while (1) {
3903 int option_index = 0;
3904 int opt;
3905 int col_start, col_end;
3907 opt = getopt_long(argc, argv,
3908 "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:",
3909 long_options, &option_index);
3910 if (opt == EOF)
3911 break;
3912 switch (opt) {
3913 case 'I':
3914 im->extra_flags |= NOMINOR;
3915 break;
3916 case 'Y':
3917 im->extra_flags |= ALTYGRID;
3918 break;
3919 case 'A':
3920 im->extra_flags |= ALTAUTOSCALE;
3921 break;
3922 case 'J':
3923 im->extra_flags |= ALTAUTOSCALE_MIN;
3924 break;
3925 case 'M':
3926 im->extra_flags |= ALTAUTOSCALE_MAX;
3927 break;
3928 case 'j':
3929 im->extra_flags |= ONLY_GRAPH;
3930 break;
3931 case 'g':
3932 im->extra_flags |= NOLEGEND;
3933 break;
3934 case 'F':
3935 im->extra_flags |= FORCE_RULES_LEGEND;
3936 break;
3937 case LONGOPT_UNITS_SI:
3938 if (im->extra_flags & FORCE_UNITS) {
3939 rrd_set_error("--units can only be used once!");
3940 setlocale(LC_NUMERIC, old_locale);
3941 return;
3942 }
3943 if (strcmp(optarg, "si") == 0)
3944 im->extra_flags |= FORCE_UNITS_SI;
3945 else {
3946 rrd_set_error("invalid argument for --units: %s", optarg);
3947 return;
3948 }
3949 break;
3950 case 'X':
3951 im->unitsexponent = atoi(optarg);
3952 break;
3953 case 'L':
3954 im->unitslength = atoi(optarg);
3955 im->forceleftspace = 1;
3956 break;
3957 case 'T':
3958 old_locale = setlocale(LC_NUMERIC, "C");
3959 im->tabwidth = atof(optarg);
3960 setlocale(LC_NUMERIC, old_locale);
3961 break;
3962 case 'S':
3963 old_locale = setlocale(LC_NUMERIC, "C");
3964 im->step = atoi(optarg);
3965 setlocale(LC_NUMERIC, old_locale);
3966 break;
3967 case 'N':
3968 im->gridfit = 0;
3969 break;
3970 case 'P':
3971 im->with_markup = 1;
3972 break;
3973 case 's':
3974 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3975 rrd_set_error("start time: %s", parsetime_error);
3976 return;
3977 }
3978 break;
3979 case 'e':
3980 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3981 rrd_set_error("end time: %s", parsetime_error);
3982 return;
3983 }
3984 break;
3985 case 'x':
3986 if (strcmp(optarg, "none") == 0) {
3987 im->draw_x_grid = 0;
3988 break;
3989 };
3990 if (sscanf(optarg,
3991 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3992 scan_gtm,
3993 &im->xlab_user.gridst,
3994 scan_mtm,
3995 &im->xlab_user.mgridst,
3996 scan_ltm,
3997 &im->xlab_user.labst,
3998 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3999 strncpy(im->xlab_form, optarg + stroff,
4000 sizeof(im->xlab_form) - 1);
4001 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4002 if ((int)
4003 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4004 rrd_set_error("unknown keyword %s", scan_gtm);
4005 return;
4006 } else if ((int)
4007 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4008 == -1) {
4009 rrd_set_error("unknown keyword %s", scan_mtm);
4010 return;
4011 } else if ((int)
4012 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4013 rrd_set_error("unknown keyword %s", scan_ltm);
4014 return;
4015 }
4016 im->xlab_user.minsec = 1;
4017 im->xlab_user.stst = im->xlab_form;
4018 } else {
4019 rrd_set_error("invalid x-grid format");
4020 return;
4021 }
4022 break;
4023 case 'y':
4025 if (strcmp(optarg, "none") == 0) {
4026 im->draw_y_grid = 0;
4027 break;
4028 };
4029 old_locale = setlocale(LC_NUMERIC, "C");
4030 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4031 setlocale(LC_NUMERIC, old_locale);
4032 if (im->ygridstep <= 0) {
4033 rrd_set_error("grid step must be > 0");
4034 return;
4035 } else if (im->ylabfact < 1) {
4036 rrd_set_error("label factor must be > 0");
4037 return;
4038 }
4039 } else {
4040 setlocale(LC_NUMERIC, old_locale);
4041 rrd_set_error("invalid y-grid format");
4042 return;
4043 }
4044 break;
4045 case 'v':
4046 strncpy(im->ylegend, optarg, 150);
4047 im->ylegend[150] = '\0';
4048 break;
4049 case 'u':
4050 old_locale = setlocale(LC_NUMERIC, "C");
4051 im->maxval = atof(optarg);
4052 setlocale(LC_NUMERIC, old_locale);
4053 break;
4054 case 'l':
4055 old_locale = setlocale(LC_NUMERIC, "C");
4056 im->minval = atof(optarg);
4057 setlocale(LC_NUMERIC, old_locale);
4058 break;
4059 case 'b':
4060 im->base = atol(optarg);
4061 if (im->base != 1024 && im->base != 1000) {
4062 rrd_set_error
4063 ("the only sensible value for base apart from 1000 is 1024");
4064 return;
4065 }
4066 break;
4067 case 'w':
4068 long_tmp = atol(optarg);
4069 if (long_tmp < 10) {
4070 rrd_set_error("width below 10 pixels");
4071 return;
4072 }
4073 im->xsize = long_tmp;
4074 break;
4075 case 'h':
4076 long_tmp = atol(optarg);
4077 if (long_tmp < 10) {
4078 rrd_set_error("height below 10 pixels");
4079 return;
4080 }
4081 im->ysize = long_tmp;
4082 break;
4083 case 'D':
4084 im->extra_flags |= FULL_SIZE_MODE;
4085 break;
4086 case 'i':
4087 /* interlaced png not supported at the moment */
4088 break;
4089 case 'r':
4090 im->rigid = 1;
4091 break;
4092 case 'f':
4093 im->imginfo = optarg;
4094 break;
4095 case 'a':
4096 if ((int)
4097 (im->imgformat = if_conv(optarg)) == -1) {
4098 rrd_set_error("unsupported graphics format '%s'", optarg);
4099 return;
4100 }
4101 break;
4102 case 'z':
4103 im->lazy = 1;
4104 break;
4105 case 'E':
4106 im->slopemode = 1;
4107 break;
4108 case 'o':
4109 im->logarithmic = 1;
4110 break;
4111 case 'c':
4112 if (sscanf(optarg,
4113 "%10[A-Z]#%n%8lx%n",
4114 col_nam, &col_start, &color, &col_end) == 2) {
4115 int ci;
4116 int col_len = col_end - col_start;
4118 switch (col_len) {
4119 case 3:
4120 color =
4121 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4122 0x011000) |
4123 ((color & 0x00F)
4124 * 0x001100)
4125 | 0x000000FF);
4126 break;
4127 case 4:
4128 color =
4129 (((color & 0xF000) *
4130 0x11000) | ((color & 0x0F00) *
4131 0x01100) | ((color &
4132 0x00F0) *
4133 0x00110) |
4134 ((color & 0x000F) * 0x00011)
4135 );
4136 break;
4137 case 6:
4138 color = (color << 8) + 0xff /* shift left by 8 */ ;
4139 break;
4140 case 8:
4141 break;
4142 default:
4143 rrd_set_error("the color format is #RRGGBB[AA]");
4144 return;
4145 }
4146 if ((ci = grc_conv(col_nam)) != -1) {
4147 im->graph_col[ci] = gfx_hex_to_col(color);
4148 } else {
4149 rrd_set_error("invalid color name '%s'", col_nam);
4150 return;
4151 }
4152 } else {
4153 rrd_set_error("invalid color def format");
4154 return;
4155 }
4156 break;
4157 case 'n':{
4158 char prop[15];
4159 double size = 1;
4160 int end;
4162 old_locale = setlocale(LC_NUMERIC, "C");
4163 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4164 int sindex, propidx;
4166 setlocale(LC_NUMERIC, old_locale);
4167 if ((sindex = text_prop_conv(prop)) != -1) {
4168 for (propidx = sindex;
4169 propidx < TEXT_PROP_LAST; propidx++) {
4170 if (size > 0) {
4171 rrd_set_font_desc(im,propidx,NULL,size);
4172 }
4173 if ((int) strlen(optarg) > end+2) {
4174 if (optarg[end] == ':') {
4175 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4176 } else {
4177 rrd_set_error
4178 ("expected : after font size in '%s'",
4179 optarg);
4180 return;
4181 }
4182 }
4183 /* only run the for loop for DEFAULT (0) for
4184 all others, we break here. woodo programming */
4185 if (propidx == sindex && sindex != 0)
4186 break;
4187 }
4188 } else {
4189 rrd_set_error("invalid fonttag '%s'", prop);
4190 return;
4191 }
4192 } else {
4193 setlocale(LC_NUMERIC, old_locale);
4194 rrd_set_error("invalid text property format");
4195 return;
4196 }
4197 break;
4198 }
4199 case 'm':
4200 old_locale = setlocale(LC_NUMERIC, "C");
4201 im->zoom = atof(optarg);
4202 setlocale(LC_NUMERIC, old_locale);
4203 if (im->zoom <= 0.0) {
4204 rrd_set_error("zoom factor must be > 0");
4205 return;
4206 }
4207 break;
4208 case 't':
4209 strncpy(im->title, optarg, 150);
4210 im->title[150] = '\0';
4211 break;
4212 case 'R':
4213 if (strcmp(optarg, "normal") == 0) {
4214 cairo_font_options_set_antialias
4215 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4216 cairo_font_options_set_hint_style
4217 (im->font_options, CAIRO_HINT_STYLE_FULL);
4218 } else if (strcmp(optarg, "light") == 0) {
4219 cairo_font_options_set_antialias
4220 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4221 cairo_font_options_set_hint_style
4222 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4223 } else if (strcmp(optarg, "mono") == 0) {
4224 cairo_font_options_set_antialias
4225 (im->font_options, CAIRO_ANTIALIAS_NONE);
4226 cairo_font_options_set_hint_style
4227 (im->font_options, CAIRO_HINT_STYLE_FULL);
4228 } else {
4229 rrd_set_error("unknown font-render-mode '%s'", optarg);
4230 return;
4231 }
4232 break;
4233 case 'G':
4234 if (strcmp(optarg, "normal") == 0)
4235 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4236 else if (strcmp(optarg, "mono") == 0)
4237 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4238 else {
4239 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4240 return;
4241 }
4242 break;
4243 case 'B':
4244 /* not supported curently */
4245 break;
4246 case 'W':
4247 strncpy(im->watermark, optarg, 100);
4248 im->watermark[99] = '\0';
4249 break;
4250 case 'd':
4251 {
4252 if (im->daemon_addr != NULL)
4253 {
4254 rrd_set_error ("You cannot specify --daemon "
4255 "more than once.");
4256 return;
4257 }
4259 im->daemon_addr = strdup(optarg);
4260 if (im->daemon_addr == NULL)
4261 {
4262 rrd_set_error("strdup failed");
4263 return;
4264 }
4266 break;
4267 }
4268 case '?':
4269 if (optopt != 0)
4270 rrd_set_error("unknown option '%c'", optopt);
4271 else
4272 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4273 return;
4274 }
4275 } /* while (1) */
4277 { /* try to connect to rrdcached */
4278 int status = rrdc_connect(im->daemon_addr);
4279 if (status != 0) return;
4280 }
4282 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4283 pango_layout_context_changed(im->layout);
4287 if (im->logarithmic && im->minval <= 0) {
4288 rrd_set_error
4289 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4290 return;
4291 }
4293 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4294 /* error string is set in rrd_parsetime.c */
4295 return;
4296 }
4298 if (start_tmp < 3600 * 24 * 365 * 10) {
4299 rrd_set_error
4300 ("the first entry to fetch should be after 1980 (%ld)",
4301 start_tmp);
4302 return;
4303 }
4305 if (end_tmp < start_tmp) {
4306 rrd_set_error
4307 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4308 return;
4309 }
4311 im->start = start_tmp;
4312 im->end = end_tmp;
4313 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4314 }
4316 int rrd_graph_color(
4317 image_desc_t
4318 *im,
4319 char *var,
4320 char *err,
4321 int optional)
4322 {
4323 char *color;
4324 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4326 color = strstr(var, "#");
4327 if (color == NULL) {
4328 if (optional == 0) {
4329 rrd_set_error("Found no color in %s", err);
4330 return 0;
4331 }
4332 return 0;
4333 } else {
4334 int n = 0;
4335 char *rest;
4336 long unsigned int col;
4338 rest = strstr(color, ":");
4339 if (rest != NULL)
4340 n = rest - color;
4341 else
4342 n = strlen(color);
4343 switch (n) {
4344 case 7:
4345 sscanf(color, "#%6lx%n", &col, &n);
4346 col = (col << 8) + 0xff /* shift left by 8 */ ;
4347 if (n != 7)
4348 rrd_set_error("Color problem in %s", err);
4349 break;
4350 case 9:
4351 sscanf(color, "#%8lx%n", &col, &n);
4352 if (n == 9)
4353 break;
4354 default:
4355 rrd_set_error("Color problem in %s", err);
4356 }
4357 if (rrd_test_error())
4358 return 0;
4359 gdp->col = gfx_hex_to_col(col);
4360 return n;
4361 }
4362 }
4365 int bad_format(
4366 char *fmt)
4367 {
4368 char *ptr;
4369 int n = 0;
4371 ptr = fmt;
4372 while (*ptr != '\0')
4373 if (*ptr++ == '%') {
4375 /* line cannot end with percent char */
4376 if (*ptr == '\0')
4377 return 1;
4378 /* '%s', '%S' and '%%' are allowed */
4379 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4380 ptr++;
4381 /* %c is allowed (but use only with vdef!) */
4382 else if (*ptr == 'c') {
4383 ptr++;
4384 n = 1;
4385 }
4387 /* or else '% 6.2lf' and such are allowed */
4388 else {
4389 /* optional padding character */
4390 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4391 ptr++;
4392 /* This should take care of 'm.n' with all three optional */
4393 while (*ptr >= '0' && *ptr <= '9')
4394 ptr++;
4395 if (*ptr == '.')
4396 ptr++;
4397 while (*ptr >= '0' && *ptr <= '9')
4398 ptr++;
4399 /* Either 'le', 'lf' or 'lg' must follow here */
4400 if (*ptr++ != 'l')
4401 return 1;
4402 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4403 ptr++;
4404 else
4405 return 1;
4406 n++;
4407 }
4408 }
4410 return (n != 1);
4411 }
4414 int vdef_parse(
4415 struct graph_desc_t
4416 *gdes,
4417 const char *const str)
4418 {
4419 /* A VDEF currently is either "func" or "param,func"
4420 * so the parsing is rather simple. Change if needed.
4421 */
4422 double param;
4423 char func[30];
4424 int n;
4425 char *old_locale;
4427 n = 0;
4428 old_locale = setlocale(LC_NUMERIC, "C");
4429 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4430 setlocale(LC_NUMERIC, old_locale);
4431 if (n == (int) strlen(str)) { /* matched */
4432 ;
4433 } else {
4434 n = 0;
4435 sscanf(str, "%29[A-Z]%n", func, &n);
4436 if (n == (int) strlen(str)) { /* matched */
4437 param = DNAN;
4438 } else {
4439 rrd_set_error
4440 ("Unknown function string '%s' in VDEF '%s'",
4441 str, gdes->vname);
4442 return -1;
4443 }
4444 }
4445 if (!strcmp("PERCENT", func))
4446 gdes->vf.op = VDEF_PERCENT;
4447 else if (!strcmp("MAXIMUM", func))
4448 gdes->vf.op = VDEF_MAXIMUM;
4449 else if (!strcmp("AVERAGE", func))
4450 gdes->vf.op = VDEF_AVERAGE;
4451 else if (!strcmp("STDEV", func))
4452 gdes->vf.op = VDEF_STDEV;
4453 else if (!strcmp("MINIMUM", func))
4454 gdes->vf.op = VDEF_MINIMUM;
4455 else if (!strcmp("TOTAL", func))
4456 gdes->vf.op = VDEF_TOTAL;
4457 else if (!strcmp("FIRST", func))
4458 gdes->vf.op = VDEF_FIRST;
4459 else if (!strcmp("LAST", func))
4460 gdes->vf.op = VDEF_LAST;
4461 else if (!strcmp("LSLSLOPE", func))
4462 gdes->vf.op = VDEF_LSLSLOPE;
4463 else if (!strcmp("LSLINT", func))
4464 gdes->vf.op = VDEF_LSLINT;
4465 else if (!strcmp("LSLCORREL", func))
4466 gdes->vf.op = VDEF_LSLCORREL;
4467 else {
4468 rrd_set_error
4469 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4470 return -1;
4471 };
4472 switch (gdes->vf.op) {
4473 case VDEF_PERCENT:
4474 if (isnan(param)) { /* no parameter given */
4475 rrd_set_error
4476 ("Function '%s' needs parameter in VDEF '%s'\n",
4477 func, gdes->vname);
4478 return -1;
4479 };
4480 if (param >= 0.0 && param <= 100.0) {
4481 gdes->vf.param = param;
4482 gdes->vf.val = DNAN; /* undefined */
4483 gdes->vf.when = 0; /* undefined */
4484 } else {
4485 rrd_set_error
4486 ("Parameter '%f' out of range in VDEF '%s'\n",
4487 param, gdes->vname);
4488 return -1;
4489 };
4490 break;
4491 case VDEF_MAXIMUM:
4492 case VDEF_AVERAGE:
4493 case VDEF_STDEV:
4494 case VDEF_MINIMUM:
4495 case VDEF_TOTAL:
4496 case VDEF_FIRST:
4497 case VDEF_LAST:
4498 case VDEF_LSLSLOPE:
4499 case VDEF_LSLINT:
4500 case VDEF_LSLCORREL:
4501 if (isnan(param)) {
4502 gdes->vf.param = DNAN;
4503 gdes->vf.val = DNAN;
4504 gdes->vf.when = 0;
4505 } else {
4506 rrd_set_error
4507 ("Function '%s' needs no parameter in VDEF '%s'\n",
4508 func, gdes->vname);
4509 return -1;
4510 };
4511 break;
4512 };
4513 return 0;
4514 }
4517 int vdef_calc(
4518 image_desc_t *im,
4519 int gdi)
4520 {
4521 graph_desc_t *src, *dst;
4522 rrd_value_t *data;
4523 long step, steps;
4525 dst = &im->gdes[gdi];
4526 src = &im->gdes[dst->vidx];
4527 data = src->data + src->ds;
4529 steps = (src->end - src->start) / src->step;
4530 #if 0
4531 printf
4532 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4533 src->start, src->end, steps);
4534 #endif
4535 switch (dst->vf.op) {
4536 case VDEF_PERCENT:{
4537 rrd_value_t *array;
4538 int field;
4539 if ((array = malloc(steps * sizeof(double))) == NULL) {
4540 rrd_set_error("malloc VDEV_PERCENT");
4541 return -1;
4542 }
4543 for (step = 0; step < steps; step++) {
4544 array[step] = data[step * src->ds_cnt];
4545 }
4546 qsort(array, step, sizeof(double), vdef_percent_compar);
4547 field = (steps - 1) * dst->vf.param / 100;
4548 dst->vf.val = array[field];
4549 dst->vf.when = 0; /* no time component */
4550 free(array);
4551 #if 0
4552 for (step = 0; step < steps; step++)
4553 printf("DEBUG: %3li:%10.2f %c\n",
4554 step, array[step], step == field ? '*' : ' ');
4555 #endif
4556 }
4557 break;
4558 case VDEF_MAXIMUM:
4559 step = 0;
4560 while (step != steps && isnan(data[step * src->ds_cnt]))
4561 step++;
4562 if (step == steps) {
4563 dst->vf.val = DNAN;
4564 dst->vf.when = 0;
4565 } else {
4566 dst->vf.val = data[step * src->ds_cnt];
4567 dst->vf.when = src->start + (step + 1) * src->step;
4568 }
4569 while (step != steps) {
4570 if (finite(data[step * src->ds_cnt])) {
4571 if (data[step * src->ds_cnt] > dst->vf.val) {
4572 dst->vf.val = data[step * src->ds_cnt];
4573 dst->vf.when = src->start + (step + 1) * src->step;
4574 }
4575 }
4576 step++;
4577 }
4578 break;
4579 case VDEF_TOTAL:
4580 case VDEF_STDEV:
4581 case VDEF_AVERAGE:{
4582 int cnt = 0;
4583 double sum = 0.0;
4584 double average = 0.0;
4586 for (step = 0; step < steps; step++) {
4587 if (finite(data[step * src->ds_cnt])) {
4588 sum += data[step * src->ds_cnt];
4589 cnt++;
4590 };
4591 }
4592 if (cnt) {
4593 if (dst->vf.op == VDEF_TOTAL) {
4594 dst->vf.val = sum * src->step;
4595 dst->vf.when = 0; /* no time component */
4596 } else if (dst->vf.op == VDEF_AVERAGE) {
4597 dst->vf.val = sum / cnt;
4598 dst->vf.when = 0; /* no time component */
4599 } else {
4600 average = sum / cnt;
4601 sum = 0.0;
4602 for (step = 0; step < steps; step++) {
4603 if (finite(data[step * src->ds_cnt])) {
4604 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4605 };
4606 }
4607 dst->vf.val = pow(sum / cnt, 0.5);
4608 dst->vf.when = 0; /* no time component */
4609 };
4610 } else {
4611 dst->vf.val = DNAN;
4612 dst->vf.when = 0;
4613 }
4614 }
4615 break;
4616 case VDEF_MINIMUM:
4617 step = 0;
4618 while (step != steps && isnan(data[step * src->ds_cnt]))
4619 step++;
4620 if (step == steps) {
4621 dst->vf.val = DNAN;
4622 dst->vf.when = 0;
4623 } else {
4624 dst->vf.val = data[step * src->ds_cnt];
4625 dst->vf.when = src->start + (step + 1) * src->step;
4626 }
4627 while (step != steps) {
4628 if (finite(data[step * src->ds_cnt])) {
4629 if (data[step * src->ds_cnt] < dst->vf.val) {
4630 dst->vf.val = data[step * src->ds_cnt];
4631 dst->vf.when = src->start + (step + 1) * src->step;
4632 }
4633 }
4634 step++;
4635 }
4636 break;
4637 case VDEF_FIRST:
4638 /* The time value returned here is one step before the
4639 * actual time value. This is the start of the first
4640 * non-NaN interval.
4641 */
4642 step = 0;
4643 while (step != steps && isnan(data[step * src->ds_cnt]))
4644 step++;
4645 if (step == steps) { /* all entries were NaN */
4646 dst->vf.val = DNAN;
4647 dst->vf.when = 0;
4648 } else {
4649 dst->vf.val = data[step * src->ds_cnt];
4650 dst->vf.when = src->start + step * src->step;
4651 }
4652 break;
4653 case VDEF_LAST:
4654 /* The time value returned here is the
4655 * actual time value. This is the end of the last
4656 * non-NaN interval.
4657 */
4658 step = steps - 1;
4659 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4660 step--;
4661 if (step < 0) { /* all entries were NaN */
4662 dst->vf.val = DNAN;
4663 dst->vf.when = 0;
4664 } else {
4665 dst->vf.val = data[step * src->ds_cnt];
4666 dst->vf.when = src->start + (step + 1) * src->step;
4667 }
4668 break;
4669 case VDEF_LSLSLOPE:
4670 case VDEF_LSLINT:
4671 case VDEF_LSLCORREL:{
4672 /* Bestfit line by linear least squares method */
4674 int cnt = 0;
4675 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4677 SUMx = 0;
4678 SUMy = 0;
4679 SUMxy = 0;
4680 SUMxx = 0;
4681 SUMyy = 0;
4682 for (step = 0; step < steps; step++) {
4683 if (finite(data[step * src->ds_cnt])) {
4684 cnt++;
4685 SUMx += step;
4686 SUMxx += step * step;
4687 SUMxy += step * data[step * src->ds_cnt];
4688 SUMy += data[step * src->ds_cnt];
4689 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4690 };
4691 }
4693 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4694 y_intercept = (SUMy - slope * SUMx) / cnt;
4695 correl =
4696 (SUMxy -
4697 (SUMx * SUMy) / cnt) /
4698 sqrt((SUMxx -
4699 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4700 if (cnt) {
4701 if (dst->vf.op == VDEF_LSLSLOPE) {
4702 dst->vf.val = slope;
4703 dst->vf.when = 0;
4704 } else if (dst->vf.op == VDEF_LSLINT) {
4705 dst->vf.val = y_intercept;
4706 dst->vf.when = 0;
4707 } else if (dst->vf.op == VDEF_LSLCORREL) {
4708 dst->vf.val = correl;
4709 dst->vf.when = 0;
4710 };
4711 } else {
4712 dst->vf.val = DNAN;
4713 dst->vf.when = 0;
4714 }
4715 }
4716 break;
4717 }
4718 return 0;
4719 }
4721 /* NaN < -INF < finite_values < INF */
4722 int vdef_percent_compar(
4723 const void
4724 *a,
4725 const void
4726 *b)
4727 {
4728 /* Equality is not returned; this doesn't hurt except
4729 * (maybe) for a little performance.
4730 */
4732 /* First catch NaN values. They are smallest */
4733 if (isnan(*(double *) a))
4734 return -1;
4735 if (isnan(*(double *) b))
4736 return 1;
4737 /* NaN doesn't reach this part so INF and -INF are extremes.
4738 * The sign from isinf() is compatible with the sign we return
4739 */
4740 if (isinf(*(double *) a))
4741 return isinf(*(double *) a);
4742 if (isinf(*(double *) b))
4743 return isinf(*(double *) b);
4744 /* If we reach this, both values must be finite */
4745 if (*(double *) a < *(double *) b)
4746 return -1;
4747 else
4748 return 1;
4749 }
4751 void grinfo_push(
4752 image_desc_t *im,
4753 char *key,
4754 rrd_info_type_t type,
4755 rrd_infoval_t value)
4756 {
4757 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4758 if (im->grinfo == NULL) {
4759 im->grinfo = im->grinfo_current;
4760 }
4761 }