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