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