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