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