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