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