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