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