94616cb44f44cf5f79ec674e440ee6abd3db2a15
1 /****************************************************************************
2 * RRDtool 1.4.3 Copyright by Tobi Oetiker, 1997-2010
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
14 #include "rrd_tool.h"
16 /* for basename */
17 #ifdef HAVE_LIBGEN_H
18 # include <libgen.h>
19 #else
20 #include "plbasename.h"
21 #endif
23 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
24 #include <io.h>
25 #include <fcntl.h>
26 #endif
28 #include <time.h>
30 #include <locale.h>
32 #ifdef HAVE_LANGINFO_H
33 #include <langinfo.h>
34 #endif
36 #include "rrd_graph.h"
37 #include "rrd_client.h"
39 /* some constant definitions */
43 #ifndef RRD_DEFAULT_FONT
44 /* there is special code later to pick Cour.ttf when running on windows */
45 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
46 #endif
48 text_prop_t text_prop[] = {
49 {8.0, RRD_DEFAULT_FONT,NULL}
50 , /* default */
51 {9.0, RRD_DEFAULT_FONT,NULL}
52 , /* title */
53 {7.0, RRD_DEFAULT_FONT,NULL}
54 , /* axis */
55 {8.0, RRD_DEFAULT_FONT,NULL}
56 , /* unit */
57 {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
58 ,
59 {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
60 };
62 char week_fmt[128] = "Week %V";
64 xlab_t xlab[] = {
65 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
66 ,
67 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
68 ,
69 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
70 ,
71 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
72 ,
73 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
74 ,
75 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
76 ,
77 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
78 ,
79 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
80 ,
81 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
82 ,
83 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
84 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
85 ,
86 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
87 ,
88 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
89 ,
90 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
91 ,
92 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, week_fmt}
93 ,
94 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600, week_fmt}
95 ,
96 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
97 "%b"}
98 ,
99 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
100 "%b"}
101 ,
102 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
103 ,
104 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
105 365 * 24 * 3600, "%y"}
106 ,
107 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
108 };
110 /* sensible y label intervals ...*/
112 ylab_t ylab[] = {
113 {0.1, {1, 2, 5, 10}
114 }
115 ,
116 {0.2, {1, 5, 10, 20}
117 }
118 ,
119 {0.5, {1, 2, 4, 10}
120 }
121 ,
122 {1.0, {1, 2, 5, 10}
123 }
124 ,
125 {2.0, {1, 5, 10, 20}
126 }
127 ,
128 {5.0, {1, 2, 4, 10}
129 }
130 ,
131 {10.0, {1, 2, 5, 10}
132 }
133 ,
134 {20.0, {1, 5, 10, 20}
135 }
136 ,
137 {50.0, {1, 2, 4, 10}
138 }
139 ,
140 {100.0, {1, 2, 5, 10}
141 }
142 ,
143 {200.0, {1, 5, 10, 20}
144 }
145 ,
146 {500.0, {1, 2, 4, 10}
147 }
148 ,
149 {0.0, {0, 0, 0, 0}
150 }
151 };
154 gfx_color_t graph_col[] = /* default colors */
155 {
156 {1.00, 1.00, 1.00, 1.00}, /* canvas */
157 {0.95, 0.95, 0.95, 1.00}, /* background */
158 {0.81, 0.81, 0.81, 1.00}, /* shade A */
159 {0.62, 0.62, 0.62, 1.00}, /* shade B */
160 {0.56, 0.56, 0.56, 0.75}, /* grid */
161 {0.87, 0.31, 0.31, 0.60}, /* major grid */
162 {0.00, 0.00, 0.00, 1.00}, /* font */
163 {0.50, 0.12, 0.12, 1.00}, /* arrow */
164 {0.12, 0.12, 0.12, 1.00}, /* axis */
165 {0.00, 0.00, 0.00, 1.00} /* frame */
166 };
169 /* #define DEBUG */
171 #ifdef DEBUG
172 # define DPRINT(x) (void)(printf x, printf("\n"))
173 #else
174 # define DPRINT(x)
175 #endif
178 /* initialize with xtr(im,0); */
179 int xtr(
180 image_desc_t *im,
181 time_t mytime)
182 {
183 static double pixie;
185 if (mytime == 0) {
186 pixie = (double) im->xsize / (double) (im->end - im->start);
187 return im->xorigin;
188 }
189 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
190 }
192 /* translate data values into y coordinates */
193 double ytr(
194 image_desc_t *im,
195 double value)
196 {
197 static double pixie;
198 double yval;
200 if (isnan(value)) {
201 if (!im->logarithmic)
202 pixie = (double) im->ysize / (im->maxval - im->minval);
203 else
204 pixie =
205 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
206 yval = im->yorigin;
207 } else if (!im->logarithmic) {
208 yval = im->yorigin - pixie * (value - im->minval);
209 } else {
210 if (value < im->minval) {
211 yval = im->yorigin;
212 } else {
213 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
214 }
215 }
216 return yval;
217 }
221 /* conversion function for symbolic entry names */
224 #define conv_if(VV,VVV) \
225 if (strcmp(#VV, string) == 0) return VVV ;
227 enum gf_en gf_conv(
228 char *string)
229 {
231 conv_if(PRINT, GF_PRINT);
232 conv_if(GPRINT, GF_GPRINT);
233 conv_if(COMMENT, GF_COMMENT);
234 conv_if(HRULE, GF_HRULE);
235 conv_if(VRULE, GF_VRULE);
236 conv_if(LINE, GF_LINE);
237 conv_if(AREA, GF_AREA);
238 conv_if(GRAD, GF_GRAD);
239 conv_if(STACK, GF_STACK);
240 conv_if(TICK, GF_TICK);
241 conv_if(TEXTALIGN, GF_TEXTALIGN);
242 conv_if(DEF, GF_DEF);
243 conv_if(CDEF, GF_CDEF);
244 conv_if(VDEF, GF_VDEF);
245 conv_if(XPORT, GF_XPORT);
246 conv_if(SHIFT, GF_SHIFT);
248 return (enum gf_en)(-1);
249 }
251 enum gfx_if_en if_conv(
252 char *string)
253 {
255 conv_if(PNG, IF_PNG);
256 conv_if(SVG, IF_SVG);
257 conv_if(EPS, IF_EPS);
258 conv_if(PDF, IF_PDF);
259 conv_if(XML, IF_XML);
260 conv_if(XMLENUM, IF_XMLENUM);
261 conv_if(CSV, IF_CSV);
262 conv_if(TSV, IF_TSV);
263 conv_if(SSV, IF_SSV);
264 conv_if(JSON, IF_JSON);
265 conv_if(JSONTIME, IF_JSONTIME);
267 return (enum gfx_if_en)(-1);
268 }
270 enum gfx_type_en type_conv(
271 char *string)
272 {
273 conv_if(TIME , GTYPE_TIME);
274 conv_if(XY, GTYPE_XY);
275 return (enum gfx_type_en)(-1);
276 }
278 enum tmt_en tmt_conv(
279 char *string)
280 {
282 conv_if(SECOND, TMT_SECOND);
283 conv_if(MINUTE, TMT_MINUTE);
284 conv_if(HOUR, TMT_HOUR);
285 conv_if(DAY, TMT_DAY);
286 conv_if(WEEK, TMT_WEEK);
287 conv_if(MONTH, TMT_MONTH);
288 conv_if(YEAR, TMT_YEAR);
289 return (enum tmt_en)(-1);
290 }
292 enum grc_en grc_conv(
293 char *string)
294 {
296 conv_if(BACK, GRC_BACK);
297 conv_if(CANVAS, GRC_CANVAS);
298 conv_if(SHADEA, GRC_SHADEA);
299 conv_if(SHADEB, GRC_SHADEB);
300 conv_if(GRID, GRC_GRID);
301 conv_if(MGRID, GRC_MGRID);
302 conv_if(FONT, GRC_FONT);
303 conv_if(ARROW, GRC_ARROW);
304 conv_if(AXIS, GRC_AXIS);
305 conv_if(FRAME, GRC_FRAME);
307 return (enum grc_en)(-1);
308 }
310 enum text_prop_en text_prop_conv(
311 char *string)
312 {
314 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
315 conv_if(TITLE, TEXT_PROP_TITLE);
316 conv_if(AXIS, TEXT_PROP_AXIS);
317 conv_if(UNIT, TEXT_PROP_UNIT);
318 conv_if(LEGEND, TEXT_PROP_LEGEND);
319 conv_if(WATERMARK, TEXT_PROP_WATERMARK);
320 return (enum text_prop_en)(-1);
321 }
324 #undef conv_if
326 int im_free(
327 image_desc_t *im)
328 {
329 unsigned long i, ii;
330 cairo_status_t status = (cairo_status_t) 0;
332 if (im == NULL)
333 return 0;
335 if (im->daemon_addr != NULL)
336 free(im->daemon_addr);
338 for (i = 0; i < (unsigned) im->gdes_c; i++) {
339 if (im->gdes[i].data_first) {
340 /* careful here, because a single pointer can occur several times */
341 free(im->gdes[i].data);
342 if (im->gdes[i].ds_namv) {
343 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
344 free(im->gdes[i].ds_namv[ii]);
345 free(im->gdes[i].ds_namv);
346 }
347 }
348 /* free allocated memory used for dashed lines */
349 if (im->gdes[i].p_dashes != NULL)
350 free(im->gdes[i].p_dashes);
352 free(im->gdes[i].p_data);
353 free(im->gdes[i].rpnp);
354 }
355 free(im->gdes);
357 for (i = 0; i < DIM(text_prop);i++){
358 pango_font_description_free(im->text_prop[i].font_desc);
359 im->text_prop[i].font_desc = NULL;
360 }
362 if (im->font_options)
363 cairo_font_options_destroy(im->font_options);
365 if (im->cr) {
366 status = cairo_status(im->cr);
367 cairo_destroy(im->cr);
368 }
371 if (im->rendered_image) {
372 free(im->rendered_image);
373 }
375 if (im->layout) {
376 g_object_unref (im->layout);
377 }
379 if (im->surface)
380 cairo_surface_destroy(im->surface);
382 if (status)
383 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
384 cairo_status_to_string(status));
386 return 0;
387 }
389 /* find SI magnitude symbol for the given number*/
390 void auto_scale(
391 image_desc_t *im, /* image description */
392 double *value,
393 char **symb_ptr,
394 double *magfact)
395 {
397 char *symbol[] = { "a", /* 10e-18 Atto */
398 "f", /* 10e-15 Femto */
399 "p", /* 10e-12 Pico */
400 "n", /* 10e-9 Nano */
401 "u", /* 10e-6 Micro */
402 "m", /* 10e-3 Milli */
403 " ", /* Base */
404 "k", /* 10e3 Kilo */
405 "M", /* 10e6 Mega */
406 "G", /* 10e9 Giga */
407 "T", /* 10e12 Tera */
408 "P", /* 10e15 Peta */
409 "E"
410 }; /* 10e18 Exa */
412 int symbcenter = 6;
413 int sindex;
415 if (*value == 0.0 || isnan(*value)) {
416 sindex = 0;
417 *magfact = 1.0;
418 } else {
419 sindex = floor(log(fabs(*value)) / log((double) im->base));
420 *magfact = pow((double) im->base, (double) sindex);
421 (*value) /= (*magfact);
422 }
423 if (sindex <= symbcenter && sindex >= -symbcenter) {
424 (*symb_ptr) = symbol[sindex + symbcenter];
425 } else {
426 (*symb_ptr) = "?";
427 }
428 }
430 /* power prefixes */
432 static char si_symbol[] = {
433 'y', /* 10e-24 Yocto */
434 'z', /* 10e-21 Zepto */
435 'a', /* 10e-18 Atto */
436 'f', /* 10e-15 Femto */
437 'p', /* 10e-12 Pico */
438 'n', /* 10e-9 Nano */
439 'u', /* 10e-6 Micro */
440 'm', /* 10e-3 Milli */
441 ' ', /* Base */
442 'k', /* 10e3 Kilo */
443 'M', /* 10e6 Mega */
444 'G', /* 10e9 Giga */
445 'T', /* 10e12 Tera */
446 'P', /* 10e15 Peta */
447 'E', /* 10e18 Exa */
448 'Z', /* 10e21 Zeta */
449 'Y' /* 10e24 Yotta */
450 };
451 static const int si_symbcenter = 8;
453 /* find SI magnitude symbol for the numbers on the y-axis*/
454 void si_unit(
455 image_desc_t *im /* image description */
456 )
457 {
459 double digits, viewdigits = 0;
461 digits =
462 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
463 log((double) im->base));
465 if (im->unitsexponent != 9999) {
466 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
467 viewdigits = floor((double)(im->unitsexponent / 3));
468 } else {
469 viewdigits = digits;
470 }
472 im->magfact = pow((double) im->base, digits);
474 #ifdef DEBUG
475 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
476 #endif
478 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
480 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
481 ((viewdigits + si_symbcenter) >= 0))
482 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
483 else
484 im->symbol = '?';
485 }
487 /* move min and max values around to become sensible */
489 void expand_range(
490 image_desc_t *im)
491 {
492 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
493 600.0, 500.0, 400.0, 300.0, 250.0,
494 200.0, 125.0, 100.0, 90.0, 80.0,
495 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
496 25.0, 20.0, 10.0, 9.0, 8.0,
497 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
498 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
499 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
500 };
502 double scaled_min, scaled_max;
503 double adj;
504 int i;
508 #ifdef DEBUG
509 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
510 im->minval, im->maxval, im->magfact);
511 #endif
513 if (isnan(im->ygridstep)) {
514 if (im->extra_flags & ALTAUTOSCALE) {
515 /* measure the amplitude of the function. Make sure that
516 graph boundaries are slightly higher then max/min vals
517 so we can see amplitude on the graph */
518 double delt, fact;
520 delt = im->maxval - im->minval;
521 adj = delt * 0.1;
522 fact = 2.0 * pow(10.0,
523 floor(log10
524 (max(fabs(im->minval), fabs(im->maxval)) /
525 im->magfact)) - 2);
526 if (delt < fact) {
527 adj = (fact - delt) * 0.55;
528 #ifdef DEBUG
529 printf
530 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
531 im->minval, im->maxval, delt, fact, adj);
532 #endif
533 }
534 im->minval -= adj;
535 im->maxval += adj;
536 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
537 /* measure the amplitude of the function. Make sure that
538 graph boundaries are slightly lower than min vals
539 so we can see amplitude on the graph */
540 adj = (im->maxval - im->minval) * 0.1;
541 im->minval -= adj;
542 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
543 /* measure the amplitude of the function. Make sure that
544 graph boundaries are slightly higher than max vals
545 so we can see amplitude on the graph */
546 adj = (im->maxval - im->minval) * 0.1;
547 im->maxval += adj;
548 } else {
549 scaled_min = im->minval / im->magfact;
550 scaled_max = im->maxval / im->magfact;
552 for (i = 1; sensiblevalues[i] > 0; i++) {
553 if (sensiblevalues[i - 1] >= scaled_min &&
554 sensiblevalues[i] <= scaled_min)
555 im->minval = sensiblevalues[i] * (im->magfact);
557 if (-sensiblevalues[i - 1] <= scaled_min &&
558 -sensiblevalues[i] >= scaled_min)
559 im->minval = -sensiblevalues[i - 1] * (im->magfact);
561 if (sensiblevalues[i - 1] >= scaled_max &&
562 sensiblevalues[i] <= scaled_max)
563 im->maxval = sensiblevalues[i - 1] * (im->magfact);
565 if (-sensiblevalues[i - 1] <= scaled_max &&
566 -sensiblevalues[i] >= scaled_max)
567 im->maxval = -sensiblevalues[i] * (im->magfact);
568 }
569 }
570 } else {
571 /* adjust min and max to the grid definition if there is one */
572 im->minval = (double) im->ylabfact * im->ygridstep *
573 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
574 im->maxval = (double) im->ylabfact * im->ygridstep *
575 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
576 }
578 #ifdef DEBUG
579 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
580 im->minval, im->maxval, im->magfact);
581 #endif
582 }
585 void apply_gridfit(
586 image_desc_t *im)
587 {
588 if (isnan(im->minval) || isnan(im->maxval))
589 return;
590 ytr(im, DNAN);
591 if (im->logarithmic) {
592 double ya, yb, ypix, ypixfrac;
593 double log10_range = log10(im->maxval) - log10(im->minval);
595 ya = pow((double) 10, floor(log10(im->minval)));
596 while (ya < im->minval)
597 ya *= 10;
598 if (ya > im->maxval)
599 return; /* don't have y=10^x gridline */
600 yb = ya * 10;
601 if (yb <= im->maxval) {
602 /* we have at least 2 y=10^x gridlines.
603 Make sure distance between them in pixels
604 are an integer by expanding im->maxval */
605 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
606 double factor = y_pixel_delta / floor(y_pixel_delta);
607 double new_log10_range = factor * log10_range;
608 double new_ymax_log10 = log10(im->minval) + new_log10_range;
610 im->maxval = pow(10, new_ymax_log10);
611 ytr(im, DNAN); /* reset precalc */
612 log10_range = log10(im->maxval) - log10(im->minval);
613 }
614 /* make sure first y=10^x gridline is located on
615 integer pixel position by moving scale slightly
616 downwards (sub-pixel movement) */
617 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
618 ypixfrac = ypix - floor(ypix);
619 if (ypixfrac > 0 && ypixfrac < 1) {
620 double yfrac = ypixfrac / im->ysize;
622 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
623 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
624 ytr(im, DNAN); /* reset precalc */
625 }
626 } else {
627 /* Make sure we have an integer pixel distance between
628 each minor gridline */
629 double ypos1 = ytr(im, im->minval);
630 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
631 double y_pixel_delta = ypos1 - ypos2;
632 double factor = y_pixel_delta / floor(y_pixel_delta);
633 double new_range = factor * (im->maxval - im->minval);
634 double gridstep = im->ygrid_scale.gridstep;
635 double minor_y, minor_y_px, minor_y_px_frac;
637 if (im->maxval > 0.0)
638 im->maxval = im->minval + new_range;
639 else
640 im->minval = im->maxval - new_range;
641 ytr(im, DNAN); /* reset precalc */
642 /* make sure first minor gridline is on integer pixel y coord */
643 minor_y = gridstep * floor(im->minval / gridstep);
644 while (minor_y < im->minval)
645 minor_y += gridstep;
646 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
647 minor_y_px_frac = minor_y_px - floor(minor_y_px);
648 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
649 double yfrac = minor_y_px_frac / im->ysize;
650 double range = im->maxval - im->minval;
652 im->minval = im->minval - yfrac * range;
653 im->maxval = im->maxval - yfrac * range;
654 ytr(im, DNAN); /* reset precalc */
655 }
656 calc_horizontal_grid(im); /* recalc with changed im->maxval */
657 }
658 }
660 /* reduce data reimplementation by Alex */
662 void reduce_data(
663 enum cf_en cf, /* which consolidation function ? */
664 unsigned long cur_step, /* step the data currently is in */
665 time_t *start, /* start, end and step as requested ... */
666 time_t *end, /* ... by the application will be ... */
667 unsigned long *step, /* ... adjusted to represent reality */
668 unsigned long *ds_cnt, /* number of data sources in file */
669 rrd_value_t **data)
670 { /* two dimensional array containing the data */
671 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
672 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
673 0;
674 rrd_value_t *srcptr, *dstptr;
676 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
677 dstptr = *data;
678 srcptr = *data;
679 row_cnt = ((*end) - (*start)) / cur_step;
681 #ifdef DEBUG
682 #define DEBUG_REDUCE
683 #endif
684 #ifdef DEBUG_REDUCE
685 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
686 row_cnt, reduce_factor, *start, *end, cur_step);
687 for (col = 0; col < row_cnt; col++) {
688 printf("time %10lu: ", *start + (col + 1) * cur_step);
689 for (i = 0; i < *ds_cnt; i++)
690 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
691 printf("\n");
692 }
693 #endif
695 /* We have to combine [reduce_factor] rows of the source
696 ** into one row for the destination. Doing this we also
697 ** need to take care to combine the correct rows. First
698 ** alter the start and end time so that they are multiples
699 ** of the new step time. We cannot reduce the amount of
700 ** time so we have to move the end towards the future and
701 ** the start towards the past.
702 */
703 end_offset = (*end) % (*step);
704 start_offset = (*start) % (*step);
706 /* If there is a start offset (which cannot be more than
707 ** one destination row), skip the appropriate number of
708 ** source rows and one destination row. The appropriate
709 ** number is what we do know (start_offset/cur_step) of
710 ** the new interval (*step/cur_step aka reduce_factor).
711 */
712 #ifdef DEBUG_REDUCE
713 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
714 printf("row_cnt before: %lu\n", row_cnt);
715 #endif
716 if (start_offset) {
717 (*start) = (*start) - start_offset;
718 skiprows = reduce_factor - start_offset / cur_step;
719 srcptr += skiprows * *ds_cnt;
720 for (col = 0; col < (*ds_cnt); col++)
721 *dstptr++ = DNAN;
722 row_cnt -= skiprows;
723 }
724 #ifdef DEBUG_REDUCE
725 printf("row_cnt between: %lu\n", row_cnt);
726 #endif
728 /* At the end we have some rows that are not going to be
729 ** used, the amount is end_offset/cur_step
730 */
731 if (end_offset) {
732 (*end) = (*end) - end_offset + (*step);
733 skiprows = end_offset / cur_step;
734 row_cnt -= skiprows;
735 }
736 #ifdef DEBUG_REDUCE
737 printf("row_cnt after: %lu\n", row_cnt);
738 #endif
740 /* Sanity check: row_cnt should be multiple of reduce_factor */
741 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
743 if (row_cnt % reduce_factor) {
744 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
745 row_cnt, reduce_factor);
746 printf("BUG in reduce_data()\n");
747 exit(1);
748 }
750 /* Now combine reduce_factor intervals at a time
751 ** into one interval for the destination.
752 */
754 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
755 for (col = 0; col < (*ds_cnt); col++) {
756 rrd_value_t newval = DNAN;
757 unsigned long validval = 0;
759 for (i = 0; i < reduce_factor; i++) {
760 if (isnan(srcptr[i * (*ds_cnt) + col])) {
761 continue;
762 }
763 validval++;
764 if (isnan(newval))
765 newval = srcptr[i * (*ds_cnt) + col];
766 else {
767 switch (cf) {
768 case CF_HWPREDICT:
769 case CF_MHWPREDICT:
770 case CF_DEVSEASONAL:
771 case CF_DEVPREDICT:
772 case CF_SEASONAL:
773 case CF_AVERAGE:
774 newval += srcptr[i * (*ds_cnt) + col];
775 break;
776 case CF_MINIMUM:
777 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
778 break;
779 case CF_FAILURES:
780 /* an interval contains a failure if any subintervals contained a failure */
781 case CF_MAXIMUM:
782 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
783 break;
784 case CF_LAST:
785 newval = srcptr[i * (*ds_cnt) + col];
786 break;
787 }
788 }
789 }
790 if (validval == 0) {
791 newval = DNAN;
792 } else {
793 switch (cf) {
794 case CF_HWPREDICT:
795 case CF_MHWPREDICT:
796 case CF_DEVSEASONAL:
797 case CF_DEVPREDICT:
798 case CF_SEASONAL:
799 case CF_AVERAGE:
800 newval /= validval;
801 break;
802 case CF_MINIMUM:
803 case CF_FAILURES:
804 case CF_MAXIMUM:
805 case CF_LAST:
806 break;
807 }
808 }
809 *dstptr++ = newval;
810 }
811 srcptr += (*ds_cnt) * reduce_factor;
812 row_cnt -= reduce_factor;
813 }
814 /* If we had to alter the endtime, we didn't have enough
815 ** source rows to fill the last row. Fill it with NaN.
816 */
817 if (end_offset)
818 for (col = 0; col < (*ds_cnt); col++)
819 *dstptr++ = DNAN;
820 #ifdef DEBUG_REDUCE
821 row_cnt = ((*end) - (*start)) / *step;
822 srcptr = *data;
823 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
824 row_cnt, *start, *end, *step);
825 for (col = 0; col < row_cnt; col++) {
826 printf("time %10lu: ", *start + (col + 1) * (*step));
827 for (i = 0; i < *ds_cnt; i++)
828 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
829 printf("\n");
830 }
831 #endif
832 }
835 /* get the data required for the graphs from the
836 relevant rrds ... */
838 int data_fetch(
839 image_desc_t *im)
840 {
841 int i, ii;
842 int skip;
844 /* pull the data from the rrd files ... */
845 for (i = 0; i < (int) im->gdes_c; i++) {
846 /* only GF_DEF elements fetch data */
847 if (im->gdes[i].gf != GF_DEF)
848 continue;
850 skip = 0;
851 /* do we have it already ? */
852 for (ii = 0; ii < i; ii++) {
853 if (im->gdes[ii].gf != GF_DEF)
854 continue;
855 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
856 && (im->gdes[i].cf == im->gdes[ii].cf)
857 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
858 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
859 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
860 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
861 /* OK, the data is already there.
862 ** Just copy the header portion
863 */
864 im->gdes[i].start = im->gdes[ii].start;
865 im->gdes[i].end = im->gdes[ii].end;
866 im->gdes[i].step = im->gdes[ii].step;
867 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
868 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
869 im->gdes[i].data = im->gdes[ii].data;
870 im->gdes[i].data_first = 0;
871 skip = 1;
872 }
873 if (skip)
874 break;
875 }
876 if (!skip) {
877 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
878 const char *rrd_daemon;
879 int status;
881 if (im->gdes[i].daemon[0] != 0)
882 rrd_daemon = im->gdes[i].daemon;
883 else
884 rrd_daemon = im->daemon_addr;
886 /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
887 * case. If "daemon" holds the same value as in the previous
888 * iteration, no actual new connection is established - the
889 * existing connection is re-used. */
890 rrdc_connect (rrd_daemon);
892 /* If connecting was successfull, use the daemon to query the data.
893 * If there is no connection, for example because no daemon address
894 * was specified, (try to) use the local file directly. */
895 if (rrdc_is_connected (rrd_daemon))
896 {
897 status = rrdc_fetch (im->gdes[i].rrd,
898 cf_to_string (im->gdes[i].cf),
899 &im->gdes[i].start,
900 &im->gdes[i].end,
901 &ft_step,
902 &im->gdes[i].ds_cnt,
903 &im->gdes[i].ds_namv,
904 &im->gdes[i].data);
905 if (status != 0)
906 return (status);
907 }
908 else
909 {
910 if ((rrd_fetch_fn(im->gdes[i].rrd,
911 im->gdes[i].cf,
912 &im->gdes[i].start,
913 &im->gdes[i].end,
914 &ft_step,
915 &im->gdes[i].ds_cnt,
916 &im->gdes[i].ds_namv,
917 &im->gdes[i].data)) == -1) {
918 return -1;
919 }
920 }
921 im->gdes[i].data_first = 1;
923 /* must reduce to at least im->step
924 otherwhise we end up with more data than we can handle in the
925 chart and visibility of data will be random */
926 im->gdes[i].step = max(im->gdes[i].step,im->step);
927 if (ft_step < im->gdes[i].step) {
928 reduce_data(im->gdes[i].cf_reduce,
929 ft_step,
930 &im->gdes[i].start,
931 &im->gdes[i].end,
932 &im->gdes[i].step,
933 &im->gdes[i].ds_cnt, &im->gdes[i].data);
934 } else {
935 im->gdes[i].step = ft_step;
936 }
937 }
939 /* lets see if the required data source is really there */
940 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
941 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
942 im->gdes[i].ds = ii;
943 }
944 }
945 if (im->gdes[i].ds == -1) {
946 rrd_set_error("No DS called '%s' in '%s'",
947 im->gdes[i].ds_nam, im->gdes[i].rrd);
948 return -1;
949 }
951 }
952 return 0;
953 }
955 /* evaluate the expressions in the CDEF functions */
957 /*************************************************************
958 * CDEF stuff
959 *************************************************************/
961 long find_var_wrapper(
962 void *arg1,
963 char *key)
964 {
965 return find_var((image_desc_t *) arg1, key);
966 }
968 /* find gdes containing var*/
969 long find_var(
970 image_desc_t *im,
971 char *key)
972 {
973 long ii;
975 for (ii = 0; ii < im->gdes_c - 1; ii++) {
976 if ((im->gdes[ii].gf == GF_DEF
977 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
978 && (strcmp(im->gdes[ii].vname, key) == 0)) {
979 return ii;
980 }
981 }
982 return -1;
983 }
985 /* find the greatest common divisor for all the numbers
986 in the 0 terminated num array */
987 long lcd(
988 long *num)
989 {
990 long rest;
991 int i;
993 for (i = 0; num[i + 1] != 0; i++) {
994 do {
995 rest = num[i] % num[i + 1];
996 num[i] = num[i + 1];
997 num[i + 1] = rest;
998 } while (rest != 0);
999 num[i + 1] = num[i];
1000 }
1001 /* return i==0?num[i]:num[i-1]; */
1002 return num[i];
1003 }
1005 /* run the rpn calculator on all the VDEF and CDEF arguments */
1006 int data_calc(
1007 image_desc_t *im)
1008 {
1010 int gdi;
1011 int dataidx;
1012 long *steparray, rpi;
1013 int stepcnt;
1014 time_t now;
1015 rpnstack_t rpnstack;
1017 rpnstack_init(&rpnstack);
1019 for (gdi = 0; gdi < im->gdes_c; gdi++) {
1020 /* Look for GF_VDEF and GF_CDEF in the same loop,
1021 * so CDEFs can use VDEFs and vice versa
1022 */
1023 switch (im->gdes[gdi].gf) {
1024 case GF_XPORT:
1025 break;
1026 case GF_SHIFT:{
1027 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
1029 /* remove current shift */
1030 vdp->start -= vdp->shift;
1031 vdp->end -= vdp->shift;
1033 /* vdef */
1034 if (im->gdes[gdi].shidx >= 0)
1035 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1036 /* constant */
1037 else
1038 vdp->shift = im->gdes[gdi].shval;
1040 /* normalize shift to multiple of consolidated step */
1041 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1043 /* apply shift */
1044 vdp->start += vdp->shift;
1045 vdp->end += vdp->shift;
1046 break;
1047 }
1048 case GF_VDEF:
1049 /* A VDEF has no DS. This also signals other parts
1050 * of rrdtool that this is a VDEF value, not a CDEF.
1051 */
1052 im->gdes[gdi].ds_cnt = 0;
1053 if (vdef_calc(im, gdi)) {
1054 rrd_set_error("Error processing VDEF '%s'",
1055 im->gdes[gdi].vname);
1056 rpnstack_free(&rpnstack);
1057 return -1;
1058 }
1059 break;
1060 case GF_CDEF:
1061 im->gdes[gdi].ds_cnt = 1;
1062 im->gdes[gdi].ds = 0;
1063 im->gdes[gdi].data_first = 1;
1064 im->gdes[gdi].start = 0;
1065 im->gdes[gdi].end = 0;
1066 steparray = NULL;
1067 stepcnt = 0;
1068 dataidx = -1;
1070 /* Find the variables in the expression.
1071 * - VDEF variables are substituted by their values
1072 * and the opcode is changed into OP_NUMBER.
1073 * - CDEF variables are analized for their step size,
1074 * the lowest common denominator of all the step
1075 * sizes of the data sources involved is calculated
1076 * and the resulting number is the step size for the
1077 * resulting data source.
1078 */
1079 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1080 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1081 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1082 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1084 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1085 #if 0
1086 printf
1087 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1088 im->gdes[gdi].vname, im->gdes[ptr].vname);
1089 printf("DEBUG: value from vdef is %f\n",
1090 im->gdes[ptr].vf.val);
1091 #endif
1092 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1093 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1094 } else { /* normal variables and PREF(variables) */
1096 /* add one entry to the array that keeps track of the step sizes of the
1097 * data sources going into the CDEF. */
1098 if ((steparray =
1099 (long*)rrd_realloc(steparray,
1100 (++stepcnt +
1101 1) * sizeof(*steparray))) == NULL) {
1102 rrd_set_error("realloc steparray");
1103 rpnstack_free(&rpnstack);
1104 return -1;
1105 };
1107 steparray[stepcnt - 1] = im->gdes[ptr].step;
1109 /* adjust start and end of cdef (gdi) so
1110 * that it runs from the latest start point
1111 * to the earliest endpoint of any of the
1112 * rras involved (ptr)
1113 */
1115 if (im->gdes[gdi].start < im->gdes[ptr].start)
1116 im->gdes[gdi].start = im->gdes[ptr].start;
1118 if (im->gdes[gdi].end == 0 ||
1119 im->gdes[gdi].end > im->gdes[ptr].end)
1120 im->gdes[gdi].end = im->gdes[ptr].end;
1122 /* store pointer to the first element of
1123 * the rra providing data for variable,
1124 * further save step size and data source
1125 * count of this rra
1126 */
1127 im->gdes[gdi].rpnp[rpi].data =
1128 im->gdes[ptr].data + im->gdes[ptr].ds;
1129 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1130 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1132 /* backoff the *.data ptr; this is done so
1133 * rpncalc() function doesn't have to treat
1134 * the first case differently
1135 */
1136 } /* if ds_cnt != 0 */
1137 } /* if OP_VARIABLE */
1138 } /* loop through all rpi */
1140 /* move the data pointers to the correct period */
1141 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1142 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1143 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1144 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1145 long diff =
1146 im->gdes[gdi].start - im->gdes[ptr].start;
1148 if (diff > 0)
1149 im->gdes[gdi].rpnp[rpi].data +=
1150 (diff / im->gdes[ptr].step) *
1151 im->gdes[ptr].ds_cnt;
1152 }
1153 }
1155 if (steparray == NULL) {
1156 rrd_set_error("rpn expressions without DEF"
1157 " or CDEF variables are not supported");
1158 rpnstack_free(&rpnstack);
1159 return -1;
1160 }
1161 steparray[stepcnt] = 0;
1162 /* Now find the resulting step. All steps in all
1163 * used RRAs have to be visited
1164 */
1165 im->gdes[gdi].step = lcd(steparray);
1166 free(steparray);
1167 if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1168 im->gdes[gdi].start)
1169 / im->gdes[gdi].step)
1170 * sizeof(double))) == NULL) {
1171 rrd_set_error("malloc im->gdes[gdi].data");
1172 rpnstack_free(&rpnstack);
1173 return -1;
1174 }
1176 /* Step through the new cdef results array and
1177 * calculate the values
1178 */
1179 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1180 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1181 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1183 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1184 * in this case we are advancing by timesteps;
1185 * we use the fact that time_t is a synonym for long
1186 */
1187 if (rpn_calc(rpnp, &rpnstack, (long) now,
1188 im->gdes[gdi].data, ++dataidx) == -1) {
1189 /* rpn_calc sets the error string */
1190 rpnstack_free(&rpnstack);
1191 return -1;
1192 }
1193 } /* enumerate over time steps within a CDEF */
1194 break;
1195 default:
1196 continue;
1197 }
1198 } /* enumerate over CDEFs */
1199 rpnstack_free(&rpnstack);
1200 return 0;
1201 }
1203 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1204 /* yes we are loosing precision by doing tos with floats instead of doubles
1205 but it seems more stable this way. */
1207 static int AlmostEqual2sComplement(
1208 float A,
1209 float B,
1210 int maxUlps)
1211 {
1213 int aInt = *(int *) &A;
1214 int bInt = *(int *) &B;
1215 int intDiff;
1217 /* Make sure maxUlps is non-negative and small enough that the
1218 default NAN won't compare as equal to anything. */
1220 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1222 /* Make aInt lexicographically ordered as a twos-complement int */
1224 if (aInt < 0)
1225 aInt = 0x80000000l - aInt;
1227 /* Make bInt lexicographically ordered as a twos-complement int */
1229 if (bInt < 0)
1230 bInt = 0x80000000l - bInt;
1232 intDiff = abs(aInt - bInt);
1234 if (intDiff <= maxUlps)
1235 return 1;
1237 return 0;
1238 }
1240 /* massage data so, that we get one value for each x coordinate in the graph */
1241 int data_proc(
1242 image_desc_t *im)
1243 {
1244 long i, ii;
1245 double pixstep = (double) (im->end - im->start)
1246 / (double) im->xsize; /* how much time
1247 passes in one pixel */
1248 double paintval;
1249 double minval = DNAN, maxval = DNAN;
1251 unsigned long gr_time;
1253 /* memory for the processed data */
1254 for (i = 0; i < im->gdes_c; i++) {
1255 if ((im->gdes[i].gf == GF_LINE)
1256 || (im->gdes[i].gf == GF_AREA)
1257 || (im->gdes[i].gf == GF_TICK)
1258 || (im->gdes[i].gf == GF_GRAD)
1259 ) {
1260 if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1261 * sizeof(rrd_value_t))) == NULL) {
1262 rrd_set_error("malloc data_proc");
1263 return -1;
1264 }
1265 }
1266 }
1268 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1269 long vidx;
1271 gr_time = im->start + pixstep * i; /* time of the current step */
1272 paintval = 0.0;
1274 for (ii = 0; ii < im->gdes_c; ii++) {
1275 double value;
1277 switch (im->gdes[ii].gf) {
1278 case GF_LINE:
1279 case GF_AREA:
1280 case GF_GRAD:
1281 case GF_TICK:
1282 if (!im->gdes[ii].stack)
1283 paintval = 0.0;
1284 value = im->gdes[ii].yrule;
1285 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1286 /* The time of the data doesn't necessarily match
1287 ** the time of the graph. Beware.
1288 */
1289 vidx = im->gdes[ii].vidx;
1290 if (im->gdes[vidx].gf == GF_VDEF) {
1291 value = im->gdes[vidx].vf.val;
1292 } else
1293 if (((long int) gr_time >=
1294 (long int) im->gdes[vidx].start)
1295 && ((long int) gr_time <
1296 (long int) im->gdes[vidx].end)) {
1297 value = im->gdes[vidx].data[(unsigned long)
1298 floor((double)
1299 (gr_time -
1300 im->gdes[vidx].
1301 start)
1302 /
1303 im->gdes[vidx].step)
1304 * im->gdes[vidx].ds_cnt +
1305 im->gdes[vidx].ds];
1306 } else {
1307 value = DNAN;
1308 }
1309 };
1311 if (!isnan(value)) {
1312 paintval += value;
1313 im->gdes[ii].p_data[i] = paintval;
1314 /* GF_TICK: the data values are not
1315 ** relevant for min and max
1316 */
1317 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1318 if ((isnan(minval) || paintval < minval) &&
1319 !(im->logarithmic && paintval <= 0.0))
1320 minval = paintval;
1321 if (isnan(maxval) || paintval > maxval)
1322 maxval = paintval;
1323 }
1324 } else {
1325 im->gdes[ii].p_data[i] = DNAN;
1326 }
1327 break;
1328 case GF_STACK:
1329 rrd_set_error
1330 ("STACK should already be turned into LINE or AREA here");
1331 return -1;
1332 break;
1333 default:
1334 break;
1335 }
1336 }
1337 }
1339 /* if min or max have not been asigned a value this is because
1340 there was no data in the graph ... this is not good ...
1341 lets set these to dummy values then ... */
1343 if (im->logarithmic) {
1344 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1345 minval = 0.0; /* catching this right away below */
1346 maxval = 5.1;
1347 }
1348 /* in logarithm mode, where minval is smaller or equal
1349 to 0 make the beast just way smaller than maxval */
1350 if (minval <= 0) {
1351 minval = maxval / 10e8;
1352 }
1353 } else {
1354 if (isnan(minval) || isnan(maxval)) {
1355 minval = 0.0;
1356 maxval = 1.0;
1357 }
1358 }
1360 /* adjust min and max values given by the user */
1361 /* for logscale we add something on top */
1362 if (isnan(im->minval)
1363 || ((!im->rigid) && im->minval > minval)
1364 ) {
1365 if (im->logarithmic)
1366 im->minval = minval / 2.0;
1367 else
1368 im->minval = minval;
1369 }
1370 if (isnan(im->maxval)
1371 || (!im->rigid && im->maxval < maxval)
1372 ) {
1373 if (im->logarithmic)
1374 im->maxval = maxval * 2.0;
1375 else
1376 im->maxval = maxval;
1377 }
1379 /* make sure min is smaller than max */
1380 if (im->minval > im->maxval) {
1381 if (im->minval > 0)
1382 im->minval = 0.99 * im->maxval;
1383 else
1384 im->minval = 1.01 * im->maxval;
1385 }
1387 /* make sure min and max are not equal */
1388 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1389 if (im->maxval > 0)
1390 im->maxval *= 1.01;
1391 else
1392 im->maxval *= 0.99;
1394 /* make sure min and max are not both zero */
1395 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1396 im->maxval = 1.0;
1397 }
1398 }
1399 return 0;
1400 }
1402 static int find_first_weekday(void){
1403 static int first_weekday = -1;
1404 if (first_weekday == -1){
1405 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1406 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1407 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1408 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1409 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1410 else first_weekday = 1; /* we go for a monday default */
1411 #else
1412 first_weekday = 1;
1413 #endif
1414 }
1415 return first_weekday;
1416 }
1418 /* identify the point where the first gridline, label ... gets placed */
1420 time_t find_first_time(
1421 time_t start, /* what is the initial time */
1422 enum tmt_en baseint, /* what is the basic interval */
1423 long basestep /* how many if these do we jump a time */
1424 )
1425 {
1426 struct tm tm;
1428 localtime_r(&start, &tm);
1429 /* let mktime figure this dst on its own */
1430 tm.tm_isdst = -1;
1432 switch (baseint) {
1433 case TMT_SECOND:
1434 tm. tm_sec -= tm.tm_sec % basestep;
1436 break;
1437 case TMT_MINUTE:
1438 tm. tm_sec = 0;
1439 tm. tm_min -= tm.tm_min % basestep;
1441 break;
1442 case TMT_HOUR:
1443 tm. tm_sec = 0;
1444 tm. tm_min = 0;
1445 tm. tm_hour -= tm.tm_hour % basestep;
1447 break;
1448 case TMT_DAY:
1449 /* we do NOT look at the basestep for this ... */
1450 tm. tm_sec = 0;
1451 tm. tm_min = 0;
1452 tm. tm_hour = 0;
1454 break;
1455 case TMT_WEEK:
1456 /* we do NOT look at the basestep for this ... */
1457 tm. tm_sec = 0;
1458 tm. tm_min = 0;
1459 tm. tm_hour = 0;
1460 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1462 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1463 tm. tm_mday -= 7; /* we want the *previous* week */
1465 break;
1466 case TMT_MONTH:
1467 tm. tm_sec = 0;
1468 tm. tm_min = 0;
1469 tm. tm_hour = 0;
1470 tm. tm_mday = 1;
1471 tm. tm_mon -= tm.tm_mon % basestep;
1473 break;
1475 case TMT_YEAR:
1476 tm. tm_sec = 0;
1477 tm. tm_min = 0;
1478 tm. tm_hour = 0;
1479 tm. tm_mday = 1;
1480 tm. tm_mon = 0;
1481 tm. tm_year -= (
1482 tm.tm_year + 1900) %basestep;
1484 }
1485 return mktime(&tm);
1486 }
1488 /* identify the point where the next gridline, label ... gets placed */
1489 time_t find_next_time(
1490 time_t current, /* what is the initial time */
1491 enum tmt_en baseint, /* what is the basic interval */
1492 long basestep /* how many if these do we jump a time */
1493 )
1494 {
1495 struct tm tm;
1496 time_t madetime;
1498 localtime_r(¤t, &tm);
1499 /* let mktime figure this dst on its own */
1500 tm.tm_isdst = -1;
1502 int limit = 2;
1503 switch (baseint) {
1504 case TMT_SECOND: limit = 7200; break;
1505 case TMT_MINUTE: limit = 120; break;
1506 case TMT_HOUR: limit = 2; break;
1507 default: limit = 2; break;
1508 }
1509 do {
1510 switch (baseint) {
1511 case TMT_SECOND:
1512 tm. tm_sec += basestep;
1514 break;
1515 case TMT_MINUTE:
1516 tm. tm_min += basestep;
1518 break;
1519 case TMT_HOUR:
1520 tm. tm_hour += basestep;
1522 break;
1523 case TMT_DAY:
1524 tm. tm_mday += basestep;
1526 break;
1527 case TMT_WEEK:
1528 tm. tm_mday += 7 * basestep;
1530 break;
1531 case TMT_MONTH:
1532 tm. tm_mon += basestep;
1534 break;
1535 case TMT_YEAR:
1536 tm. tm_year += basestep;
1537 }
1538 madetime = mktime(&tm);
1539 } while (madetime == -1 && limit-- >= 0); /* this is necessary to skip impossible times
1540 like the daylight saving time skips */
1541 return madetime;
1543 }
1546 /* calculate values required for PRINT and GPRINT functions */
1548 int print_calc(
1549 image_desc_t *im)
1550 {
1551 long i, ii, validsteps;
1552 double printval;
1553 struct tm tmvdef;
1554 int graphelement = 0;
1555 long vidx;
1556 int max_ii;
1557 double magfact = -1;
1558 char *si_symb = "";
1559 char *percent_s;
1560 int prline_cnt = 0;
1562 /* wow initializing tmvdef is quite a task :-) */
1563 time_t now = time(NULL);
1565 localtime_r(&now, &tmvdef);
1566 for (i = 0; i < im->gdes_c; i++) {
1567 vidx = im->gdes[i].vidx;
1568 switch (im->gdes[i].gf) {
1569 case GF_PRINT:
1570 case GF_GPRINT:
1571 /* PRINT and GPRINT can now print VDEF generated values.
1572 * There's no need to do any calculations on them as these
1573 * calculations were already made.
1574 */
1575 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1576 printval = im->gdes[vidx].vf.val;
1577 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1578 } else { /* need to calculate max,min,avg etcetera */
1579 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1580 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1581 printval = DNAN;
1582 validsteps = 0;
1583 for (ii = im->gdes[vidx].ds;
1584 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1585 if (!finite(im->gdes[vidx].data[ii]))
1586 continue;
1587 if (isnan(printval)) {
1588 printval = im->gdes[vidx].data[ii];
1589 validsteps++;
1590 continue;
1591 }
1593 switch (im->gdes[i].cf) {
1594 case CF_HWPREDICT:
1595 case CF_MHWPREDICT:
1596 case CF_DEVPREDICT:
1597 case CF_DEVSEASONAL:
1598 case CF_SEASONAL:
1599 case CF_AVERAGE:
1600 validsteps++;
1601 printval += im->gdes[vidx].data[ii];
1602 break;
1603 case CF_MINIMUM:
1604 printval = min(printval, im->gdes[vidx].data[ii]);
1605 break;
1606 case CF_FAILURES:
1607 case CF_MAXIMUM:
1608 printval = max(printval, im->gdes[vidx].data[ii]);
1609 break;
1610 case CF_LAST:
1611 printval = im->gdes[vidx].data[ii];
1612 }
1613 }
1614 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1615 if (validsteps > 1) {
1616 printval = (printval / validsteps);
1617 }
1618 }
1619 } /* prepare printval */
1621 if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1622 /* Magfact is set to -1 upon entry to print_calc. If it
1623 * is still less than 0, then we need to run auto_scale.
1624 * Otherwise, put the value into the correct units. If
1625 * the value is 0, then do not set the symbol or magnification
1626 * so next the calculation will be performed again. */
1627 if (magfact < 0.0) {
1628 auto_scale(im, &printval, &si_symb, &magfact);
1629 if (printval == 0.0)
1630 magfact = -1.0;
1631 } else {
1632 printval /= magfact;
1633 }
1634 *(++percent_s) = 's';
1635 } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1636 auto_scale(im, &printval, &si_symb, &magfact);
1637 }
1639 if (im->gdes[i].gf == GF_PRINT) {
1640 rrd_infoval_t prline;
1642 if (im->gdes[i].strftm) {
1643 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1644 if (im->gdes[vidx].vf.never == 1) {
1645 time_clean(prline.u_str, im->gdes[i].format);
1646 } else {
1647 strftime(prline.u_str,
1648 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1649 }
1650 } else if (bad_format(im->gdes[i].format)) {
1651 rrd_set_error
1652 ("bad format for PRINT in '%s'", im->gdes[i].format);
1653 return -1;
1654 } else {
1655 prline.u_str =
1656 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1657 }
1658 grinfo_push(im,
1659 sprintf_alloc
1660 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1661 free(prline.u_str);
1662 } else {
1663 /* GF_GPRINT */
1665 if (im->gdes[i].strftm) {
1666 if (im->gdes[vidx].vf.never == 1) {
1667 time_clean(im->gdes[i].legend, im->gdes[i].format);
1668 } else {
1669 strftime(im->gdes[i].legend,
1670 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1671 }
1672 } else {
1673 if (bad_format(im->gdes[i].format)) {
1674 rrd_set_error
1675 ("bad format for GPRINT in '%s'",
1676 im->gdes[i].format);
1677 return -1;
1678 }
1679 snprintf(im->gdes[i].legend,
1680 FMT_LEG_LEN - 2,
1681 im->gdes[i].format, printval, si_symb);
1682 }
1683 graphelement = 1;
1684 }
1685 break;
1686 case GF_LINE:
1687 case GF_AREA:
1688 case GF_GRAD:
1689 case GF_TICK:
1690 graphelement = 1;
1691 break;
1692 case GF_HRULE:
1693 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1694 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1695 };
1696 graphelement = 1;
1697 break;
1698 case GF_VRULE:
1699 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1700 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1701 };
1702 graphelement = 1;
1703 break;
1704 case GF_COMMENT:
1705 case GF_TEXTALIGN:
1706 case GF_DEF:
1707 case GF_CDEF:
1708 case GF_VDEF:
1709 #ifdef WITH_PIECHART
1710 case GF_PART:
1711 #endif
1712 case GF_SHIFT:
1713 case GF_XPORT:
1714 break;
1715 case GF_STACK:
1716 rrd_set_error
1717 ("STACK should already be turned into LINE or AREA here");
1718 return -1;
1719 break;
1720 case GF_XAXIS:
1721 case GF_YAXIS:
1722 break;
1723 }
1724 }
1725 return graphelement;
1726 }
1730 /* place legends with color spots */
1731 int leg_place(
1732 image_desc_t *im,
1733 int calc_width)
1734 {
1735 /* graph labels */
1736 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1737 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1738 int fill = 0, fill_last;
1739 double legendwidth; // = im->ximg - 2 * border;
1740 int leg_c = 0;
1741 double leg_x = border;
1742 int leg_y = 0; //im->yimg;
1743 int leg_cc;
1744 double glue = 0;
1745 int i, ii, mark = 0;
1746 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1747 int *legspace;
1748 char *tab;
1749 char saved_legend[FMT_LEG_LEN + 5];
1751 if(calc_width){
1752 legendwidth = 0;
1753 }
1754 else{
1755 legendwidth = im->legendwidth - 2 * border;
1756 }
1759 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1760 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1761 rrd_set_error("malloc for legspace");
1762 return -1;
1763 }
1765 for (i = 0; i < im->gdes_c; i++) {
1766 char prt_fctn; /*special printfunctions */
1767 if(calc_width){
1768 strncpy(saved_legend, im->gdes[i].legend, sizeof saved_legend);
1769 }
1771 fill_last = fill;
1772 /* hide legends for rules which are not displayed */
1773 if (im->gdes[i].gf == GF_TEXTALIGN) {
1774 default_txtalign = im->gdes[i].txtalign;
1775 }
1777 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1778 if (im->gdes[i].gf == GF_HRULE
1779 && (im->gdes[i].yrule <
1780 im->minval || im->gdes[i].yrule > im->maxval))
1781 im->gdes[i].legend[0] = '\0';
1782 if (im->gdes[i].gf == GF_VRULE
1783 && (im->gdes[i].xrule <
1784 im->start || im->gdes[i].xrule > im->end))
1785 im->gdes[i].legend[0] = '\0';
1786 }
1788 /* turn \\t into tab */
1789 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1790 memmove(tab, tab + 1, strlen(tab));
1791 tab[0] = (char) 9;
1792 }
1794 leg_cc = strlen(im->gdes[i].legend);
1795 /* is there a controle code at the end of the legend string ? */
1796 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1797 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1798 leg_cc -= 2;
1799 im->gdes[i].legend[leg_cc] = '\0';
1800 } else {
1801 prt_fctn = '\0';
1802 }
1803 /* only valid control codes */
1804 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1805 prt_fctn != 'r' &&
1806 prt_fctn != 'j' &&
1807 prt_fctn != 'c' &&
1808 prt_fctn != 'u' &&
1809 prt_fctn != '.' &&
1810 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1811 free(legspace);
1812 rrd_set_error
1813 ("Unknown control code at the end of '%s\\%c'",
1814 im->gdes[i].legend, prt_fctn);
1815 return -1;
1816 }
1817 /* \n -> \l */
1818 if (prt_fctn == 'n') {
1819 prt_fctn = 'l';
1820 }
1821 /* \. is a null operation to allow strings ending in \x */
1822 if (prt_fctn == '.') {
1823 prt_fctn = '\0';
1824 }
1826 /* remove exess space from the end of the legend for \g */
1827 while (prt_fctn == 'g' &&
1828 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1829 leg_cc--;
1830 im->gdes[i].legend[leg_cc] = '\0';
1831 }
1833 if (leg_cc != 0) {
1835 /* no interleg space if string ends in \g */
1836 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1837 if (fill > 0) {
1838 fill += legspace[i];
1839 }
1840 fill +=
1841 gfx_get_text_width(im,
1842 fill + border,
1843 im->
1844 text_prop
1845 [TEXT_PROP_LEGEND].
1846 font_desc,
1847 im->tabwidth, im->gdes[i].legend);
1848 leg_c++;
1849 } else {
1850 legspace[i] = 0;
1851 }
1852 /* who said there was a special tag ... ? */
1853 if (prt_fctn == 'g') {
1854 prt_fctn = '\0';
1855 }
1857 if (prt_fctn == '\0') {
1858 if(calc_width && (fill > legendwidth)){
1859 legendwidth = fill;
1860 }
1861 if (i == im->gdes_c - 1 || fill > legendwidth) {
1862 /* just one legend item is left right or center */
1863 switch (default_txtalign) {
1864 case TXA_RIGHT:
1865 prt_fctn = 'r';
1866 break;
1867 case TXA_CENTER:
1868 prt_fctn = 'c';
1869 break;
1870 case TXA_JUSTIFIED:
1871 prt_fctn = 'j';
1872 break;
1873 default:
1874 prt_fctn = 'l';
1875 break;
1876 }
1877 }
1878 /* is it time to place the legends ? */
1879 if (fill > legendwidth) {
1880 if (leg_c > 1) {
1881 /* go back one */
1882 i--;
1883 fill = fill_last;
1884 leg_c--;
1885 }
1886 }
1887 if (leg_c == 1 && prt_fctn == 'j') {
1888 prt_fctn = 'l';
1889 }
1890 }
1892 if (prt_fctn != '\0') {
1893 leg_x = border;
1894 if (leg_c >= 2 && prt_fctn == 'j') {
1895 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1896 } else {
1897 glue = 0;
1898 }
1899 if (prt_fctn == 'c')
1900 leg_x = border + (double)(legendwidth - fill) / 2.0;
1901 if (prt_fctn == 'r')
1902 leg_x = legendwidth - fill + border;
1903 for (ii = mark; ii <= i; ii++) {
1904 if (im->gdes[ii].legend[0] == '\0')
1905 continue; /* skip empty legends */
1906 im->gdes[ii].leg_x = leg_x;
1907 im->gdes[ii].leg_y = leg_y + border;
1908 leg_x +=
1909 (double)gfx_get_text_width(im, leg_x,
1910 im->
1911 text_prop
1912 [TEXT_PROP_LEGEND].
1913 font_desc,
1914 im->tabwidth, im->gdes[ii].legend)
1915 +(double)legspace[ii]
1916 + glue;
1917 }
1918 if (leg_x > border || prt_fctn == 's')
1919 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1920 if (prt_fctn == 's')
1921 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1922 if (prt_fctn == 'u')
1923 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1925 if(calc_width && (fill > legendwidth)){
1926 legendwidth = fill;
1927 }
1928 fill = 0;
1929 leg_c = 0;
1930 mark = ii;
1931 }
1933 if(calc_width){
1934 strncpy(im->gdes[i].legend, saved_legend, sizeof im->gdes[0].legend);
1935 }
1936 }
1938 if(calc_width){
1939 im->legendwidth = legendwidth + 2 * border;
1940 }
1941 else{
1942 im->legendheight = leg_y + border * 0.6;
1943 }
1944 free(legspace);
1945 }
1946 return 0;
1947 }
1949 /* create a grid on the graph. it determines what to do
1950 from the values of xsize, start and end */
1952 /* the xaxis labels are determined from the number of seconds per pixel
1953 in the requested graph */
1955 int calc_horizontal_grid(
1956 image_desc_t
1957 *im)
1958 {
1959 double range;
1960 double scaledrange;
1961 int pixel, i;
1962 int gridind = 0;
1963 int decimals, fractionals;
1965 im->ygrid_scale.labfact = 2;
1966 range = im->maxval - im->minval;
1967 scaledrange = range / im->magfact;
1968 /* does the scale of this graph make it impossible to put lines
1969 on it? If so, give up. */
1970 if (isnan(scaledrange)) {
1971 return 0;
1972 }
1974 /* find grid spaceing */
1975 pixel = 1;
1976 if (isnan(im->ygridstep)) {
1977 if (im->extra_flags & ALTYGRID) {
1978 /* find the value with max number of digits. Get number of digits */
1979 decimals =
1980 ceil(log10
1981 (max(fabs(im->maxval), fabs(im->minval)) *
1982 im->viewfactor / im->magfact));
1983 if (decimals <= 0) /* everything is small. make place for zero */
1984 decimals = 1;
1985 im->ygrid_scale.gridstep =
1986 pow((double) 10,
1987 floor(log10(range * im->viewfactor / im->magfact))) /
1988 im->viewfactor * im->magfact;
1989 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1990 im->ygrid_scale.gridstep = 0.1;
1991 /* should have at least 5 lines but no more then 15 */
1992 if (range / im->ygrid_scale.gridstep < 5
1993 && im->ygrid_scale.gridstep >= 30)
1994 im->ygrid_scale.gridstep /= 10;
1995 if (range / im->ygrid_scale.gridstep > 15)
1996 im->ygrid_scale.gridstep *= 10;
1997 if (range / im->ygrid_scale.gridstep > 5) {
1998 im->ygrid_scale.labfact = 1;
1999 if (range / im->ygrid_scale.gridstep > 8
2000 || im->ygrid_scale.gridstep <
2001 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
2002 im->ygrid_scale.labfact = 2;
2003 } else {
2004 im->ygrid_scale.gridstep /= 5;
2005 im->ygrid_scale.labfact = 5;
2006 }
2007 fractionals =
2008 floor(log10
2009 (im->ygrid_scale.gridstep *
2010 (double) im->ygrid_scale.labfact * im->viewfactor /
2011 im->magfact));
2012 if (fractionals < 0) { /* small amplitude. */
2013 int len = decimals - fractionals + 1;
2015 if (im->unitslength < len + 2)
2016 im->unitslength = len + 2;
2017 snprintf(im->ygrid_scale.labfmt, sizeof im->ygrid_scale.labfmt,
2018 "%%%d.%df%s", len,
2019 -fractionals, (im->symbol != ' ' ? " %c" : ""));
2020 } else {
2021 int len = decimals + 1;
2023 if (im->unitslength < len + 2)
2024 im->unitslength = len + 2;
2025 snprintf(im->ygrid_scale.labfmt, sizeof im->ygrid_scale.labfmt,
2026 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
2027 }
2028 } else { /* classic rrd grid */
2029 for (i = 0; ylab[i].grid > 0; i++) {
2030 pixel = im->ysize / (scaledrange / ylab[i].grid);
2031 gridind = i;
2032 if (pixel >= 5)
2033 break;
2034 }
2036 for (i = 0; i < 4; i++) {
2037 if (pixel * ylab[gridind].lfac[i] >=
2038 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2039 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2040 break;
2041 }
2042 }
2044 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2045 }
2046 } else {
2047 im->ygrid_scale.gridstep = im->ygridstep;
2048 im->ygrid_scale.labfact = im->ylabfact;
2049 }
2050 return 1;
2051 }
2053 int draw_horizontal_grid(
2054 image_desc_t
2055 *im)
2056 {
2057 int i;
2058 double scaledstep;
2059 char graph_label[100];
2060 int nlabels = 0;
2061 double X0 = im->xorigin;
2062 double X1 = im->xorigin + im->xsize;
2063 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2064 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2065 double MaxY;
2066 double second_axis_magfact = 0;
2067 char *second_axis_symb = "";
2069 scaledstep =
2070 im->ygrid_scale.gridstep /
2071 (double) im->magfact * (double) im->viewfactor;
2072 MaxY = scaledstep * (double) egrid;
2073 for (i = sgrid; i <= egrid; i++) {
2074 double Y0 = ytr(im,
2075 im->ygrid_scale.gridstep * i);
2076 double YN = ytr(im,
2077 im->ygrid_scale.gridstep * (i + 1));
2079 if (floor(Y0 + 0.5) >=
2080 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2081 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2082 with the chosen settings. Add a label if required by settings, or if
2083 there is only one label so far and the next grid line is out of bounds. */
2084 if (i % im->ygrid_scale.labfact == 0
2085 || (nlabels == 1
2086 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2087 if (im->symbol == ' ') {
2088 if (im->extra_flags & ALTYGRID) {
2089 snprintf(graph_label, sizeof graph_label,
2090 im->ygrid_scale.labfmt,
2091 scaledstep * (double) i);
2092 } else {
2093 if (MaxY < 10) {
2094 snprintf(graph_label, sizeof graph_label, "%4.1f",
2095 scaledstep * (double) i);
2096 } else {
2097 snprintf(graph_label, sizeof graph_label, "%4.0f",
2098 scaledstep * (double) i);
2099 }
2100 }
2101 } else {
2102 char sisym = (i == 0 ? ' ' : im->symbol);
2104 if (im->extra_flags & ALTYGRID) {
2105 snprintf(graph_label, sizeof graph_label,
2106 im->ygrid_scale.labfmt,
2107 scaledstep * (double) i, sisym);
2108 } else {
2109 if (MaxY < 10) {
2110 snprintf(graph_label, sizeof graph_label, "%4.1f %c",
2111 scaledstep * (double) i, sisym);
2112 } else {
2113 snprintf(graph_label, sizeof graph_label, "%4.0f %c",
2114 scaledstep * (double) i, sisym);
2115 }
2116 }
2117 }
2118 nlabels++;
2119 if (im->second_axis_scale != 0){
2120 char graph_label_right[100];
2121 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2122 if (im->second_axis_format[0] == '\0'){
2123 if (!second_axis_magfact){
2124 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2125 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2126 }
2127 sval /= second_axis_magfact;
2129 if(MaxY < 10) {
2130 snprintf(graph_label_right, sizeof graph_label_right, "%5.1f %s",sval,second_axis_symb);
2131 } else {
2132 snprintf(graph_label_right, sizeof graph_label_right, "%5.0f %s",sval,second_axis_symb);
2133 }
2134 }
2135 else {
2136 snprintf(graph_label_right, sizeof graph_label_right, im->second_axis_format,sval,"");
2137 }
2138 gfx_text ( im,
2139 X1+7, Y0,
2140 im->graph_col[GRC_FONT],
2141 im->text_prop[TEXT_PROP_AXIS].font_desc,
2142 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2143 graph_label_right );
2144 }
2146 gfx_text(im,
2147 X0 -
2148 im->
2149 text_prop[TEXT_PROP_AXIS].
2150 size, Y0,
2151 im->graph_col[GRC_FONT],
2152 im->
2153 text_prop[TEXT_PROP_AXIS].
2154 font_desc,
2155 im->tabwidth, 0.0,
2156 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2157 gfx_line(im, X0 - 2, Y0, X0, Y0,
2158 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2159 gfx_line(im, X1, Y0, X1 + 2, Y0,
2160 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2161 gfx_dashed_line(im, X0 - 2, Y0,
2162 X1 + 2, Y0,
2163 MGRIDWIDTH,
2164 im->
2165 graph_col
2166 [GRC_MGRID],
2167 im->grid_dash_on, im->grid_dash_off);
2168 } else if (!(im->extra_flags & NOMINOR)) {
2169 gfx_line(im,
2170 X0 - 2, Y0,
2171 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2172 gfx_line(im, X1, Y0, X1 + 2, Y0,
2173 GRIDWIDTH, im->graph_col[GRC_GRID]);
2174 gfx_dashed_line(im, X0 - 1, Y0,
2175 X1 + 1, Y0,
2176 GRIDWIDTH,
2177 im->
2178 graph_col[GRC_GRID],
2179 im->grid_dash_on, im->grid_dash_off);
2180 }
2181 }
2182 }
2183 return 1;
2184 }
2186 /* this is frexp for base 10 */
2187 double frexp10(
2188 double,
2189 double *);
2190 double frexp10(
2191 double x,
2192 double *e)
2193 {
2194 double mnt;
2195 int iexp;
2197 iexp = floor(log((double)fabs(x)) / log((double)10));
2198 mnt = x / pow(10.0, iexp);
2199 if (mnt >= 10.0) {
2200 iexp++;
2201 mnt = x / pow(10.0, iexp);
2202 }
2203 *e = iexp;
2204 return mnt;
2205 }
2208 /* logaritmic horizontal grid */
2209 int horizontal_log_grid(
2210 image_desc_t
2211 *im)
2212 {
2213 double yloglab[][10] = {
2214 {
2215 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2216 0.0, 0.0, 0.0}, {
2217 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2218 0.0, 0.0, 0.0}, {
2219 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2220 0.0, 0.0, 0.0}, {
2221 1.0, 2.0, 4.0,
2222 6.0, 8.0, 10.,
2223 0.0,
2224 0.0, 0.0, 0.0}, {
2225 1.0,
2226 2.0,
2227 3.0,
2228 4.0,
2229 5.0,
2230 6.0,
2231 7.0,
2232 8.0,
2233 9.0,
2234 10.},
2235 {
2236 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2237 };
2238 int i, j, val_exp, min_exp;
2239 double nex; /* number of decades in data */
2240 double logscale; /* scale in logarithmic space */
2241 int exfrac = 1; /* decade spacing */
2242 int mid = -1; /* row in yloglab for major grid */
2243 double mspac; /* smallest major grid spacing (pixels) */
2244 int flab; /* first value in yloglab to use */
2245 double value, tmp, pre_value;
2246 double X0, X1, Y0;
2247 char graph_label[100];
2249 nex = log10(im->maxval / im->minval);
2250 logscale = im->ysize / nex;
2251 /* major spacing for data with high dynamic range */
2252 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2253 if (exfrac == 1)
2254 exfrac = 3;
2255 else
2256 exfrac += 3;
2257 }
2259 /* major spacing for less dynamic data */
2260 do {
2261 /* search best row in yloglab */
2262 mid++;
2263 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2264 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2265 }
2266 while (mspac >
2267 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2268 if (mid)
2269 mid--;
2270 /* find first value in yloglab */
2271 for (flab = 0;
2272 yloglab[mid][flab] < 10
2273 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2274 if (yloglab[mid][flab] == 10.0) {
2275 tmp += 1.0;
2276 flab = 0;
2277 }
2278 val_exp = tmp;
2279 if (val_exp % exfrac)
2280 val_exp += abs(-val_exp % exfrac);
2281 X0 = im->xorigin;
2282 X1 = im->xorigin + im->xsize;
2283 /* draw grid */
2284 pre_value = DNAN;
2285 while (1) {
2287 value = yloglab[mid][flab] * pow(10.0, val_exp);
2288 if (AlmostEqual2sComplement(value, pre_value, 4))
2289 break; /* it seems we are not converging */
2290 pre_value = value;
2291 Y0 = ytr(im, value);
2292 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2293 break;
2294 /* major grid line */
2295 gfx_line(im,
2296 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2297 gfx_line(im, X1, Y0, X1 + 2, Y0,
2298 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2299 gfx_dashed_line(im, X0 - 2, Y0,
2300 X1 + 2, Y0,
2301 MGRIDWIDTH,
2302 im->
2303 graph_col
2304 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2305 /* label */
2306 if (im->extra_flags & FORCE_UNITS_SI) {
2307 int scale;
2308 double pvalue;
2309 char symbol;
2311 scale = floor(val_exp / 3.0);
2312 if (value >= 1.0)
2313 pvalue = pow(10.0, val_exp % 3);
2314 else
2315 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2316 pvalue *= yloglab[mid][flab];
2317 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2318 && ((scale + si_symbcenter) >= 0))
2319 symbol = si_symbol[scale + si_symbcenter];
2320 else
2321 symbol = '?';
2322 snprintf(graph_label, sizeof graph_label, "%3.0f %c", pvalue, symbol);
2323 } else {
2324 snprintf(graph_label, sizeof graph_label, "%3.0e", value);
2325 }
2326 if (im->second_axis_scale != 0){
2327 char graph_label_right[100];
2328 double sval = value*im->second_axis_scale+im->second_axis_shift;
2329 if (im->second_axis_format[0] == '\0'){
2330 if (im->extra_flags & FORCE_UNITS_SI) {
2331 double mfac = 1;
2332 char *symb = "";
2333 auto_scale(im,&sval,&symb,&mfac);
2334 snprintf(graph_label_right, sizeof graph_label_right, "%4.0f %s", sval,symb);
2335 }
2336 else {
2337 snprintf(graph_label_right, sizeof graph_label_right, "%3.0e", sval);
2338 }
2339 }
2340 else {
2341 snprintf(graph_label_right, sizeof graph_label_right, im->second_axis_format,sval,"");
2342 }
2344 gfx_text ( im,
2345 X1+7, Y0,
2346 im->graph_col[GRC_FONT],
2347 im->text_prop[TEXT_PROP_AXIS].font_desc,
2348 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2349 graph_label_right );
2350 }
2352 gfx_text(im,
2353 X0 -
2354 im->
2355 text_prop[TEXT_PROP_AXIS].
2356 size, Y0,
2357 im->graph_col[GRC_FONT],
2358 im->
2359 text_prop[TEXT_PROP_AXIS].
2360 font_desc,
2361 im->tabwidth, 0.0,
2362 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2363 /* minor grid */
2364 if (mid < 4 && exfrac == 1) {
2365 /* find first and last minor line behind current major line
2366 * i is the first line and j tha last */
2367 if (flab == 0) {
2368 min_exp = val_exp - 1;
2369 for (i = 1; yloglab[mid][i] < 10.0; i++);
2370 i = yloglab[mid][i - 1] + 1;
2371 j = 10;
2372 } else {
2373 min_exp = val_exp;
2374 i = yloglab[mid][flab - 1] + 1;
2375 j = yloglab[mid][flab];
2376 }
2378 /* draw minor lines below current major line */
2379 for (; i < j; i++) {
2381 value = i * pow(10.0, min_exp);
2382 if (value < im->minval)
2383 continue;
2384 Y0 = ytr(im, value);
2385 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2386 break;
2387 /* draw lines */
2388 gfx_line(im,
2389 X0 - 2, Y0,
2390 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2391 gfx_line(im, X1, Y0, X1 + 2, Y0,
2392 GRIDWIDTH, im->graph_col[GRC_GRID]);
2393 gfx_dashed_line(im, X0 - 1, Y0,
2394 X1 + 1, Y0,
2395 GRIDWIDTH,
2396 im->
2397 graph_col[GRC_GRID],
2398 im->grid_dash_on, im->grid_dash_off);
2399 }
2400 } else if (exfrac > 1) {
2401 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2402 value = pow(10.0, i);
2403 if (value < im->minval)
2404 continue;
2405 Y0 = ytr(im, value);
2406 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2407 break;
2408 /* draw lines */
2409 gfx_line(im,
2410 X0 - 2, Y0,
2411 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2412 gfx_line(im, X1, Y0, X1 + 2, Y0,
2413 GRIDWIDTH, im->graph_col[GRC_GRID]);
2414 gfx_dashed_line(im, X0 - 1, Y0,
2415 X1 + 1, Y0,
2416 GRIDWIDTH,
2417 im->
2418 graph_col[GRC_GRID],
2419 im->grid_dash_on, im->grid_dash_off);
2420 }
2421 }
2423 /* next decade */
2424 if (yloglab[mid][++flab] == 10.0) {
2425 flab = 0;
2426 val_exp += exfrac;
2427 }
2428 }
2430 /* draw minor lines after highest major line */
2431 if (mid < 4 && exfrac == 1) {
2432 /* find first and last minor line below current major line
2433 * i is the first line and j tha last */
2434 if (flab == 0) {
2435 min_exp = val_exp - 1;
2436 for (i = 1; yloglab[mid][i] < 10.0; i++);
2437 i = yloglab[mid][i - 1] + 1;
2438 j = 10;
2439 } else {
2440 min_exp = val_exp;
2441 i = yloglab[mid][flab - 1] + 1;
2442 j = yloglab[mid][flab];
2443 }
2445 /* draw minor lines below current major line */
2446 for (; i < j; i++) {
2448 value = i * pow(10.0, min_exp);
2449 if (value < im->minval)
2450 continue;
2451 Y0 = ytr(im, value);
2452 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2453 break;
2454 /* draw lines */
2455 gfx_line(im,
2456 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2457 gfx_line(im, X1, Y0, X1 + 2, Y0,
2458 GRIDWIDTH, im->graph_col[GRC_GRID]);
2459 gfx_dashed_line(im, X0 - 1, Y0,
2460 X1 + 1, Y0,
2461 GRIDWIDTH,
2462 im->
2463 graph_col[GRC_GRID],
2464 im->grid_dash_on, im->grid_dash_off);
2465 }
2466 }
2467 /* fancy minor gridlines */
2468 else if (exfrac > 1) {
2469 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2470 value = pow(10.0, i);
2471 if (value < im->minval)
2472 continue;
2473 Y0 = ytr(im, value);
2474 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2475 break;
2476 /* draw lines */
2477 gfx_line(im,
2478 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2479 gfx_line(im, X1, Y0, X1 + 2, Y0,
2480 GRIDWIDTH, im->graph_col[GRC_GRID]);
2481 gfx_dashed_line(im, X0 - 1, Y0,
2482 X1 + 1, Y0,
2483 GRIDWIDTH,
2484 im->
2485 graph_col[GRC_GRID],
2486 im->grid_dash_on, im->grid_dash_off);
2487 }
2488 }
2490 return 1;
2491 }
2494 void vertical_grid(
2495 image_desc_t *im)
2496 {
2497 int xlab_sel; /* which sort of label and grid ? */
2498 time_t ti, tilab, timajor;
2499 long factor;
2500 char graph_label[100];
2501 double X0, Y0, Y1; /* points for filled graph and more */
2502 struct tm tm;
2504 /* the type of time grid is determined by finding
2505 the number of seconds per pixel in the graph */
2506 if (im->xlab_user.minsec == -1) {
2507 factor = (im->end - im->start) / im->xsize;
2508 xlab_sel = 0;
2509 while (xlab[xlab_sel + 1].minsec !=
2510 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2511 xlab_sel++;
2512 } /* pick the last one */
2513 while (xlab[xlab_sel - 1].minsec ==
2514 xlab[xlab_sel].minsec
2515 && xlab[xlab_sel].length > (im->end - im->start)) {
2516 xlab_sel--;
2517 } /* go back to the smallest size */
2518 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2519 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2520 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2521 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2522 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2523 im->xlab_user.labst = xlab[xlab_sel].labst;
2524 im->xlab_user.precis = xlab[xlab_sel].precis;
2525 im->xlab_user.stst = xlab[xlab_sel].stst;
2526 }
2528 /* y coords are the same for every line ... */
2529 Y0 = im->yorigin;
2530 Y1 = im->yorigin - im->ysize;
2531 /* paint the minor grid */
2532 if (!(im->extra_flags & NOMINOR)) {
2533 for (ti = find_first_time(im->start,
2534 im->
2535 xlab_user.
2536 gridtm,
2537 im->
2538 xlab_user.
2539 gridst),
2540 timajor =
2541 find_first_time(im->start,
2542 im->xlab_user.
2543 mgridtm,
2544 im->xlab_user.
2545 mgridst);
2546 ti < im->end && ti != -1;
2547 ti =
2548 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2549 ) {
2550 /* are we inside the graph ? */
2551 if (ti < im->start || ti > im->end)
2552 continue;
2553 while (timajor < ti && timajor != -1) {
2554 timajor = find_next_time(timajor,
2555 im->
2556 xlab_user.
2557 mgridtm, im->xlab_user.mgridst);
2558 }
2559 if (timajor == -1) break; /* fail in case of problems with time increments */
2560 if (ti == timajor)
2561 continue; /* skip as falls on major grid line */
2562 X0 = xtr(im, ti);
2563 gfx_line(im, X0, Y1 - 2, X0, Y1,
2564 GRIDWIDTH, im->graph_col[GRC_GRID]);
2565 gfx_line(im, X0, Y0, X0, Y0 + 2,
2566 GRIDWIDTH, im->graph_col[GRC_GRID]);
2567 gfx_dashed_line(im, X0, Y0 + 1, X0,
2568 Y1 - 1, GRIDWIDTH,
2569 im->
2570 graph_col[GRC_GRID],
2571 im->grid_dash_on, im->grid_dash_off);
2572 }
2573 }
2575 /* paint the major grid */
2576 for (ti = find_first_time(im->start,
2577 im->
2578 xlab_user.
2579 mgridtm,
2580 im->
2581 xlab_user.
2582 mgridst);
2583 ti < im->end && ti != -1;
2584 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2585 ) {
2586 /* are we inside the graph ? */
2587 if (ti < im->start || ti > im->end)
2588 continue;
2589 X0 = xtr(im, ti);
2590 gfx_line(im, X0, Y1 - 2, X0, Y1,
2591 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2592 gfx_line(im, X0, Y0, X0, Y0 + 3,
2593 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2594 gfx_dashed_line(im, X0, Y0 + 3, X0,
2595 Y1 - 2, MGRIDWIDTH,
2596 im->
2597 graph_col
2598 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2599 }
2600 /* paint the labels below the graph */
2601 for (ti =
2602 find_first_time(im->start -
2603 im->xlab_user.
2604 precis / 2,
2605 im->xlab_user.
2606 labtm,
2607 im->xlab_user.
2608 labst);
2609 (ti <=
2610 im->end -
2611 im->xlab_user.precis / 2) && ti != -1;
2612 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2613 ) {
2614 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2615 /* are we inside the graph ? */
2616 if (tilab < im->start || tilab > im->end)
2617 continue;
2618 #if HAVE_STRFTIME
2619 localtime_r(&tilab, &tm);
2620 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2621 #else
2622 # error "your libc has no strftime I guess we'll abort the exercise here."
2623 #endif
2624 gfx_text(im,
2625 xtr(im, tilab),
2626 Y0 + 3,
2627 im->graph_col[GRC_FONT],
2628 im->
2629 text_prop[TEXT_PROP_AXIS].
2630 font_desc,
2631 im->tabwidth, 0.0,
2632 GFX_H_CENTER, GFX_V_TOP, graph_label);
2633 }
2635 }
2638 void axis_paint(
2639 image_desc_t *im)
2640 {
2641 /* draw x and y axis */
2642 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2643 im->xorigin+im->xsize,im->yorigin-im->ysize,
2644 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2646 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2647 im->xorigin+im->xsize,im->yorigin-im->ysize,
2648 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2650 gfx_line(im, im->xorigin - 4,
2651 im->yorigin,
2652 im->xorigin + im->xsize +
2653 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2654 gfx_line(im, im->xorigin,
2655 im->yorigin + 4,
2656 im->xorigin,
2657 im->yorigin - im->ysize -
2658 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2659 /* arrow for X and Y axis direction */
2660 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 */
2661 im->graph_col[GRC_ARROW]);
2662 gfx_close_path(im);
2663 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 */
2664 im->graph_col[GRC_ARROW]);
2665 gfx_close_path(im);
2666 if (im->second_axis_scale != 0){
2667 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2668 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2669 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2670 gfx_new_area ( im,
2671 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2672 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2673 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2674 im->graph_col[GRC_ARROW]);
2675 gfx_close_path(im);
2676 }
2678 }
2680 void grid_paint(
2681 image_desc_t *im)
2682 {
2683 long i;
2684 int res = 0;
2685 double X0, Y0; /* points for filled graph and more */
2686 struct gfx_color_t water_color;
2688 if (im->draw_3d_border > 0) {
2689 /* draw 3d border */
2690 i = im->draw_3d_border;
2691 gfx_new_area(im, 0, im->yimg,
2692 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2693 gfx_add_point(im, im->ximg - i, i);
2694 gfx_add_point(im, im->ximg, 0);
2695 gfx_add_point(im, 0, 0);
2696 gfx_close_path(im);
2697 gfx_new_area(im, i, im->yimg - i,
2698 im->ximg - i,
2699 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2700 gfx_add_point(im, im->ximg, 0);
2701 gfx_add_point(im, im->ximg, im->yimg);
2702 gfx_add_point(im, 0, im->yimg);
2703 gfx_close_path(im);
2704 }
2705 if (im->draw_x_grid == 1)
2706 vertical_grid(im);
2707 if (im->draw_y_grid == 1) {
2708 if (im->logarithmic) {
2709 res = horizontal_log_grid(im);
2710 } else {
2711 res = draw_horizontal_grid(im);
2712 }
2714 /* dont draw horizontal grid if there is no min and max val */
2715 if (!res) {
2716 char *nodata = "No Data found";
2718 gfx_text(im, im->ximg / 2,
2719 (2 * im->yorigin -
2720 im->ysize) / 2,
2721 im->graph_col[GRC_FONT],
2722 im->
2723 text_prop[TEXT_PROP_AXIS].
2724 font_desc,
2725 im->tabwidth, 0.0,
2726 GFX_H_CENTER, GFX_V_CENTER, nodata);
2727 }
2728 }
2730 /* yaxis unit description */
2731 if (im->ylegend[0] != '\0'){
2732 gfx_text(im,
2733 im->xOriginLegendY+10,
2734 im->yOriginLegendY,
2735 im->graph_col[GRC_FONT],
2736 im->
2737 text_prop[TEXT_PROP_UNIT].
2738 font_desc,
2739 im->tabwidth,
2740 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2742 }
2743 if (im->second_axis_legend[0] != '\0'){
2744 gfx_text( im,
2745 im->xOriginLegendY2+10,
2746 im->yOriginLegendY2,
2747 im->graph_col[GRC_FONT],
2748 im->text_prop[TEXT_PROP_UNIT].font_desc,
2749 im->tabwidth,
2750 RRDGRAPH_YLEGEND_ANGLE,
2751 GFX_H_CENTER, GFX_V_CENTER,
2752 im->second_axis_legend);
2753 }
2755 /* graph title */
2756 gfx_text(im,
2757 im->xOriginTitle, im->yOriginTitle+6,
2758 im->graph_col[GRC_FONT],
2759 im->
2760 text_prop[TEXT_PROP_TITLE].
2761 font_desc,
2762 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2763 /* rrdtool 'logo' */
2764 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2765 water_color = im->graph_col[GRC_FONT];
2766 water_color.alpha = 0.3;
2767 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2768 gfx_text(im, xpos, 5,
2769 water_color,
2770 im->
2771 text_prop[TEXT_PROP_WATERMARK].
2772 font_desc, im->tabwidth,
2773 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2774 }
2775 /* graph watermark */
2776 if (im->watermark[0] != '\0') {
2777 water_color = im->graph_col[GRC_FONT];
2778 water_color.alpha = 0.3;
2779 gfx_text(im,
2780 im->ximg / 2, im->yimg - 6,
2781 water_color,
2782 im->
2783 text_prop[TEXT_PROP_WATERMARK].
2784 font_desc, im->tabwidth, 0,
2785 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2786 }
2788 /* graph labels */
2789 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2790 for (i = 0; i < im->gdes_c; i++) {
2791 if (im->gdes[i].legend[0] == '\0')
2792 continue;
2793 /* im->gdes[i].leg_y is the bottom of the legend */
2794 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2795 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2796 gfx_text(im, X0, Y0,
2797 im->graph_col[GRC_FONT],
2798 im->
2799 text_prop
2800 [TEXT_PROP_LEGEND].font_desc,
2801 im->tabwidth, 0.0,
2802 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2803 /* The legend for GRAPH items starts with "M " to have
2804 enough space for the box */
2805 if (im->gdes[i].gf != GF_PRINT &&
2806 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2807 double boxH, boxV;
2808 double X1, Y1;
2810 boxH = gfx_get_text_width(im, 0,
2811 im->
2812 text_prop
2813 [TEXT_PROP_LEGEND].
2814 font_desc,
2815 im->tabwidth, "o") * 1.2;
2816 boxV = boxH;
2817 /* shift the box up a bit */
2818 Y0 -= boxV * 0.4;
2820 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2821 cairo_save(im->cr);
2822 cairo_new_path(im->cr);
2823 cairo_set_line_width(im->cr, 1.0);
2824 gfx_line(im,
2825 X0, Y0 - boxV / 2,
2826 X0 + boxH, Y0 - boxV / 2,
2827 1.0, im->gdes[i].col);
2828 gfx_close_path(im);
2829 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2830 cairo_save(im->cr);
2831 cairo_new_path(im->cr);
2832 cairo_set_line_width(im->cr, 1.0);
2833 gfx_line(im,
2834 X0 + boxH / 2, Y0,
2835 X0 + boxH / 2, Y0 - boxV,
2836 1.0, im->gdes[i].col);
2837 gfx_close_path(im);
2838 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2839 cairo_save(im->cr);
2840 cairo_new_path(im->cr);
2841 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2842 gfx_line(im,
2843 X0, Y0,
2844 X0 + boxH, Y0 - boxV,
2845 im->gdes[i].linewidth, im->gdes[i].col);
2846 gfx_close_path(im);
2847 } else {
2848 /* make sure transparent colors show up the same way as in the graph */
2849 gfx_new_area(im,
2850 X0, Y0 - boxV,
2851 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2852 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2853 gfx_close_path(im);
2854 gfx_new_area(im, X0, Y0 - boxV, X0,
2855 Y0, X0 + boxH, Y0, im->gdes[i].col);
2856 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2857 gfx_close_path(im);
2858 cairo_save(im->cr);
2859 cairo_new_path(im->cr);
2860 cairo_set_line_width(im->cr, 1.0);
2861 X1 = X0 + boxH;
2862 Y1 = Y0 - boxV;
2863 gfx_line_fit(im, &X0, &Y0);
2864 gfx_line_fit(im, &X1, &Y1);
2865 cairo_move_to(im->cr, X0, Y0);
2866 cairo_line_to(im->cr, X1, Y0);
2867 cairo_line_to(im->cr, X1, Y1);
2868 cairo_line_to(im->cr, X0, Y1);
2869 cairo_close_path(im->cr);
2870 cairo_set_source_rgba(im->cr,
2871 im->graph_col[GRC_FRAME].red,
2872 im->graph_col[GRC_FRAME].green,
2873 im->graph_col[GRC_FRAME].blue,
2874 im->graph_col[GRC_FRAME].alpha);
2875 }
2876 if (im->gdes[i].dash) {
2877 /* make box borders in legend dashed if the graph is dashed */
2878 double dashes[] = {
2879 3.0
2880 };
2881 cairo_set_dash(im->cr, dashes, 1, 0.0);
2882 }
2883 cairo_stroke(im->cr);
2884 cairo_restore(im->cr);
2885 }
2886 }
2887 }
2888 }
2891 /*****************************************************
2892 * lazy check make sure we rely need to create this graph
2893 *****************************************************/
2895 int lazy_check(
2896 image_desc_t *im)
2897 {
2898 FILE *fd = NULL;
2899 int size = 1;
2900 struct stat imgstat;
2902 if (im->lazy == 0)
2903 return 0; /* no lazy option */
2904 if (strlen(im->graphfile) == 0)
2905 return 0; /* inmemory option */
2906 if (stat(im->graphfile, &imgstat) != 0)
2907 return 0; /* can't stat */
2908 /* one pixel in the existing graph is more then what we would
2909 change here ... */
2910 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2911 return 0;
2912 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2913 return 0; /* the file does not exist */
2914 switch (im->imgformat) {
2915 case IF_PNG:
2916 size = PngSize(fd, &(im->ximg), &(im->yimg));
2917 break;
2918 default:
2919 size = 1;
2920 }
2921 fclose(fd);
2922 return size;
2923 }
2926 int graph_size_location(
2927 image_desc_t
2928 *im,
2929 int elements)
2930 {
2931 /* The actual size of the image to draw is determined from
2932 ** several sources. The size given on the command line is
2933 ** the graph area but we need more as we have to draw labels
2934 ** and other things outside the graph area. If the option
2935 ** --full-size-mode is selected the size defines the total
2936 ** image size and the size available for the graph is
2937 ** calculated.
2938 */
2940 /** +---+-----------------------------------+
2941 ** | y |...............graph title.........|
2942 ** | +---+-------------------------------+
2943 ** | a | y | |
2944 ** | x | | |
2945 ** | i | a | |
2946 ** | s | x | main graph area |
2947 ** | | i | |
2948 ** | t | s | |
2949 ** | i | | |
2950 ** | t | l | |
2951 ** | l | b +-------------------------------+
2952 ** | e | l | x axis labels |
2953 ** +---+---+-------------------------------+
2954 ** |....................legends............|
2955 ** +---------------------------------------+
2956 ** | watermark |
2957 ** +---------------------------------------+
2958 */
2960 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2961 0, Xylabel = 0, Xmain = 0, Ymain =
2962 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2964 // no legends and no the shall be plotted it's easy
2965 if (im->extra_flags & ONLY_GRAPH) {
2966 im->xorigin = 0;
2967 im->ximg = im->xsize;
2968 im->yimg = im->ysize;
2969 im->yorigin = im->ysize;
2970 xtr(im, 0);
2971 ytr(im, DNAN);
2972 return 0;
2973 }
2975 if(im->watermark[0] != '\0') {
2976 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2977 }
2979 // calculate the width of the left vertical legend
2980 if (im->ylegend[0] != '\0') {
2981 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2982 }
2984 // calculate the width of the right vertical legend
2985 if (im->second_axis_legend[0] != '\0') {
2986 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2987 }
2988 else{
2989 Xvertical2 = Xspacing;
2990 }
2992 if (im->title[0] != '\0') {
2993 /* The title is placed "inbetween" two text lines so it
2994 ** automatically has some vertical spacing. The horizontal
2995 ** spacing is added here, on each side.
2996 */
2997 /* if necessary, reduce the font size of the title until it fits the image width */
2998 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2999 }
3000 else{
3001 // we have no title; get a little clearing from the top
3002 Ytitle = Yspacing;
3003 }
3005 if (elements) {
3006 if (im->draw_x_grid) {
3007 // calculate the height of the horizontal labelling
3008 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
3009 }
3010 if (im->draw_y_grid || im->forceleftspace) {
3011 // calculate the width of the vertical labelling
3012 Xylabel =
3013 gfx_get_text_width(im, 0,
3014 im->text_prop[TEXT_PROP_AXIS].font_desc,
3015 im->tabwidth, "0") * im->unitslength;
3016 }
3017 }
3019 // add some space to the labelling
3020 Xylabel += Xspacing;
3022 /* If the legend is printed besides the graph the width has to be
3023 ** calculated first. Placing the legend north or south of the
3024 ** graph requires the width calculation first, so the legend is
3025 ** skipped for the moment.
3026 */
3027 im->legendheight = 0;
3028 im->legendwidth = 0;
3029 if (!(im->extra_flags & NOLEGEND)) {
3030 if(im->legendposition == WEST || im->legendposition == EAST){
3031 if (leg_place(im, 1) == -1){
3032 return -1;
3033 }
3034 }
3035 }
3037 if (im->extra_flags & FULL_SIZE_MODE) {
3039 /* The actual size of the image to draw has been determined by the user.
3040 ** The graph area is the space remaining after accounting for the legend,
3041 ** the watermark, the axis labels, and the title.
3042 */
3043 im->ximg = im->xsize;
3044 im->yimg = im->ysize;
3045 Xmain = im->ximg;
3046 Ymain = im->yimg;
3048 /* Now calculate the total size. Insert some spacing where
3049 desired. im->xorigin and im->yorigin need to correspond
3050 with the lower left corner of the main graph area or, if
3051 this one is not set, the imaginary box surrounding the
3052 pie chart area. */
3053 /* Initial size calculation for the main graph area */
3055 Xmain -= Xylabel;// + Xspacing;
3056 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3057 Xmain -= im->legendwidth;// + Xspacing;
3058 }
3059 if (im->second_axis_scale != 0){
3060 Xmain -= Xylabel;
3061 }
3062 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3063 Xmain -= Xspacing;
3064 }
3066 Xmain -= Xvertical + Xvertical2;
3068 /* limit the remaining space to 0 */
3069 if(Xmain < 1){
3070 Xmain = 1;
3071 }
3072 im->xsize = Xmain;
3074 /* Putting the legend north or south, the height can now be calculated */
3075 if (!(im->extra_flags & NOLEGEND)) {
3076 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3077 im->legendwidth = im->ximg;
3078 if (leg_place(im, 0) == -1){
3079 return -1;
3080 }
3081 }
3082 }
3084 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3085 Ymain -= Yxlabel + im->legendheight;
3086 }
3087 else{
3088 Ymain -= Yxlabel;
3089 }
3091 /* reserve space for the title *or* some padding above the graph */
3092 Ymain -= Ytitle;
3094 /* reserve space for padding below the graph */
3095 if (im->extra_flags & NOLEGEND) {
3096 Ymain -= 0.5*Yspacing;
3097 }
3099 if (im->watermark[0] != '\0') {
3100 Ymain -= Ywatermark;
3101 }
3102 /* limit the remaining height to 0 */
3103 if(Ymain < 1){
3104 Ymain = 1;
3105 }
3106 im->ysize = Ymain;
3107 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3109 /* The actual size of the image to draw is determined from
3110 ** several sources. The size given on the command line is
3111 ** the graph area but we need more as we have to draw labels
3112 ** and other things outside the graph area.
3113 */
3115 if (elements) {
3116 Xmain = im->xsize; // + Xspacing;
3117 Ymain = im->ysize;
3118 }
3120 im->ximg = Xmain + Xylabel;
3121 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3122 im->ximg += Xspacing;
3123 }
3125 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3126 im->ximg += im->legendwidth;// + Xspacing;
3127 }
3128 if (im->second_axis_scale != 0){
3129 im->ximg += Xylabel;
3130 }
3132 im->ximg += Xvertical + Xvertical2;
3134 if (!(im->extra_flags & NOLEGEND)) {
3135 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3136 im->legendwidth = im->ximg;
3137 if (leg_place(im, 0) == -1){
3138 return -1;
3139 }
3140 }
3141 }
3143 im->yimg = Ymain + Yxlabel;
3144 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3145 im->yimg += im->legendheight;
3146 }
3148 /* reserve space for the title *or* some padding above the graph */
3149 if (Ytitle) {
3150 im->yimg += Ytitle;
3151 } else {
3152 im->yimg += 1.5 * Yspacing;
3153 }
3154 /* reserve space for padding below the graph */
3155 if (im->extra_flags & NOLEGEND) {
3156 im->yimg += 0.5*Yspacing;
3157 }
3159 if (im->watermark[0] != '\0') {
3160 im->yimg += Ywatermark;
3161 }
3162 }
3165 /* In case of putting the legend in west or east position the first
3166 ** legend calculation might lead to wrong positions if some items
3167 ** are not aligned on the left hand side (e.g. centered) as the
3168 ** legendwidth wight have been increased after the item was placed.
3169 ** In this case the positions have to be recalculated.
3170 */
3171 if (!(im->extra_flags & NOLEGEND)) {
3172 if(im->legendposition == WEST || im->legendposition == EAST){
3173 if (leg_place(im, 0) == -1){
3174 return -1;
3175 }
3176 }
3177 }
3179 /* After calculating all dimensions
3180 ** it is now possible to calculate
3181 ** all offsets.
3182 */
3183 switch(im->legendposition){
3184 case NORTH:
3185 im->xOriginTitle = (im->ximg / 2);
3186 im->yOriginTitle = 0;
3188 im->xOriginLegend = 0;
3189 im->yOriginLegend = Ytitle;
3191 im->xOriginLegendY = 0;
3192 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3194 im->xorigin = Xvertical + Xylabel;
3195 im->yorigin = Ytitle + im->legendheight + Ymain;
3197 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3198 if (im->second_axis_scale != 0){
3199 im->xOriginLegendY2 += Xylabel;
3200 }
3201 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3203 break;
3205 case WEST:
3206 im->xOriginTitle = im->legendwidth + im->xsize / 2;
3207 im->yOriginTitle = 0;
3209 im->xOriginLegend = 0;
3210 im->yOriginLegend = Ytitle;
3212 im->xOriginLegendY = im->legendwidth;
3213 im->yOriginLegendY = Ytitle + (Ymain / 2);
3215 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3216 im->yorigin = Ytitle + Ymain;
3218 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3219 if (im->second_axis_scale != 0){
3220 im->xOriginLegendY2 += Xylabel;
3221 }
3222 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3224 break;
3226 case SOUTH:
3227 im->xOriginTitle = im->ximg / 2;
3228 im->yOriginTitle = 0;
3230 im->xOriginLegend = 0;
3231 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3233 im->xOriginLegendY = 0;
3234 im->yOriginLegendY = Ytitle + (Ymain / 2);
3236 im->xorigin = Xvertical + Xylabel;
3237 im->yorigin = Ytitle + Ymain;
3239 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3240 if (im->second_axis_scale != 0){
3241 im->xOriginLegendY2 += Xylabel;
3242 }
3243 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3245 break;
3247 case EAST:
3248 im->xOriginTitle = im->xsize / 2;
3249 im->yOriginTitle = 0;
3251 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3252 if (im->second_axis_scale != 0){
3253 im->xOriginLegend += Xylabel;
3254 }
3255 im->yOriginLegend = Ytitle;
3257 im->xOriginLegendY = 0;
3258 im->yOriginLegendY = Ytitle + (Ymain / 2);
3260 im->xorigin = Xvertical + Xylabel;
3261 im->yorigin = Ytitle + Ymain;
3263 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3264 if (im->second_axis_scale != 0){
3265 im->xOriginLegendY2 += Xylabel;
3266 }
3267 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3269 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3270 im->xOriginTitle += Xspacing;
3271 im->xOriginLegend += Xspacing;
3272 im->xOriginLegendY += Xspacing;
3273 im->xorigin += Xspacing;
3274 im->xOriginLegendY2 += Xspacing;
3275 }
3276 break;
3277 }
3279 xtr(im, 0);
3280 ytr(im, DNAN);
3281 return 0;
3282 }
3284 static cairo_status_t cairo_output(
3285 void *closure,
3286 const unsigned char
3287 *data,
3288 unsigned int length)
3289 {
3290 image_desc_t *im = (image_desc_t*)closure;
3292 im->rendered_image =
3293 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3294 if (im->rendered_image == NULL)
3295 return CAIRO_STATUS_WRITE_ERROR;
3296 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3297 im->rendered_image_size += length;
3298 return CAIRO_STATUS_SUCCESS;
3299 }
3301 /* draw that picture thing ... */
3302 int graph_paint(
3303 image_desc_t *im)
3304 {
3305 int lazy = lazy_check(im);
3306 int cnt;
3308 /* imgformat XML or higher dispatch to xport
3309 * output format there is selected via graph_type
3310 */
3311 if (im->imgformat >= IF_XML) {
3312 return rrd_graph_xport(im);
3313 }
3315 /* pull the data from the rrd files ... */
3316 if (data_fetch(im) == -1)
3317 return -1;
3318 /* evaluate VDEF and CDEF operations ... */
3319 if (data_calc(im) == -1)
3320 return -1;
3321 /* calculate and PRINT and GPRINT definitions. We have to do it at
3322 * this point because it will affect the length of the legends
3323 * if there are no graph elements (i==0) we stop here ...
3324 * if we are lazy, try to quit ...
3325 */
3326 cnt = print_calc(im);
3327 if (cnt < 0)
3328 return -1;
3330 /* if we want and can be lazy ... quit now */
3331 if (cnt == 0)
3332 return 0;
3334 /* otherwise call graph_paint_timestring */
3335 switch (im->graph_type) {
3336 case GTYPE_TIME:
3337 return graph_paint_timestring(im,lazy,cnt);
3338 break;
3339 case GTYPE_XY:
3340 return graph_paint_xy(im,lazy,cnt);
3341 break;
3342 }
3343 /* final return with error*/
3344 rrd_set_error("Graph type %i is not implemented",im->graph_type);
3345 return -1;
3346 }
3348 int graph_paint_timestring(
3349 image_desc_t *im, int lazy, int cnt)
3350 {
3351 int i,ii;
3352 double areazero = 0.0;
3353 graph_desc_t *lastgdes = NULL;
3354 rrd_infoval_t info;
3356 /**************************************************************
3357 *** Calculating sizes and locations became a bit confusing ***
3358 *** so I moved this into a separate function. ***
3359 **************************************************************/
3360 if (graph_size_location(im, cnt) == -1)
3361 return -1;
3363 info.u_cnt = im->xorigin;
3364 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3365 info.u_cnt = im->yorigin - im->ysize;
3366 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3367 info.u_cnt = im->xsize;
3368 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3369 info.u_cnt = im->ysize;
3370 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3371 info.u_cnt = im->ximg;
3372 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3373 info.u_cnt = im->yimg;
3374 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3375 info.u_cnt = im->start;
3376 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3377 info.u_cnt = im->end;
3378 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3380 /* if we want and can be lazy ... quit now */
3381 if (lazy)
3382 return 0;
3384 /* get actual drawing data and find min and max values */
3385 if (data_proc(im) == -1)
3386 return -1;
3387 if (!im->logarithmic) {
3388 si_unit(im);
3389 }
3391 /* identify si magnitude Kilo, Mega Giga ? */
3392 if (!im->rigid && !im->logarithmic)
3393 expand_range(im); /* make sure the upper and lower limit are
3394 sensible values */
3396 info.u_val = im->minval;
3397 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3398 info.u_val = im->maxval;
3399 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3402 if (!calc_horizontal_grid(im))
3403 return -1;
3404 /* reset precalc */
3405 ytr(im, DNAN);
3406 /* if (im->gridfit)
3407 apply_gridfit(im); */
3409 /* set up cairo */
3410 if (graph_cairo_setup(im)) { return -1; }
3412 /* other stuff */
3413 if (im->minval > 0.0)
3414 areazero = im->minval;
3415 if (im->maxval < 0.0)
3416 areazero = im->maxval;
3417 for (i = 0; i < im->gdes_c; i++) {
3418 switch (im->gdes[i].gf) {
3419 case GF_CDEF:
3420 case GF_VDEF:
3421 case GF_DEF:
3422 case GF_PRINT:
3423 case GF_GPRINT:
3424 case GF_COMMENT:
3425 case GF_TEXTALIGN:
3426 case GF_HRULE:
3427 case GF_VRULE:
3428 case GF_XPORT:
3429 case GF_SHIFT:
3430 case GF_XAXIS:
3431 case GF_YAXIS:
3432 break;
3433 case GF_TICK:
3434 for (ii = 0; ii < im->xsize; ii++) {
3435 if (!isnan(im->gdes[i].p_data[ii])
3436 && im->gdes[i].p_data[ii] != 0.0) {
3437 if (im->gdes[i].yrule > 0) {
3438 gfx_line(im,
3439 im->xorigin + ii,
3440 im->yorigin + 1.0,
3441 im->xorigin + ii,
3442 im->yorigin -
3443 im->gdes[i].yrule *
3444 im->ysize, 1.0, im->gdes[i].col);
3445 } else if (im->gdes[i].yrule < 0) {
3446 gfx_line(im,
3447 im->xorigin + ii,
3448 im->yorigin - im->ysize - 1.0,
3449 im->xorigin + ii,
3450 im->yorigin - im->ysize -
3451 im->gdes[i].
3452 yrule *
3453 im->ysize, 1.0, im->gdes[i].col);
3454 }
3455 }
3456 }
3457 break;
3458 case GF_LINE:
3459 case GF_AREA:
3460 case GF_GRAD: {
3461 rrd_value_t diffval = im->maxval - im->minval;
3462 rrd_value_t maxlimit = im->maxval + 9 * diffval;
3463 rrd_value_t minlimit = im->minval - 9 * diffval;
3464 for (ii = 0; ii < im->xsize; ii++) {
3465 /* fix data points at oo and -oo */
3466 if (isinf(im->gdes[i].p_data[ii])) {
3467 if (im->gdes[i].p_data[ii] > 0) {
3468 im->gdes[i].p_data[ii] = im->maxval;
3469 } else {
3470 im->gdes[i].p_data[ii] = im->minval;
3471 }
3472 }
3473 /* some versions of cairo go unstable when trying
3474 to draw way out of the canvas ... lets not even try */
3475 if (im->gdes[i].p_data[ii] > maxlimit) {
3476 im->gdes[i].p_data[ii] = maxlimit;
3477 }
3478 if (im->gdes[i].p_data[ii] < minlimit) {
3479 im->gdes[i].p_data[ii] = minlimit;
3480 }
3481 } /* for */
3483 /* *******************************************************
3484 a ___. (a,t)
3485 | | ___
3486 ____| | | |
3487 | |___|
3488 -------|--t-1--t--------------------------------
3490 if we know the value at time t was a then
3491 we draw a square from t-1 to t with the value a.
3493 ********************************************************* */
3494 if (im->gdes[i].col.alpha != 0.0) {
3495 /* GF_LINE and friend */
3496 if (im->gdes[i].gf == GF_LINE) {
3497 double last_y = 0.0;
3498 int draw_on = 0;
3500 cairo_save(im->cr);
3501 cairo_new_path(im->cr);
3502 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3503 if (im->gdes[i].dash) {
3504 cairo_set_dash(im->cr,
3505 im->gdes[i].p_dashes,
3506 im->gdes[i].ndash, im->gdes[i].offset);
3507 }
3509 for (ii = 1; ii < im->xsize; ii++) {
3510 if (isnan(im->gdes[i].p_data[ii])
3511 || (im->slopemode == 1
3512 && isnan(im->gdes[i].p_data[ii - 1]))) {
3513 draw_on = 0;
3514 continue;
3515 }
3516 if (draw_on == 0) {
3517 last_y = ytr(im, im->gdes[i].p_data[ii]);
3518 if (im->slopemode == 0) {
3519 double x = ii - 1 + im->xorigin;
3520 double y = last_y;
3522 gfx_line_fit(im, &x, &y);
3523 cairo_move_to(im->cr, x, y);
3524 x = ii + im->xorigin;
3525 y = last_y;
3526 gfx_line_fit(im, &x, &y);
3527 cairo_line_to(im->cr, x, y);
3528 } else {
3529 double x = ii - 1 + im->xorigin;
3530 double y =
3531 ytr(im, im->gdes[i].p_data[ii - 1]);
3532 gfx_line_fit(im, &x, &y);
3533 cairo_move_to(im->cr, x, y);
3534 x = ii + im->xorigin;
3535 y = last_y;
3536 gfx_line_fit(im, &x, &y);
3537 cairo_line_to(im->cr, x, y);
3538 }
3539 draw_on = 1;
3540 } else {
3541 double x1 = ii + im->xorigin;
3542 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3544 if (im->slopemode == 0
3545 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3546 double x = ii - 1 + im->xorigin;
3547 double y = y1;
3549 gfx_line_fit(im, &x, &y);
3550 cairo_line_to(im->cr, x, y);
3551 };
3552 last_y = y1;
3553 gfx_line_fit(im, &x1, &y1);
3554 cairo_line_to(im->cr, x1, y1);
3555 };
3556 }
3557 cairo_set_source_rgba(im->cr,
3558 im->gdes[i].
3559 col.red,
3560 im->gdes[i].
3561 col.green,
3562 im->gdes[i].
3563 col.blue, im->gdes[i].col.alpha);
3564 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3565 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3566 cairo_stroke(im->cr);
3567 cairo_restore(im->cr);
3568 } else {
3569 double lastx=0;
3570 double lasty=0;
3571 int idxI = -1;
3572 double *foreY =
3573 (double *) malloc(sizeof(double) * im->xsize * 2);
3574 double *foreX =
3575 (double *) malloc(sizeof(double) * im->xsize * 2);
3576 double *backY =
3577 (double *) malloc(sizeof(double) * im->xsize * 2);
3578 double *backX =
3579 (double *) malloc(sizeof(double) * im->xsize * 2);
3580 int drawem = 0;
3582 for (ii = 0; ii <= im->xsize; ii++) {
3583 double ybase, ytop;
3585 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3586 int cntI = 1;
3587 int lastI = 0;
3589 while (cntI < idxI
3590 &&
3591 AlmostEqual2sComplement(foreY
3592 [lastI],
3593 foreY[cntI], 4)
3594 &&
3595 AlmostEqual2sComplement(foreY
3596 [lastI],
3597 foreY
3598 [cntI + 1], 4)) {
3599 cntI++;
3600 }
3601 if (im->gdes[i].gf != GF_GRAD) {
3602 gfx_new_area(im,
3603 backX[0], backY[0],
3604 foreX[0], foreY[0],
3605 foreX[cntI],
3606 foreY[cntI], im->gdes[i].col);
3607 } else {
3608 lastx = foreX[cntI];
3609 lasty = foreY[cntI];
3610 }
3611 while (cntI < idxI) {
3612 lastI = cntI;
3613 cntI++;
3614 while (cntI < idxI
3615 &&
3616 AlmostEqual2sComplement(foreY
3617 [lastI],
3618 foreY[cntI], 4)
3619 &&
3620 AlmostEqual2sComplement(foreY
3621 [lastI],
3622 foreY
3623 [cntI
3624 + 1], 4)) {
3625 cntI++;
3626 }
3627 if (im->gdes[i].gf != GF_GRAD) {
3628 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3629 } else {
3630 gfx_add_rect_fadey(im,
3631 lastx, foreY[0],
3632 foreX[cntI], foreY[cntI], lasty,
3633 im->gdes[i].col,
3634 im->gdes[i].col2,
3635 im->gdes[i].gradheight
3636 );
3637 lastx = foreX[cntI];
3638 lasty = foreY[cntI];
3639 }
3640 }
3641 if (im->gdes[i].gf != GF_GRAD) {
3642 gfx_add_point(im, backX[idxI], backY[idxI]);
3643 } else {
3644 gfx_add_rect_fadey(im,
3645 lastx, foreY[0],
3646 backX[idxI], backY[idxI], lasty,
3647 im->gdes[i].col,
3648 im->gdes[i].col2,
3649 im->gdes[i].gradheight);
3650 lastx = backX[idxI];
3651 lasty = backY[idxI];
3652 }
3653 while (idxI > 1) {
3654 lastI = idxI;
3655 idxI--;
3656 while (idxI > 1
3657 &&
3658 AlmostEqual2sComplement(backY
3659 [lastI],
3660 backY[idxI], 4)
3661 &&
3662 AlmostEqual2sComplement(backY
3663 [lastI],
3664 backY
3665 [idxI
3666 - 1], 4)) {
3667 idxI--;
3668 }
3669 if (im->gdes[i].gf != GF_GRAD) {
3670 gfx_add_point(im, backX[idxI], backY[idxI]);
3671 } else {
3672 gfx_add_rect_fadey(im,
3673 lastx, foreY[0],
3674 backX[idxI], backY[idxI], lasty,
3675 im->gdes[i].col,
3676 im->gdes[i].col2,
3677 im->gdes[i].gradheight);
3678 lastx = backX[idxI];
3679 lasty = backY[idxI];
3680 }
3681 }
3682 idxI = -1;
3683 drawem = 0;
3684 if (im->gdes[i].gf != GF_GRAD)
3685 gfx_close_path(im);
3686 }
3687 if (drawem != 0) {
3688 drawem = 0;
3689 idxI = -1;
3690 }
3691 if (ii == im->xsize)
3692 break;
3693 if (im->slopemode == 0 && ii == 0) {
3694 continue;
3695 }
3696 if (isnan(im->gdes[i].p_data[ii])) {
3697 drawem = 1;
3698 continue;
3699 }
3700 ytop = ytr(im, im->gdes[i].p_data[ii]);
3701 if (lastgdes && im->gdes[i].stack) {
3702 ybase = ytr(im, lastgdes->p_data[ii]);
3703 } else {
3704 ybase = ytr(im, areazero);
3705 }
3706 if (ybase == ytop) {
3707 drawem = 1;
3708 continue;
3709 }
3711 if (ybase > ytop) {
3712 double extra = ytop;
3714 ytop = ybase;
3715 ybase = extra;
3716 }
3717 if (im->slopemode == 0) {
3718 backY[++idxI] = ybase - 0.2;
3719 backX[idxI] = ii + im->xorigin - 1;
3720 foreY[idxI] = ytop + 0.2;
3721 foreX[idxI] = ii + im->xorigin - 1;
3722 }
3723 backY[++idxI] = ybase - 0.2;
3724 backX[idxI] = ii + im->xorigin;
3725 foreY[idxI] = ytop + 0.2;
3726 foreX[idxI] = ii + im->xorigin;
3727 }
3728 /* close up any remaining area */
3729 free(foreY);
3730 free(foreX);
3731 free(backY);
3732 free(backX);
3733 } /* else GF_LINE */
3734 }
3735 /* if color != 0x0 */
3736 /* make sure we do not run into trouble when stacking on NaN */
3737 for (ii = 0; ii < im->xsize; ii++) {
3738 if (isnan(im->gdes[i].p_data[ii])) {
3739 if (lastgdes && (im->gdes[i].stack)) {
3740 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3741 } else {
3742 im->gdes[i].p_data[ii] = areazero;
3743 }
3744 }
3745 }
3746 lastgdes = &(im->gdes[i]);
3747 break;
3748 } /* GF_AREA, GF_LINE, GF_GRAD */
3749 case GF_STACK:
3750 rrd_set_error
3751 ("STACK should already be turned into LINE or AREA here");
3752 return -1;
3753 break;
3754 } /* switch */
3755 }
3756 cairo_reset_clip(im->cr);
3758 /* grid_paint also does the text */
3759 if (!(im->extra_flags & ONLY_GRAPH))
3760 grid_paint(im);
3761 if (!(im->extra_flags & ONLY_GRAPH))
3762 axis_paint(im);
3763 /* the RULES are the last thing to paint ... */
3764 for (i = 0; i < im->gdes_c; i++) {
3766 switch (im->gdes[i].gf) {
3767 case GF_HRULE:
3768 if (im->gdes[i].yrule >= im->minval
3769 && im->gdes[i].yrule <= im->maxval) {
3770 cairo_save(im->cr);
3771 if (im->gdes[i].dash) {
3772 cairo_set_dash(im->cr,
3773 im->gdes[i].p_dashes,
3774 im->gdes[i].ndash, im->gdes[i].offset);
3775 }
3776 gfx_line(im, im->xorigin,
3777 ytr(im, im->gdes[i].yrule),
3778 im->xorigin + im->xsize,
3779 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3780 cairo_stroke(im->cr);
3781 cairo_restore(im->cr);
3782 }
3783 break;
3784 case GF_VRULE:
3785 if (im->gdes[i].xrule >= im->start
3786 && im->gdes[i].xrule <= im->end) {
3787 cairo_save(im->cr);
3788 if (im->gdes[i].dash) {
3789 cairo_set_dash(im->cr,
3790 im->gdes[i].p_dashes,
3791 im->gdes[i].ndash, im->gdes[i].offset);
3792 }
3793 gfx_line(im,
3794 xtr(im, im->gdes[i].xrule),
3795 im->yorigin, xtr(im,
3796 im->
3797 gdes[i].
3798 xrule),
3799 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3800 cairo_stroke(im->cr);
3801 cairo_restore(im->cr);
3802 }
3803 break;
3804 default:
3805 break;
3806 }
3807 }
3808 /* close the graph via cairo*/
3809 return graph_cairo_finish(im);
3810 }
3812 int graph_cairo_setup (image_desc_t *im)
3813 {
3814 /* the actual graph is created by going through the individual
3815 graph elements and then drawing them */
3816 cairo_surface_destroy(im->surface);
3817 switch (im->imgformat) {
3818 case IF_PNG:
3819 im->surface =
3820 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3821 im->ximg * im->zoom,
3822 im->yimg * im->zoom);
3823 break;
3824 case IF_PDF:
3825 im->gridfit = 0;
3826 im->surface = strlen(im->graphfile)
3827 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3828 im->yimg * im->zoom)
3829 : cairo_pdf_surface_create_for_stream
3830 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3831 break;
3832 case IF_EPS:
3833 im->gridfit = 0;
3834 im->surface = strlen(im->graphfile)
3835 ?
3836 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3837 im->yimg * im->zoom)
3838 : cairo_ps_surface_create_for_stream
3839 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3840 break;
3841 case IF_SVG:
3842 im->gridfit = 0;
3843 im->surface = strlen(im->graphfile)
3844 ?
3845 cairo_svg_surface_create(im->
3846 graphfile,
3847 im->ximg * im->zoom, im->yimg * im->zoom)
3848 : cairo_svg_surface_create_for_stream
3849 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3850 cairo_svg_surface_restrict_to_version
3851 (im->surface, CAIRO_SVG_VERSION_1_1);
3852 break;
3853 case IF_XML:
3854 case IF_XMLENUM:
3855 case IF_CSV:
3856 case IF_TSV:
3857 case IF_SSV:
3858 case IF_JSON:
3859 case IF_JSONTIME:
3860 break;
3861 };
3862 cairo_destroy(im->cr);
3863 im->cr = cairo_create(im->surface);
3864 cairo_set_antialias(im->cr, im->graph_antialias);
3865 cairo_scale(im->cr, im->zoom, im->zoom);
3866 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3867 gfx_new_area(im, 0, 0, 0, im->yimg,
3868 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3869 gfx_add_point(im, im->ximg, 0);
3870 gfx_close_path(im);
3871 gfx_new_area(im, im->xorigin,
3872 im->yorigin,
3873 im->xorigin +
3874 im->xsize, im->yorigin,
3875 im->xorigin +
3876 im->xsize,
3877 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3878 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3879 gfx_close_path(im);
3880 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3881 im->xsize, im->ysize + 2.0);
3882 cairo_clip(im->cr);
3883 return 0;
3884 }
3886 int graph_cairo_finish (image_desc_t *im)
3887 {
3889 switch (im->imgformat) {
3890 case IF_PNG:
3891 {
3892 cairo_status_t status;
3894 status = strlen(im->graphfile) ?
3895 cairo_surface_write_to_png(im->surface, im->graphfile)
3896 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3897 im);
3899 if (status != CAIRO_STATUS_SUCCESS) {
3900 rrd_set_error("Could not save png to '%s'", im->graphfile);
3901 return 1;
3902 }
3903 break;
3904 }
3905 case IF_XML:
3906 case IF_XMLENUM:
3907 case IF_CSV:
3908 case IF_TSV:
3909 case IF_SSV:
3910 case IF_JSON:
3911 case IF_JSONTIME:
3912 break;
3913 default:
3914 if (strlen(im->graphfile)) {
3915 cairo_show_page(im->cr);
3916 } else {
3917 cairo_surface_finish(im->surface);
3918 }
3919 break;
3920 }
3922 return 0;
3923 }
3925 int graph_paint_xy(
3926 image_desc_t *im, int lazy, int cnt)
3927 {
3928 /* to stop compiler warnings for now */
3929 lazy=cnt=(int)im->gdes_c;
3930 rrd_set_error("XY diagramm not implemented");
3931 return -1;
3932 }
3934 /*****************************************************
3935 * graph stuff
3936 *****************************************************/
3938 int gdes_alloc(
3939 image_desc_t *im)
3940 {
3942 im->gdes_c++;
3943 if ((im->gdes = (graph_desc_t *)
3944 rrd_realloc(im->gdes, (im->gdes_c)
3945 * sizeof(graph_desc_t))) == NULL) {
3946 rrd_set_error("realloc graph_descs");
3947 return -1;
3948 }
3950 /* set to zero */
3951 memset(&(im->gdes[im->gdes_c - 1]),0,sizeof(graph_desc_t));
3953 im->gdes[im->gdes_c - 1].step = im->step;
3954 im->gdes[im->gdes_c - 1].step_orig = im->step;
3955 im->gdes[im->gdes_c - 1].stack = 0;
3956 im->gdes[im->gdes_c - 1].linewidth = 0;
3957 im->gdes[im->gdes_c - 1].debug = 0;
3958 im->gdes[im->gdes_c - 1].start = im->start;
3959 im->gdes[im->gdes_c - 1].start_orig = im->start;
3960 im->gdes[im->gdes_c - 1].end = im->end;
3961 im->gdes[im->gdes_c - 1].end_orig = im->end;
3962 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3963 im->gdes[im->gdes_c - 1].data = NULL;
3964 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3965 im->gdes[im->gdes_c - 1].data_first = 0;
3966 im->gdes[im->gdes_c - 1].p_data = NULL;
3967 im->gdes[im->gdes_c - 1].rpnp = NULL;
3968 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3969 im->gdes[im->gdes_c - 1].shift = 0.0;
3970 im->gdes[im->gdes_c - 1].dash = 0;
3971 im->gdes[im->gdes_c - 1].ndash = 0;
3972 im->gdes[im->gdes_c - 1].offset = 0;
3973 im->gdes[im->gdes_c - 1].col.red = 0.0;
3974 im->gdes[im->gdes_c - 1].col.green = 0.0;
3975 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3976 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3977 im->gdes[im->gdes_c - 1].col2.red = 0.0;
3978 im->gdes[im->gdes_c - 1].col2.green = 0.0;
3979 im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3980 im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3981 im->gdes[im->gdes_c - 1].gradheight = 50.0;
3982 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3983 im->gdes[im->gdes_c - 1].format[0] = '\0';
3984 im->gdes[im->gdes_c - 1].strftm = 0;
3985 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3986 im->gdes[im->gdes_c - 1].ds = -1;
3987 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3988 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3989 im->gdes[im->gdes_c - 1].yrule = DNAN;
3990 im->gdes[im->gdes_c - 1].xrule = 0;
3991 im->gdes[im->gdes_c - 1].daemon[0] = 0;
3992 return 0;
3993 }
3995 /* copies input untill the first unescaped colon is found
3996 or until input ends. backslashes have to be escaped as well */
3997 int scan_for_col(
3998 const char *const input,
3999 int len,
4000 char *const output)
4001 {
4002 int inp, outp = 0;
4004 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
4005 if (input[inp] == '\\'
4006 && input[inp + 1] != '\0'
4007 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
4008 output[outp++] = input[++inp];
4009 } else {
4010 output[outp++] = input[inp];
4011 }
4012 }
4013 output[outp] = '\0';
4014 return inp;
4015 }
4017 /* Now just a wrapper around rrd_graph_v */
4018 int rrd_graph(
4019 int argc,
4020 char **argv,
4021 char ***prdata,
4022 int *xsize,
4023 int *ysize,
4024 FILE * stream,
4025 double *ymin,
4026 double *ymax)
4027 {
4028 int prlines = 0;
4029 rrd_info_t *grinfo = NULL;
4030 rrd_info_t *walker;
4032 grinfo = rrd_graph_v(argc, argv);
4033 if (grinfo == NULL)
4034 return -1;
4035 walker = grinfo;
4036 (*prdata) = NULL;
4037 while (walker) {
4038 if (strcmp(walker->key, "image_info") == 0) {
4039 prlines++;
4040 if (((*prdata) =
4041 (char**)rrd_realloc((*prdata),
4042 (prlines + 1) * sizeof(char *))) == NULL) {
4043 rrd_set_error("realloc prdata");
4044 return 0;
4045 }
4046 /* imginfo goes to position 0 in the prdata array */
4047 (*prdata)[prlines - 1] = strdup(walker->value.u_str);
4048 (*prdata)[prlines] = NULL;
4049 }
4050 /* skip anything else */
4051 walker = walker->next;
4052 }
4053 walker = grinfo;
4054 *xsize = 0;
4055 *ysize = 0;
4056 *ymin = 0;
4057 *ymax = 0;
4058 while (walker) {
4059 if (strcmp(walker->key, "image_width") == 0) {
4060 *xsize = walker->value.u_cnt;
4061 } else if (strcmp(walker->key, "image_height") == 0) {
4062 *ysize = walker->value.u_cnt;
4063 } else if (strcmp(walker->key, "value_min") == 0) {
4064 *ymin = walker->value.u_val;
4065 } else if (strcmp(walker->key, "value_max") == 0) {
4066 *ymax = walker->value.u_val;
4067 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
4068 prlines++;
4069 if (((*prdata) =
4070 (char**)rrd_realloc((*prdata),
4071 (prlines + 1) * sizeof(char *))) == NULL) {
4072 rrd_set_error("realloc prdata");
4073 return 0;
4074 }
4075 (*prdata)[prlines - 1] = strdup(walker->value.u_str);
4076 (*prdata)[prlines] = NULL;
4077 } else if (strcmp(walker->key, "image") == 0) {
4078 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
4079 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
4080 rrd_set_error("writing image");
4081 return 0;
4082 }
4083 }
4084 /* skip anything else */
4085 walker = walker->next;
4086 }
4087 rrd_info_free(grinfo);
4088 return 0;
4089 }
4092 /* Some surgery done on this function, it became ridiculously big.
4093 ** Things moved:
4094 ** - initializing now in rrd_graph_init()
4095 ** - options parsing now in rrd_graph_options()
4096 ** - script parsing now in rrd_graph_script()
4097 */
4099 rrd_info_t *rrd_graph_v(
4100 int argc,
4101 char **argv)
4102 {
4103 image_desc_t im;
4104 rrd_info_t *grinfo;
4105 char *old_locale;
4106 rrd_graph_init(&im);
4107 /* a dummy surface so that we can measure text sizes for placements */
4108 old_locale = setlocale(LC_NUMERIC, NULL);
4109 setlocale(LC_NUMERIC, "C");
4110 rrd_graph_options(argc, argv, &im);
4111 if (rrd_test_error()) {
4112 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4113 rrd_info_free(im.grinfo);
4114 im_free(&im);
4115 return NULL;
4116 }
4118 if (optind >= argc) {
4119 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4120 rrd_info_free(im.grinfo);
4121 im_free(&im);
4122 rrd_set_error("missing filename");
4123 return NULL;
4124 }
4126 if (strlen(argv[optind]) >= MAXPATH) {
4127 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4128 rrd_set_error("filename (including path) too long");
4129 rrd_info_free(im.grinfo);
4130 im_free(&im);
4131 return NULL;
4132 }
4134 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4135 im.graphfile[MAXPATH - 1] = '\0';
4137 if (strcmp(im.graphfile, "-") == 0) {
4138 im.graphfile[0] = '\0';
4139 }
4141 rrd_graph_script(argc, argv, &im, 1);
4142 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4144 if (rrd_test_error()) {
4145 rrd_info_free(im.grinfo);
4146 im_free(&im);
4147 return NULL;
4148 }
4150 /* Everything is now read and the actual work can start */
4151 if (graph_paint(&im) == -1) {
4152 rrd_info_free(im.grinfo);
4153 im_free(&im);
4154 return NULL;
4155 }
4157 /* The image is generated and needs to be output.
4158 ** Also, if needed, print a line with information about the image.
4159 */
4161 if (im.imginfo) {
4162 rrd_infoval_t info;
4163 char *path;
4164 char *filename;
4166 path = strdup(im.graphfile);
4167 filename = basename(path);
4168 info.u_str =
4169 sprintf_alloc(im.imginfo,
4170 filename,
4171 (long) (im.zoom *
4172 im.ximg), (long) (im.zoom * im.yimg));
4173 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4174 free(info.u_str);
4175 free(path);
4176 }
4177 if (im.rendered_image) {
4178 rrd_infoval_t img;
4180 img.u_blo.size = im.rendered_image_size;
4181 img.u_blo.ptr = im.rendered_image;
4182 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4183 }
4184 grinfo = im.grinfo;
4185 im_free(&im);
4186 return grinfo;
4187 }
4189 static void
4190 rrd_set_font_desc (
4191 image_desc_t *im,int prop,char *font, double size ){
4192 if (font){
4193 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4194 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4195 /* if we already got one, drop it first */
4196 pango_font_description_free(im->text_prop[prop].font_desc);
4197 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4198 };
4199 if (size > 0){
4200 im->text_prop[prop].size = size;
4201 };
4202 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4203 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4204 };
4205 }
4207 void rrd_graph_init(
4208 image_desc_t
4209 *im)
4210 {
4211 unsigned int i;
4212 char *deffont = getenv("RRD_DEFAULT_FONT");
4213 static PangoFontMap *fontmap = NULL;
4214 PangoContext *context;
4216 /* zero the whole structure first */
4217 memset(im,0,sizeof(image_desc_t));
4219 #ifdef HAVE_TZSET
4220 tzset();
4221 #endif
4222 im->graph_type = GTYPE_TIME;
4223 im->base = 1000;
4224 im->daemon_addr = NULL;
4225 im->draw_x_grid = 1;
4226 im->draw_y_grid = 1;
4227 im->draw_3d_border = 2;
4228 im->dynamic_labels = 0;
4229 im->extra_flags = 0;
4230 im->font_options = cairo_font_options_create();
4231 im->forceleftspace = 0;
4232 im->gdes_c = 0;
4233 im->gdes = NULL;
4234 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4235 im->grid_dash_off = 1;
4236 im->grid_dash_on = 1;
4237 im->gridfit = 1;
4238 im->grinfo = (rrd_info_t *) NULL;
4239 im->grinfo_current = (rrd_info_t *) NULL;
4240 im->imgformat = IF_PNG;
4241 im->imginfo = NULL;
4242 im->lazy = 0;
4243 im->legenddirection = TOP_DOWN;
4244 im->legendheight = 0;
4245 im->legendposition = SOUTH;
4246 im->legendwidth = 0;
4247 im->logarithmic = 0;
4248 im->maxval = DNAN;
4249 im->minval = 0;
4250 im->minval = DNAN;
4251 im->magfact = 1;
4252 im->prt_c = 0;
4253 im->rigid = 0;
4254 im->rendered_image_size = 0;
4255 im->rendered_image = NULL;
4256 im->slopemode = 0;
4257 im->step = 0;
4258 im->symbol = ' ';
4259 im->tabwidth = 40.0;
4260 im->title[0] = '\0';
4261 im->unitsexponent = 9999;
4262 im->unitslength = 6;
4263 im->viewfactor = 1.0;
4264 im->watermark[0] = '\0';
4265 im->with_markup = 0;
4266 im->ximg = 0;
4267 im->xlab_user.minsec = -1;
4268 im->xorigin = 0;
4269 im->xOriginLegend = 0;
4270 im->xOriginLegendY = 0;
4271 im->xOriginLegendY2 = 0;
4272 im->xOriginTitle = 0;
4273 im->xsize = 400;
4274 im->ygridstep = DNAN;
4275 im->yimg = 0;
4276 im->ylegend[0] = '\0';
4277 im->second_axis_scale = 0; /* 0 disables it */
4278 im->second_axis_shift = 0; /* no shift by default */
4279 im->second_axis_legend[0] = '\0';
4280 im->second_axis_format[0] = '\0';
4281 im->yorigin = 0;
4282 im->yOriginLegend = 0;
4283 im->yOriginLegendY = 0;
4284 im->yOriginLegendY2 = 0;
4285 im->yOriginTitle = 0;
4286 im->ysize = 100;
4287 im->zoom = 1;
4289 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4290 im->cr = cairo_create(im->surface);
4292 for (i = 0; i < DIM(text_prop); i++) {
4293 im->text_prop[i].size = -1;
4294 im->text_prop[i].font_desc = NULL;
4295 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4296 }
4298 if (fontmap == NULL){
4299 fontmap = pango_cairo_font_map_get_default();
4300 }
4302 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4304 pango_cairo_context_set_resolution(context, 100);
4306 pango_cairo_update_context(im->cr,context);
4308 im->layout = pango_layout_new(context);
4309 g_object_unref (context);
4311 // im->layout = pango_cairo_create_layout(im->cr);
4314 cairo_font_options_set_hint_style
4315 (im->font_options, CAIRO_HINT_STYLE_FULL);
4316 cairo_font_options_set_hint_metrics
4317 (im->font_options, CAIRO_HINT_METRICS_ON);
4318 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4322 for (i = 0; i < DIM(graph_col); i++)
4323 im->graph_col[i] = graph_col[i];
4326 }
4329 void rrd_graph_options(
4330 int argc,
4331 char *argv[],
4332 image_desc_t
4333 *im)
4334 {
4335 int stroff;
4336 char *parsetime_error = NULL;
4337 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4338 time_t start_tmp = 0, end_tmp = 0;
4339 long long_tmp;
4340 rrd_time_value_t start_tv, end_tv;
4341 long unsigned int color;
4343 /* defines for long options without a short equivalent. should be bytes,
4344 and may not collide with (the ASCII value of) short options */
4345 #define LONGOPT_UNITS_SI 255
4347 /* *INDENT-OFF* */
4348 struct option long_options[] = {
4349 { "alt-autoscale", no_argument, 0, 'A'},
4350 { "imgformat", required_argument, 0, 'a'},
4351 { "font-smoothing-threshold", required_argument, 0, 'B'},
4352 { "base", required_argument, 0, 'b'},
4353 { "color", required_argument, 0, 'c'},
4354 { "full-size-mode", no_argument, 0, 'D'},
4355 { "daemon", required_argument, 0, 'd'},
4356 { "slope-mode", no_argument, 0, 'E'},
4357 { "end", required_argument, 0, 'e'},
4358 { "force-rules-legend", no_argument, 0, 'F'},
4359 { "imginfo", required_argument, 0, 'f'},
4360 { "graph-render-mode", required_argument, 0, 'G'},
4361 { "no-legend", no_argument, 0, 'g'},
4362 { "height", required_argument, 0, 'h'},
4363 { "no-minor", no_argument, 0, 'I'},
4364 { "interlaced", no_argument, 0, 'i'},
4365 { "alt-autoscale-min", no_argument, 0, 'J'},
4366 { "only-graph", no_argument, 0, 'j'},
4367 { "units-length", required_argument, 0, 'L'},
4368 { "lower-limit", required_argument, 0, 'l'},
4369 { "alt-autoscale-max", no_argument, 0, 'M'},
4370 { "zoom", required_argument, 0, 'm'},
4371 { "no-gridfit", no_argument, 0, 'N'},
4372 { "font", required_argument, 0, 'n'},
4373 { "logarithmic", no_argument, 0, 'o'},
4374 { "pango-markup", no_argument, 0, 'P'},
4375 { "font-render-mode", required_argument, 0, 'R'},
4376 { "rigid", no_argument, 0, 'r'},
4377 { "step", required_argument, 0, 'S'},
4378 { "start", required_argument, 0, 's'},
4379 { "tabwidth", required_argument, 0, 'T'},
4380 { "title", required_argument, 0, 't'},
4381 { "upper-limit", required_argument, 0, 'u'},
4382 { "vertical-label", required_argument, 0, 'v'},
4383 { "watermark", required_argument, 0, 'W'},
4384 { "width", required_argument, 0, 'w'},
4385 { "units-exponent", required_argument, 0, 'X'},
4386 { "x-grid", required_argument, 0, 'x'},
4387 { "alt-y-grid", no_argument, 0, 'Y'},
4388 { "y-grid", required_argument, 0, 'y'},
4389 { "lazy", no_argument, 0, 'z'},
4390 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4391 { "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 */
4392 { "disable-rrdtool-tag",no_argument, 0, 1001},
4393 { "right-axis", required_argument, 0, 1002},
4394 { "right-axis-label", required_argument, 0, 1003},
4395 { "right-axis-format", required_argument, 0, 1004},
4396 { "legend-position", required_argument, 0, 1005},
4397 { "legend-direction", required_argument, 0, 1006},
4398 { "border", required_argument, 0, 1007},
4399 { "grid-dash", required_argument, 0, 1008},
4400 { "dynamic-labels", no_argument, 0, 1009},
4401 { "week-fmt", required_argument, 0, 1010},
4402 { "graph-type", required_argument, 0, 1011},
4403 { 0, 0, 0, 0}
4404 };
4405 /* *INDENT-ON* */
4407 optind = 0;
4408 opterr = 0; /* initialize getopt */
4409 rrd_parsetime("end-24h", &start_tv);
4410 rrd_parsetime("now", &end_tv);
4411 while (1) {
4412 int option_index = 0;
4413 int opt;
4414 int col_start, col_end;
4416 opt = getopt_long(argc, argv,
4417 "Aa:B:b:c:Dd:Ee:Ff:G:gh:IiJjL:l:Mm:Nn:oPR:rS:s:T:t:u:v:W:w:X:x:Yy:z",
4418 long_options, &option_index);
4419 if (opt == EOF)
4420 break;
4421 switch (opt) {
4422 case 'I':
4423 im->extra_flags |= NOMINOR;
4424 break;
4425 case 'Y':
4426 im->extra_flags |= ALTYGRID;
4427 break;
4428 case 'A':
4429 im->extra_flags |= ALTAUTOSCALE;
4430 break;
4431 case 'J':
4432 im->extra_flags |= ALTAUTOSCALE_MIN;
4433 break;
4434 case 'M':
4435 im->extra_flags |= ALTAUTOSCALE_MAX;
4436 break;
4437 case 'j':
4438 im->extra_flags |= ONLY_GRAPH;
4439 break;
4440 case 'g':
4441 im->extra_flags |= NOLEGEND;
4442 break;
4443 case 1005:
4444 if (strcmp(optarg, "north") == 0) {
4445 im->legendposition = NORTH;
4446 } else if (strcmp(optarg, "west") == 0) {
4447 im->legendposition = WEST;
4448 } else if (strcmp(optarg, "south") == 0) {
4449 im->legendposition = SOUTH;
4450 } else if (strcmp(optarg, "east") == 0) {
4451 im->legendposition = EAST;
4452 } else {
4453 rrd_set_error("unknown legend-position '%s'", optarg);
4454 return;
4455 }
4456 break;
4457 case 1006:
4458 if (strcmp(optarg, "topdown") == 0) {
4459 im->legenddirection = TOP_DOWN;
4460 } else if (strcmp(optarg, "bottomup") == 0) {
4461 im->legenddirection = BOTTOM_UP;
4462 } else {
4463 rrd_set_error("unknown legend-position '%s'", optarg);
4464 return;
4465 }
4466 break;
4467 case 'F':
4468 im->extra_flags |= FORCE_RULES_LEGEND;
4469 break;
4470 case 1001:
4471 im->extra_flags |= NO_RRDTOOL_TAG;
4472 break;
4473 case LONGOPT_UNITS_SI:
4474 if (im->extra_flags & FORCE_UNITS) {
4475 rrd_set_error("--units can only be used once!");
4476 return;
4477 }
4478 if (strcmp(optarg, "si") == 0)
4479 im->extra_flags |= FORCE_UNITS_SI;
4480 else {
4481 rrd_set_error("invalid argument for --units: %s", optarg);
4482 return;
4483 }
4484 break;
4485 case 'X':
4486 im->unitsexponent = atoi(optarg);
4487 break;
4488 case 'L':
4489 im->unitslength = atoi(optarg);
4490 im->forceleftspace = 1;
4491 break;
4492 case 'T':
4493 im->tabwidth = atof(optarg);
4494 break;
4495 case 'S':
4496 im->step = atoi(optarg);
4497 break;
4498 case 'N':
4499 im->gridfit = 0;
4500 break;
4501 case 'P':
4502 im->with_markup = 1;
4503 break;
4504 case 's':
4505 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4506 rrd_set_error("start time: %s", parsetime_error);
4507 return;
4508 }
4509 break;
4510 case 'e':
4511 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4512 rrd_set_error("end time: %s", parsetime_error);
4513 return;
4514 }
4515 break;
4516 case 'x':
4517 if (strcmp(optarg, "none") == 0) {
4518 im->draw_x_grid = 0;
4519 break;
4520 };
4521 if (sscanf(optarg,
4522 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4523 scan_gtm,
4524 &im->xlab_user.gridst,
4525 scan_mtm,
4526 &im->xlab_user.mgridst,
4527 scan_ltm,
4528 &im->xlab_user.labst,
4529 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4530 strncpy(im->xlab_form, optarg + stroff,
4531 sizeof(im->xlab_form) - 1);
4532 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4533 if ((int)
4534 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4535 rrd_set_error("unknown keyword %s", scan_gtm);
4536 return;
4537 } else if ((int)
4538 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4539 == -1) {
4540 rrd_set_error("unknown keyword %s", scan_mtm);
4541 return;
4542 } else if ((int)
4543 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4544 rrd_set_error("unknown keyword %s", scan_ltm);
4545 return;
4546 }
4547 im->xlab_user.minsec = 1;
4548 im->xlab_user.stst = im->xlab_form;
4549 } else {
4550 rrd_set_error("invalid x-grid format");
4551 return;
4552 }
4553 break;
4554 case 'y':
4556 if (strcmp(optarg, "none") == 0) {
4557 im->draw_y_grid = 0;
4558 break;
4559 };
4560 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4561 if (im->ygridstep <= 0) {
4562 rrd_set_error("grid step must be > 0");
4563 return;
4564 } else if (im->ylabfact < 1) {
4565 rrd_set_error("label factor must be > 0");
4566 return;
4567 }
4568 } else {
4569 rrd_set_error("invalid y-grid format");
4570 return;
4571 }
4572 break;
4573 case 1007:
4574 im->draw_3d_border = atoi(optarg);
4575 break;
4576 case 1008: /* grid-dash */
4577 if(sscanf(optarg,
4578 "%lf:%lf",
4579 &im->grid_dash_on,
4580 &im->grid_dash_off) != 2) {
4581 rrd_set_error("expected grid-dash format float:float");
4582 return;
4583 }
4584 break;
4585 case 1009: /* enable dynamic labels */
4586 im->dynamic_labels = 1;
4587 break;
4588 case 1010:
4589 strncpy(week_fmt,optarg,sizeof week_fmt);
4590 week_fmt[(sizeof week_fmt)-1]='\0';
4591 break;
4592 case 1002: /* right y axis */
4594 if(sscanf(optarg,
4595 "%lf:%lf",
4596 &im->second_axis_scale,
4597 &im->second_axis_shift) == 2) {
4598 if(im->second_axis_scale==0){
4599 rrd_set_error("the second_axis_scale must not be 0");
4600 return;
4601 }
4602 } else {
4603 rrd_set_error("invalid right-axis format expected scale:shift");
4604 return;
4605 }
4606 break;
4607 case 1003:
4608 strncpy(im->second_axis_legend,optarg,150);
4609 im->second_axis_legend[150]='\0';
4610 break;
4611 case 1004:
4612 if (bad_format(optarg)){
4613 rrd_set_error("use either %le or %lf formats");
4614 return;
4615 }
4616 strncpy(im->second_axis_format,optarg,150);
4617 im->second_axis_format[150]='\0';
4618 break;
4619 case 'v':
4620 strncpy(im->ylegend, optarg, 150);
4621 im->ylegend[150] = '\0';
4622 break;
4623 case 'u':
4624 im->maxval = atof(optarg);
4625 break;
4626 case 'l':
4627 im->minval = atof(optarg);
4628 break;
4629 case 'b':
4630 im->base = atol(optarg);
4631 if (im->base != 1024 && im->base != 1000) {
4632 rrd_set_error
4633 ("the only sensible value for base apart from 1000 is 1024");
4634 return;
4635 }
4636 break;
4637 case 'w':
4638 long_tmp = atol(optarg);
4639 if (long_tmp < 10) {
4640 rrd_set_error("width below 10 pixels");
4641 return;
4642 }
4643 im->xsize = long_tmp;
4644 break;
4645 case 'h':
4646 long_tmp = atol(optarg);
4647 if (long_tmp < 10) {
4648 rrd_set_error("height below 10 pixels");
4649 return;
4650 }
4651 im->ysize = long_tmp;
4652 break;
4653 case 'D':
4654 im->extra_flags |= FULL_SIZE_MODE;
4655 break;
4656 case 'i':
4657 /* interlaced png not supported at the moment */
4658 break;
4659 case 'r':
4660 im->rigid = 1;
4661 break;
4662 case 'f':
4663 im->imginfo = optarg;
4664 break;
4665 case 'a':
4666 if ((int)
4667 (im->imgformat = if_conv(optarg)) == -1) {
4668 rrd_set_error("unsupported graphics format '%s'", optarg);
4669 return;
4670 }
4671 break;
4672 case 1011:
4673 if ((int)
4674 (im->graph_type = type_conv(optarg)) == -1) {
4675 rrd_set_error("unsupported graphics type '%s'", optarg);
4676 return;
4677 }
4678 break;
4679 case 'z':
4680 im->lazy = 1;
4681 break;
4682 case 'E':
4683 im->slopemode = 1;
4684 break;
4685 case 'o':
4686 im->logarithmic = 1;
4687 break;
4688 case 'c':
4689 if (sscanf(optarg,
4690 "%10[A-Z]#%n%8lx%n",
4691 col_nam, &col_start, &color, &col_end) == 2) {
4692 int ci;
4693 int col_len = col_end - col_start;
4695 switch (col_len) {
4696 case 3:
4697 color =
4698 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4699 0x011000) |
4700 ((color & 0x00F)
4701 * 0x001100)
4702 | 0x000000FF);
4703 break;
4704 case 4:
4705 color =
4706 (((color & 0xF000) *
4707 0x11000) | ((color & 0x0F00) *
4708 0x01100) | ((color &
4709 0x00F0) *
4710 0x00110) |
4711 ((color & 0x000F) * 0x00011)
4712 );
4713 break;
4714 case 6:
4715 color = (color << 8) + 0xff /* shift left by 8 */ ;
4716 break;
4717 case 8:
4718 break;
4719 default:
4720 rrd_set_error("the color format is #RRGGBB[AA]");
4721 return;
4722 }
4723 if ((ci = grc_conv(col_nam)) != -1) {
4724 im->graph_col[ci] = gfx_hex_to_col(color);
4725 } else {
4726 rrd_set_error("invalid color name '%s'", col_nam);
4727 return;
4728 }
4729 } else {
4730 rrd_set_error("invalid color def format");
4731 return;
4732 }
4733 break;
4734 case 'n':{
4735 char prop[15];
4736 double size = 1;
4737 int end;
4739 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4740 int sindex, propidx;
4742 if ((sindex = text_prop_conv(prop)) != -1) {
4743 for (propidx = sindex;
4744 propidx < TEXT_PROP_LAST; propidx++) {
4745 if (size > 0) {
4746 rrd_set_font_desc(im,propidx,NULL,size);
4747 }
4748 if ((int) strlen(optarg) > end+2) {
4749 if (optarg[end] == ':') {
4750 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4751 } else {
4752 rrd_set_error
4753 ("expected : after font size in '%s'",
4754 optarg);
4755 return;
4756 }
4757 }
4758 /* only run the for loop for DEFAULT (0) for
4759 all others, we break here. woodo programming */
4760 if (propidx == sindex && sindex != 0)
4761 break;
4762 }
4763 } else {
4764 rrd_set_error("invalid fonttag '%s'", prop);
4765 return;
4766 }
4767 } else {
4768 rrd_set_error("invalid text property format");
4769 return;
4770 }
4771 break;
4772 }
4773 case 'm':
4774 im->zoom = atof(optarg);
4775 if (im->zoom <= 0.0) {
4776 rrd_set_error("zoom factor must be > 0");
4777 return;
4778 }
4779 break;
4780 case 't':
4781 strncpy(im->title, optarg, 150);
4782 im->title[150] = '\0';
4783 break;
4784 case 'R':
4785 if (strcmp(optarg, "normal") == 0) {
4786 cairo_font_options_set_antialias
4787 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4788 cairo_font_options_set_hint_style
4789 (im->font_options, CAIRO_HINT_STYLE_FULL);
4790 } else if (strcmp(optarg, "light") == 0) {
4791 cairo_font_options_set_antialias
4792 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4793 cairo_font_options_set_hint_style
4794 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4795 } else if (strcmp(optarg, "mono") == 0) {
4796 cairo_font_options_set_antialias
4797 (im->font_options, CAIRO_ANTIALIAS_NONE);
4798 cairo_font_options_set_hint_style
4799 (im->font_options, CAIRO_HINT_STYLE_FULL);
4800 } else {
4801 rrd_set_error("unknown font-render-mode '%s'", optarg);
4802 return;
4803 }
4804 break;
4805 case 'G':
4806 if (strcmp(optarg, "normal") == 0)
4807 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4808 else if (strcmp(optarg, "mono") == 0)
4809 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4810 else {
4811 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4812 return;
4813 }
4814 break;
4815 case 'B':
4816 /* not supported curently */
4817 break;
4818 case 'W':
4819 strncpy(im->watermark, optarg, 100);
4820 im->watermark[99] = '\0';
4821 break;
4822 case 'd':
4823 {
4824 if (im->daemon_addr != NULL)
4825 {
4826 rrd_set_error ("You cannot specify --daemon "
4827 "more than once.");
4828 return;
4829 }
4831 im->daemon_addr = strdup(optarg);
4832 if (im->daemon_addr == NULL)
4833 {
4834 rrd_set_error("strdup failed");
4835 return;
4836 }
4838 break;
4839 }
4840 case '?':
4841 if (optopt != 0)
4842 rrd_set_error("unknown option '%c'", optopt);
4843 else
4844 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4845 return;
4846 }
4847 } /* while (1) */
4849 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4850 pango_layout_context_changed(im->layout);
4854 if (im->logarithmic && im->minval <= 0) {
4855 rrd_set_error
4856 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4857 return;
4858 }
4860 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4861 /* error string is set in rrd_parsetime.c */
4862 return;
4863 }
4865 if (start_tmp < 3600 * 24 * 365 * 10) {
4866 rrd_set_error
4867 ("the first entry to fetch should be after 1980 (%ld)",
4868 start_tmp);
4869 return;
4870 }
4872 if (end_tmp < start_tmp) {
4873 rrd_set_error
4874 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4875 return;
4876 }
4878 im->start = start_tmp;
4879 im->end = end_tmp;
4880 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4881 }
4883 int rrd_graph_color(
4884 image_desc_t
4885 *im,
4886 char *var,
4887 char *err,
4888 int optional)
4889 {
4890 char *color;
4891 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4893 color = strstr(var, "#");
4894 if (color == NULL) {
4895 if (optional == 0) {
4896 rrd_set_error("Found no color in %s", err);
4897 return 0;
4898 }
4899 return 0;
4900 } else {
4901 int n = 0;
4902 char *rest;
4903 long unsigned int col;
4905 rest = strstr(color, ":");
4906 if (rest != NULL)
4907 n = rest - color;
4908 else
4909 n = strlen(color);
4910 switch (n) {
4911 case 7:
4912 sscanf(color, "#%6lx%n", &col, &n);
4913 col = (col << 8) + 0xff /* shift left by 8 */ ;
4914 if (n != 7)
4915 rrd_set_error("Color problem in %s", err);
4916 break;
4917 case 9:
4918 sscanf(color, "#%8lx%n", &col, &n);
4919 if (n == 9)
4920 break;
4921 default:
4922 rrd_set_error("Color problem in %s", err);
4923 }
4924 if (rrd_test_error())
4925 return 0;
4926 gdp->col = gfx_hex_to_col(col);
4927 return n;
4928 }
4929 }
4932 int bad_format(
4933 char *fmt)
4934 {
4935 char *ptr;
4936 int n = 0;
4938 ptr = fmt;
4939 while (*ptr != '\0')
4940 if (*ptr++ == '%') {
4942 /* line cannot end with percent char */
4943 if (*ptr == '\0')
4944 return 1;
4945 /* '%s', '%S' and '%%' are allowed */
4946 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4947 ptr++;
4948 /* %c is allowed (but use only with vdef!) */
4949 else if (*ptr == 'c') {
4950 ptr++;
4951 n = 1;
4952 }
4954 /* or else '% 6.2lf' and such are allowed */
4955 else {
4956 /* optional padding character */
4957 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4958 ptr++;
4959 /* This should take care of 'm.n' with all three optional */
4960 while (*ptr >= '0' && *ptr <= '9')
4961 ptr++;
4962 if (*ptr == '.')
4963 ptr++;
4964 while (*ptr >= '0' && *ptr <= '9')
4965 ptr++;
4966 /* Either 'le', 'lf' or 'lg' must follow here */
4967 if (*ptr++ != 'l')
4968 return 1;
4969 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4970 ptr++;
4971 else
4972 return 1;
4973 n++;
4974 }
4975 }
4977 return (n != 1);
4978 }
4981 int vdef_parse(
4982 struct graph_desc_t
4983 *gdes,
4984 const char *const str)
4985 {
4986 /* A VDEF currently is either "func" or "param,func"
4987 * so the parsing is rather simple. Change if needed.
4988 */
4989 double param;
4990 char func[30];
4991 int n;
4993 n = 0;
4994 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4995 if (n == (int) strlen(str)) { /* matched */
4996 ;
4997 } else {
4998 n = 0;
4999 sscanf(str, "%29[A-Z]%n", func, &n);
5000 if (n == (int) strlen(str)) { /* matched */
5001 param = DNAN;
5002 } else {
5003 rrd_set_error
5004 ("Unknown function string '%s' in VDEF '%s'",
5005 str, gdes->vname);
5006 return -1;
5007 }
5008 }
5009 if (!strcmp("PERCENT", func))
5010 gdes->vf.op = VDEF_PERCENT;
5011 else if (!strcmp("PERCENTNAN", func))
5012 gdes->vf.op = VDEF_PERCENTNAN;
5013 else if (!strcmp("MAXIMUM", func))
5014 gdes->vf.op = VDEF_MAXIMUM;
5015 else if (!strcmp("AVERAGE", func))
5016 gdes->vf.op = VDEF_AVERAGE;
5017 else if (!strcmp("STDEV", func))
5018 gdes->vf.op = VDEF_STDEV;
5019 else if (!strcmp("MINIMUM", func))
5020 gdes->vf.op = VDEF_MINIMUM;
5021 else if (!strcmp("TOTAL", func))
5022 gdes->vf.op = VDEF_TOTAL;
5023 else if (!strcmp("FIRST", func))
5024 gdes->vf.op = VDEF_FIRST;
5025 else if (!strcmp("LAST", func))
5026 gdes->vf.op = VDEF_LAST;
5027 else if (!strcmp("LSLSLOPE", func))
5028 gdes->vf.op = VDEF_LSLSLOPE;
5029 else if (!strcmp("LSLINT", func))
5030 gdes->vf.op = VDEF_LSLINT;
5031 else if (!strcmp("LSLCORREL", func))
5032 gdes->vf.op = VDEF_LSLCORREL;
5033 else {
5034 rrd_set_error
5035 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
5036 return -1;
5037 };
5038 switch (gdes->vf.op) {
5039 case VDEF_PERCENT:
5040 case VDEF_PERCENTNAN:
5041 if (isnan(param)) { /* no parameter given */
5042 rrd_set_error
5043 ("Function '%s' needs parameter in VDEF '%s'\n",
5044 func, gdes->vname);
5045 return -1;
5046 };
5047 if (param >= 0.0 && param <= 100.0) {
5048 gdes->vf.param = param;
5049 gdes->vf.val = DNAN; /* undefined */
5050 gdes->vf.when = 0; /* undefined */
5051 gdes->vf.never = 1;
5052 } else {
5053 rrd_set_error
5054 ("Parameter '%f' out of range in VDEF '%s'\n",
5055 param, gdes->vname);
5056 return -1;
5057 };
5058 break;
5059 case VDEF_MAXIMUM:
5060 case VDEF_AVERAGE:
5061 case VDEF_STDEV:
5062 case VDEF_MINIMUM:
5063 case VDEF_TOTAL:
5064 case VDEF_FIRST:
5065 case VDEF_LAST:
5066 case VDEF_LSLSLOPE:
5067 case VDEF_LSLINT:
5068 case VDEF_LSLCORREL:
5069 if (isnan(param)) {
5070 gdes->vf.param = DNAN;
5071 gdes->vf.val = DNAN;
5072 gdes->vf.when = 0;
5073 gdes->vf.never = 1;
5074 } else {
5075 rrd_set_error
5076 ("Function '%s' needs no parameter in VDEF '%s'\n",
5077 func, gdes->vname);
5078 return -1;
5079 };
5080 break;
5081 };
5082 return 0;
5083 }
5086 int vdef_calc(
5087 image_desc_t *im,
5088 int gdi)
5089 {
5090 graph_desc_t *src, *dst;
5091 rrd_value_t *data;
5092 long step, steps;
5094 dst = &im->gdes[gdi];
5095 src = &im->gdes[dst->vidx];
5096 data = src->data + src->ds;
5098 steps = (src->end - src->start) / src->step;
5099 #if 0
5100 printf
5101 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
5102 src->start, src->end, steps);
5103 #endif
5104 switch (dst->vf.op) {
5105 case VDEF_PERCENT:{
5106 rrd_value_t *array;
5107 int field;
5108 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5109 rrd_set_error("malloc VDEV_PERCENT");
5110 return -1;
5111 }
5112 for (step = 0; step < steps; step++) {
5113 array[step] = data[step * src->ds_cnt];
5114 }
5115 qsort(array, step, sizeof(double), vdef_percent_compar);
5116 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5117 dst->vf.val = array[field];
5118 dst->vf.when = 0; /* no time component */
5119 dst->vf.never = 1;
5120 free(array);
5121 #if 0
5122 for (step = 0; step < steps; step++)
5123 printf("DEBUG: %3li:%10.2f %c\n",
5124 step, array[step], step == field ? '*' : ' ');
5125 #endif
5126 }
5127 break;
5128 case VDEF_PERCENTNAN:{
5129 rrd_value_t *array;
5130 int field;
5131 /* count number of "valid" values */
5132 int nancount=0;
5133 for (step = 0; step < steps; step++) {
5134 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5135 }
5136 /* and allocate it */
5137 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5138 rrd_set_error("malloc VDEV_PERCENT");
5139 return -1;
5140 }
5141 /* and fill it in */
5142 field=0;
5143 for (step = 0; step < steps; step++) {
5144 if (!isnan(data[step * src->ds_cnt])) {
5145 array[field] = data[step * src->ds_cnt];
5146 field++;
5147 }
5148 }
5149 qsort(array, nancount, sizeof(double), vdef_percent_compar);
5150 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5151 dst->vf.val = array[field];
5152 dst->vf.when = 0; /* no time component */
5153 dst->vf.never = 1;
5154 free(array);
5155 }
5156 break;
5157 case VDEF_MAXIMUM:
5158 step = 0;
5159 while (step != steps && isnan(data[step * src->ds_cnt]))
5160 step++;
5161 if (step == steps) {
5162 dst->vf.val = DNAN;
5163 dst->vf.when = 0;
5164 dst->vf.never = 1;
5165 } else {
5166 dst->vf.val = data[step * src->ds_cnt];
5167 dst->vf.when = src->start + (step + 1) * src->step;
5168 dst->vf.never = 0;
5169 }
5170 while (step != steps) {
5171 if (finite(data[step * src->ds_cnt])) {
5172 if (data[step * src->ds_cnt] > dst->vf.val) {
5173 dst->vf.val = data[step * src->ds_cnt];
5174 dst->vf.when = src->start + (step + 1) * src->step;
5175 dst->vf.never = 0;
5176 }
5177 }
5178 step++;
5179 }
5180 break;
5181 case VDEF_TOTAL:
5182 case VDEF_STDEV:
5183 case VDEF_AVERAGE:{
5184 int cnt = 0;
5185 double sum = 0.0;
5186 double average = 0.0;
5188 for (step = 0; step < steps; step++) {
5189 if (finite(data[step * src->ds_cnt])) {
5190 sum += data[step * src->ds_cnt];
5191 cnt++;
5192 };
5193 }
5194 if (cnt) {
5195 if (dst->vf.op == VDEF_TOTAL) {
5196 dst->vf.val = sum * src->step;
5197 dst->vf.when = 0; /* no time component */
5198 dst->vf.never = 1;
5199 } else if (dst->vf.op == VDEF_AVERAGE) {
5200 dst->vf.val = sum / cnt;
5201 dst->vf.when = 0; /* no time component */
5202 dst->vf.never = 1;
5203 } else {
5204 average = sum / cnt;
5205 sum = 0.0;
5206 for (step = 0; step < steps; step++) {
5207 if (finite(data[step * src->ds_cnt])) {
5208 sum += pow((data[step * src->ds_cnt] - average), 2.0);
5209 };
5210 }
5211 dst->vf.val = pow(sum / cnt, 0.5);
5212 dst->vf.when = 0; /* no time component */
5213 dst->vf.never = 1;
5214 };
5215 } else {
5216 dst->vf.val = DNAN;
5217 dst->vf.when = 0;
5218 dst->vf.never = 1;
5219 }
5220 }
5221 break;
5222 case VDEF_MINIMUM:
5223 step = 0;
5224 while (step != steps && isnan(data[step * src->ds_cnt]))
5225 step++;
5226 if (step == steps) {
5227 dst->vf.val = DNAN;
5228 dst->vf.when = 0;
5229 dst->vf.never = 1;
5230 } else {
5231 dst->vf.val = data[step * src->ds_cnt];
5232 dst->vf.when = src->start + (step + 1) * src->step;
5233 dst->vf.never = 0;
5234 }
5235 while (step != steps) {
5236 if (finite(data[step * src->ds_cnt])) {
5237 if (data[step * src->ds_cnt] < dst->vf.val) {
5238 dst->vf.val = data[step * src->ds_cnt];
5239 dst->vf.when = src->start + (step + 1) * src->step;
5240 dst->vf.never = 0;
5241 }
5242 }
5243 step++;
5244 }
5245 break;
5246 case VDEF_FIRST:
5247 /* The time value returned here is one step before the
5248 * actual time value. This is the start of the first
5249 * non-NaN interval.
5250 */
5251 step = 0;
5252 while (step != steps && isnan(data[step * src->ds_cnt]))
5253 step++;
5254 if (step == steps) { /* all entries were NaN */
5255 dst->vf.val = DNAN;
5256 dst->vf.when = 0;
5257 dst->vf.never = 1;
5258 } else {
5259 dst->vf.val = data[step * src->ds_cnt];
5260 dst->vf.when = src->start + step * src->step;
5261 dst->vf.never = 0;
5262 }
5263 break;
5264 case VDEF_LAST:
5265 /* The time value returned here is the
5266 * actual time value. This is the end of the last
5267 * non-NaN interval.
5268 */
5269 step = steps - 1;
5270 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5271 step--;
5272 if (step < 0) { /* all entries were NaN */
5273 dst->vf.val = DNAN;
5274 dst->vf.when = 0;
5275 dst->vf.never = 1;
5276 } else {
5277 dst->vf.val = data[step * src->ds_cnt];
5278 dst->vf.when = src->start + (step + 1) * src->step;
5279 dst->vf.never = 0;
5280 }
5281 break;
5282 case VDEF_LSLSLOPE:
5283 case VDEF_LSLINT:
5284 case VDEF_LSLCORREL:{
5285 /* Bestfit line by linear least squares method */
5287 int cnt = 0;
5288 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5290 SUMx = 0;
5291 SUMy = 0;
5292 SUMxy = 0;
5293 SUMxx = 0;
5294 SUMyy = 0;
5295 for (step = 0; step < steps; step++) {
5296 if (finite(data[step * src->ds_cnt])) {
5297 cnt++;
5298 SUMx += step;
5299 SUMxx += step * step;
5300 SUMxy += step * data[step * src->ds_cnt];
5301 SUMy += data[step * src->ds_cnt];
5302 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5303 };
5304 }
5306 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5307 y_intercept = (SUMy - slope * SUMx) / cnt;
5308 correl =
5309 (SUMxy -
5310 (SUMx * SUMy) / cnt) /
5311 sqrt((SUMxx -
5312 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5313 if (cnt) {
5314 if (dst->vf.op == VDEF_LSLSLOPE) {
5315 dst->vf.val = slope;
5316 dst->vf.when = 0;
5317 dst->vf.never = 1;
5318 } else if (dst->vf.op == VDEF_LSLINT) {
5319 dst->vf.val = y_intercept;
5320 dst->vf.when = 0;
5321 dst->vf.never = 1;
5322 } else if (dst->vf.op == VDEF_LSLCORREL) {
5323 dst->vf.val = correl;
5324 dst->vf.when = 0;
5325 dst->vf.never = 1;
5326 };
5327 } else {
5328 dst->vf.val = DNAN;
5329 dst->vf.when = 0;
5330 dst->vf.never = 1;
5331 }
5332 }
5333 break;
5334 }
5335 return 0;
5336 }
5338 /* NaN < -INF < finite_values < INF */
5339 int vdef_percent_compar(
5340 const void
5341 *a,
5342 const void
5343 *b)
5344 {
5345 /* Equality is not returned; this doesn't hurt except
5346 * (maybe) for a little performance.
5347 */
5349 /* First catch NaN values. They are smallest */
5350 if (isnan(*(double *) a))
5351 return -1;
5352 if (isnan(*(double *) b))
5353 return 1;
5354 /* NaN doesn't reach this part so INF and -INF are extremes.
5355 * The sign from isinf() is compatible with the sign we return
5356 */
5357 if (isinf(*(double *) a))
5358 return isinf(*(double *) a);
5359 if (isinf(*(double *) b))
5360 return isinf(*(double *) b);
5361 /* If we reach this, both values must be finite */
5362 if (*(double *) a < *(double *) b)
5363 return -1;
5364 else
5365 return 1;
5366 }
5368 void grinfo_push(
5369 image_desc_t *im,
5370 char *key,
5371 rrd_info_type_t type,
5372 rrd_infoval_t value)
5373 {
5374 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5375 if (im->grinfo == NULL) {
5376 im->grinfo = im->grinfo_current;
5377 }
5378 }
5381 void time_clean(
5382 char *result,
5383 char *format)
5384 {
5385 int j, jj;
5387 /* Handling based on
5388 - ANSI C99 Specifications http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5389 - Single UNIX Specification version 2 http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
5390 - POSIX:2001/Single UNIX Specification version 3 http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5391 - POSIX:2008 Specifications http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5392 Specifications tells
5393 "If a conversion specifier is not one of the above, the behavior is undefined."
5395 C99 tells
5396 "A conversion specifier consists of a % character, possibly followed by an E or O modifier character (described below), followed by a character that determines the behavior of the conversion specifier.
5398 POSIX:2001 tells
5399 "A conversion specification consists of a '%' character, possibly followed by an E or O modifier, and a terminating conversion specifier character that determines the conversion specification's behavior."
5401 POSIX:2008 introduce more complexe behavior that are not handled here.
5403 According to this, this code will replace:
5404 - % followed by @ by a %@
5405 - % followed by by a %SPACE
5406 - % followed by . by a %.
5407 - % followed by % by a %
5408 - % followed by t by a TAB
5409 - % followed by E then anything by '-'
5410 - % followed by O then anything by '-'
5411 - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5412 */
5414 jj = 0;
5415 for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5416 if (format[j] == '%') {
5417 if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5418 result[jj++] = '-';
5419 j+=2; /* We skip next 2 following char */
5420 } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5421 (format[j+1] == 'g') || (format[j+1] == 'H') ||
5422 (format[j+1] == 'I') || (format[j+1] == 'm') ||
5423 (format[j+1] == 'M') || (format[j+1] == 'S') ||
5424 (format[j+1] == 'U') || (format[j+1] == 'V') ||
5425 (format[j+1] == 'W') || (format[j+1] == 'y')) {
5426 result[jj++] = '-';
5427 if (jj < FMT_LEG_LEN) {
5428 result[jj++] = '-';
5429 }
5430 j++; /* We skip the following char */
5431 } else if (format[j+1] == 'j') {
5432 result[jj++] = '-';
5433 if (jj < FMT_LEG_LEN - 1) {
5434 result[jj++] = '-';
5435 result[jj++] = '-';
5436 }
5437 j++; /* We skip the following char */
5438 } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5439 /* Assuming Year on 4 digit */
5440 result[jj++] = '-';
5441 if (jj < FMT_LEG_LEN - 2) {
5442 result[jj++] = '-';
5443 result[jj++] = '-';
5444 result[jj++] = '-';
5445 }
5446 j++; /* We skip the following char */
5447 } else if (format[j+1] == 'R') {
5448 result[jj++] = '-';
5449 if (jj < FMT_LEG_LEN - 3) {
5450 result[jj++] = '-';
5451 result[jj++] = ':';
5452 result[jj++] = '-';
5453 result[jj++] = '-';
5454 }
5455 j++; /* We skip the following char */
5456 } else if (format[j+1] == 'T') {
5457 result[jj++] = '-';
5458 if (jj < FMT_LEG_LEN - 6) {
5459 result[jj++] = '-';
5460 result[jj++] = ':';
5461 result[jj++] = '-';
5462 result[jj++] = '-';
5463 result[jj++] = ':';
5464 result[jj++] = '-';
5465 result[jj++] = '-';
5466 }
5467 j++; /* We skip the following char */
5468 } else if (format[j+1] == 'F') {
5469 result[jj++] = '-';
5470 if (jj < FMT_LEG_LEN - 8) {
5471 result[jj++] = '-';
5472 result[jj++] = '-';
5473 result[jj++] = '-';
5474 result[jj++] = '-';
5475 result[jj++] = '-';
5476 result[jj++] = '-';
5477 result[jj++] = '-';
5478 result[jj++] = '-';
5479 result[jj++] = '-';
5480 }
5481 j++; /* We skip the following char */
5482 } else if (format[j+1] == 'D') {
5483 result[jj++] = '-';
5484 if (jj < FMT_LEG_LEN - 6) {
5485 result[jj++] = '-';
5486 result[jj++] = '/';
5487 result[jj++] = '-';
5488 result[jj++] = '-';
5489 result[jj++] = '/';
5490 result[jj++] = '-';
5491 result[jj++] = '-';
5492 }
5493 j++; /* We skip the following char */
5494 } else if (format[j+1] == 'n') {
5495 result[jj++] = '\r';
5496 result[jj++] = '\n';
5497 j++; /* We skip the following char */
5498 } else if (format[j+1] == 't') {
5499 result[jj++] = '\t';
5500 j++; /* We skip the following char */
5501 } else if (format[j+1] == '%') {
5502 result[jj++] = '%';
5503 j++; /* We skip the following char */
5504 } else if (format[j+1] == ' ') {
5505 if (jj < FMT_LEG_LEN - 1) {
5506 result[jj++] = '%';
5507 result[jj++] = ' ';
5508 }
5509 j++; /* We skip the following char */
5510 } else if (format[j+1] == '.') {
5511 if (jj < FMT_LEG_LEN - 1) {
5512 result[jj++] = '%';
5513 result[jj++] = '.';
5514 }
5515 j++; /* We skip the following char */
5516 } else if (format[j+1] == '@') {
5517 if (jj < FMT_LEG_LEN - 1) {
5518 result[jj++] = '%';
5519 result[jj++] = '@';
5520 }
5521 j++; /* We skip the following char */
5522 } else {
5523 result[jj++] = '-';
5524 j++; /* We skip the following char */
5525 }
5526 } else {
5527 result[jj++] = format[j];
5528 }
5529 }
5530 result[jj] = '\0'; /* We must force the end of the string */
5531 }