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_y_prev = 0; // im->yimg;
1744 int leg_cc;
1745 double glue = 0;
1746 int i, ii, mark = 0;
1747 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1748 int *legspace;
1749 char *tab;
1750 char saved_legend[FMT_LEG_LEN + 5];
1752 if(calc_width){
1753 legendwidth = 0;
1754 }
1755 else{
1756 legendwidth = im->legendwidth - 2 * border;
1757 }
1760 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1761 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1762 rrd_set_error("malloc for legspace");
1763 return -1;
1764 }
1766 for (i = 0; i < im->gdes_c; i++) {
1767 char prt_fctn; /*special printfunctions */
1768 if(calc_width){
1769 strncpy(saved_legend, im->gdes[i].legend, sizeof saved_legend);
1770 }
1772 fill_last = fill;
1773 /* hide legends for rules which are not displayed */
1774 if (im->gdes[i].gf == GF_TEXTALIGN) {
1775 default_txtalign = im->gdes[i].txtalign;
1776 }
1778 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1779 if (im->gdes[i].gf == GF_HRULE
1780 && (im->gdes[i].yrule <
1781 im->minval || im->gdes[i].yrule > im->maxval))
1782 im->gdes[i].legend[0] = '\0';
1783 if (im->gdes[i].gf == GF_VRULE
1784 && (im->gdes[i].xrule <
1785 im->start || im->gdes[i].xrule > im->end))
1786 im->gdes[i].legend[0] = '\0';
1787 }
1789 /* turn \\t into tab */
1790 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1791 memmove(tab, tab + 1, strlen(tab));
1792 tab[0] = (char) 9;
1793 }
1795 leg_cc = strlen(im->gdes[i].legend);
1796 /* is there a controle code at the end of the legend string ? */
1797 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1798 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1799 leg_cc -= 2;
1800 im->gdes[i].legend[leg_cc] = '\0';
1801 } else {
1802 prt_fctn = '\0';
1803 }
1804 /* only valid control codes */
1805 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1806 prt_fctn != 'r' &&
1807 prt_fctn != 'j' &&
1808 prt_fctn != 'c' &&
1809 prt_fctn != 'u' &&
1810 prt_fctn != '.' &&
1811 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1812 free(legspace);
1813 rrd_set_error
1814 ("Unknown control code at the end of '%s\\%c'",
1815 im->gdes[i].legend, prt_fctn);
1816 return -1;
1817 }
1818 /* \n -> \l */
1819 if (prt_fctn == 'n') {
1820 prt_fctn = 'l';
1821 }
1822 /* \. is a null operation to allow strings ending in \x */
1823 if (prt_fctn == '.') {
1824 prt_fctn = '\0';
1825 }
1827 /* remove exess space from the end of the legend for \g */
1828 while (prt_fctn == 'g' &&
1829 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1830 leg_cc--;
1831 im->gdes[i].legend[leg_cc] = '\0';
1832 }
1834 if (leg_cc != 0) {
1836 /* no interleg space if string ends in \g */
1837 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1838 if (fill > 0) {
1839 fill += legspace[i];
1840 }
1841 fill +=
1842 gfx_get_text_width(im,
1843 fill + border,
1844 im->
1845 text_prop
1846 [TEXT_PROP_LEGEND].
1847 font_desc,
1848 im->tabwidth, im->gdes[i].legend);
1849 leg_c++;
1850 } else {
1851 legspace[i] = 0;
1852 }
1853 /* who said there was a special tag ... ? */
1854 if (prt_fctn == 'g') {
1855 prt_fctn = '\0';
1856 }
1858 if (prt_fctn == '\0') {
1859 if(calc_width && (fill > legendwidth)){
1860 legendwidth = fill;
1861 }
1862 if (i == im->gdes_c - 1 || fill > legendwidth) {
1863 /* just one legend item is left right or center */
1864 switch (default_txtalign) {
1865 case TXA_RIGHT:
1866 prt_fctn = 'r';
1867 break;
1868 case TXA_CENTER:
1869 prt_fctn = 'c';
1870 break;
1871 case TXA_JUSTIFIED:
1872 prt_fctn = 'j';
1873 break;
1874 default:
1875 prt_fctn = 'l';
1876 break;
1877 }
1878 }
1879 /* is it time to place the legends ? */
1880 if (fill > legendwidth) {
1881 if (leg_c > 1) {
1882 /* go back one */
1883 i--;
1884 fill = fill_last;
1885 leg_c--;
1886 }
1887 }
1888 if (leg_c == 1 && prt_fctn == 'j') {
1889 prt_fctn = 'l';
1890 }
1891 }
1893 if (prt_fctn != '\0') {
1894 leg_x = border;
1895 if (leg_c >= 2 && prt_fctn == 'j') {
1896 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1897 } else {
1898 glue = 0;
1899 }
1900 if (prt_fctn == 'c')
1901 leg_x = border + (double)(legendwidth - fill) / 2.0;
1902 if (prt_fctn == 'r')
1903 leg_x = legendwidth - fill + border;
1904 for (ii = mark; ii <= i; ii++) {
1905 if (im->gdes[ii].legend[0] == '\0')
1906 continue; /* skip empty legends */
1907 im->gdes[ii].leg_x = leg_x;
1908 im->gdes[ii].leg_y = leg_y + border;
1909 leg_x +=
1910 (double)gfx_get_text_width(im, leg_x,
1911 im->
1912 text_prop
1913 [TEXT_PROP_LEGEND].
1914 font_desc,
1915 im->tabwidth, im->gdes[ii].legend)
1916 +(double)legspace[ii]
1917 + glue;
1918 }
1919 leg_y_prev = leg_y;
1920 if (leg_x > border || prt_fctn == 's')
1921 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1922 if (prt_fctn == 's')
1923 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1924 if (prt_fctn == 'u')
1925 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1927 if(calc_width && (fill > legendwidth)){
1928 legendwidth = fill;
1929 }
1930 fill = 0;
1931 leg_c = 0;
1932 mark = ii;
1933 }
1935 if(calc_width){
1936 strncpy(im->gdes[i].legend, saved_legend, sizeof im->gdes[0].legend);
1937 }
1938 }
1940 if(calc_width){
1941 im->legendwidth = legendwidth + 2 * border;
1942 }
1943 else{
1944 im->legendheight = leg_y + border * 0.6;
1945 }
1946 free(legspace);
1947 }
1948 return 0;
1949 }
1951 /* create a grid on the graph. it determines what to do
1952 from the values of xsize, start and end */
1954 /* the xaxis labels are determined from the number of seconds per pixel
1955 in the requested graph */
1957 int calc_horizontal_grid(
1958 image_desc_t
1959 *im)
1960 {
1961 double range;
1962 double scaledrange;
1963 int pixel, i;
1964 int gridind = 0;
1965 int decimals, fractionals;
1967 im->ygrid_scale.labfact = 2;
1968 range = im->maxval - im->minval;
1969 scaledrange = range / im->magfact;
1970 /* does the scale of this graph make it impossible to put lines
1971 on it? If so, give up. */
1972 if (isnan(scaledrange)) {
1973 return 0;
1974 }
1976 /* find grid spaceing */
1977 pixel = 1;
1978 if (isnan(im->ygridstep)) {
1979 if (im->extra_flags & ALTYGRID) {
1980 /* find the value with max number of digits. Get number of digits */
1981 decimals =
1982 ceil(log10
1983 (max(fabs(im->maxval), fabs(im->minval)) *
1984 im->viewfactor / im->magfact));
1985 if (decimals <= 0) /* everything is small. make place for zero */
1986 decimals = 1;
1987 im->ygrid_scale.gridstep =
1988 pow((double) 10,
1989 floor(log10(range * im->viewfactor / im->magfact))) /
1990 im->viewfactor * im->magfact;
1991 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1992 im->ygrid_scale.gridstep = 0.1;
1993 /* should have at least 5 lines but no more then 15 */
1994 if (range / im->ygrid_scale.gridstep < 5
1995 && im->ygrid_scale.gridstep >= 30)
1996 im->ygrid_scale.gridstep /= 10;
1997 if (range / im->ygrid_scale.gridstep > 15)
1998 im->ygrid_scale.gridstep *= 10;
1999 if (range / im->ygrid_scale.gridstep > 5) {
2000 im->ygrid_scale.labfact = 1;
2001 if (range / im->ygrid_scale.gridstep > 8
2002 || im->ygrid_scale.gridstep <
2003 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
2004 im->ygrid_scale.labfact = 2;
2005 } else {
2006 im->ygrid_scale.gridstep /= 5;
2007 im->ygrid_scale.labfact = 5;
2008 }
2009 fractionals =
2010 floor(log10
2011 (im->ygrid_scale.gridstep *
2012 (double) im->ygrid_scale.labfact * im->viewfactor /
2013 im->magfact));
2014 if (fractionals < 0) { /* small amplitude. */
2015 int len = decimals - fractionals + 1;
2017 if (im->unitslength < len + 2)
2018 im->unitslength = len + 2;
2019 snprintf(im->ygrid_scale.labfmt, sizeof im->ygrid_scale.labfmt,
2020 "%%%d.%df%s", len,
2021 -fractionals, (im->symbol != ' ' ? " %c" : ""));
2022 } else {
2023 int len = decimals + 1;
2025 if (im->unitslength < len + 2)
2026 im->unitslength = len + 2;
2027 snprintf(im->ygrid_scale.labfmt, sizeof im->ygrid_scale.labfmt,
2028 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
2029 }
2030 } else { /* classic rrd grid */
2031 for (i = 0; ylab[i].grid > 0; i++) {
2032 pixel = im->ysize / (scaledrange / ylab[i].grid);
2033 gridind = i;
2034 if (pixel >= 5)
2035 break;
2036 }
2038 for (i = 0; i < 4; i++) {
2039 if (pixel * ylab[gridind].lfac[i] >=
2040 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2041 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2042 break;
2043 }
2044 }
2046 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2047 }
2048 } else {
2049 im->ygrid_scale.gridstep = im->ygridstep;
2050 im->ygrid_scale.labfact = im->ylabfact;
2051 }
2052 return 1;
2053 }
2055 int draw_horizontal_grid(
2056 image_desc_t
2057 *im)
2058 {
2059 int i;
2060 double scaledstep;
2061 char graph_label[100];
2062 int nlabels = 0;
2063 double X0 = im->xorigin;
2064 double X1 = im->xorigin + im->xsize;
2065 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2066 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2067 double MaxY;
2068 double second_axis_magfact = 0;
2069 char *second_axis_symb = "";
2071 scaledstep =
2072 im->ygrid_scale.gridstep /
2073 (double) im->magfact * (double) im->viewfactor;
2074 MaxY = scaledstep * (double) egrid;
2075 for (i = sgrid; i <= egrid; i++) {
2076 double Y0 = ytr(im,
2077 im->ygrid_scale.gridstep * i);
2078 double YN = ytr(im,
2079 im->ygrid_scale.gridstep * (i + 1));
2081 if (floor(Y0 + 0.5) >=
2082 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2083 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2084 with the chosen settings. Add a label if required by settings, or if
2085 there is only one label so far and the next grid line is out of bounds. */
2086 if (i % im->ygrid_scale.labfact == 0
2087 || (nlabels == 1
2088 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2089 if (im->symbol == ' ') {
2090 if (im->extra_flags & ALTYGRID) {
2091 snprintf(graph_label, sizeof graph_label,
2092 im->ygrid_scale.labfmt,
2093 scaledstep * (double) i);
2094 } else {
2095 if (MaxY < 10) {
2096 snprintf(graph_label, sizeof graph_label, "%4.1f",
2097 scaledstep * (double) i);
2098 } else {
2099 snprintf(graph_label, sizeof graph_label, "%4.0f",
2100 scaledstep * (double) i);
2101 }
2102 }
2103 } else {
2104 char sisym = (i == 0 ? ' ' : im->symbol);
2106 if (im->extra_flags & ALTYGRID) {
2107 snprintf(graph_label, sizeof graph_label,
2108 im->ygrid_scale.labfmt,
2109 scaledstep * (double) i, sisym);
2110 } else {
2111 if (MaxY < 10) {
2112 snprintf(graph_label, sizeof graph_label, "%4.1f %c",
2113 scaledstep * (double) i, sisym);
2114 } else {
2115 snprintf(graph_label, sizeof graph_label, "%4.0f %c",
2116 scaledstep * (double) i, sisym);
2117 }
2118 }
2119 }
2120 nlabels++;
2121 if (im->second_axis_scale != 0){
2122 char graph_label_right[100];
2123 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2124 if (im->second_axis_format[0] == '\0'){
2125 if (!second_axis_magfact){
2126 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2127 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2128 }
2129 sval /= second_axis_magfact;
2131 if(MaxY < 10) {
2132 snprintf(graph_label_right, sizeof graph_label_right, "%5.1f %s",sval,second_axis_symb);
2133 } else {
2134 snprintf(graph_label_right, sizeof graph_label_right, "%5.0f %s",sval,second_axis_symb);
2135 }
2136 }
2137 else {
2138 snprintf(graph_label_right, sizeof graph_label_right, im->second_axis_format,sval,"");
2139 }
2140 gfx_text ( im,
2141 X1+7, Y0,
2142 im->graph_col[GRC_FONT],
2143 im->text_prop[TEXT_PROP_AXIS].font_desc,
2144 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2145 graph_label_right );
2146 }
2148 gfx_text(im,
2149 X0 -
2150 im->
2151 text_prop[TEXT_PROP_AXIS].
2152 size, Y0,
2153 im->graph_col[GRC_FONT],
2154 im->
2155 text_prop[TEXT_PROP_AXIS].
2156 font_desc,
2157 im->tabwidth, 0.0,
2158 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2159 gfx_line(im, X0 - 2, Y0, X0, Y0,
2160 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2161 gfx_line(im, X1, Y0, X1 + 2, Y0,
2162 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2163 gfx_dashed_line(im, X0 - 2, Y0,
2164 X1 + 2, Y0,
2165 MGRIDWIDTH,
2166 im->
2167 graph_col
2168 [GRC_MGRID],
2169 im->grid_dash_on, im->grid_dash_off);
2170 } else if (!(im->extra_flags & NOMINOR)) {
2171 gfx_line(im,
2172 X0 - 2, Y0,
2173 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2174 gfx_line(im, X1, Y0, X1 + 2, Y0,
2175 GRIDWIDTH, im->graph_col[GRC_GRID]);
2176 gfx_dashed_line(im, X0 - 1, Y0,
2177 X1 + 1, Y0,
2178 GRIDWIDTH,
2179 im->
2180 graph_col[GRC_GRID],
2181 im->grid_dash_on, im->grid_dash_off);
2182 }
2183 }
2184 }
2185 return 1;
2186 }
2188 /* this is frexp for base 10 */
2189 double frexp10(
2190 double,
2191 double *);
2192 double frexp10(
2193 double x,
2194 double *e)
2195 {
2196 double mnt;
2197 int iexp;
2199 iexp = floor(log((double)fabs(x)) / log((double)10));
2200 mnt = x / pow(10.0, iexp);
2201 if (mnt >= 10.0) {
2202 iexp++;
2203 mnt = x / pow(10.0, iexp);
2204 }
2205 *e = iexp;
2206 return mnt;
2207 }
2210 /* logaritmic horizontal grid */
2211 int horizontal_log_grid(
2212 image_desc_t
2213 *im)
2214 {
2215 double yloglab[][10] = {
2216 {
2217 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2218 0.0, 0.0, 0.0}, {
2219 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2220 0.0, 0.0, 0.0}, {
2221 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2222 0.0, 0.0, 0.0}, {
2223 1.0, 2.0, 4.0,
2224 6.0, 8.0, 10.,
2225 0.0,
2226 0.0, 0.0, 0.0}, {
2227 1.0,
2228 2.0,
2229 3.0,
2230 4.0,
2231 5.0,
2232 6.0,
2233 7.0,
2234 8.0,
2235 9.0,
2236 10.},
2237 {
2238 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2239 };
2240 int i, j, val_exp, min_exp;
2241 double nex; /* number of decades in data */
2242 double logscale; /* scale in logarithmic space */
2243 int exfrac = 1; /* decade spacing */
2244 int mid = -1; /* row in yloglab for major grid */
2245 double mspac; /* smallest major grid spacing (pixels) */
2246 int flab; /* first value in yloglab to use */
2247 double value, tmp, pre_value;
2248 double X0, X1, Y0;
2249 char graph_label[100];
2251 nex = log10(im->maxval / im->minval);
2252 logscale = im->ysize / nex;
2253 /* major spacing for data with high dynamic range */
2254 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2255 if (exfrac == 1)
2256 exfrac = 3;
2257 else
2258 exfrac += 3;
2259 }
2261 /* major spacing for less dynamic data */
2262 do {
2263 /* search best row in yloglab */
2264 mid++;
2265 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2266 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2267 }
2268 while (mspac >
2269 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2270 if (mid)
2271 mid--;
2272 /* find first value in yloglab */
2273 for (flab = 0;
2274 yloglab[mid][flab] < 10
2275 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2276 if (yloglab[mid][flab] == 10.0) {
2277 tmp += 1.0;
2278 flab = 0;
2279 }
2280 val_exp = tmp;
2281 if (val_exp % exfrac)
2282 val_exp += abs(-val_exp % exfrac);
2283 X0 = im->xorigin;
2284 X1 = im->xorigin + im->xsize;
2285 /* draw grid */
2286 pre_value = DNAN;
2287 while (1) {
2289 value = yloglab[mid][flab] * pow(10.0, val_exp);
2290 if (AlmostEqual2sComplement(value, pre_value, 4))
2291 break; /* it seems we are not converging */
2292 pre_value = value;
2293 Y0 = ytr(im, value);
2294 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2295 break;
2296 /* major grid line */
2297 gfx_line(im,
2298 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2299 gfx_line(im, X1, Y0, X1 + 2, Y0,
2300 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2301 gfx_dashed_line(im, X0 - 2, Y0,
2302 X1 + 2, Y0,
2303 MGRIDWIDTH,
2304 im->
2305 graph_col
2306 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2307 /* label */
2308 if (im->extra_flags & FORCE_UNITS_SI) {
2309 int scale;
2310 double pvalue;
2311 char symbol;
2313 scale = floor(val_exp / 3.0);
2314 if (value >= 1.0)
2315 pvalue = pow(10.0, val_exp % 3);
2316 else
2317 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2318 pvalue *= yloglab[mid][flab];
2319 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2320 && ((scale + si_symbcenter) >= 0))
2321 symbol = si_symbol[scale + si_symbcenter];
2322 else
2323 symbol = '?';
2324 snprintf(graph_label, sizeof graph_label, "%3.0f %c", pvalue, symbol);
2325 } else {
2326 snprintf(graph_label, sizeof graph_label, "%3.0e", value);
2327 }
2328 if (im->second_axis_scale != 0){
2329 char graph_label_right[100];
2330 double sval = value*im->second_axis_scale+im->second_axis_shift;
2331 if (im->second_axis_format[0] == '\0'){
2332 if (im->extra_flags & FORCE_UNITS_SI) {
2333 double mfac = 1;
2334 char *symb = "";
2335 auto_scale(im,&sval,&symb,&mfac);
2336 snprintf(graph_label_right, sizeof graph_label_right, "%4.0f %s", sval,symb);
2337 }
2338 else {
2339 snprintf(graph_label_right, sizeof graph_label_right, "%3.0e", sval);
2340 }
2341 }
2342 else {
2343 snprintf(graph_label_right, sizeof graph_label_right, im->second_axis_format,sval,"");
2344 }
2346 gfx_text ( im,
2347 X1+7, Y0,
2348 im->graph_col[GRC_FONT],
2349 im->text_prop[TEXT_PROP_AXIS].font_desc,
2350 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2351 graph_label_right );
2352 }
2354 gfx_text(im,
2355 X0 -
2356 im->
2357 text_prop[TEXT_PROP_AXIS].
2358 size, Y0,
2359 im->graph_col[GRC_FONT],
2360 im->
2361 text_prop[TEXT_PROP_AXIS].
2362 font_desc,
2363 im->tabwidth, 0.0,
2364 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2365 /* minor grid */
2366 if (mid < 4 && exfrac == 1) {
2367 /* find first and last minor line behind current major line
2368 * i is the first line and j tha last */
2369 if (flab == 0) {
2370 min_exp = val_exp - 1;
2371 for (i = 1; yloglab[mid][i] < 10.0; i++);
2372 i = yloglab[mid][i - 1] + 1;
2373 j = 10;
2374 } else {
2375 min_exp = val_exp;
2376 i = yloglab[mid][flab - 1] + 1;
2377 j = yloglab[mid][flab];
2378 }
2380 /* draw minor lines below current major line */
2381 for (; i < j; i++) {
2383 value = i * pow(10.0, min_exp);
2384 if (value < im->minval)
2385 continue;
2386 Y0 = ytr(im, value);
2387 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2388 break;
2389 /* draw lines */
2390 gfx_line(im,
2391 X0 - 2, Y0,
2392 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2393 gfx_line(im, X1, Y0, X1 + 2, Y0,
2394 GRIDWIDTH, im->graph_col[GRC_GRID]);
2395 gfx_dashed_line(im, X0 - 1, Y0,
2396 X1 + 1, Y0,
2397 GRIDWIDTH,
2398 im->
2399 graph_col[GRC_GRID],
2400 im->grid_dash_on, im->grid_dash_off);
2401 }
2402 } else if (exfrac > 1) {
2403 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2404 value = pow(10.0, i);
2405 if (value < im->minval)
2406 continue;
2407 Y0 = ytr(im, value);
2408 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2409 break;
2410 /* draw lines */
2411 gfx_line(im,
2412 X0 - 2, Y0,
2413 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2414 gfx_line(im, X1, Y0, X1 + 2, Y0,
2415 GRIDWIDTH, im->graph_col[GRC_GRID]);
2416 gfx_dashed_line(im, X0 - 1, Y0,
2417 X1 + 1, Y0,
2418 GRIDWIDTH,
2419 im->
2420 graph_col[GRC_GRID],
2421 im->grid_dash_on, im->grid_dash_off);
2422 }
2423 }
2425 /* next decade */
2426 if (yloglab[mid][++flab] == 10.0) {
2427 flab = 0;
2428 val_exp += exfrac;
2429 }
2430 }
2432 /* draw minor lines after highest major line */
2433 if (mid < 4 && exfrac == 1) {
2434 /* find first and last minor line below current major line
2435 * i is the first line and j tha last */
2436 if (flab == 0) {
2437 min_exp = val_exp - 1;
2438 for (i = 1; yloglab[mid][i] < 10.0; i++);
2439 i = yloglab[mid][i - 1] + 1;
2440 j = 10;
2441 } else {
2442 min_exp = val_exp;
2443 i = yloglab[mid][flab - 1] + 1;
2444 j = yloglab[mid][flab];
2445 }
2447 /* draw minor lines below current major line */
2448 for (; i < j; i++) {
2450 value = i * pow(10.0, min_exp);
2451 if (value < im->minval)
2452 continue;
2453 Y0 = ytr(im, value);
2454 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2455 break;
2456 /* draw lines */
2457 gfx_line(im,
2458 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2459 gfx_line(im, X1, Y0, X1 + 2, Y0,
2460 GRIDWIDTH, im->graph_col[GRC_GRID]);
2461 gfx_dashed_line(im, X0 - 1, Y0,
2462 X1 + 1, Y0,
2463 GRIDWIDTH,
2464 im->
2465 graph_col[GRC_GRID],
2466 im->grid_dash_on, im->grid_dash_off);
2467 }
2468 }
2469 /* fancy minor gridlines */
2470 else if (exfrac > 1) {
2471 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2472 value = pow(10.0, i);
2473 if (value < im->minval)
2474 continue;
2475 Y0 = ytr(im, value);
2476 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2477 break;
2478 /* draw lines */
2479 gfx_line(im,
2480 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2481 gfx_line(im, X1, Y0, X1 + 2, Y0,
2482 GRIDWIDTH, im->graph_col[GRC_GRID]);
2483 gfx_dashed_line(im, X0 - 1, Y0,
2484 X1 + 1, Y0,
2485 GRIDWIDTH,
2486 im->
2487 graph_col[GRC_GRID],
2488 im->grid_dash_on, im->grid_dash_off);
2489 }
2490 }
2492 return 1;
2493 }
2496 void vertical_grid(
2497 image_desc_t *im)
2498 {
2499 int xlab_sel; /* which sort of label and grid ? */
2500 time_t ti, tilab, timajor;
2501 long factor;
2502 char graph_label[100];
2503 double X0, Y0, Y1; /* points for filled graph and more */
2504 struct tm tm;
2506 /* the type of time grid is determined by finding
2507 the number of seconds per pixel in the graph */
2508 if (im->xlab_user.minsec == -1) {
2509 factor = (im->end - im->start) / im->xsize;
2510 xlab_sel = 0;
2511 while (xlab[xlab_sel + 1].minsec !=
2512 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2513 xlab_sel++;
2514 } /* pick the last one */
2515 while (xlab[xlab_sel - 1].minsec ==
2516 xlab[xlab_sel].minsec
2517 && xlab[xlab_sel].length > (im->end - im->start)) {
2518 xlab_sel--;
2519 } /* go back to the smallest size */
2520 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2521 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2522 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2523 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2524 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2525 im->xlab_user.labst = xlab[xlab_sel].labst;
2526 im->xlab_user.precis = xlab[xlab_sel].precis;
2527 im->xlab_user.stst = xlab[xlab_sel].stst;
2528 }
2530 /* y coords are the same for every line ... */
2531 Y0 = im->yorigin;
2532 Y1 = im->yorigin - im->ysize;
2533 /* paint the minor grid */
2534 if (!(im->extra_flags & NOMINOR)) {
2535 for (ti = find_first_time(im->start,
2536 im->
2537 xlab_user.
2538 gridtm,
2539 im->
2540 xlab_user.
2541 gridst),
2542 timajor =
2543 find_first_time(im->start,
2544 im->xlab_user.
2545 mgridtm,
2546 im->xlab_user.
2547 mgridst);
2548 ti < im->end && ti != -1;
2549 ti =
2550 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2551 ) {
2552 /* are we inside the graph ? */
2553 if (ti < im->start || ti > im->end)
2554 continue;
2555 while (timajor < ti && timajor != -1) {
2556 timajor = find_next_time(timajor,
2557 im->
2558 xlab_user.
2559 mgridtm, im->xlab_user.mgridst);
2560 }
2561 if (timajor == -1) break; /* fail in case of problems with time increments */
2562 if (ti == timajor)
2563 continue; /* skip as falls on major grid line */
2564 X0 = xtr(im, ti);
2565 gfx_line(im, X0, Y1 - 2, X0, Y1,
2566 GRIDWIDTH, im->graph_col[GRC_GRID]);
2567 gfx_line(im, X0, Y0, X0, Y0 + 2,
2568 GRIDWIDTH, im->graph_col[GRC_GRID]);
2569 gfx_dashed_line(im, X0, Y0 + 1, X0,
2570 Y1 - 1, GRIDWIDTH,
2571 im->
2572 graph_col[GRC_GRID],
2573 im->grid_dash_on, im->grid_dash_off);
2574 }
2575 }
2577 /* paint the major grid */
2578 for (ti = find_first_time(im->start,
2579 im->
2580 xlab_user.
2581 mgridtm,
2582 im->
2583 xlab_user.
2584 mgridst);
2585 ti < im->end && ti != -1;
2586 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2587 ) {
2588 /* are we inside the graph ? */
2589 if (ti < im->start || ti > im->end)
2590 continue;
2591 X0 = xtr(im, ti);
2592 gfx_line(im, X0, Y1 - 2, X0, Y1,
2593 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2594 gfx_line(im, X0, Y0, X0, Y0 + 3,
2595 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2596 gfx_dashed_line(im, X0, Y0 + 3, X0,
2597 Y1 - 2, MGRIDWIDTH,
2598 im->
2599 graph_col
2600 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2601 }
2602 /* paint the labels below the graph */
2603 for (ti =
2604 find_first_time(im->start -
2605 im->xlab_user.
2606 precis / 2,
2607 im->xlab_user.
2608 labtm,
2609 im->xlab_user.
2610 labst);
2611 (ti <=
2612 im->end -
2613 im->xlab_user.precis / 2) && ti != -1;
2614 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2615 ) {
2616 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2617 /* are we inside the graph ? */
2618 if (tilab < im->start || tilab > im->end)
2619 continue;
2620 #if HAVE_STRFTIME
2621 localtime_r(&tilab, &tm);
2622 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2623 #else
2624 # error "your libc has no strftime I guess we'll abort the exercise here."
2625 #endif
2626 gfx_text(im,
2627 xtr(im, tilab),
2628 Y0 + 3,
2629 im->graph_col[GRC_FONT],
2630 im->
2631 text_prop[TEXT_PROP_AXIS].
2632 font_desc,
2633 im->tabwidth, 0.0,
2634 GFX_H_CENTER, GFX_V_TOP, graph_label);
2635 }
2637 }
2640 void axis_paint(
2641 image_desc_t *im)
2642 {
2643 /* draw x and y axis */
2644 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2645 im->xorigin+im->xsize,im->yorigin-im->ysize,
2646 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2648 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2649 im->xorigin+im->xsize,im->yorigin-im->ysize,
2650 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2652 gfx_line(im, im->xorigin - 4,
2653 im->yorigin,
2654 im->xorigin + im->xsize +
2655 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2656 gfx_line(im, im->xorigin,
2657 im->yorigin + 4,
2658 im->xorigin,
2659 im->yorigin - im->ysize -
2660 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2661 /* arrow for X and Y axis direction */
2662 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 */
2663 im->graph_col[GRC_ARROW]);
2664 gfx_close_path(im);
2665 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 */
2666 im->graph_col[GRC_ARROW]);
2667 gfx_close_path(im);
2668 if (im->second_axis_scale != 0){
2669 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2670 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2671 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2672 gfx_new_area ( im,
2673 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2674 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2675 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2676 im->graph_col[GRC_ARROW]);
2677 gfx_close_path(im);
2678 }
2680 }
2682 void grid_paint(
2683 image_desc_t *im)
2684 {
2685 long i;
2686 int res = 0;
2687 double X0, Y0; /* points for filled graph and more */
2688 struct gfx_color_t water_color;
2690 if (im->draw_3d_border > 0) {
2691 /* draw 3d border */
2692 i = im->draw_3d_border;
2693 gfx_new_area(im, 0, im->yimg,
2694 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2695 gfx_add_point(im, im->ximg - i, i);
2696 gfx_add_point(im, im->ximg, 0);
2697 gfx_add_point(im, 0, 0);
2698 gfx_close_path(im);
2699 gfx_new_area(im, i, im->yimg - i,
2700 im->ximg - i,
2701 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2702 gfx_add_point(im, im->ximg, 0);
2703 gfx_add_point(im, im->ximg, im->yimg);
2704 gfx_add_point(im, 0, im->yimg);
2705 gfx_close_path(im);
2706 }
2707 if (im->draw_x_grid == 1)
2708 vertical_grid(im);
2709 if (im->draw_y_grid == 1) {
2710 if (im->logarithmic) {
2711 res = horizontal_log_grid(im);
2712 } else {
2713 res = draw_horizontal_grid(im);
2714 }
2716 /* dont draw horizontal grid if there is no min and max val */
2717 if (!res) {
2718 char *nodata = "No Data found";
2720 gfx_text(im, im->ximg / 2,
2721 (2 * im->yorigin -
2722 im->ysize) / 2,
2723 im->graph_col[GRC_FONT],
2724 im->
2725 text_prop[TEXT_PROP_AXIS].
2726 font_desc,
2727 im->tabwidth, 0.0,
2728 GFX_H_CENTER, GFX_V_CENTER, nodata);
2729 }
2730 }
2732 /* yaxis unit description */
2733 if (im->ylegend[0] != '\0'){
2734 gfx_text(im,
2735 im->xOriginLegendY+10,
2736 im->yOriginLegendY,
2737 im->graph_col[GRC_FONT],
2738 im->
2739 text_prop[TEXT_PROP_UNIT].
2740 font_desc,
2741 im->tabwidth,
2742 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2744 }
2745 if (im->second_axis_legend[0] != '\0'){
2746 gfx_text( im,
2747 im->xOriginLegendY2+10,
2748 im->yOriginLegendY2,
2749 im->graph_col[GRC_FONT],
2750 im->text_prop[TEXT_PROP_UNIT].font_desc,
2751 im->tabwidth,
2752 RRDGRAPH_YLEGEND_ANGLE,
2753 GFX_H_CENTER, GFX_V_CENTER,
2754 im->second_axis_legend);
2755 }
2757 /* graph title */
2758 gfx_text(im,
2759 im->xOriginTitle, im->yOriginTitle+6,
2760 im->graph_col[GRC_FONT],
2761 im->
2762 text_prop[TEXT_PROP_TITLE].
2763 font_desc,
2764 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2765 /* rrdtool 'logo' */
2766 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2767 water_color = im->graph_col[GRC_FONT];
2768 water_color.alpha = 0.3;
2769 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2770 gfx_text(im, xpos, 5,
2771 water_color,
2772 im->
2773 text_prop[TEXT_PROP_WATERMARK].
2774 font_desc, im->tabwidth,
2775 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2776 }
2777 /* graph watermark */
2778 if (im->watermark[0] != '\0') {
2779 water_color = im->graph_col[GRC_FONT];
2780 water_color.alpha = 0.3;
2781 gfx_text(im,
2782 im->ximg / 2, im->yimg - 6,
2783 water_color,
2784 im->
2785 text_prop[TEXT_PROP_WATERMARK].
2786 font_desc, im->tabwidth, 0,
2787 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2788 }
2790 /* graph labels */
2791 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2792 for (i = 0; i < im->gdes_c; i++) {
2793 if (im->gdes[i].legend[0] == '\0')
2794 continue;
2795 /* im->gdes[i].leg_y is the bottom of the legend */
2796 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2797 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2798 gfx_text(im, X0, Y0,
2799 im->graph_col[GRC_FONT],
2800 im->
2801 text_prop
2802 [TEXT_PROP_LEGEND].font_desc,
2803 im->tabwidth, 0.0,
2804 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2805 /* The legend for GRAPH items starts with "M " to have
2806 enough space for the box */
2807 if (im->gdes[i].gf != GF_PRINT &&
2808 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2809 double boxH, boxV;
2810 double X1, Y1;
2812 boxH = gfx_get_text_width(im, 0,
2813 im->
2814 text_prop
2815 [TEXT_PROP_LEGEND].
2816 font_desc,
2817 im->tabwidth, "o") * 1.2;
2818 boxV = boxH;
2819 /* shift the box up a bit */
2820 Y0 -= boxV * 0.4;
2822 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2823 cairo_save(im->cr);
2824 cairo_new_path(im->cr);
2825 cairo_set_line_width(im->cr, 1.0);
2826 gfx_line(im,
2827 X0, Y0 - boxV / 2,
2828 X0 + boxH, Y0 - boxV / 2,
2829 1.0, im->gdes[i].col);
2830 gfx_close_path(im);
2831 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2832 cairo_save(im->cr);
2833 cairo_new_path(im->cr);
2834 cairo_set_line_width(im->cr, 1.0);
2835 gfx_line(im,
2836 X0 + boxH / 2, Y0,
2837 X0 + boxH / 2, Y0 - boxV,
2838 1.0, im->gdes[i].col);
2839 gfx_close_path(im);
2840 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2841 cairo_save(im->cr);
2842 cairo_new_path(im->cr);
2843 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2844 gfx_line(im,
2845 X0, Y0,
2846 X0 + boxH, Y0 - boxV,
2847 im->gdes[i].linewidth, im->gdes[i].col);
2848 gfx_close_path(im);
2849 } else {
2850 /* make sure transparent colors show up the same way as in the graph */
2851 gfx_new_area(im,
2852 X0, Y0 - boxV,
2853 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2854 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2855 gfx_close_path(im);
2856 gfx_new_area(im, X0, Y0 - boxV, X0,
2857 Y0, X0 + boxH, Y0, im->gdes[i].col);
2858 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2859 gfx_close_path(im);
2860 cairo_save(im->cr);
2861 cairo_new_path(im->cr);
2862 cairo_set_line_width(im->cr, 1.0);
2863 X1 = X0 + boxH;
2864 Y1 = Y0 - boxV;
2865 gfx_line_fit(im, &X0, &Y0);
2866 gfx_line_fit(im, &X1, &Y1);
2867 cairo_move_to(im->cr, X0, Y0);
2868 cairo_line_to(im->cr, X1, Y0);
2869 cairo_line_to(im->cr, X1, Y1);
2870 cairo_line_to(im->cr, X0, Y1);
2871 cairo_close_path(im->cr);
2872 cairo_set_source_rgba(im->cr,
2873 im->graph_col[GRC_FRAME].red,
2874 im->graph_col[GRC_FRAME].green,
2875 im->graph_col[GRC_FRAME].blue,
2876 im->graph_col[GRC_FRAME].alpha);
2877 }
2878 if (im->gdes[i].dash) {
2879 /* make box borders in legend dashed if the graph is dashed */
2880 double dashes[] = {
2881 3.0
2882 };
2883 cairo_set_dash(im->cr, dashes, 1, 0.0);
2884 }
2885 cairo_stroke(im->cr);
2886 cairo_restore(im->cr);
2887 }
2888 }
2889 }
2890 }
2893 /*****************************************************
2894 * lazy check make sure we rely need to create this graph
2895 *****************************************************/
2897 int lazy_check(
2898 image_desc_t *im)
2899 {
2900 FILE *fd = NULL;
2901 int size = 1;
2902 struct stat imgstat;
2904 if (im->lazy == 0)
2905 return 0; /* no lazy option */
2906 if (strlen(im->graphfile) == 0)
2907 return 0; /* inmemory option */
2908 if (stat(im->graphfile, &imgstat) != 0)
2909 return 0; /* can't stat */
2910 /* one pixel in the existing graph is more then what we would
2911 change here ... */
2912 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2913 return 0;
2914 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2915 return 0; /* the file does not exist */
2916 switch (im->imgformat) {
2917 case IF_PNG:
2918 size = PngSize(fd, &(im->ximg), &(im->yimg));
2919 break;
2920 default:
2921 size = 1;
2922 }
2923 fclose(fd);
2924 return size;
2925 }
2928 int graph_size_location(
2929 image_desc_t
2930 *im,
2931 int elements)
2932 {
2933 /* The actual size of the image to draw is determined from
2934 ** several sources. The size given on the command line is
2935 ** the graph area but we need more as we have to draw labels
2936 ** and other things outside the graph area. If the option
2937 ** --full-size-mode is selected the size defines the total
2938 ** image size and the size available for the graph is
2939 ** calculated.
2940 */
2942 /** +---+-----------------------------------+
2943 ** | y |...............graph title.........|
2944 ** | +---+-------------------------------+
2945 ** | a | y | |
2946 ** | x | | |
2947 ** | i | a | |
2948 ** | s | x | main graph area |
2949 ** | | i | |
2950 ** | t | s | |
2951 ** | i | | |
2952 ** | t | l | |
2953 ** | l | b +-------------------------------+
2954 ** | e | l | x axis labels |
2955 ** +---+---+-------------------------------+
2956 ** |....................legends............|
2957 ** +---------------------------------------+
2958 ** | watermark |
2959 ** +---------------------------------------+
2960 */
2962 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2963 0, Xylabel = 0, Xmain = 0, Ymain =
2964 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2966 // no legends and no the shall be plotted it's easy
2967 if (im->extra_flags & ONLY_GRAPH) {
2968 im->xorigin = 0;
2969 im->ximg = im->xsize;
2970 im->yimg = im->ysize;
2971 im->yorigin = im->ysize;
2972 xtr(im, 0);
2973 ytr(im, DNAN);
2974 return 0;
2975 }
2977 if(im->watermark[0] != '\0') {
2978 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2979 }
2981 // calculate the width of the left vertical legend
2982 if (im->ylegend[0] != '\0') {
2983 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2984 }
2986 // calculate the width of the right vertical legend
2987 if (im->second_axis_legend[0] != '\0') {
2988 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2989 }
2990 else{
2991 Xvertical2 = Xspacing;
2992 }
2994 if (im->title[0] != '\0') {
2995 /* The title is placed "inbetween" two text lines so it
2996 ** automatically has some vertical spacing. The horizontal
2997 ** spacing is added here, on each side.
2998 */
2999 /* if necessary, reduce the font size of the title until it fits the image width */
3000 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
3001 }
3002 else{
3003 // we have no title; get a little clearing from the top
3004 Ytitle = Yspacing;
3005 }
3007 if (elements) {
3008 if (im->draw_x_grid) {
3009 // calculate the height of the horizontal labelling
3010 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
3011 }
3012 if (im->draw_y_grid || im->forceleftspace) {
3013 // calculate the width of the vertical labelling
3014 Xylabel =
3015 gfx_get_text_width(im, 0,
3016 im->text_prop[TEXT_PROP_AXIS].font_desc,
3017 im->tabwidth, "0") * im->unitslength;
3018 }
3019 }
3021 // add some space to the labelling
3022 Xylabel += Xspacing;
3024 /* If the legend is printed besides the graph the width has to be
3025 ** calculated first. Placing the legend north or south of the
3026 ** graph requires the width calculation first, so the legend is
3027 ** skipped for the moment.
3028 */
3029 im->legendheight = 0;
3030 im->legendwidth = 0;
3031 if (!(im->extra_flags & NOLEGEND)) {
3032 if(im->legendposition == WEST || im->legendposition == EAST){
3033 if (leg_place(im, 1) == -1){
3034 return -1;
3035 }
3036 }
3037 }
3039 if (im->extra_flags & FULL_SIZE_MODE) {
3041 /* The actual size of the image to draw has been determined by the user.
3042 ** The graph area is the space remaining after accounting for the legend,
3043 ** the watermark, the axis labels, and the title.
3044 */
3045 im->ximg = im->xsize;
3046 im->yimg = im->ysize;
3047 Xmain = im->ximg;
3048 Ymain = im->yimg;
3050 /* Now calculate the total size. Insert some spacing where
3051 desired. im->xorigin and im->yorigin need to correspond
3052 with the lower left corner of the main graph area or, if
3053 this one is not set, the imaginary box surrounding the
3054 pie chart area. */
3055 /* Initial size calculation for the main graph area */
3057 Xmain -= Xylabel;// + Xspacing;
3058 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3059 Xmain -= im->legendwidth;// + Xspacing;
3060 }
3061 if (im->second_axis_scale != 0){
3062 Xmain -= Xylabel;
3063 }
3064 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3065 Xmain -= Xspacing;
3066 }
3068 Xmain -= Xvertical + Xvertical2;
3070 /* limit the remaining space to 0 */
3071 if(Xmain < 1){
3072 Xmain = 1;
3073 }
3074 im->xsize = Xmain;
3076 /* Putting the legend north or south, the height can now be calculated */
3077 if (!(im->extra_flags & NOLEGEND)) {
3078 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3079 im->legendwidth = im->ximg;
3080 if (leg_place(im, 0) == -1){
3081 return -1;
3082 }
3083 }
3084 }
3086 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3087 Ymain -= Yxlabel + im->legendheight;
3088 }
3089 else{
3090 Ymain -= Yxlabel;
3091 }
3093 /* reserve space for the title *or* some padding above the graph */
3094 Ymain -= Ytitle;
3096 /* reserve space for padding below the graph */
3097 if (im->extra_flags & NOLEGEND) {
3098 Ymain -= 0.5*Yspacing;
3099 }
3101 if (im->watermark[0] != '\0') {
3102 Ymain -= Ywatermark;
3103 }
3104 /* limit the remaining height to 0 */
3105 if(Ymain < 1){
3106 Ymain = 1;
3107 }
3108 im->ysize = Ymain;
3109 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3111 /* The actual size of the image to draw is determined from
3112 ** several sources. The size given on the command line is
3113 ** the graph area but we need more as we have to draw labels
3114 ** and other things outside the graph area.
3115 */
3117 if (elements) {
3118 Xmain = im->xsize; // + Xspacing;
3119 Ymain = im->ysize;
3120 }
3122 im->ximg = Xmain + Xylabel;
3123 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3124 im->ximg += Xspacing;
3125 }
3127 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3128 im->ximg += im->legendwidth;// + Xspacing;
3129 }
3130 if (im->second_axis_scale != 0){
3131 im->ximg += Xylabel;
3132 }
3134 im->ximg += Xvertical + Xvertical2;
3136 if (!(im->extra_flags & NOLEGEND)) {
3137 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3138 im->legendwidth = im->ximg;
3139 if (leg_place(im, 0) == -1){
3140 return -1;
3141 }
3142 }
3143 }
3145 im->yimg = Ymain + Yxlabel;
3146 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3147 im->yimg += im->legendheight;
3148 }
3150 /* reserve space for the title *or* some padding above the graph */
3151 if (Ytitle) {
3152 im->yimg += Ytitle;
3153 } else {
3154 im->yimg += 1.5 * Yspacing;
3155 }
3156 /* reserve space for padding below the graph */
3157 if (im->extra_flags & NOLEGEND) {
3158 im->yimg += 0.5*Yspacing;
3159 }
3161 if (im->watermark[0] != '\0') {
3162 im->yimg += Ywatermark;
3163 }
3164 }
3167 /* In case of putting the legend in west or east position the first
3168 ** legend calculation might lead to wrong positions if some items
3169 ** are not aligned on the left hand side (e.g. centered) as the
3170 ** legendwidth wight have been increased after the item was placed.
3171 ** In this case the positions have to be recalculated.
3172 */
3173 if (!(im->extra_flags & NOLEGEND)) {
3174 if(im->legendposition == WEST || im->legendposition == EAST){
3175 if (leg_place(im, 0) == -1){
3176 return -1;
3177 }
3178 }
3179 }
3181 /* After calculating all dimensions
3182 ** it is now possible to calculate
3183 ** all offsets.
3184 */
3185 switch(im->legendposition){
3186 case NORTH:
3187 im->xOriginTitle = (im->ximg / 2);
3188 im->yOriginTitle = 0;
3190 im->xOriginLegend = 0;
3191 im->yOriginLegend = Ytitle;
3193 im->xOriginLegendY = 0;
3194 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3196 im->xorigin = Xvertical + Xylabel;
3197 im->yorigin = Ytitle + im->legendheight + Ymain;
3199 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3200 if (im->second_axis_scale != 0){
3201 im->xOriginLegendY2 += Xylabel;
3202 }
3203 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3205 break;
3207 case WEST:
3208 im->xOriginTitle = im->legendwidth + im->xsize / 2;
3209 im->yOriginTitle = 0;
3211 im->xOriginLegend = 0;
3212 im->yOriginLegend = Ytitle;
3214 im->xOriginLegendY = im->legendwidth;
3215 im->yOriginLegendY = Ytitle + (Ymain / 2);
3217 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3218 im->yorigin = Ytitle + Ymain;
3220 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3221 if (im->second_axis_scale != 0){
3222 im->xOriginLegendY2 += Xylabel;
3223 }
3224 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3226 break;
3228 case SOUTH:
3229 im->xOriginTitle = im->ximg / 2;
3230 im->yOriginTitle = 0;
3232 im->xOriginLegend = 0;
3233 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3235 im->xOriginLegendY = 0;
3236 im->yOriginLegendY = Ytitle + (Ymain / 2);
3238 im->xorigin = Xvertical + Xylabel;
3239 im->yorigin = Ytitle + Ymain;
3241 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3242 if (im->second_axis_scale != 0){
3243 im->xOriginLegendY2 += Xylabel;
3244 }
3245 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3247 break;
3249 case EAST:
3250 im->xOriginTitle = im->xsize / 2;
3251 im->yOriginTitle = 0;
3253 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3254 if (im->second_axis_scale != 0){
3255 im->xOriginLegend += Xylabel;
3256 }
3257 im->yOriginLegend = Ytitle;
3259 im->xOriginLegendY = 0;
3260 im->yOriginLegendY = Ytitle + (Ymain / 2);
3262 im->xorigin = Xvertical + Xylabel;
3263 im->yorigin = Ytitle + Ymain;
3265 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3266 if (im->second_axis_scale != 0){
3267 im->xOriginLegendY2 += Xylabel;
3268 }
3269 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3271 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3272 im->xOriginTitle += Xspacing;
3273 im->xOriginLegend += Xspacing;
3274 im->xOriginLegendY += Xspacing;
3275 im->xorigin += Xspacing;
3276 im->xOriginLegendY2 += Xspacing;
3277 }
3278 break;
3279 }
3281 xtr(im, 0);
3282 ytr(im, DNAN);
3283 return 0;
3284 }
3286 static cairo_status_t cairo_output(
3287 void *closure,
3288 const unsigned char
3289 *data,
3290 unsigned int length)
3291 {
3292 image_desc_t *im = (image_desc_t*)closure;
3294 im->rendered_image =
3295 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3296 if (im->rendered_image == NULL)
3297 return CAIRO_STATUS_WRITE_ERROR;
3298 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3299 im->rendered_image_size += length;
3300 return CAIRO_STATUS_SUCCESS;
3301 }
3303 /* draw that picture thing ... */
3304 int graph_paint(
3305 image_desc_t *im)
3306 {
3307 int lazy = lazy_check(im);
3308 int cnt;
3310 /* imgformat XML or higher dispatch to xport
3311 * output format there is selected via graph_type
3312 */
3313 if (im->imgformat >= IF_XML) {
3314 return rrd_graph_xport(im);
3315 }
3317 /* pull the data from the rrd files ... */
3318 if (data_fetch(im) == -1)
3319 return -1;
3320 /* evaluate VDEF and CDEF operations ... */
3321 if (data_calc(im) == -1)
3322 return -1;
3323 /* calculate and PRINT and GPRINT definitions. We have to do it at
3324 * this point because it will affect the length of the legends
3325 * if there are no graph elements (i==0) we stop here ...
3326 * if we are lazy, try to quit ...
3327 */
3328 cnt = print_calc(im);
3329 if (cnt < 0)
3330 return -1;
3332 /* if we want and can be lazy ... quit now */
3333 if (cnt == 0)
3334 return 0;
3336 /* otherwise call graph_paint_timestring */
3337 switch (im->graph_type) {
3338 case GTYPE_TIME:
3339 return graph_paint_timestring(im,lazy,cnt);
3340 break;
3341 case GTYPE_XY:
3342 return graph_paint_xy(im,lazy,cnt);
3343 break;
3344 }
3345 /* final return with error*/
3346 rrd_set_error("Graph type %i is not implemented",im->graph_type);
3347 return -1;
3348 }
3350 int graph_paint_timestring(
3351 image_desc_t *im, int lazy, int cnt)
3352 {
3353 int i,ii;
3354 double areazero = 0.0;
3355 graph_desc_t *lastgdes = NULL;
3356 rrd_infoval_t info;
3358 /**************************************************************
3359 *** Calculating sizes and locations became a bit confusing ***
3360 *** so I moved this into a separate function. ***
3361 **************************************************************/
3362 if (graph_size_location(im, cnt) == -1)
3363 return -1;
3365 info.u_cnt = im->xorigin;
3366 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3367 info.u_cnt = im->yorigin - im->ysize;
3368 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3369 info.u_cnt = im->xsize;
3370 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3371 info.u_cnt = im->ysize;
3372 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3373 info.u_cnt = im->ximg;
3374 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3375 info.u_cnt = im->yimg;
3376 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3377 info.u_cnt = im->start;
3378 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3379 info.u_cnt = im->end;
3380 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3382 /* if we want and can be lazy ... quit now */
3383 if (lazy)
3384 return 0;
3386 /* get actual drawing data and find min and max values */
3387 if (data_proc(im) == -1)
3388 return -1;
3389 if (!im->logarithmic) {
3390 si_unit(im);
3391 }
3393 /* identify si magnitude Kilo, Mega Giga ? */
3394 if (!im->rigid && !im->logarithmic)
3395 expand_range(im); /* make sure the upper and lower limit are
3396 sensible values */
3398 info.u_val = im->minval;
3399 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3400 info.u_val = im->maxval;
3401 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3404 if (!calc_horizontal_grid(im))
3405 return -1;
3406 /* reset precalc */
3407 ytr(im, DNAN);
3408 /* if (im->gridfit)
3409 apply_gridfit(im); */
3411 /* set up cairo */
3412 if (graph_cairo_setup(im)) { return -1; }
3414 /* other stuff */
3415 if (im->minval > 0.0)
3416 areazero = im->minval;
3417 if (im->maxval < 0.0)
3418 areazero = im->maxval;
3419 for (i = 0; i < im->gdes_c; i++) {
3420 switch (im->gdes[i].gf) {
3421 case GF_CDEF:
3422 case GF_VDEF:
3423 case GF_DEF:
3424 case GF_PRINT:
3425 case GF_GPRINT:
3426 case GF_COMMENT:
3427 case GF_TEXTALIGN:
3428 case GF_HRULE:
3429 case GF_VRULE:
3430 case GF_XPORT:
3431 case GF_SHIFT:
3432 case GF_XAXIS:
3433 case GF_YAXIS:
3434 break;
3435 case GF_TICK:
3436 for (ii = 0; ii < im->xsize; ii++) {
3437 if (!isnan(im->gdes[i].p_data[ii])
3438 && im->gdes[i].p_data[ii] != 0.0) {
3439 if (im->gdes[i].yrule > 0) {
3440 gfx_line(im,
3441 im->xorigin + ii,
3442 im->yorigin + 1.0,
3443 im->xorigin + ii,
3444 im->yorigin -
3445 im->gdes[i].yrule *
3446 im->ysize, 1.0, im->gdes[i].col);
3447 } else if (im->gdes[i].yrule < 0) {
3448 gfx_line(im,
3449 im->xorigin + ii,
3450 im->yorigin - im->ysize - 1.0,
3451 im->xorigin + ii,
3452 im->yorigin - im->ysize -
3453 im->gdes[i].
3454 yrule *
3455 im->ysize, 1.0, im->gdes[i].col);
3456 }
3457 }
3458 }
3459 break;
3460 case GF_LINE:
3461 case GF_AREA:
3462 case GF_GRAD: {
3463 rrd_value_t diffval = im->maxval - im->minval;
3464 rrd_value_t maxlimit = im->maxval + 9 * diffval;
3465 rrd_value_t minlimit = im->minval - 9 * diffval;
3466 for (ii = 0; ii < im->xsize; ii++) {
3467 /* fix data points at oo and -oo */
3468 if (isinf(im->gdes[i].p_data[ii])) {
3469 if (im->gdes[i].p_data[ii] > 0) {
3470 im->gdes[i].p_data[ii] = im->maxval;
3471 } else {
3472 im->gdes[i].p_data[ii] = im->minval;
3473 }
3474 }
3475 /* some versions of cairo go unstable when trying
3476 to draw way out of the canvas ... lets not even try */
3477 if (im->gdes[i].p_data[ii] > maxlimit) {
3478 im->gdes[i].p_data[ii] = maxlimit;
3479 }
3480 if (im->gdes[i].p_data[ii] < minlimit) {
3481 im->gdes[i].p_data[ii] = minlimit;
3482 }
3483 } /* for */
3485 /* *******************************************************
3486 a ___. (a,t)
3487 | | ___
3488 ____| | | |
3489 | |___|
3490 -------|--t-1--t--------------------------------
3492 if we know the value at time t was a then
3493 we draw a square from t-1 to t with the value a.
3495 ********************************************************* */
3496 if (im->gdes[i].col.alpha != 0.0) {
3497 /* GF_LINE and friend */
3498 if (im->gdes[i].gf == GF_LINE) {
3499 double last_y = 0.0;
3500 int draw_on = 0;
3502 cairo_save(im->cr);
3503 cairo_new_path(im->cr);
3504 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3505 if (im->gdes[i].dash) {
3506 cairo_set_dash(im->cr,
3507 im->gdes[i].p_dashes,
3508 im->gdes[i].ndash, im->gdes[i].offset);
3509 }
3511 for (ii = 1; ii < im->xsize; ii++) {
3512 if (isnan(im->gdes[i].p_data[ii])
3513 || (im->slopemode == 1
3514 && isnan(im->gdes[i].p_data[ii - 1]))) {
3515 draw_on = 0;
3516 continue;
3517 }
3518 if (draw_on == 0) {
3519 last_y = ytr(im, im->gdes[i].p_data[ii]);
3520 if (im->slopemode == 0) {
3521 double x = ii - 1 + im->xorigin;
3522 double y = last_y;
3524 gfx_line_fit(im, &x, &y);
3525 cairo_move_to(im->cr, x, y);
3526 x = ii + im->xorigin;
3527 y = last_y;
3528 gfx_line_fit(im, &x, &y);
3529 cairo_line_to(im->cr, x, y);
3530 } else {
3531 double x = ii - 1 + im->xorigin;
3532 double y =
3533 ytr(im, im->gdes[i].p_data[ii - 1]);
3534 gfx_line_fit(im, &x, &y);
3535 cairo_move_to(im->cr, x, y);
3536 x = ii + im->xorigin;
3537 y = last_y;
3538 gfx_line_fit(im, &x, &y);
3539 cairo_line_to(im->cr, x, y);
3540 }
3541 draw_on = 1;
3542 } else {
3543 double x1 = ii + im->xorigin;
3544 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3546 if (im->slopemode == 0
3547 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3548 double x = ii - 1 + im->xorigin;
3549 double y = y1;
3551 gfx_line_fit(im, &x, &y);
3552 cairo_line_to(im->cr, x, y);
3553 };
3554 last_y = y1;
3555 gfx_line_fit(im, &x1, &y1);
3556 cairo_line_to(im->cr, x1, y1);
3557 };
3558 }
3559 cairo_set_source_rgba(im->cr,
3560 im->gdes[i].
3561 col.red,
3562 im->gdes[i].
3563 col.green,
3564 im->gdes[i].
3565 col.blue, im->gdes[i].col.alpha);
3566 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3567 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3568 cairo_stroke(im->cr);
3569 cairo_restore(im->cr);
3570 } else {
3571 double lastx=0;
3572 double lasty=0;
3573 int idxI = -1;
3574 double *foreY =
3575 (double *) malloc(sizeof(double) * im->xsize * 2);
3576 double *foreX =
3577 (double *) malloc(sizeof(double) * im->xsize * 2);
3578 double *backY =
3579 (double *) malloc(sizeof(double) * im->xsize * 2);
3580 double *backX =
3581 (double *) malloc(sizeof(double) * im->xsize * 2);
3582 int drawem = 0;
3584 for (ii = 0; ii <= im->xsize; ii++) {
3585 double ybase, ytop;
3587 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3588 int cntI = 1;
3589 int lastI = 0;
3591 while (cntI < idxI
3592 &&
3593 AlmostEqual2sComplement(foreY
3594 [lastI],
3595 foreY[cntI], 4)
3596 &&
3597 AlmostEqual2sComplement(foreY
3598 [lastI],
3599 foreY
3600 [cntI + 1], 4)) {
3601 cntI++;
3602 }
3603 if (im->gdes[i].gf != GF_GRAD) {
3604 gfx_new_area(im,
3605 backX[0], backY[0],
3606 foreX[0], foreY[0],
3607 foreX[cntI],
3608 foreY[cntI], im->gdes[i].col);
3609 } else {
3610 lastx = foreX[cntI];
3611 lasty = foreY[cntI];
3612 }
3613 while (cntI < idxI) {
3614 lastI = cntI;
3615 cntI++;
3616 while (cntI < idxI
3617 &&
3618 AlmostEqual2sComplement(foreY
3619 [lastI],
3620 foreY[cntI], 4)
3621 &&
3622 AlmostEqual2sComplement(foreY
3623 [lastI],
3624 foreY
3625 [cntI
3626 + 1], 4)) {
3627 cntI++;
3628 }
3629 if (im->gdes[i].gf != GF_GRAD) {
3630 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3631 } else {
3632 gfx_add_rect_fadey(im,
3633 lastx, foreY[0],
3634 foreX[cntI], foreY[cntI], lasty,
3635 im->gdes[i].col,
3636 im->gdes[i].col2,
3637 im->gdes[i].gradheight
3638 );
3639 lastx = foreX[cntI];
3640 lasty = foreY[cntI];
3641 }
3642 }
3643 if (im->gdes[i].gf != GF_GRAD) {
3644 gfx_add_point(im, backX[idxI], backY[idxI]);
3645 } else {
3646 gfx_add_rect_fadey(im,
3647 lastx, foreY[0],
3648 backX[idxI], backY[idxI], lasty,
3649 im->gdes[i].col,
3650 im->gdes[i].col2,
3651 im->gdes[i].gradheight);
3652 lastx = backX[idxI];
3653 lasty = backY[idxI];
3654 }
3655 while (idxI > 1) {
3656 lastI = idxI;
3657 idxI--;
3658 while (idxI > 1
3659 &&
3660 AlmostEqual2sComplement(backY
3661 [lastI],
3662 backY[idxI], 4)
3663 &&
3664 AlmostEqual2sComplement(backY
3665 [lastI],
3666 backY
3667 [idxI
3668 - 1], 4)) {
3669 idxI--;
3670 }
3671 if (im->gdes[i].gf != GF_GRAD) {
3672 gfx_add_point(im, backX[idxI], backY[idxI]);
3673 } else {
3674 gfx_add_rect_fadey(im,
3675 lastx, foreY[0],
3676 backX[idxI], backY[idxI], lasty,
3677 im->gdes[i].col,
3678 im->gdes[i].col2,
3679 im->gdes[i].gradheight);
3680 lastx = backX[idxI];
3681 lasty = backY[idxI];
3682 }
3683 }
3684 idxI = -1;
3685 drawem = 0;
3686 if (im->gdes[i].gf != GF_GRAD)
3687 gfx_close_path(im);
3688 }
3689 if (drawem != 0) {
3690 drawem = 0;
3691 idxI = -1;
3692 }
3693 if (ii == im->xsize)
3694 break;
3695 if (im->slopemode == 0 && ii == 0) {
3696 continue;
3697 }
3698 if (isnan(im->gdes[i].p_data[ii])) {
3699 drawem = 1;
3700 continue;
3701 }
3702 ytop = ytr(im, im->gdes[i].p_data[ii]);
3703 if (lastgdes && im->gdes[i].stack) {
3704 ybase = ytr(im, lastgdes->p_data[ii]);
3705 } else {
3706 ybase = ytr(im, areazero);
3707 }
3708 if (ybase == ytop) {
3709 drawem = 1;
3710 continue;
3711 }
3713 if (ybase > ytop) {
3714 double extra = ytop;
3716 ytop = ybase;
3717 ybase = extra;
3718 }
3719 if (im->slopemode == 0) {
3720 backY[++idxI] = ybase - 0.2;
3721 backX[idxI] = ii + im->xorigin - 1;
3722 foreY[idxI] = ytop + 0.2;
3723 foreX[idxI] = ii + im->xorigin - 1;
3724 }
3725 backY[++idxI] = ybase - 0.2;
3726 backX[idxI] = ii + im->xorigin;
3727 foreY[idxI] = ytop + 0.2;
3728 foreX[idxI] = ii + im->xorigin;
3729 }
3730 /* close up any remaining area */
3731 free(foreY);
3732 free(foreX);
3733 free(backY);
3734 free(backX);
3735 } /* else GF_LINE */
3736 }
3737 /* if color != 0x0 */
3738 /* make sure we do not run into trouble when stacking on NaN */
3739 for (ii = 0; ii < im->xsize; ii++) {
3740 if (isnan(im->gdes[i].p_data[ii])) {
3741 if (lastgdes && (im->gdes[i].stack)) {
3742 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3743 } else {
3744 im->gdes[i].p_data[ii] = areazero;
3745 }
3746 }
3747 }
3748 lastgdes = &(im->gdes[i]);
3749 break;
3750 } /* GF_AREA, GF_LINE, GF_GRAD */
3751 case GF_STACK:
3752 rrd_set_error
3753 ("STACK should already be turned into LINE or AREA here");
3754 return -1;
3755 break;
3756 } /* switch */
3757 }
3758 cairo_reset_clip(im->cr);
3760 /* grid_paint also does the text */
3761 if (!(im->extra_flags & ONLY_GRAPH))
3762 grid_paint(im);
3763 if (!(im->extra_flags & ONLY_GRAPH))
3764 axis_paint(im);
3765 /* the RULES are the last thing to paint ... */
3766 for (i = 0; i < im->gdes_c; i++) {
3768 switch (im->gdes[i].gf) {
3769 case GF_HRULE:
3770 if (im->gdes[i].yrule >= im->minval
3771 && im->gdes[i].yrule <= im->maxval) {
3772 cairo_save(im->cr);
3773 if (im->gdes[i].dash) {
3774 cairo_set_dash(im->cr,
3775 im->gdes[i].p_dashes,
3776 im->gdes[i].ndash, im->gdes[i].offset);
3777 }
3778 gfx_line(im, im->xorigin,
3779 ytr(im, im->gdes[i].yrule),
3780 im->xorigin + im->xsize,
3781 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3782 cairo_stroke(im->cr);
3783 cairo_restore(im->cr);
3784 }
3785 break;
3786 case GF_VRULE:
3787 if (im->gdes[i].xrule >= im->start
3788 && im->gdes[i].xrule <= im->end) {
3789 cairo_save(im->cr);
3790 if (im->gdes[i].dash) {
3791 cairo_set_dash(im->cr,
3792 im->gdes[i].p_dashes,
3793 im->gdes[i].ndash, im->gdes[i].offset);
3794 }
3795 gfx_line(im,
3796 xtr(im, im->gdes[i].xrule),
3797 im->yorigin, xtr(im,
3798 im->
3799 gdes[i].
3800 xrule),
3801 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3802 cairo_stroke(im->cr);
3803 cairo_restore(im->cr);
3804 }
3805 break;
3806 default:
3807 break;
3808 }
3809 }
3810 /* close the graph via cairo*/
3811 return graph_cairo_finish(im);
3812 }
3814 int graph_cairo_setup (image_desc_t *im)
3815 {
3816 /* the actual graph is created by going through the individual
3817 graph elements and then drawing them */
3818 cairo_surface_destroy(im->surface);
3819 switch (im->imgformat) {
3820 case IF_PNG:
3821 im->surface =
3822 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3823 im->ximg * im->zoom,
3824 im->yimg * im->zoom);
3825 break;
3826 case IF_PDF:
3827 im->gridfit = 0;
3828 im->surface = strlen(im->graphfile)
3829 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3830 im->yimg * im->zoom)
3831 : cairo_pdf_surface_create_for_stream
3832 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3833 break;
3834 case IF_EPS:
3835 im->gridfit = 0;
3836 im->surface = strlen(im->graphfile)
3837 ?
3838 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3839 im->yimg * im->zoom)
3840 : cairo_ps_surface_create_for_stream
3841 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3842 break;
3843 case IF_SVG:
3844 im->gridfit = 0;
3845 im->surface = strlen(im->graphfile)
3846 ?
3847 cairo_svg_surface_create(im->
3848 graphfile,
3849 im->ximg * im->zoom, im->yimg * im->zoom)
3850 : cairo_svg_surface_create_for_stream
3851 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3852 cairo_svg_surface_restrict_to_version
3853 (im->surface, CAIRO_SVG_VERSION_1_1);
3854 break;
3855 case IF_XML:
3856 case IF_XMLENUM:
3857 case IF_CSV:
3858 case IF_TSV:
3859 case IF_SSV:
3860 case IF_JSON:
3861 case IF_JSONTIME:
3862 break;
3863 };
3864 cairo_destroy(im->cr);
3865 im->cr = cairo_create(im->surface);
3866 cairo_set_antialias(im->cr, im->graph_antialias);
3867 cairo_scale(im->cr, im->zoom, im->zoom);
3868 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3869 gfx_new_area(im, 0, 0, 0, im->yimg,
3870 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3871 gfx_add_point(im, im->ximg, 0);
3872 gfx_close_path(im);
3873 gfx_new_area(im, im->xorigin,
3874 im->yorigin,
3875 im->xorigin +
3876 im->xsize, im->yorigin,
3877 im->xorigin +
3878 im->xsize,
3879 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3880 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3881 gfx_close_path(im);
3882 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3883 im->xsize, im->ysize + 2.0);
3884 cairo_clip(im->cr);
3885 return 0;
3886 }
3888 int graph_cairo_finish (image_desc_t *im)
3889 {
3891 switch (im->imgformat) {
3892 case IF_PNG:
3893 {
3894 cairo_status_t status;
3896 status = strlen(im->graphfile) ?
3897 cairo_surface_write_to_png(im->surface, im->graphfile)
3898 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3899 im);
3901 if (status != CAIRO_STATUS_SUCCESS) {
3902 rrd_set_error("Could not save png to '%s'", im->graphfile);
3903 return 1;
3904 }
3905 break;
3906 }
3907 case IF_XML:
3908 case IF_XMLENUM:
3909 case IF_CSV:
3910 case IF_TSV:
3911 case IF_SSV:
3912 case IF_JSON:
3913 case IF_JSONTIME:
3914 break;
3915 default:
3916 if (strlen(im->graphfile)) {
3917 cairo_show_page(im->cr);
3918 } else {
3919 cairo_surface_finish(im->surface);
3920 }
3921 break;
3922 }
3924 return 0;
3925 }
3927 int graph_paint_xy(
3928 image_desc_t *im, int lazy, int cnt)
3929 {
3930 /* to stop compiler warnings for now */
3931 lazy=cnt=(int)im->gdes_c;
3932 rrd_set_error("XY diagramm not implemented");
3933 return -1;
3934 }
3936 /*****************************************************
3937 * graph stuff
3938 *****************************************************/
3940 int gdes_alloc(
3941 image_desc_t *im)
3942 {
3944 im->gdes_c++;
3945 if ((im->gdes = (graph_desc_t *)
3946 rrd_realloc(im->gdes, (im->gdes_c)
3947 * sizeof(graph_desc_t))) == NULL) {
3948 rrd_set_error("realloc graph_descs");
3949 return -1;
3950 }
3952 /* set to zero */
3953 memset(&(im->gdes[im->gdes_c - 1]),0,sizeof(graph_desc_t));
3955 im->gdes[im->gdes_c - 1].step = im->step;
3956 im->gdes[im->gdes_c - 1].step_orig = im->step;
3957 im->gdes[im->gdes_c - 1].stack = 0;
3958 im->gdes[im->gdes_c - 1].linewidth = 0;
3959 im->gdes[im->gdes_c - 1].debug = 0;
3960 im->gdes[im->gdes_c - 1].start = im->start;
3961 im->gdes[im->gdes_c - 1].start_orig = im->start;
3962 im->gdes[im->gdes_c - 1].end = im->end;
3963 im->gdes[im->gdes_c - 1].end_orig = im->end;
3964 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3965 im->gdes[im->gdes_c - 1].data = NULL;
3966 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3967 im->gdes[im->gdes_c - 1].data_first = 0;
3968 im->gdes[im->gdes_c - 1].p_data = NULL;
3969 im->gdes[im->gdes_c - 1].rpnp = NULL;
3970 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3971 im->gdes[im->gdes_c - 1].shift = 0.0;
3972 im->gdes[im->gdes_c - 1].dash = 0;
3973 im->gdes[im->gdes_c - 1].ndash = 0;
3974 im->gdes[im->gdes_c - 1].offset = 0;
3975 im->gdes[im->gdes_c - 1].col.red = 0.0;
3976 im->gdes[im->gdes_c - 1].col.green = 0.0;
3977 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3978 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3979 im->gdes[im->gdes_c - 1].col2.red = 0.0;
3980 im->gdes[im->gdes_c - 1].col2.green = 0.0;
3981 im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3982 im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3983 im->gdes[im->gdes_c - 1].gradheight = 50.0;
3984 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3985 im->gdes[im->gdes_c - 1].format[0] = '\0';
3986 im->gdes[im->gdes_c - 1].strftm = 0;
3987 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3988 im->gdes[im->gdes_c - 1].ds = -1;
3989 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3990 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3991 im->gdes[im->gdes_c - 1].yrule = DNAN;
3992 im->gdes[im->gdes_c - 1].xrule = 0;
3993 im->gdes[im->gdes_c - 1].daemon[0] = 0;
3994 return 0;
3995 }
3997 /* copies input untill the first unescaped colon is found
3998 or until input ends. backslashes have to be escaped as well */
3999 int scan_for_col(
4000 const char *const input,
4001 int len,
4002 char *const output)
4003 {
4004 int inp, outp = 0;
4006 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
4007 if (input[inp] == '\\'
4008 && input[inp + 1] != '\0'
4009 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
4010 output[outp++] = input[++inp];
4011 } else {
4012 output[outp++] = input[inp];
4013 }
4014 }
4015 output[outp] = '\0';
4016 return inp;
4017 }
4019 /* Now just a wrapper around rrd_graph_v */
4020 int rrd_graph(
4021 int argc,
4022 char **argv,
4023 char ***prdata,
4024 int *xsize,
4025 int *ysize,
4026 FILE * stream,
4027 double *ymin,
4028 double *ymax)
4029 {
4030 int prlines = 0;
4031 rrd_info_t *grinfo = NULL;
4032 rrd_info_t *walker;
4034 grinfo = rrd_graph_v(argc, argv);
4035 if (grinfo == NULL)
4036 return -1;
4037 walker = grinfo;
4038 (*prdata) = NULL;
4039 while (walker) {
4040 if (strcmp(walker->key, "image_info") == 0) {
4041 prlines++;
4042 if (((*prdata) =
4043 (char**)rrd_realloc((*prdata),
4044 (prlines + 1) * sizeof(char *))) == NULL) {
4045 rrd_set_error("realloc prdata");
4046 return 0;
4047 }
4048 /* imginfo goes to position 0 in the prdata array */
4049 (*prdata)[prlines - 1] = strdup(walker->value.u_str);
4050 (*prdata)[prlines] = NULL;
4051 }
4052 /* skip anything else */
4053 walker = walker->next;
4054 }
4055 walker = grinfo;
4056 *xsize = 0;
4057 *ysize = 0;
4058 *ymin = 0;
4059 *ymax = 0;
4060 while (walker) {
4061 if (strcmp(walker->key, "image_width") == 0) {
4062 *xsize = walker->value.u_cnt;
4063 } else if (strcmp(walker->key, "image_height") == 0) {
4064 *ysize = walker->value.u_cnt;
4065 } else if (strcmp(walker->key, "value_min") == 0) {
4066 *ymin = walker->value.u_val;
4067 } else if (strcmp(walker->key, "value_max") == 0) {
4068 *ymax = walker->value.u_val;
4069 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
4070 prlines++;
4071 if (((*prdata) =
4072 (char**)rrd_realloc((*prdata),
4073 (prlines + 1) * sizeof(char *))) == NULL) {
4074 rrd_set_error("realloc prdata");
4075 return 0;
4076 }
4077 (*prdata)[prlines - 1] = strdup(walker->value.u_str);
4078 (*prdata)[prlines] = NULL;
4079 } else if (strcmp(walker->key, "image") == 0) {
4080 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
4081 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
4082 rrd_set_error("writing image");
4083 return 0;
4084 }
4085 }
4086 /* skip anything else */
4087 walker = walker->next;
4088 }
4089 rrd_info_free(grinfo);
4090 return 0;
4091 }
4094 /* Some surgery done on this function, it became ridiculously big.
4095 ** Things moved:
4096 ** - initializing now in rrd_graph_init()
4097 ** - options parsing now in rrd_graph_options()
4098 ** - script parsing now in rrd_graph_script()
4099 */
4101 rrd_info_t *rrd_graph_v(
4102 int argc,
4103 char **argv)
4104 {
4105 image_desc_t im;
4106 rrd_info_t *grinfo;
4107 char *old_locale;
4108 rrd_graph_init(&im);
4109 /* a dummy surface so that we can measure text sizes for placements */
4110 old_locale = setlocale(LC_NUMERIC, NULL);
4111 setlocale(LC_NUMERIC, "C");
4112 rrd_graph_options(argc, argv, &im);
4113 if (rrd_test_error()) {
4114 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4115 rrd_info_free(im.grinfo);
4116 im_free(&im);
4117 return NULL;
4118 }
4120 if (optind >= argc) {
4121 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4122 rrd_info_free(im.grinfo);
4123 im_free(&im);
4124 rrd_set_error("missing filename");
4125 return NULL;
4126 }
4128 if (strlen(argv[optind]) >= MAXPATH) {
4129 setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4130 rrd_set_error("filename (including path) too long");
4131 rrd_info_free(im.grinfo);
4132 im_free(&im);
4133 return NULL;
4134 }
4136 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4137 im.graphfile[MAXPATH - 1] = '\0';
4139 if (strcmp(im.graphfile, "-") == 0) {
4140 im.graphfile[0] = '\0';
4141 }
4143 rrd_graph_script(argc, argv, &im, 1);
4144 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4146 if (rrd_test_error()) {
4147 rrd_info_free(im.grinfo);
4148 im_free(&im);
4149 return NULL;
4150 }
4152 /* Everything is now read and the actual work can start */
4153 if (graph_paint(&im) == -1) {
4154 rrd_info_free(im.grinfo);
4155 im_free(&im);
4156 return NULL;
4157 }
4159 /* The image is generated and needs to be output.
4160 ** Also, if needed, print a line with information about the image.
4161 */
4163 if (im.imginfo) {
4164 rrd_infoval_t info;
4165 char *path;
4166 char *filename;
4168 path = strdup(im.graphfile);
4169 filename = basename(path);
4170 info.u_str =
4171 sprintf_alloc(im.imginfo,
4172 filename,
4173 (long) (im.zoom *
4174 im.ximg), (long) (im.zoom * im.yimg));
4175 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4176 free(info.u_str);
4177 free(path);
4178 }
4179 if (im.rendered_image) {
4180 rrd_infoval_t img;
4182 img.u_blo.size = im.rendered_image_size;
4183 img.u_blo.ptr = im.rendered_image;
4184 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4185 }
4186 grinfo = im.grinfo;
4187 im_free(&im);
4188 return grinfo;
4189 }
4191 static void
4192 rrd_set_font_desc (
4193 image_desc_t *im,int prop,char *font, double size ){
4194 if (font){
4195 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4196 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4197 /* if we already got one, drop it first */
4198 pango_font_description_free(im->text_prop[prop].font_desc);
4199 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4200 };
4201 if (size > 0){
4202 im->text_prop[prop].size = size;
4203 };
4204 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4205 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4206 };
4207 }
4209 void rrd_graph_init(
4210 image_desc_t
4211 *im)
4212 {
4213 unsigned int i;
4214 char *deffont = getenv("RRD_DEFAULT_FONT");
4215 static PangoFontMap *fontmap = NULL;
4216 PangoContext *context;
4218 /* zero the whole structure first */
4219 memset(im,0,sizeof(image_desc_t));
4221 #ifdef HAVE_TZSET
4222 tzset();
4223 #endif
4224 im->graph_type = GTYPE_TIME;
4225 im->base = 1000;
4226 im->daemon_addr = NULL;
4227 im->draw_x_grid = 1;
4228 im->draw_y_grid = 1;
4229 im->draw_3d_border = 2;
4230 im->dynamic_labels = 0;
4231 im->extra_flags = 0;
4232 im->font_options = cairo_font_options_create();
4233 im->forceleftspace = 0;
4234 im->gdes_c = 0;
4235 im->gdes = NULL;
4236 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4237 im->grid_dash_off = 1;
4238 im->grid_dash_on = 1;
4239 im->gridfit = 1;
4240 im->grinfo = (rrd_info_t *) NULL;
4241 im->grinfo_current = (rrd_info_t *) NULL;
4242 im->imgformat = IF_PNG;
4243 im->imginfo = NULL;
4244 im->lazy = 0;
4245 im->legenddirection = TOP_DOWN;
4246 im->legendheight = 0;
4247 im->legendposition = SOUTH;
4248 im->legendwidth = 0;
4249 im->logarithmic = 0;
4250 im->maxval = DNAN;
4251 im->minval = 0;
4252 im->minval = DNAN;
4253 im->magfact = 1;
4254 im->prt_c = 0;
4255 im->rigid = 0;
4256 im->rendered_image_size = 0;
4257 im->rendered_image = NULL;
4258 im->slopemode = 0;
4259 im->step = 0;
4260 im->symbol = ' ';
4261 im->tabwidth = 40.0;
4262 im->title[0] = '\0';
4263 im->unitsexponent = 9999;
4264 im->unitslength = 6;
4265 im->viewfactor = 1.0;
4266 im->watermark[0] = '\0';
4267 im->with_markup = 0;
4268 im->ximg = 0;
4269 im->xlab_user.minsec = -1;
4270 im->xorigin = 0;
4271 im->xOriginLegend = 0;
4272 im->xOriginLegendY = 0;
4273 im->xOriginLegendY2 = 0;
4274 im->xOriginTitle = 0;
4275 im->xsize = 400;
4276 im->ygridstep = DNAN;
4277 im->yimg = 0;
4278 im->ylegend[0] = '\0';
4279 im->second_axis_scale = 0; /* 0 disables it */
4280 im->second_axis_shift = 0; /* no shift by default */
4281 im->second_axis_legend[0] = '\0';
4282 im->second_axis_format[0] = '\0';
4283 im->yorigin = 0;
4284 im->yOriginLegend = 0;
4285 im->yOriginLegendY = 0;
4286 im->yOriginLegendY2 = 0;
4287 im->yOriginTitle = 0;
4288 im->ysize = 100;
4289 im->zoom = 1;
4291 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4292 im->cr = cairo_create(im->surface);
4294 for (i = 0; i < DIM(text_prop); i++) {
4295 im->text_prop[i].size = -1;
4296 im->text_prop[i].font_desc = NULL;
4297 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4298 }
4300 if (fontmap == NULL){
4301 fontmap = pango_cairo_font_map_get_default();
4302 }
4304 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4306 pango_cairo_context_set_resolution(context, 100);
4308 pango_cairo_update_context(im->cr,context);
4310 im->layout = pango_layout_new(context);
4311 g_object_unref (context);
4313 // im->layout = pango_cairo_create_layout(im->cr);
4316 cairo_font_options_set_hint_style
4317 (im->font_options, CAIRO_HINT_STYLE_FULL);
4318 cairo_font_options_set_hint_metrics
4319 (im->font_options, CAIRO_HINT_METRICS_ON);
4320 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4324 for (i = 0; i < DIM(graph_col); i++)
4325 im->graph_col[i] = graph_col[i];
4328 }
4331 void rrd_graph_options(
4332 int argc,
4333 char *argv[],
4334 image_desc_t
4335 *im)
4336 {
4337 int stroff;
4338 char *parsetime_error = NULL;
4339 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4340 time_t start_tmp = 0, end_tmp = 0;
4341 long long_tmp;
4342 rrd_time_value_t start_tv, end_tv;
4343 long unsigned int color;
4345 /* defines for long options without a short equivalent. should be bytes,
4346 and may not collide with (the ASCII value of) short options */
4347 #define LONGOPT_UNITS_SI 255
4349 /* *INDENT-OFF* */
4350 struct option long_options[] = {
4351 { "alt-autoscale", no_argument, 0, 'A'},
4352 { "imgformat", required_argument, 0, 'a'},
4353 { "font-smoothing-threshold", required_argument, 0, 'B'},
4354 { "base", required_argument, 0, 'b'},
4355 { "color", required_argument, 0, 'c'},
4356 { "full-size-mode", no_argument, 0, 'D'},
4357 { "daemon", required_argument, 0, 'd'},
4358 { "slope-mode", no_argument, 0, 'E'},
4359 { "end", required_argument, 0, 'e'},
4360 { "force-rules-legend", no_argument, 0, 'F'},
4361 { "imginfo", required_argument, 0, 'f'},
4362 { "graph-render-mode", required_argument, 0, 'G'},
4363 { "no-legend", no_argument, 0, 'g'},
4364 { "height", required_argument, 0, 'h'},
4365 { "no-minor", no_argument, 0, 'I'},
4366 { "interlaced", no_argument, 0, 'i'},
4367 { "alt-autoscale-min", no_argument, 0, 'J'},
4368 { "only-graph", no_argument, 0, 'j'},
4369 { "units-length", required_argument, 0, 'L'},
4370 { "lower-limit", required_argument, 0, 'l'},
4371 { "alt-autoscale-max", no_argument, 0, 'M'},
4372 { "zoom", required_argument, 0, 'm'},
4373 { "no-gridfit", no_argument, 0, 'N'},
4374 { "font", required_argument, 0, 'n'},
4375 { "logarithmic", no_argument, 0, 'o'},
4376 { "pango-markup", no_argument, 0, 'P'},
4377 { "font-render-mode", required_argument, 0, 'R'},
4378 { "rigid", no_argument, 0, 'r'},
4379 { "step", required_argument, 0, 'S'},
4380 { "start", required_argument, 0, 's'},
4381 { "tabwidth", required_argument, 0, 'T'},
4382 { "title", required_argument, 0, 't'},
4383 { "upper-limit", required_argument, 0, 'u'},
4384 { "vertical-label", required_argument, 0, 'v'},
4385 { "watermark", required_argument, 0, 'W'},
4386 { "width", required_argument, 0, 'w'},
4387 { "units-exponent", required_argument, 0, 'X'},
4388 { "x-grid", required_argument, 0, 'x'},
4389 { "alt-y-grid", no_argument, 0, 'Y'},
4390 { "y-grid", required_argument, 0, 'y'},
4391 { "lazy", no_argument, 0, 'z'},
4392 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4393 { "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 */
4394 { "disable-rrdtool-tag",no_argument, 0, 1001},
4395 { "right-axis", required_argument, 0, 1002},
4396 { "right-axis-label", required_argument, 0, 1003},
4397 { "right-axis-format", required_argument, 0, 1004},
4398 { "legend-position", required_argument, 0, 1005},
4399 { "legend-direction", required_argument, 0, 1006},
4400 { "border", required_argument, 0, 1007},
4401 { "grid-dash", required_argument, 0, 1008},
4402 { "dynamic-labels", no_argument, 0, 1009},
4403 { "week-fmt", required_argument, 0, 1010},
4404 { "graph-type", required_argument, 0, 1011},
4405 { 0, 0, 0, 0}
4406 };
4407 /* *INDENT-ON* */
4409 optind = 0;
4410 opterr = 0; /* initialize getopt */
4411 rrd_parsetime("end-24h", &start_tv);
4412 rrd_parsetime("now", &end_tv);
4413 while (1) {
4414 int option_index = 0;
4415 int opt;
4416 int col_start, col_end;
4418 opt = getopt_long(argc, argv,
4419 "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",
4420 long_options, &option_index);
4421 if (opt == EOF)
4422 break;
4423 switch (opt) {
4424 case 'I':
4425 im->extra_flags |= NOMINOR;
4426 break;
4427 case 'Y':
4428 im->extra_flags |= ALTYGRID;
4429 break;
4430 case 'A':
4431 im->extra_flags |= ALTAUTOSCALE;
4432 break;
4433 case 'J':
4434 im->extra_flags |= ALTAUTOSCALE_MIN;
4435 break;
4436 case 'M':
4437 im->extra_flags |= ALTAUTOSCALE_MAX;
4438 break;
4439 case 'j':
4440 im->extra_flags |= ONLY_GRAPH;
4441 break;
4442 case 'g':
4443 im->extra_flags |= NOLEGEND;
4444 break;
4445 case 1005:
4446 if (strcmp(optarg, "north") == 0) {
4447 im->legendposition = NORTH;
4448 } else if (strcmp(optarg, "west") == 0) {
4449 im->legendposition = WEST;
4450 } else if (strcmp(optarg, "south") == 0) {
4451 im->legendposition = SOUTH;
4452 } else if (strcmp(optarg, "east") == 0) {
4453 im->legendposition = EAST;
4454 } else {
4455 rrd_set_error("unknown legend-position '%s'", optarg);
4456 return;
4457 }
4458 break;
4459 case 1006:
4460 if (strcmp(optarg, "topdown") == 0) {
4461 im->legenddirection = TOP_DOWN;
4462 } else if (strcmp(optarg, "bottomup") == 0) {
4463 im->legenddirection = BOTTOM_UP;
4464 } else {
4465 rrd_set_error("unknown legend-position '%s'", optarg);
4466 return;
4467 }
4468 break;
4469 case 'F':
4470 im->extra_flags |= FORCE_RULES_LEGEND;
4471 break;
4472 case 1001:
4473 im->extra_flags |= NO_RRDTOOL_TAG;
4474 break;
4475 case LONGOPT_UNITS_SI:
4476 if (im->extra_flags & FORCE_UNITS) {
4477 rrd_set_error("--units can only be used once!");
4478 return;
4479 }
4480 if (strcmp(optarg, "si") == 0)
4481 im->extra_flags |= FORCE_UNITS_SI;
4482 else {
4483 rrd_set_error("invalid argument for --units: %s", optarg);
4484 return;
4485 }
4486 break;
4487 case 'X':
4488 im->unitsexponent = atoi(optarg);
4489 break;
4490 case 'L':
4491 im->unitslength = atoi(optarg);
4492 im->forceleftspace = 1;
4493 break;
4494 case 'T':
4495 im->tabwidth = atof(optarg);
4496 break;
4497 case 'S':
4498 im->step = atoi(optarg);
4499 break;
4500 case 'N':
4501 im->gridfit = 0;
4502 break;
4503 case 'P':
4504 im->with_markup = 1;
4505 break;
4506 case 's':
4507 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4508 rrd_set_error("start time: %s", parsetime_error);
4509 return;
4510 }
4511 break;
4512 case 'e':
4513 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4514 rrd_set_error("end time: %s", parsetime_error);
4515 return;
4516 }
4517 break;
4518 case 'x':
4519 if (strcmp(optarg, "none") == 0) {
4520 im->draw_x_grid = 0;
4521 break;
4522 };
4523 if (sscanf(optarg,
4524 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4525 scan_gtm,
4526 &im->xlab_user.gridst,
4527 scan_mtm,
4528 &im->xlab_user.mgridst,
4529 scan_ltm,
4530 &im->xlab_user.labst,
4531 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4532 strncpy(im->xlab_form, optarg + stroff,
4533 sizeof(im->xlab_form) - 1);
4534 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4535 if ((int)
4536 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4537 rrd_set_error("unknown keyword %s", scan_gtm);
4538 return;
4539 } else if ((int)
4540 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4541 == -1) {
4542 rrd_set_error("unknown keyword %s", scan_mtm);
4543 return;
4544 } else if ((int)
4545 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4546 rrd_set_error("unknown keyword %s", scan_ltm);
4547 return;
4548 }
4549 im->xlab_user.minsec = 1;
4550 im->xlab_user.stst = im->xlab_form;
4551 } else {
4552 rrd_set_error("invalid x-grid format");
4553 return;
4554 }
4555 break;
4556 case 'y':
4558 if (strcmp(optarg, "none") == 0) {
4559 im->draw_y_grid = 0;
4560 break;
4561 };
4562 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4563 if (im->ygridstep <= 0) {
4564 rrd_set_error("grid step must be > 0");
4565 return;
4566 } else if (im->ylabfact < 1) {
4567 rrd_set_error("label factor must be > 0");
4568 return;
4569 }
4570 } else {
4571 rrd_set_error("invalid y-grid format");
4572 return;
4573 }
4574 break;
4575 case 1007:
4576 im->draw_3d_border = atoi(optarg);
4577 break;
4578 case 1008: /* grid-dash */
4579 if(sscanf(optarg,
4580 "%lf:%lf",
4581 &im->grid_dash_on,
4582 &im->grid_dash_off) != 2) {
4583 rrd_set_error("expected grid-dash format float:float");
4584 return;
4585 }
4586 break;
4587 case 1009: /* enable dynamic labels */
4588 im->dynamic_labels = 1;
4589 break;
4590 case 1010:
4591 strncpy(week_fmt,optarg,sizeof week_fmt);
4592 week_fmt[(sizeof week_fmt)-1]='\0';
4593 break;
4594 case 1002: /* right y axis */
4596 if(sscanf(optarg,
4597 "%lf:%lf",
4598 &im->second_axis_scale,
4599 &im->second_axis_shift) == 2) {
4600 if(im->second_axis_scale==0){
4601 rrd_set_error("the second_axis_scale must not be 0");
4602 return;
4603 }
4604 } else {
4605 rrd_set_error("invalid right-axis format expected scale:shift");
4606 return;
4607 }
4608 break;
4609 case 1003:
4610 strncpy(im->second_axis_legend,optarg,150);
4611 im->second_axis_legend[150]='\0';
4612 break;
4613 case 1004:
4614 if (bad_format(optarg)){
4615 rrd_set_error("use either %le or %lf formats");
4616 return;
4617 }
4618 strncpy(im->second_axis_format,optarg,150);
4619 im->second_axis_format[150]='\0';
4620 break;
4621 case 'v':
4622 strncpy(im->ylegend, optarg, 150);
4623 im->ylegend[150] = '\0';
4624 break;
4625 case 'u':
4626 im->maxval = atof(optarg);
4627 break;
4628 case 'l':
4629 im->minval = atof(optarg);
4630 break;
4631 case 'b':
4632 im->base = atol(optarg);
4633 if (im->base != 1024 && im->base != 1000) {
4634 rrd_set_error
4635 ("the only sensible value for base apart from 1000 is 1024");
4636 return;
4637 }
4638 break;
4639 case 'w':
4640 long_tmp = atol(optarg);
4641 if (long_tmp < 10) {
4642 rrd_set_error("width below 10 pixels");
4643 return;
4644 }
4645 im->xsize = long_tmp;
4646 break;
4647 case 'h':
4648 long_tmp = atol(optarg);
4649 if (long_tmp < 10) {
4650 rrd_set_error("height below 10 pixels");
4651 return;
4652 }
4653 im->ysize = long_tmp;
4654 break;
4655 case 'D':
4656 im->extra_flags |= FULL_SIZE_MODE;
4657 break;
4658 case 'i':
4659 /* interlaced png not supported at the moment */
4660 break;
4661 case 'r':
4662 im->rigid = 1;
4663 break;
4664 case 'f':
4665 im->imginfo = optarg;
4666 break;
4667 case 'a':
4668 if ((int)
4669 (im->imgformat = if_conv(optarg)) == -1) {
4670 rrd_set_error("unsupported graphics format '%s'", optarg);
4671 return;
4672 }
4673 break;
4674 case 1011:
4675 if ((int)
4676 (im->graph_type = type_conv(optarg)) == -1) {
4677 rrd_set_error("unsupported graphics type '%s'", optarg);
4678 return;
4679 }
4680 break;
4681 case 'z':
4682 im->lazy = 1;
4683 break;
4684 case 'E':
4685 im->slopemode = 1;
4686 break;
4687 case 'o':
4688 im->logarithmic = 1;
4689 break;
4690 case 'c':
4691 if (sscanf(optarg,
4692 "%10[A-Z]#%n%8lx%n",
4693 col_nam, &col_start, &color, &col_end) == 2) {
4694 int ci;
4695 int col_len = col_end - col_start;
4697 switch (col_len) {
4698 case 3:
4699 color =
4700 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4701 0x011000) |
4702 ((color & 0x00F)
4703 * 0x001100)
4704 | 0x000000FF);
4705 break;
4706 case 4:
4707 color =
4708 (((color & 0xF000) *
4709 0x11000) | ((color & 0x0F00) *
4710 0x01100) | ((color &
4711 0x00F0) *
4712 0x00110) |
4713 ((color & 0x000F) * 0x00011)
4714 );
4715 break;
4716 case 6:
4717 color = (color << 8) + 0xff /* shift left by 8 */ ;
4718 break;
4719 case 8:
4720 break;
4721 default:
4722 rrd_set_error("the color format is #RRGGBB[AA]");
4723 return;
4724 }
4725 if ((ci = grc_conv(col_nam)) != -1) {
4726 im->graph_col[ci] = gfx_hex_to_col(color);
4727 } else {
4728 rrd_set_error("invalid color name '%s'", col_nam);
4729 return;
4730 }
4731 } else {
4732 rrd_set_error("invalid color def format");
4733 return;
4734 }
4735 break;
4736 case 'n':{
4737 char prop[15];
4738 double size = 1;
4739 int end;
4741 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4742 int sindex, propidx;
4744 if ((sindex = text_prop_conv(prop)) != -1) {
4745 for (propidx = sindex;
4746 propidx < TEXT_PROP_LAST; propidx++) {
4747 if (size > 0) {
4748 rrd_set_font_desc(im,propidx,NULL,size);
4749 }
4750 if ((int) strlen(optarg) > end+2) {
4751 if (optarg[end] == ':') {
4752 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4753 } else {
4754 rrd_set_error
4755 ("expected : after font size in '%s'",
4756 optarg);
4757 return;
4758 }
4759 }
4760 /* only run the for loop for DEFAULT (0) for
4761 all others, we break here. woodo programming */
4762 if (propidx == sindex && sindex != 0)
4763 break;
4764 }
4765 } else {
4766 rrd_set_error("invalid fonttag '%s'", prop);
4767 return;
4768 }
4769 } else {
4770 rrd_set_error("invalid text property format");
4771 return;
4772 }
4773 break;
4774 }
4775 case 'm':
4776 im->zoom = atof(optarg);
4777 if (im->zoom <= 0.0) {
4778 rrd_set_error("zoom factor must be > 0");
4779 return;
4780 }
4781 break;
4782 case 't':
4783 strncpy(im->title, optarg, 150);
4784 im->title[150] = '\0';
4785 break;
4786 case 'R':
4787 if (strcmp(optarg, "normal") == 0) {
4788 cairo_font_options_set_antialias
4789 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4790 cairo_font_options_set_hint_style
4791 (im->font_options, CAIRO_HINT_STYLE_FULL);
4792 } else if (strcmp(optarg, "light") == 0) {
4793 cairo_font_options_set_antialias
4794 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4795 cairo_font_options_set_hint_style
4796 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4797 } else if (strcmp(optarg, "mono") == 0) {
4798 cairo_font_options_set_antialias
4799 (im->font_options, CAIRO_ANTIALIAS_NONE);
4800 cairo_font_options_set_hint_style
4801 (im->font_options, CAIRO_HINT_STYLE_FULL);
4802 } else {
4803 rrd_set_error("unknown font-render-mode '%s'", optarg);
4804 return;
4805 }
4806 break;
4807 case 'G':
4808 if (strcmp(optarg, "normal") == 0)
4809 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4810 else if (strcmp(optarg, "mono") == 0)
4811 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4812 else {
4813 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4814 return;
4815 }
4816 break;
4817 case 'B':
4818 /* not supported curently */
4819 break;
4820 case 'W':
4821 strncpy(im->watermark, optarg, 100);
4822 im->watermark[99] = '\0';
4823 break;
4824 case 'd':
4825 {
4826 if (im->daemon_addr != NULL)
4827 {
4828 rrd_set_error ("You cannot specify --daemon "
4829 "more than once.");
4830 return;
4831 }
4833 im->daemon_addr = strdup(optarg);
4834 if (im->daemon_addr == NULL)
4835 {
4836 rrd_set_error("strdup failed");
4837 return;
4838 }
4840 break;
4841 }
4842 case '?':
4843 if (optopt != 0)
4844 rrd_set_error("unknown option '%c'", optopt);
4845 else
4846 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4847 return;
4848 }
4849 } /* while (1) */
4851 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4852 pango_layout_context_changed(im->layout);
4856 if (im->logarithmic && im->minval <= 0) {
4857 rrd_set_error
4858 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4859 return;
4860 }
4862 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4863 /* error string is set in rrd_parsetime.c */
4864 return;
4865 }
4867 if (start_tmp < 3600 * 24 * 365 * 10) {
4868 rrd_set_error
4869 ("the first entry to fetch should be after 1980 (%ld)",
4870 start_tmp);
4871 return;
4872 }
4874 if (end_tmp < start_tmp) {
4875 rrd_set_error
4876 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4877 return;
4878 }
4880 im->start = start_tmp;
4881 im->end = end_tmp;
4882 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4883 }
4885 int rrd_graph_color(
4886 image_desc_t
4887 *im,
4888 char *var,
4889 char *err,
4890 int optional)
4891 {
4892 char *color;
4893 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4895 color = strstr(var, "#");
4896 if (color == NULL) {
4897 if (optional == 0) {
4898 rrd_set_error("Found no color in %s", err);
4899 return 0;
4900 }
4901 return 0;
4902 } else {
4903 int n = 0;
4904 char *rest;
4905 long unsigned int col;
4907 rest = strstr(color, ":");
4908 if (rest != NULL)
4909 n = rest - color;
4910 else
4911 n = strlen(color);
4912 switch (n) {
4913 case 7:
4914 sscanf(color, "#%6lx%n", &col, &n);
4915 col = (col << 8) + 0xff /* shift left by 8 */ ;
4916 if (n != 7)
4917 rrd_set_error("Color problem in %s", err);
4918 break;
4919 case 9:
4920 sscanf(color, "#%8lx%n", &col, &n);
4921 if (n == 9)
4922 break;
4923 default:
4924 rrd_set_error("Color problem in %s", err);
4925 }
4926 if (rrd_test_error())
4927 return 0;
4928 gdp->col = gfx_hex_to_col(col);
4929 return n;
4930 }
4931 }
4934 int bad_format(
4935 char *fmt)
4936 {
4937 char *ptr;
4938 int n = 0;
4940 ptr = fmt;
4941 while (*ptr != '\0')
4942 if (*ptr++ == '%') {
4944 /* line cannot end with percent char */
4945 if (*ptr == '\0')
4946 return 1;
4947 /* '%s', '%S' and '%%' are allowed */
4948 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4949 ptr++;
4950 /* %c is allowed (but use only with vdef!) */
4951 else if (*ptr == 'c') {
4952 ptr++;
4953 n = 1;
4954 }
4956 /* or else '% 6.2lf' and such are allowed */
4957 else {
4958 /* optional padding character */
4959 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4960 ptr++;
4961 /* This should take care of 'm.n' with all three optional */
4962 while (*ptr >= '0' && *ptr <= '9')
4963 ptr++;
4964 if (*ptr == '.')
4965 ptr++;
4966 while (*ptr >= '0' && *ptr <= '9')
4967 ptr++;
4968 /* Either 'le', 'lf' or 'lg' must follow here */
4969 if (*ptr++ != 'l')
4970 return 1;
4971 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4972 ptr++;
4973 else
4974 return 1;
4975 n++;
4976 }
4977 }
4979 return (n != 1);
4980 }
4983 int vdef_parse(
4984 struct graph_desc_t
4985 *gdes,
4986 const char *const str)
4987 {
4988 /* A VDEF currently is either "func" or "param,func"
4989 * so the parsing is rather simple. Change if needed.
4990 */
4991 double param;
4992 char func[30];
4993 int n;
4995 n = 0;
4996 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4997 if (n == (int) strlen(str)) { /* matched */
4998 ;
4999 } else {
5000 n = 0;
5001 sscanf(str, "%29[A-Z]%n", func, &n);
5002 if (n == (int) strlen(str)) { /* matched */
5003 param = DNAN;
5004 } else {
5005 rrd_set_error
5006 ("Unknown function string '%s' in VDEF '%s'",
5007 str, gdes->vname);
5008 return -1;
5009 }
5010 }
5011 if (!strcmp("PERCENT", func))
5012 gdes->vf.op = VDEF_PERCENT;
5013 else if (!strcmp("PERCENTNAN", func))
5014 gdes->vf.op = VDEF_PERCENTNAN;
5015 else if (!strcmp("MAXIMUM", func))
5016 gdes->vf.op = VDEF_MAXIMUM;
5017 else if (!strcmp("AVERAGE", func))
5018 gdes->vf.op = VDEF_AVERAGE;
5019 else if (!strcmp("STDEV", func))
5020 gdes->vf.op = VDEF_STDEV;
5021 else if (!strcmp("MINIMUM", func))
5022 gdes->vf.op = VDEF_MINIMUM;
5023 else if (!strcmp("TOTAL", func))
5024 gdes->vf.op = VDEF_TOTAL;
5025 else if (!strcmp("FIRST", func))
5026 gdes->vf.op = VDEF_FIRST;
5027 else if (!strcmp("LAST", func))
5028 gdes->vf.op = VDEF_LAST;
5029 else if (!strcmp("LSLSLOPE", func))
5030 gdes->vf.op = VDEF_LSLSLOPE;
5031 else if (!strcmp("LSLINT", func))
5032 gdes->vf.op = VDEF_LSLINT;
5033 else if (!strcmp("LSLCORREL", func))
5034 gdes->vf.op = VDEF_LSLCORREL;
5035 else {
5036 rrd_set_error
5037 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
5038 return -1;
5039 };
5040 switch (gdes->vf.op) {
5041 case VDEF_PERCENT:
5042 case VDEF_PERCENTNAN:
5043 if (isnan(param)) { /* no parameter given */
5044 rrd_set_error
5045 ("Function '%s' needs parameter in VDEF '%s'\n",
5046 func, gdes->vname);
5047 return -1;
5048 };
5049 if (param >= 0.0 && param <= 100.0) {
5050 gdes->vf.param = param;
5051 gdes->vf.val = DNAN; /* undefined */
5052 gdes->vf.when = 0; /* undefined */
5053 gdes->vf.never = 1;
5054 } else {
5055 rrd_set_error
5056 ("Parameter '%f' out of range in VDEF '%s'\n",
5057 param, gdes->vname);
5058 return -1;
5059 };
5060 break;
5061 case VDEF_MAXIMUM:
5062 case VDEF_AVERAGE:
5063 case VDEF_STDEV:
5064 case VDEF_MINIMUM:
5065 case VDEF_TOTAL:
5066 case VDEF_FIRST:
5067 case VDEF_LAST:
5068 case VDEF_LSLSLOPE:
5069 case VDEF_LSLINT:
5070 case VDEF_LSLCORREL:
5071 if (isnan(param)) {
5072 gdes->vf.param = DNAN;
5073 gdes->vf.val = DNAN;
5074 gdes->vf.when = 0;
5075 gdes->vf.never = 1;
5076 } else {
5077 rrd_set_error
5078 ("Function '%s' needs no parameter in VDEF '%s'\n",
5079 func, gdes->vname);
5080 return -1;
5081 };
5082 break;
5083 };
5084 return 0;
5085 }
5088 int vdef_calc(
5089 image_desc_t *im,
5090 int gdi)
5091 {
5092 graph_desc_t *src, *dst;
5093 rrd_value_t *data;
5094 long step, steps;
5096 dst = &im->gdes[gdi];
5097 src = &im->gdes[dst->vidx];
5098 data = src->data + src->ds;
5100 steps = (src->end - src->start) / src->step;
5101 #if 0
5102 printf
5103 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
5104 src->start, src->end, steps);
5105 #endif
5106 switch (dst->vf.op) {
5107 case VDEF_PERCENT:{
5108 rrd_value_t *array;
5109 int field;
5110 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5111 rrd_set_error("malloc VDEV_PERCENT");
5112 return -1;
5113 }
5114 for (step = 0; step < steps; step++) {
5115 array[step] = data[step * src->ds_cnt];
5116 }
5117 qsort(array, step, sizeof(double), vdef_percent_compar);
5118 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5119 dst->vf.val = array[field];
5120 dst->vf.when = 0; /* no time component */
5121 dst->vf.never = 1;
5122 free(array);
5123 #if 0
5124 for (step = 0; step < steps; step++)
5125 printf("DEBUG: %3li:%10.2f %c\n",
5126 step, array[step], step == field ? '*' : ' ');
5127 #endif
5128 }
5129 break;
5130 case VDEF_PERCENTNAN:{
5131 rrd_value_t *array;
5132 int field;
5133 /* count number of "valid" values */
5134 int nancount=0;
5135 for (step = 0; step < steps; step++) {
5136 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5137 }
5138 /* and allocate it */
5139 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5140 rrd_set_error("malloc VDEV_PERCENT");
5141 return -1;
5142 }
5143 /* and fill it in */
5144 field=0;
5145 for (step = 0; step < steps; step++) {
5146 if (!isnan(data[step * src->ds_cnt])) {
5147 array[field] = data[step * src->ds_cnt];
5148 field++;
5149 }
5150 }
5151 qsort(array, nancount, sizeof(double), vdef_percent_compar);
5152 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5153 dst->vf.val = array[field];
5154 dst->vf.when = 0; /* no time component */
5155 dst->vf.never = 1;
5156 free(array);
5157 }
5158 break;
5159 case VDEF_MAXIMUM:
5160 step = 0;
5161 while (step != steps && isnan(data[step * src->ds_cnt]))
5162 step++;
5163 if (step == steps) {
5164 dst->vf.val = DNAN;
5165 dst->vf.when = 0;
5166 dst->vf.never = 1;
5167 } else {
5168 dst->vf.val = data[step * src->ds_cnt];
5169 dst->vf.when = src->start + (step + 1) * src->step;
5170 dst->vf.never = 0;
5171 }
5172 while (step != steps) {
5173 if (finite(data[step * src->ds_cnt])) {
5174 if (data[step * src->ds_cnt] > dst->vf.val) {
5175 dst->vf.val = data[step * src->ds_cnt];
5176 dst->vf.when = src->start + (step + 1) * src->step;
5177 dst->vf.never = 0;
5178 }
5179 }
5180 step++;
5181 }
5182 break;
5183 case VDEF_TOTAL:
5184 case VDEF_STDEV:
5185 case VDEF_AVERAGE:{
5186 int cnt = 0;
5187 double sum = 0.0;
5188 double average = 0.0;
5190 for (step = 0; step < steps; step++) {
5191 if (finite(data[step * src->ds_cnt])) {
5192 sum += data[step * src->ds_cnt];
5193 cnt++;
5194 };
5195 }
5196 if (cnt) {
5197 if (dst->vf.op == VDEF_TOTAL) {
5198 dst->vf.val = sum * src->step;
5199 dst->vf.when = 0; /* no time component */
5200 dst->vf.never = 1;
5201 } else if (dst->vf.op == VDEF_AVERAGE) {
5202 dst->vf.val = sum / cnt;
5203 dst->vf.when = 0; /* no time component */
5204 dst->vf.never = 1;
5205 } else {
5206 average = sum / cnt;
5207 sum = 0.0;
5208 for (step = 0; step < steps; step++) {
5209 if (finite(data[step * src->ds_cnt])) {
5210 sum += pow((data[step * src->ds_cnt] - average), 2.0);
5211 };
5212 }
5213 dst->vf.val = pow(sum / cnt, 0.5);
5214 dst->vf.when = 0; /* no time component */
5215 dst->vf.never = 1;
5216 };
5217 } else {
5218 dst->vf.val = DNAN;
5219 dst->vf.when = 0;
5220 dst->vf.never = 1;
5221 }
5222 }
5223 break;
5224 case VDEF_MINIMUM:
5225 step = 0;
5226 while (step != steps && isnan(data[step * src->ds_cnt]))
5227 step++;
5228 if (step == steps) {
5229 dst->vf.val = DNAN;
5230 dst->vf.when = 0;
5231 dst->vf.never = 1;
5232 } else {
5233 dst->vf.val = data[step * src->ds_cnt];
5234 dst->vf.when = src->start + (step + 1) * src->step;
5235 dst->vf.never = 0;
5236 }
5237 while (step != steps) {
5238 if (finite(data[step * src->ds_cnt])) {
5239 if (data[step * src->ds_cnt] < dst->vf.val) {
5240 dst->vf.val = data[step * src->ds_cnt];
5241 dst->vf.when = src->start + (step + 1) * src->step;
5242 dst->vf.never = 0;
5243 }
5244 }
5245 step++;
5246 }
5247 break;
5248 case VDEF_FIRST:
5249 /* The time value returned here is one step before the
5250 * actual time value. This is the start of the first
5251 * non-NaN interval.
5252 */
5253 step = 0;
5254 while (step != steps && isnan(data[step * src->ds_cnt]))
5255 step++;
5256 if (step == steps) { /* all entries were NaN */
5257 dst->vf.val = DNAN;
5258 dst->vf.when = 0;
5259 dst->vf.never = 1;
5260 } else {
5261 dst->vf.val = data[step * src->ds_cnt];
5262 dst->vf.when = src->start + step * src->step;
5263 dst->vf.never = 0;
5264 }
5265 break;
5266 case VDEF_LAST:
5267 /* The time value returned here is the
5268 * actual time value. This is the end of the last
5269 * non-NaN interval.
5270 */
5271 step = steps - 1;
5272 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5273 step--;
5274 if (step < 0) { /* all entries were NaN */
5275 dst->vf.val = DNAN;
5276 dst->vf.when = 0;
5277 dst->vf.never = 1;
5278 } else {
5279 dst->vf.val = data[step * src->ds_cnt];
5280 dst->vf.when = src->start + (step + 1) * src->step;
5281 dst->vf.never = 0;
5282 }
5283 break;
5284 case VDEF_LSLSLOPE:
5285 case VDEF_LSLINT:
5286 case VDEF_LSLCORREL:{
5287 /* Bestfit line by linear least squares method */
5289 int cnt = 0;
5290 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5292 SUMx = 0;
5293 SUMy = 0;
5294 SUMxy = 0;
5295 SUMxx = 0;
5296 SUMyy = 0;
5297 for (step = 0; step < steps; step++) {
5298 if (finite(data[step * src->ds_cnt])) {
5299 cnt++;
5300 SUMx += step;
5301 SUMxx += step * step;
5302 SUMxy += step * data[step * src->ds_cnt];
5303 SUMy += data[step * src->ds_cnt];
5304 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5305 };
5306 }
5308 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5309 y_intercept = (SUMy - slope * SUMx) / cnt;
5310 correl =
5311 (SUMxy -
5312 (SUMx * SUMy) / cnt) /
5313 sqrt((SUMxx -
5314 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5315 if (cnt) {
5316 if (dst->vf.op == VDEF_LSLSLOPE) {
5317 dst->vf.val = slope;
5318 dst->vf.when = 0;
5319 dst->vf.never = 1;
5320 } else if (dst->vf.op == VDEF_LSLINT) {
5321 dst->vf.val = y_intercept;
5322 dst->vf.when = 0;
5323 dst->vf.never = 1;
5324 } else if (dst->vf.op == VDEF_LSLCORREL) {
5325 dst->vf.val = correl;
5326 dst->vf.when = 0;
5327 dst->vf.never = 1;
5328 };
5329 } else {
5330 dst->vf.val = DNAN;
5331 dst->vf.when = 0;
5332 dst->vf.never = 1;
5333 }
5334 }
5335 break;
5336 }
5337 return 0;
5338 }
5340 /* NaN < -INF < finite_values < INF */
5341 int vdef_percent_compar(
5342 const void
5343 *a,
5344 const void
5345 *b)
5346 {
5347 /* Equality is not returned; this doesn't hurt except
5348 * (maybe) for a little performance.
5349 */
5351 /* First catch NaN values. They are smallest */
5352 if (isnan(*(double *) a))
5353 return -1;
5354 if (isnan(*(double *) b))
5355 return 1;
5356 /* NaN doesn't reach this part so INF and -INF are extremes.
5357 * The sign from isinf() is compatible with the sign we return
5358 */
5359 if (isinf(*(double *) a))
5360 return isinf(*(double *) a);
5361 if (isinf(*(double *) b))
5362 return isinf(*(double *) b);
5363 /* If we reach this, both values must be finite */
5364 if (*(double *) a < *(double *) b)
5365 return -1;
5366 else
5367 return 1;
5368 }
5370 void grinfo_push(
5371 image_desc_t *im,
5372 char *key,
5373 rrd_info_type_t type,
5374 rrd_infoval_t value)
5375 {
5376 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5377 if (im->grinfo == NULL) {
5378 im->grinfo = im->grinfo_current;
5379 }
5380 }
5383 void time_clean(
5384 char *result,
5385 char *format)
5386 {
5387 int j, jj;
5389 /* Handling based on
5390 - ANSI C99 Specifications http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5391 - Single UNIX Specification version 2 http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
5392 - POSIX:2001/Single UNIX Specification version 3 http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5393 - POSIX:2008 Specifications http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5394 Specifications tells
5395 "If a conversion specifier is not one of the above, the behavior is undefined."
5397 C99 tells
5398 "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.
5400 POSIX:2001 tells
5401 "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."
5403 POSIX:2008 introduce more complexe behavior that are not handled here.
5405 According to this, this code will replace:
5406 - % followed by @ by a %@
5407 - % followed by by a %SPACE
5408 - % followed by . by a %.
5409 - % followed by % by a %
5410 - % followed by t by a TAB
5411 - % followed by E then anything by '-'
5412 - % followed by O then anything by '-'
5413 - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5414 */
5416 jj = 0;
5417 for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5418 if (format[j] == '%') {
5419 if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5420 result[jj++] = '-';
5421 j+=2; /* We skip next 2 following char */
5422 } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5423 (format[j+1] == 'g') || (format[j+1] == 'H') ||
5424 (format[j+1] == 'I') || (format[j+1] == 'm') ||
5425 (format[j+1] == 'M') || (format[j+1] == 'S') ||
5426 (format[j+1] == 'U') || (format[j+1] == 'V') ||
5427 (format[j+1] == 'W') || (format[j+1] == 'y')) {
5428 result[jj++] = '-';
5429 if (jj < FMT_LEG_LEN) {
5430 result[jj++] = '-';
5431 }
5432 j++; /* We skip the following char */
5433 } else if (format[j+1] == 'j') {
5434 result[jj++] = '-';
5435 if (jj < FMT_LEG_LEN - 1) {
5436 result[jj++] = '-';
5437 result[jj++] = '-';
5438 }
5439 j++; /* We skip the following char */
5440 } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5441 /* Assuming Year on 4 digit */
5442 result[jj++] = '-';
5443 if (jj < FMT_LEG_LEN - 2) {
5444 result[jj++] = '-';
5445 result[jj++] = '-';
5446 result[jj++] = '-';
5447 }
5448 j++; /* We skip the following char */
5449 } else if (format[j+1] == 'R') {
5450 result[jj++] = '-';
5451 if (jj < FMT_LEG_LEN - 3) {
5452 result[jj++] = '-';
5453 result[jj++] = ':';
5454 result[jj++] = '-';
5455 result[jj++] = '-';
5456 }
5457 j++; /* We skip the following char */
5458 } else if (format[j+1] == 'T') {
5459 result[jj++] = '-';
5460 if (jj < FMT_LEG_LEN - 6) {
5461 result[jj++] = '-';
5462 result[jj++] = ':';
5463 result[jj++] = '-';
5464 result[jj++] = '-';
5465 result[jj++] = ':';
5466 result[jj++] = '-';
5467 result[jj++] = '-';
5468 }
5469 j++; /* We skip the following char */
5470 } else if (format[j+1] == 'F') {
5471 result[jj++] = '-';
5472 if (jj < FMT_LEG_LEN - 8) {
5473 result[jj++] = '-';
5474 result[jj++] = '-';
5475 result[jj++] = '-';
5476 result[jj++] = '-';
5477 result[jj++] = '-';
5478 result[jj++] = '-';
5479 result[jj++] = '-';
5480 result[jj++] = '-';
5481 result[jj++] = '-';
5482 }
5483 j++; /* We skip the following char */
5484 } else if (format[j+1] == 'D') {
5485 result[jj++] = '-';
5486 if (jj < FMT_LEG_LEN - 6) {
5487 result[jj++] = '-';
5488 result[jj++] = '/';
5489 result[jj++] = '-';
5490 result[jj++] = '-';
5491 result[jj++] = '/';
5492 result[jj++] = '-';
5493 result[jj++] = '-';
5494 }
5495 j++; /* We skip the following char */
5496 } else if (format[j+1] == 'n') {
5497 result[jj++] = '\r';
5498 result[jj++] = '\n';
5499 j++; /* We skip the following char */
5500 } else if (format[j+1] == 't') {
5501 result[jj++] = '\t';
5502 j++; /* We skip the following char */
5503 } else if (format[j+1] == '%') {
5504 result[jj++] = '%';
5505 j++; /* We skip the following char */
5506 } else if (format[j+1] == ' ') {
5507 if (jj < FMT_LEG_LEN - 1) {
5508 result[jj++] = '%';
5509 result[jj++] = ' ';
5510 }
5511 j++; /* We skip the following char */
5512 } else if (format[j+1] == '.') {
5513 if (jj < FMT_LEG_LEN - 1) {
5514 result[jj++] = '%';
5515 result[jj++] = '.';
5516 }
5517 j++; /* We skip the following char */
5518 } else if (format[j+1] == '@') {
5519 if (jj < FMT_LEG_LEN - 1) {
5520 result[jj++] = '%';
5521 result[jj++] = '@';
5522 }
5523 j++; /* We skip the following char */
5524 } else {
5525 result[jj++] = '-';
5526 j++; /* We skip the following char */
5527 }
5528 } else {
5529 result[jj++] = format[j];
5530 }
5531 }
5532 result[jj] = '\0'; /* We must force the end of the string */
5533 }