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