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