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