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