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