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