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