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 }
1362 static int find_first_weekday(void){
1363 static int first_weekday = -1;
1364 if (first_weekday == -1){
1365 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1366 /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1367 long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1368 if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1369 else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1370 else first_weekday = 1; /* we go for a monday default */
1371 #else
1372 first_weekday = 1;
1373 #endif
1374 }
1375 return first_weekday;
1376 }
1378 /* identify the point where the first gridline, label ... gets placed */
1380 time_t find_first_time(
1381 time_t start, /* what is the initial time */
1382 enum tmt_en baseint, /* what is the basic interval */
1383 long basestep /* how many if these do we jump a time */
1384 )
1385 {
1386 struct tm tm;
1388 localtime_r(&start, &tm);
1390 switch (baseint) {
1391 case TMT_SECOND:
1392 tm. tm_sec -= tm.tm_sec % basestep;
1394 break;
1395 case TMT_MINUTE:
1396 tm. tm_sec = 0;
1397 tm. tm_min -= tm.tm_min % basestep;
1399 break;
1400 case TMT_HOUR:
1401 tm. tm_sec = 0;
1402 tm. tm_min = 0;
1403 tm. tm_hour -= tm.tm_hour % basestep;
1405 break;
1406 case TMT_DAY:
1407 /* we do NOT look at the basestep for this ... */
1408 tm. tm_sec = 0;
1409 tm. tm_min = 0;
1410 tm. tm_hour = 0;
1412 break;
1413 case TMT_WEEK:
1414 /* we do NOT look at the basestep for this ... */
1415 tm. tm_sec = 0;
1416 tm. tm_min = 0;
1417 tm. tm_hour = 0;
1418 tm. tm_mday -= tm.tm_wday - find_first_weekday();
1420 if (tm.tm_wday == 0 && find_first_weekday() > 0)
1421 tm. tm_mday -= 7; /* we want the *previous* week */
1423 break;
1424 case TMT_MONTH:
1425 tm. tm_sec = 0;
1426 tm. tm_min = 0;
1427 tm. tm_hour = 0;
1428 tm. tm_mday = 1;
1429 tm. tm_mon -= tm.tm_mon % basestep;
1431 break;
1433 case TMT_YEAR:
1434 tm. tm_sec = 0;
1435 tm. tm_min = 0;
1436 tm. tm_hour = 0;
1437 tm. tm_mday = 1;
1438 tm. tm_mon = 0;
1439 tm. tm_year -= (
1440 tm.tm_year + 1900) %basestep;
1442 }
1443 return mktime(&tm);
1444 }
1446 /* identify the point where the next gridline, label ... gets placed */
1447 time_t find_next_time(
1448 time_t current, /* what is the initial time */
1449 enum tmt_en baseint, /* what is the basic interval */
1450 long basestep /* how many if these do we jump a time */
1451 )
1452 {
1453 struct tm tm;
1454 time_t madetime;
1456 localtime_r(¤t, &tm);
1458 do {
1459 switch (baseint) {
1460 case TMT_SECOND:
1461 tm. tm_sec += basestep;
1463 break;
1464 case TMT_MINUTE:
1465 tm. tm_min += basestep;
1467 break;
1468 case TMT_HOUR:
1469 tm. tm_hour += basestep;
1471 break;
1472 case TMT_DAY:
1473 tm. tm_mday += basestep;
1475 break;
1476 case TMT_WEEK:
1477 tm. tm_mday += 7 * basestep;
1479 break;
1480 case TMT_MONTH:
1481 tm. tm_mon += basestep;
1483 break;
1484 case TMT_YEAR:
1485 tm. tm_year += basestep;
1486 }
1487 madetime = mktime(&tm);
1488 } while (madetime == -1); /* this is necessary to skip impssible times
1489 like the daylight saving time skips */
1490 return madetime;
1492 }
1495 /* calculate values required for PRINT and GPRINT functions */
1497 int print_calc(
1498 image_desc_t *im)
1499 {
1500 long i, ii, validsteps;
1501 double printval;
1502 struct tm tmvdef;
1503 int graphelement = 0;
1504 long vidx;
1505 int max_ii;
1506 double magfact = -1;
1507 char *si_symb = "";
1508 char *percent_s;
1509 int prline_cnt = 0;
1511 /* wow initializing tmvdef is quite a task :-) */
1512 time_t now = time(NULL);
1514 localtime_r(&now, &tmvdef);
1515 for (i = 0; i < im->gdes_c; i++) {
1516 vidx = im->gdes[i].vidx;
1517 switch (im->gdes[i].gf) {
1518 case GF_PRINT:
1519 case GF_GPRINT:
1520 /* PRINT and GPRINT can now print VDEF generated values.
1521 * There's no need to do any calculations on them as these
1522 * calculations were already made.
1523 */
1524 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1525 printval = im->gdes[vidx].vf.val;
1526 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1527 } else { /* need to calculate max,min,avg etcetera */
1528 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1529 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1530 printval = DNAN;
1531 validsteps = 0;
1532 for (ii = im->gdes[vidx].ds;
1533 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1534 if (!finite(im->gdes[vidx].data[ii]))
1535 continue;
1536 if (isnan(printval)) {
1537 printval = im->gdes[vidx].data[ii];
1538 validsteps++;
1539 continue;
1540 }
1542 switch (im->gdes[i].cf) {
1543 case CF_HWPREDICT:
1544 case CF_MHWPREDICT:
1545 case CF_DEVPREDICT:
1546 case CF_DEVSEASONAL:
1547 case CF_SEASONAL:
1548 case CF_AVERAGE:
1549 validsteps++;
1550 printval += im->gdes[vidx].data[ii];
1551 break;
1552 case CF_MINIMUM:
1553 printval = min(printval, im->gdes[vidx].data[ii]);
1554 break;
1555 case CF_FAILURES:
1556 case CF_MAXIMUM:
1557 printval = max(printval, im->gdes[vidx].data[ii]);
1558 break;
1559 case CF_LAST:
1560 printval = im->gdes[vidx].data[ii];
1561 }
1562 }
1563 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1564 if (validsteps > 1) {
1565 printval = (printval / validsteps);
1566 }
1567 }
1568 } /* prepare printval */
1570 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1571 /* Magfact is set to -1 upon entry to print_calc. If it
1572 * is still less than 0, then we need to run auto_scale.
1573 * Otherwise, put the value into the correct units. If
1574 * the value is 0, then do not set the symbol or magnification
1575 * so next the calculation will be performed again. */
1576 if (magfact < 0.0) {
1577 auto_scale(im, &printval, &si_symb, &magfact);
1578 if (printval == 0.0)
1579 magfact = -1.0;
1580 } else {
1581 printval /= magfact;
1582 }
1583 *(++percent_s) = 's';
1584 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1585 auto_scale(im, &printval, &si_symb, &magfact);
1586 }
1588 if (im->gdes[i].gf == GF_PRINT) {
1589 rrd_infoval_t prline;
1591 if (im->gdes[i].strftm) {
1592 prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1593 strftime(prline.u_str,
1594 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1595 } else if (bad_format(im->gdes[i].format)) {
1596 rrd_set_error
1597 ("bad format for PRINT in '%s'", im->gdes[i].format);
1598 return -1;
1599 } else {
1600 prline.u_str =
1601 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1602 }
1603 grinfo_push(im,
1604 sprintf_alloc
1605 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1606 free(prline.u_str);
1607 } else {
1608 /* GF_GPRINT */
1610 if (im->gdes[i].strftm) {
1611 strftime(im->gdes[i].legend,
1612 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1613 } else {
1614 if (bad_format(im->gdes[i].format)) {
1615 rrd_set_error
1616 ("bad format for GPRINT in '%s'",
1617 im->gdes[i].format);
1618 return -1;
1619 }
1620 #ifdef HAVE_SNPRINTF
1621 snprintf(im->gdes[i].legend,
1622 FMT_LEG_LEN - 2,
1623 im->gdes[i].format, printval, si_symb);
1624 #else
1625 sprintf(im->gdes[i].legend,
1626 im->gdes[i].format, printval, si_symb);
1627 #endif
1628 }
1629 graphelement = 1;
1630 }
1631 break;
1632 case GF_LINE:
1633 case GF_AREA:
1634 case GF_TICK:
1635 graphelement = 1;
1636 break;
1637 case GF_HRULE:
1638 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1639 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1640 };
1641 graphelement = 1;
1642 break;
1643 case GF_VRULE:
1644 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1645 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1646 };
1647 graphelement = 1;
1648 break;
1649 case GF_COMMENT:
1650 case GF_TEXTALIGN:
1651 case GF_DEF:
1652 case GF_CDEF:
1653 case GF_VDEF:
1654 #ifdef WITH_PIECHART
1655 case GF_PART:
1656 #endif
1657 case GF_SHIFT:
1658 case GF_XPORT:
1659 break;
1660 case GF_STACK:
1661 rrd_set_error
1662 ("STACK should already be turned into LINE or AREA here");
1663 return -1;
1664 break;
1665 }
1666 }
1667 return graphelement;
1668 }
1672 /* place legends with color spots */
1673 int leg_place(
1674 image_desc_t *im,
1675 int calc_width)
1676 {
1677 /* graph labels */
1678 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1679 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1680 int fill = 0, fill_last;
1681 double legendwidth; // = im->ximg - 2 * border;
1682 int leg_c = 0;
1683 double leg_x = border;
1684 int leg_y = 0; //im->yimg;
1685 int leg_y_prev = 0; // im->yimg;
1686 int leg_cc;
1687 double glue = 0;
1688 int i, ii, mark = 0;
1689 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1690 int *legspace;
1691 char *tab;
1692 char saved_legend[FMT_LEG_LEN + 5];
1694 if(calc_width){
1695 legendwidth = 0;
1696 }
1697 else{
1698 legendwidth = im->legendwidth - 2 * border;
1699 }
1702 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1703 if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1704 rrd_set_error("malloc for legspace");
1705 return -1;
1706 }
1708 for (i = 0; i < im->gdes_c; i++) {
1709 char prt_fctn; /*special printfunctions */
1710 if(calc_width){
1711 strcpy(saved_legend, im->gdes[i].legend);
1712 }
1714 fill_last = fill;
1715 /* hide legends for rules which are not displayed */
1716 if (im->gdes[i].gf == GF_TEXTALIGN) {
1717 default_txtalign = im->gdes[i].txtalign;
1718 }
1720 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1721 if (im->gdes[i].gf == GF_HRULE
1722 && (im->gdes[i].yrule <
1723 im->minval || im->gdes[i].yrule > im->maxval))
1724 im->gdes[i].legend[0] = '\0';
1725 if (im->gdes[i].gf == GF_VRULE
1726 && (im->gdes[i].xrule <
1727 im->start || im->gdes[i].xrule > im->end))
1728 im->gdes[i].legend[0] = '\0';
1729 }
1731 /* turn \\t into tab */
1732 while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1733 memmove(tab, tab + 1, strlen(tab));
1734 tab[0] = (char) 9;
1735 }
1737 leg_cc = strlen(im->gdes[i].legend);
1738 /* is there a controle code at the end of the legend string ? */
1739 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1740 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1741 leg_cc -= 2;
1742 im->gdes[i].legend[leg_cc] = '\0';
1743 } else {
1744 prt_fctn = '\0';
1745 }
1746 /* only valid control codes */
1747 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1748 prt_fctn != 'r' &&
1749 prt_fctn != 'j' &&
1750 prt_fctn != 'c' &&
1751 prt_fctn != 'u' &&
1752 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1753 free(legspace);
1754 rrd_set_error
1755 ("Unknown control code at the end of '%s\\%c'",
1756 im->gdes[i].legend, prt_fctn);
1757 return -1;
1758 }
1759 /* \n -> \l */
1760 if (prt_fctn == 'n') {
1761 prt_fctn = 'l';
1762 }
1764 /* remove exess space from the end of the legend for \g */
1765 while (prt_fctn == 'g' &&
1766 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1767 leg_cc--;
1768 im->gdes[i].legend[leg_cc] = '\0';
1769 }
1771 if (leg_cc != 0) {
1773 /* no interleg space if string ends in \g */
1774 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1775 if (fill > 0) {
1776 fill += legspace[i];
1777 }
1778 fill +=
1779 gfx_get_text_width(im,
1780 fill + border,
1781 im->
1782 text_prop
1783 [TEXT_PROP_LEGEND].
1784 font_desc,
1785 im->tabwidth, im->gdes[i].legend);
1786 leg_c++;
1787 } else {
1788 legspace[i] = 0;
1789 }
1790 /* who said there was a special tag ... ? */
1791 if (prt_fctn == 'g') {
1792 prt_fctn = '\0';
1793 }
1795 if (prt_fctn == '\0') {
1796 if(calc_width && (fill > legendwidth)){
1797 legendwidth = fill;
1798 }
1799 if (i == im->gdes_c - 1 || fill > legendwidth) {
1800 /* just one legend item is left right or center */
1801 switch (default_txtalign) {
1802 case TXA_RIGHT:
1803 prt_fctn = 'r';
1804 break;
1805 case TXA_CENTER:
1806 prt_fctn = 'c';
1807 break;
1808 case TXA_JUSTIFIED:
1809 prt_fctn = 'j';
1810 break;
1811 default:
1812 prt_fctn = 'l';
1813 break;
1814 }
1815 }
1816 /* is it time to place the legends ? */
1817 if (fill > legendwidth) {
1818 if (leg_c > 1) {
1819 /* go back one */
1820 i--;
1821 fill = fill_last;
1822 leg_c--;
1823 }
1824 }
1825 if (leg_c == 1 && prt_fctn == 'j') {
1826 prt_fctn = 'l';
1827 }
1828 }
1830 if (prt_fctn != '\0') {
1831 leg_x = border;
1832 if (leg_c >= 2 && prt_fctn == 'j') {
1833 glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1834 } else {
1835 glue = 0;
1836 }
1837 if (prt_fctn == 'c')
1838 leg_x = (double)(legendwidth - fill) / 2.0;
1839 if (prt_fctn == 'r')
1840 leg_x = legendwidth - fill + border;
1841 for (ii = mark; ii <= i; ii++) {
1842 if (im->gdes[ii].legend[0] == '\0')
1843 continue; /* skip empty legends */
1844 im->gdes[ii].leg_x = leg_x;
1845 im->gdes[ii].leg_y = leg_y + border;
1846 leg_x +=
1847 (double)gfx_get_text_width(im, leg_x,
1848 im->
1849 text_prop
1850 [TEXT_PROP_LEGEND].
1851 font_desc,
1852 im->tabwidth, im->gdes[ii].legend)
1853 +(double)legspace[ii]
1854 + glue;
1855 }
1856 leg_y_prev = leg_y;
1857 if (leg_x > border || prt_fctn == 's')
1858 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1859 if (prt_fctn == 's')
1860 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1861 if (prt_fctn == 'u')
1862 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1864 if(calc_width && (fill > legendwidth)){
1865 legendwidth = fill;
1866 }
1867 fill = 0;
1868 leg_c = 0;
1869 mark = ii;
1870 }
1872 if(calc_width){
1873 strcpy(im->gdes[i].legend, saved_legend);
1874 }
1875 }
1877 if(calc_width){
1878 im->legendwidth = legendwidth + 2 * border;
1879 }
1880 else{
1881 im->legendheight = leg_y + border * 0.6;
1882 }
1883 free(legspace);
1884 }
1885 return 0;
1886 }
1888 /* create a grid on the graph. it determines what to do
1889 from the values of xsize, start and end */
1891 /* the xaxis labels are determined from the number of seconds per pixel
1892 in the requested graph */
1894 int calc_horizontal_grid(
1895 image_desc_t
1896 *im)
1897 {
1898 double range;
1899 double scaledrange;
1900 int pixel, i;
1901 int gridind = 0;
1902 int decimals, fractionals;
1904 im->ygrid_scale.labfact = 2;
1905 range = im->maxval - im->minval;
1906 scaledrange = range / im->magfact;
1907 /* does the scale of this graph make it impossible to put lines
1908 on it? If so, give up. */
1909 if (isnan(scaledrange)) {
1910 return 0;
1911 }
1913 /* find grid spaceing */
1914 pixel = 1;
1915 if (isnan(im->ygridstep)) {
1916 if (im->extra_flags & ALTYGRID) {
1917 /* find the value with max number of digits. Get number of digits */
1918 decimals =
1919 ceil(log10
1920 (max(fabs(im->maxval), fabs(im->minval)) *
1921 im->viewfactor / im->magfact));
1922 if (decimals <= 0) /* everything is small. make place for zero */
1923 decimals = 1;
1924 im->ygrid_scale.gridstep =
1925 pow((double) 10,
1926 floor(log10(range * im->viewfactor / im->magfact))) /
1927 im->viewfactor * im->magfact;
1928 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1929 im->ygrid_scale.gridstep = 0.1;
1930 /* should have at least 5 lines but no more then 15 */
1931 if (range / im->ygrid_scale.gridstep < 5
1932 && im->ygrid_scale.gridstep >= 30)
1933 im->ygrid_scale.gridstep /= 10;
1934 if (range / im->ygrid_scale.gridstep > 15)
1935 im->ygrid_scale.gridstep *= 10;
1936 if (range / im->ygrid_scale.gridstep > 5) {
1937 im->ygrid_scale.labfact = 1;
1938 if (range / im->ygrid_scale.gridstep > 8
1939 || im->ygrid_scale.gridstep <
1940 1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1941 im->ygrid_scale.labfact = 2;
1942 } else {
1943 im->ygrid_scale.gridstep /= 5;
1944 im->ygrid_scale.labfact = 5;
1945 }
1946 fractionals =
1947 floor(log10
1948 (im->ygrid_scale.gridstep *
1949 (double) im->ygrid_scale.labfact * im->viewfactor /
1950 im->magfact));
1951 if (fractionals < 0) { /* small amplitude. */
1952 int len = decimals - fractionals + 1;
1954 if (im->unitslength < len + 2)
1955 im->unitslength = len + 2;
1956 sprintf(im->ygrid_scale.labfmt,
1957 "%%%d.%df%s", len,
1958 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1959 } else {
1960 int len = decimals + 1;
1962 if (im->unitslength < len + 2)
1963 im->unitslength = len + 2;
1964 sprintf(im->ygrid_scale.labfmt,
1965 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1966 }
1967 } else { /* classic rrd grid */
1968 for (i = 0; ylab[i].grid > 0; i++) {
1969 pixel = im->ysize / (scaledrange / ylab[i].grid);
1970 gridind = i;
1971 if (pixel >= 5)
1972 break;
1973 }
1975 for (i = 0; i < 4; i++) {
1976 if (pixel * ylab[gridind].lfac[i] >=
1977 1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1978 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1979 break;
1980 }
1981 }
1983 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1984 }
1985 } else {
1986 im->ygrid_scale.gridstep = im->ygridstep;
1987 im->ygrid_scale.labfact = im->ylabfact;
1988 }
1989 return 1;
1990 }
1992 int draw_horizontal_grid(
1993 image_desc_t
1994 *im)
1995 {
1996 int i;
1997 double scaledstep;
1998 char graph_label[100];
1999 int nlabels = 0;
2000 double X0 = im->xorigin;
2001 double X1 = im->xorigin + im->xsize;
2002 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2003 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2004 double MaxY;
2005 double second_axis_magfact = 0;
2006 char *second_axis_symb = "";
2008 scaledstep =
2009 im->ygrid_scale.gridstep /
2010 (double) im->magfact * (double) im->viewfactor;
2011 MaxY = scaledstep * (double) egrid;
2012 for (i = sgrid; i <= egrid; i++) {
2013 double Y0 = ytr(im,
2014 im->ygrid_scale.gridstep * i);
2015 double YN = ytr(im,
2016 im->ygrid_scale.gridstep * (i + 1));
2018 if (floor(Y0 + 0.5) >=
2019 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2020 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2021 with the chosen settings. Add a label if required by settings, or if
2022 there is only one label so far and the next grid line is out of bounds. */
2023 if (i % im->ygrid_scale.labfact == 0
2024 || (nlabels == 1
2025 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2026 if (im->symbol == ' ') {
2027 if (im->extra_flags & ALTYGRID) {
2028 sprintf(graph_label,
2029 im->ygrid_scale.labfmt,
2030 scaledstep * (double) i);
2031 } else {
2032 if (MaxY < 10) {
2033 sprintf(graph_label, "%4.1f",
2034 scaledstep * (double) i);
2035 } else {
2036 sprintf(graph_label, "%4.0f",
2037 scaledstep * (double) i);
2038 }
2039 }
2040 } else {
2041 char sisym = (i == 0 ? ' ' : im->symbol);
2043 if (im->extra_flags & ALTYGRID) {
2044 sprintf(graph_label,
2045 im->ygrid_scale.labfmt,
2046 scaledstep * (double) i, sisym);
2047 } else {
2048 if (MaxY < 10) {
2049 sprintf(graph_label, "%4.1f %c",
2050 scaledstep * (double) i, sisym);
2051 } else {
2052 sprintf(graph_label, "%4.0f %c",
2053 scaledstep * (double) i, sisym);
2054 }
2055 }
2056 }
2057 nlabels++;
2058 if (im->second_axis_scale != 0){
2059 char graph_label_right[100];
2060 double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2061 if (im->second_axis_format[0] == '\0'){
2062 if (!second_axis_magfact){
2063 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2064 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2065 }
2066 sval /= second_axis_magfact;
2068 if(MaxY < 10) {
2069 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2070 } else {
2071 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2072 }
2073 }
2074 else {
2075 sprintf(graph_label_right,im->second_axis_format,sval);
2076 }
2077 gfx_text ( im,
2078 X1+7, Y0,
2079 im->graph_col[GRC_FONT],
2080 im->text_prop[TEXT_PROP_AXIS].font_desc,
2081 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2082 graph_label_right );
2083 }
2085 gfx_text(im,
2086 X0 -
2087 im->
2088 text_prop[TEXT_PROP_AXIS].
2089 size, Y0,
2090 im->graph_col[GRC_FONT],
2091 im->
2092 text_prop[TEXT_PROP_AXIS].
2093 font_desc,
2094 im->tabwidth, 0.0,
2095 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2096 gfx_line(im, X0 - 2, Y0, X0, Y0,
2097 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2098 gfx_line(im, X1, Y0, X1 + 2, Y0,
2099 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2100 gfx_dashed_line(im, X0 - 2, Y0,
2101 X1 + 2, Y0,
2102 MGRIDWIDTH,
2103 im->
2104 graph_col
2105 [GRC_MGRID],
2106 im->grid_dash_on, im->grid_dash_off);
2107 } else if (!(im->extra_flags & NOMINOR)) {
2108 gfx_line(im,
2109 X0 - 2, Y0,
2110 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2111 gfx_line(im, X1, Y0, X1 + 2, Y0,
2112 GRIDWIDTH, im->graph_col[GRC_GRID]);
2113 gfx_dashed_line(im, X0 - 1, Y0,
2114 X1 + 1, Y0,
2115 GRIDWIDTH,
2116 im->
2117 graph_col[GRC_GRID],
2118 im->grid_dash_on, im->grid_dash_off);
2119 }
2120 }
2121 }
2122 return 1;
2123 }
2125 /* this is frexp for base 10 */
2126 double frexp10(
2127 double,
2128 double *);
2129 double frexp10(
2130 double x,
2131 double *e)
2132 {
2133 double mnt;
2134 int iexp;
2136 iexp = floor(log((double)fabs(x)) / log((double)10));
2137 mnt = x / pow(10.0, iexp);
2138 if (mnt >= 10.0) {
2139 iexp++;
2140 mnt = x / pow(10.0, iexp);
2141 }
2142 *e = iexp;
2143 return mnt;
2144 }
2147 /* logaritmic horizontal grid */
2148 int horizontal_log_grid(
2149 image_desc_t
2150 *im)
2151 {
2152 double yloglab[][10] = {
2153 {
2154 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2155 0.0, 0.0, 0.0}, {
2156 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2157 0.0, 0.0, 0.0}, {
2158 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2159 0.0, 0.0, 0.0}, {
2160 1.0, 2.0, 4.0,
2161 6.0, 8.0, 10.,
2162 0.0,
2163 0.0, 0.0, 0.0}, {
2164 1.0,
2165 2.0,
2166 3.0,
2167 4.0,
2168 5.0,
2169 6.0,
2170 7.0,
2171 8.0,
2172 9.0,
2173 10.},
2174 {
2175 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2176 };
2177 int i, j, val_exp, min_exp;
2178 double nex; /* number of decades in data */
2179 double logscale; /* scale in logarithmic space */
2180 int exfrac = 1; /* decade spacing */
2181 int mid = -1; /* row in yloglab for major grid */
2182 double mspac; /* smallest major grid spacing (pixels) */
2183 int flab; /* first value in yloglab to use */
2184 double value, tmp, pre_value;
2185 double X0, X1, Y0;
2186 char graph_label[100];
2188 nex = log10(im->maxval / im->minval);
2189 logscale = im->ysize / nex;
2190 /* major spacing for data with high dynamic range */
2191 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2192 if (exfrac == 1)
2193 exfrac = 3;
2194 else
2195 exfrac += 3;
2196 }
2198 /* major spacing for less dynamic data */
2199 do {
2200 /* search best row in yloglab */
2201 mid++;
2202 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2203 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2204 }
2205 while (mspac >
2206 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2207 if (mid)
2208 mid--;
2209 /* find first value in yloglab */
2210 for (flab = 0;
2211 yloglab[mid][flab] < 10
2212 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2213 if (yloglab[mid][flab] == 10.0) {
2214 tmp += 1.0;
2215 flab = 0;
2216 }
2217 val_exp = tmp;
2218 if (val_exp % exfrac)
2219 val_exp += abs(-val_exp % exfrac);
2220 X0 = im->xorigin;
2221 X1 = im->xorigin + im->xsize;
2222 /* draw grid */
2223 pre_value = DNAN;
2224 while (1) {
2226 value = yloglab[mid][flab] * pow(10.0, val_exp);
2227 if (AlmostEqual2sComplement(value, pre_value, 4))
2228 break; /* it seems we are not converging */
2229 pre_value = value;
2230 Y0 = ytr(im, value);
2231 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2232 break;
2233 /* major grid line */
2234 gfx_line(im,
2235 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2236 gfx_line(im, X1, Y0, X1 + 2, Y0,
2237 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2238 gfx_dashed_line(im, X0 - 2, Y0,
2239 X1 + 2, Y0,
2240 MGRIDWIDTH,
2241 im->
2242 graph_col
2243 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2244 /* label */
2245 if (im->extra_flags & FORCE_UNITS_SI) {
2246 int scale;
2247 double pvalue;
2248 char symbol;
2250 scale = floor(val_exp / 3.0);
2251 if (value >= 1.0)
2252 pvalue = pow(10.0, val_exp % 3);
2253 else
2254 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2255 pvalue *= yloglab[mid][flab];
2256 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2257 && ((scale + si_symbcenter) >= 0))
2258 symbol = si_symbol[scale + si_symbcenter];
2259 else
2260 symbol = '?';
2261 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2262 } else {
2263 sprintf(graph_label, "%3.0e", value);
2264 }
2265 if (im->second_axis_scale != 0){
2266 char graph_label_right[100];
2267 double sval = value*im->second_axis_scale+im->second_axis_shift;
2268 if (im->second_axis_format[0] == '\0'){
2269 if (im->extra_flags & FORCE_UNITS_SI) {
2270 double mfac = 1;
2271 char *symb = "";
2272 auto_scale(im,&sval,&symb,&mfac);
2273 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2274 }
2275 else {
2276 sprintf(graph_label_right,"%3.0e", sval);
2277 }
2278 }
2279 else {
2280 sprintf(graph_label_right,im->second_axis_format,sval);
2281 }
2283 gfx_text ( im,
2284 X1+7, Y0,
2285 im->graph_col[GRC_FONT],
2286 im->text_prop[TEXT_PROP_AXIS].font_desc,
2287 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2288 graph_label_right );
2289 }
2291 gfx_text(im,
2292 X0 -
2293 im->
2294 text_prop[TEXT_PROP_AXIS].
2295 size, Y0,
2296 im->graph_col[GRC_FONT],
2297 im->
2298 text_prop[TEXT_PROP_AXIS].
2299 font_desc,
2300 im->tabwidth, 0.0,
2301 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2302 /* minor grid */
2303 if (mid < 4 && exfrac == 1) {
2304 /* find first and last minor line behind current major line
2305 * i is the first line and j tha last */
2306 if (flab == 0) {
2307 min_exp = val_exp - 1;
2308 for (i = 1; yloglab[mid][i] < 10.0; i++);
2309 i = yloglab[mid][i - 1] + 1;
2310 j = 10;
2311 } else {
2312 min_exp = val_exp;
2313 i = yloglab[mid][flab - 1] + 1;
2314 j = yloglab[mid][flab];
2315 }
2317 /* draw minor lines below current major line */
2318 for (; i < j; i++) {
2320 value = i * pow(10.0, min_exp);
2321 if (value < im->minval)
2322 continue;
2323 Y0 = ytr(im, value);
2324 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2325 break;
2326 /* draw lines */
2327 gfx_line(im,
2328 X0 - 2, Y0,
2329 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2330 gfx_line(im, X1, Y0, X1 + 2, Y0,
2331 GRIDWIDTH, im->graph_col[GRC_GRID]);
2332 gfx_dashed_line(im, X0 - 1, Y0,
2333 X1 + 1, Y0,
2334 GRIDWIDTH,
2335 im->
2336 graph_col[GRC_GRID],
2337 im->grid_dash_on, im->grid_dash_off);
2338 }
2339 } else if (exfrac > 1) {
2340 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2341 value = pow(10.0, i);
2342 if (value < im->minval)
2343 continue;
2344 Y0 = ytr(im, value);
2345 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2346 break;
2347 /* draw lines */
2348 gfx_line(im,
2349 X0 - 2, Y0,
2350 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2351 gfx_line(im, X1, Y0, X1 + 2, Y0,
2352 GRIDWIDTH, im->graph_col[GRC_GRID]);
2353 gfx_dashed_line(im, X0 - 1, Y0,
2354 X1 + 1, Y0,
2355 GRIDWIDTH,
2356 im->
2357 graph_col[GRC_GRID],
2358 im->grid_dash_on, im->grid_dash_off);
2359 }
2360 }
2362 /* next decade */
2363 if (yloglab[mid][++flab] == 10.0) {
2364 flab = 0;
2365 val_exp += exfrac;
2366 }
2367 }
2369 /* draw minor lines after highest major line */
2370 if (mid < 4 && exfrac == 1) {
2371 /* find first and last minor line below current major line
2372 * i is the first line and j tha last */
2373 if (flab == 0) {
2374 min_exp = val_exp - 1;
2375 for (i = 1; yloglab[mid][i] < 10.0; i++);
2376 i = yloglab[mid][i - 1] + 1;
2377 j = 10;
2378 } else {
2379 min_exp = val_exp;
2380 i = yloglab[mid][flab - 1] + 1;
2381 j = yloglab[mid][flab];
2382 }
2384 /* draw minor lines below current major line */
2385 for (; i < j; i++) {
2387 value = i * pow(10.0, min_exp);
2388 if (value < im->minval)
2389 continue;
2390 Y0 = ytr(im, value);
2391 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2392 break;
2393 /* draw lines */
2394 gfx_line(im,
2395 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2396 gfx_line(im, X1, Y0, X1 + 2, Y0,
2397 GRIDWIDTH, im->graph_col[GRC_GRID]);
2398 gfx_dashed_line(im, X0 - 1, Y0,
2399 X1 + 1, Y0,
2400 GRIDWIDTH,
2401 im->
2402 graph_col[GRC_GRID],
2403 im->grid_dash_on, im->grid_dash_off);
2404 }
2405 }
2406 /* fancy minor gridlines */
2407 else if (exfrac > 1) {
2408 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2409 value = pow(10.0, i);
2410 if (value < im->minval)
2411 continue;
2412 Y0 = ytr(im, value);
2413 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2414 break;
2415 /* draw lines */
2416 gfx_line(im,
2417 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2418 gfx_line(im, X1, Y0, X1 + 2, Y0,
2419 GRIDWIDTH, im->graph_col[GRC_GRID]);
2420 gfx_dashed_line(im, X0 - 1, Y0,
2421 X1 + 1, Y0,
2422 GRIDWIDTH,
2423 im->
2424 graph_col[GRC_GRID],
2425 im->grid_dash_on, im->grid_dash_off);
2426 }
2427 }
2429 return 1;
2430 }
2433 void vertical_grid(
2434 image_desc_t *im)
2435 {
2436 int xlab_sel; /* which sort of label and grid ? */
2437 time_t ti, tilab, timajor;
2438 long factor;
2439 char graph_label[100];
2440 double X0, Y0, Y1; /* points for filled graph and more */
2441 struct tm tm;
2443 /* the type of time grid is determined by finding
2444 the number of seconds per pixel in the graph */
2445 if (im->xlab_user.minsec == -1) {
2446 factor = (im->end - im->start) / im->xsize;
2447 xlab_sel = 0;
2448 while (xlab[xlab_sel + 1].minsec !=
2449 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2450 xlab_sel++;
2451 } /* pick the last one */
2452 while (xlab[xlab_sel - 1].minsec ==
2453 xlab[xlab_sel].minsec
2454 && xlab[xlab_sel].length > (im->end - im->start)) {
2455 xlab_sel--;
2456 } /* go back to the smallest size */
2457 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2458 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2459 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2460 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2461 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2462 im->xlab_user.labst = xlab[xlab_sel].labst;
2463 im->xlab_user.precis = xlab[xlab_sel].precis;
2464 im->xlab_user.stst = xlab[xlab_sel].stst;
2465 }
2467 /* y coords are the same for every line ... */
2468 Y0 = im->yorigin;
2469 Y1 = im->yorigin - im->ysize;
2470 /* paint the minor grid */
2471 if (!(im->extra_flags & NOMINOR)) {
2472 for (ti = find_first_time(im->start,
2473 im->
2474 xlab_user.
2475 gridtm,
2476 im->
2477 xlab_user.
2478 gridst),
2479 timajor =
2480 find_first_time(im->start,
2481 im->xlab_user.
2482 mgridtm,
2483 im->xlab_user.
2484 mgridst);
2485 ti < im->end;
2486 ti =
2487 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2488 ) {
2489 /* are we inside the graph ? */
2490 if (ti < im->start || ti > im->end)
2491 continue;
2492 while (timajor < ti) {
2493 timajor = find_next_time(timajor,
2494 im->
2495 xlab_user.
2496 mgridtm, im->xlab_user.mgridst);
2497 }
2498 if (ti == timajor)
2499 continue; /* skip as falls on major grid line */
2500 X0 = xtr(im, ti);
2501 gfx_line(im, X0, Y1 - 2, X0, Y1,
2502 GRIDWIDTH, im->graph_col[GRC_GRID]);
2503 gfx_line(im, X0, Y0, X0, Y0 + 2,
2504 GRIDWIDTH, im->graph_col[GRC_GRID]);
2505 gfx_dashed_line(im, X0, Y0 + 1, X0,
2506 Y1 - 1, GRIDWIDTH,
2507 im->
2508 graph_col[GRC_GRID],
2509 im->grid_dash_on, im->grid_dash_off);
2510 }
2511 }
2513 /* paint the major grid */
2514 for (ti = find_first_time(im->start,
2515 im->
2516 xlab_user.
2517 mgridtm,
2518 im->
2519 xlab_user.
2520 mgridst);
2521 ti < im->end;
2522 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2523 ) {
2524 /* are we inside the graph ? */
2525 if (ti < im->start || ti > im->end)
2526 continue;
2527 X0 = xtr(im, ti);
2528 gfx_line(im, X0, Y1 - 2, X0, Y1,
2529 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2530 gfx_line(im, X0, Y0, X0, Y0 + 3,
2531 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2532 gfx_dashed_line(im, X0, Y0 + 3, X0,
2533 Y1 - 2, MGRIDWIDTH,
2534 im->
2535 graph_col
2536 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2537 }
2538 /* paint the labels below the graph */
2539 for (ti =
2540 find_first_time(im->start -
2541 im->xlab_user.
2542 precis / 2,
2543 im->xlab_user.
2544 labtm,
2545 im->xlab_user.
2546 labst);
2547 ti <=
2548 im->end -
2549 im->xlab_user.precis / 2;
2550 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2551 ) {
2552 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2553 /* are we inside the graph ? */
2554 if (tilab < im->start || tilab > im->end)
2555 continue;
2556 #if HAVE_STRFTIME
2557 localtime_r(&tilab, &tm);
2558 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2559 #else
2560 # error "your libc has no strftime I guess we'll abort the exercise here."
2561 #endif
2562 gfx_text(im,
2563 xtr(im, tilab),
2564 Y0 + 3,
2565 im->graph_col[GRC_FONT],
2566 im->
2567 text_prop[TEXT_PROP_AXIS].
2568 font_desc,
2569 im->tabwidth, 0.0,
2570 GFX_H_CENTER, GFX_V_TOP, graph_label);
2571 }
2573 }
2576 void axis_paint(
2577 image_desc_t *im)
2578 {
2579 /* draw x and y axis */
2580 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2581 im->xorigin+im->xsize,im->yorigin-im->ysize,
2582 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2584 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2585 im->xorigin+im->xsize,im->yorigin-im->ysize,
2586 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2588 gfx_line(im, im->xorigin - 4,
2589 im->yorigin,
2590 im->xorigin + im->xsize +
2591 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2592 gfx_line(im, im->xorigin,
2593 im->yorigin + 4,
2594 im->xorigin,
2595 im->yorigin - im->ysize -
2596 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2597 /* arrow for X and Y axis direction */
2598 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 */
2599 im->graph_col[GRC_ARROW]);
2600 gfx_close_path(im);
2601 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 */
2602 im->graph_col[GRC_ARROW]);
2603 gfx_close_path(im);
2604 if (im->second_axis_scale != 0){
2605 gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2606 im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2607 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2608 gfx_new_area ( im,
2609 im->xorigin+im->xsize-2, im->yorigin-im->ysize-2,
2610 im->xorigin+im->xsize+3, im->yorigin-im->ysize-2,
2611 im->xorigin+im->xsize, im->yorigin-im->ysize-7, /* LINEOFFSET */
2612 im->graph_col[GRC_ARROW]);
2613 gfx_close_path(im);
2614 }
2616 }
2618 void grid_paint(
2619 image_desc_t *im)
2620 {
2621 long i;
2622 int res = 0;
2623 double X0, Y0; /* points for filled graph and more */
2624 struct gfx_color_t water_color;
2626 if (im->draw_3d_border > 0) {
2627 /* draw 3d border */
2628 i = im->draw_3d_border;
2629 gfx_new_area(im, 0, im->yimg,
2630 i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2631 gfx_add_point(im, im->ximg - i, i);
2632 gfx_add_point(im, im->ximg, 0);
2633 gfx_add_point(im, 0, 0);
2634 gfx_close_path(im);
2635 gfx_new_area(im, i, im->yimg - i,
2636 im->ximg - i,
2637 im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2638 gfx_add_point(im, im->ximg, 0);
2639 gfx_add_point(im, im->ximg, im->yimg);
2640 gfx_add_point(im, 0, im->yimg);
2641 gfx_close_path(im);
2642 }
2643 if (im->draw_x_grid == 1)
2644 vertical_grid(im);
2645 if (im->draw_y_grid == 1) {
2646 if (im->logarithmic) {
2647 res = horizontal_log_grid(im);
2648 } else {
2649 res = draw_horizontal_grid(im);
2650 }
2652 /* dont draw horizontal grid if there is no min and max val */
2653 if (!res) {
2654 char *nodata = "No Data found";
2656 gfx_text(im, im->ximg / 2,
2657 (2 * im->yorigin -
2658 im->ysize) / 2,
2659 im->graph_col[GRC_FONT],
2660 im->
2661 text_prop[TEXT_PROP_AXIS].
2662 font_desc,
2663 im->tabwidth, 0.0,
2664 GFX_H_CENTER, GFX_V_CENTER, nodata);
2665 }
2666 }
2668 /* yaxis unit description */
2669 if (im->ylegend[0] != '\0'){
2670 gfx_text(im,
2671 im->xOriginLegendY+10,
2672 im->yOriginLegendY,
2673 im->graph_col[GRC_FONT],
2674 im->
2675 text_prop[TEXT_PROP_UNIT].
2676 font_desc,
2677 im->tabwidth,
2678 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2680 }
2681 if (im->second_axis_legend[0] != '\0'){
2682 gfx_text( im,
2683 im->xOriginLegendY2+10,
2684 im->yOriginLegendY2,
2685 im->graph_col[GRC_FONT],
2686 im->text_prop[TEXT_PROP_UNIT].font_desc,
2687 im->tabwidth,
2688 RRDGRAPH_YLEGEND_ANGLE,
2689 GFX_H_CENTER, GFX_V_CENTER,
2690 im->second_axis_legend);
2691 }
2693 /* graph title */
2694 gfx_text(im,
2695 im->xOriginTitle, im->yOriginTitle+6,
2696 im->graph_col[GRC_FONT],
2697 im->
2698 text_prop[TEXT_PROP_TITLE].
2699 font_desc,
2700 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2701 /* rrdtool 'logo' */
2702 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2703 water_color = im->graph_col[GRC_FONT];
2704 water_color.alpha = 0.3;
2705 double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2706 gfx_text(im, xpos, 5,
2707 water_color,
2708 im->
2709 text_prop[TEXT_PROP_WATERMARK].
2710 font_desc, im->tabwidth,
2711 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2712 }
2713 /* graph watermark */
2714 if (im->watermark[0] != '\0') {
2715 water_color = im->graph_col[GRC_FONT];
2716 water_color.alpha = 0.3;
2717 gfx_text(im,
2718 im->ximg / 2, im->yimg - 6,
2719 water_color,
2720 im->
2721 text_prop[TEXT_PROP_WATERMARK].
2722 font_desc, im->tabwidth, 0,
2723 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2724 }
2726 /* graph labels */
2727 if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2728 for (i = 0; i < im->gdes_c; i++) {
2729 if (im->gdes[i].legend[0] == '\0')
2730 continue;
2731 /* im->gdes[i].leg_y is the bottom of the legend */
2732 X0 = im->xOriginLegend + im->gdes[i].leg_x;
2733 Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2734 gfx_text(im, X0, Y0,
2735 im->graph_col[GRC_FONT],
2736 im->
2737 text_prop
2738 [TEXT_PROP_LEGEND].font_desc,
2739 im->tabwidth, 0.0,
2740 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2741 /* The legend for GRAPH items starts with "M " to have
2742 enough space for the box */
2743 if (im->gdes[i].gf != GF_PRINT &&
2744 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2745 double boxH, boxV;
2746 double X1, Y1;
2748 boxH = gfx_get_text_width(im, 0,
2749 im->
2750 text_prop
2751 [TEXT_PROP_LEGEND].
2752 font_desc,
2753 im->tabwidth, "o") * 1.2;
2754 boxV = boxH;
2755 /* shift the box up a bit */
2756 Y0 -= boxV * 0.4;
2758 if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */
2759 cairo_save(im->cr);
2760 cairo_new_path(im->cr);
2761 cairo_set_line_width(im->cr, 1.0);
2762 gfx_line(im,
2763 X0, Y0 - boxV / 2,
2764 X0 + boxH, Y0 - boxV / 2,
2765 1.0, im->gdes[i].col);
2766 gfx_close_path(im);
2767 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2768 cairo_save(im->cr);
2769 cairo_new_path(im->cr);
2770 cairo_set_line_width(im->cr, 1.0);
2771 gfx_line(im,
2772 X0 + boxH / 2, Y0,
2773 X0 + boxH / 2, Y0 - boxV,
2774 1.0, im->gdes[i].col);
2775 gfx_close_path(im);
2776 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2777 cairo_save(im->cr);
2778 cairo_new_path(im->cr);
2779 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2780 gfx_line(im,
2781 X0, Y0,
2782 X0 + boxH, Y0 - boxV,
2783 im->gdes[i].linewidth, im->gdes[i].col);
2784 gfx_close_path(im);
2785 } else {
2786 /* make sure transparent colors show up the same way as in the graph */
2787 gfx_new_area(im,
2788 X0, Y0 - boxV,
2789 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2790 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2791 gfx_close_path(im);
2792 gfx_new_area(im, X0, Y0 - boxV, X0,
2793 Y0, X0 + boxH, Y0, im->gdes[i].col);
2794 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2795 gfx_close_path(im);
2796 cairo_save(im->cr);
2797 cairo_new_path(im->cr);
2798 cairo_set_line_width(im->cr, 1.0);
2799 X1 = X0 + boxH;
2800 Y1 = Y0 - boxV;
2801 gfx_line_fit(im, &X0, &Y0);
2802 gfx_line_fit(im, &X1, &Y1);
2803 cairo_move_to(im->cr, X0, Y0);
2804 cairo_line_to(im->cr, X1, Y0);
2805 cairo_line_to(im->cr, X1, Y1);
2806 cairo_line_to(im->cr, X0, Y1);
2807 cairo_close_path(im->cr);
2808 cairo_set_source_rgba(im->cr,
2809 im->graph_col[GRC_FRAME].red,
2810 im->graph_col[GRC_FRAME].green,
2811 im->graph_col[GRC_FRAME].blue,
2812 im->graph_col[GRC_FRAME].alpha);
2813 }
2814 if (im->gdes[i].dash) {
2815 /* make box borders in legend dashed if the graph is dashed */
2816 double dashes[] = {
2817 3.0
2818 };
2819 cairo_set_dash(im->cr, dashes, 1, 0.0);
2820 }
2821 cairo_stroke(im->cr);
2822 cairo_restore(im->cr);
2823 }
2824 }
2825 }
2826 }
2829 /*****************************************************
2830 * lazy check make sure we rely need to create this graph
2831 *****************************************************/
2833 int lazy_check(
2834 image_desc_t *im)
2835 {
2836 FILE *fd = NULL;
2837 int size = 1;
2838 struct stat imgstat;
2840 if (im->lazy == 0)
2841 return 0; /* no lazy option */
2842 if (strlen(im->graphfile) == 0)
2843 return 0; /* inmemory option */
2844 if (stat(im->graphfile, &imgstat) != 0)
2845 return 0; /* can't stat */
2846 /* one pixel in the existing graph is more then what we would
2847 change here ... */
2848 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2849 return 0;
2850 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2851 return 0; /* the file does not exist */
2852 switch (im->imgformat) {
2853 case IF_PNG:
2854 size = PngSize(fd, &(im->ximg), &(im->yimg));
2855 break;
2856 default:
2857 size = 1;
2858 }
2859 fclose(fd);
2860 return size;
2861 }
2864 int graph_size_location(
2865 image_desc_t
2866 *im,
2867 int elements)
2868 {
2869 /* The actual size of the image to draw is determined from
2870 ** several sources. The size given on the command line is
2871 ** the graph area but we need more as we have to draw labels
2872 ** and other things outside the graph area. If the option
2873 ** --full-size-mode is selected the size defines the total
2874 ** image size and the size available for the graph is
2875 ** calculated.
2876 */
2878 /** +---+-----------------------------------+
2879 ** | y |...............graph title.........|
2880 ** | +---+-------------------------------+
2881 ** | a | y | |
2882 ** | x | | |
2883 ** | i | a | |
2884 ** | s | x | main graph area |
2885 ** | | i | |
2886 ** | t | s | |
2887 ** | i | | |
2888 ** | t | l | |
2889 ** | l | b +-------------------------------+
2890 ** | e | l | x axis labels |
2891 ** +---+---+-------------------------------+
2892 ** |....................legends............|
2893 ** +---------------------------------------+
2894 ** | watermark |
2895 ** +---------------------------------------+
2896 */
2898 int Xvertical = 0, Xvertical2 = 0, Ytitle =
2899 0, Xylabel = 0, Xmain = 0, Ymain =
2900 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2902 // no legends and no the shall be plotted it's easy
2903 if (im->extra_flags & ONLY_GRAPH) {
2904 im->xorigin = 0;
2905 im->ximg = im->xsize;
2906 im->yimg = im->ysize;
2907 im->yorigin = im->ysize;
2908 ytr(im, DNAN);
2909 return 0;
2910 }
2912 if(im->watermark[0] != '\0') {
2913 Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2914 }
2916 // calculate the width of the left vertical legend
2917 if (im->ylegend[0] != '\0') {
2918 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2919 }
2921 // calculate the width of the right vertical legend
2922 if (im->second_axis_legend[0] != '\0') {
2923 Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2924 }
2925 else{
2926 Xvertical2 = Xspacing;
2927 }
2929 if (im->title[0] != '\0') {
2930 /* The title is placed "inbetween" two text lines so it
2931 ** automatically has some vertical spacing. The horizontal
2932 ** spacing is added here, on each side.
2933 */
2934 /* if necessary, reduce the font size of the title until it fits the image width */
2935 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2936 }
2937 else{
2938 // we have no title; get a little clearing from the top
2939 Ytitle = 1.5 * Yspacing;
2940 }
2942 if (elements) {
2943 if (im->draw_x_grid) {
2944 // calculate the height of the horizontal labelling
2945 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2946 }
2947 if (im->draw_y_grid || im->forceleftspace) {
2948 // calculate the width of the vertical labelling
2949 Xylabel =
2950 gfx_get_text_width(im, 0,
2951 im->text_prop[TEXT_PROP_AXIS].font_desc,
2952 im->tabwidth, "0") * im->unitslength;
2953 }
2954 }
2956 // add some space to the labelling
2957 Xylabel += Xspacing;
2959 /* If the legend is printed besides the graph the width has to be
2960 ** calculated first. Placing the legend north or south of the
2961 ** graph requires the width calculation first, so the legend is
2962 ** skipped for the moment.
2963 */
2964 im->legendheight = 0;
2965 im->legendwidth = 0;
2966 if (!(im->extra_flags & NOLEGEND)) {
2967 if(im->legendposition == WEST || im->legendposition == EAST){
2968 if (leg_place(im, 1) == -1){
2969 return -1;
2970 }
2971 }
2972 }
2974 if (im->extra_flags & FULL_SIZE_MODE) {
2976 /* The actual size of the image to draw has been determined by the user.
2977 ** The graph area is the space remaining after accounting for the legend,
2978 ** the watermark, the axis labels, and the title.
2979 */
2980 im->ximg = im->xsize;
2981 im->yimg = im->ysize;
2982 Xmain = im->ximg;
2983 Ymain = im->yimg;
2985 /* Now calculate the total size. Insert some spacing where
2986 desired. im->xorigin and im->yorigin need to correspond
2987 with the lower left corner of the main graph area or, if
2988 this one is not set, the imaginary box surrounding the
2989 pie chart area. */
2990 /* Initial size calculation for the main graph area */
2992 Xmain -= Xylabel;// + Xspacing;
2993 if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2994 Xmain -= im->legendwidth;// + Xspacing;
2995 }
2996 if (im->second_axis_scale != 0){
2997 Xmain -= Xylabel;
2998 }
2999 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3000 Xmain -= Xspacing;
3001 }
3003 Xmain -= Xvertical + Xvertical2;
3005 /* limit the remaining space to 0 */
3006 if(Xmain < 1){
3007 Xmain = 1;
3008 }
3009 im->xsize = Xmain;
3011 /* Putting the legend north or south, the height can now be calculated */
3012 if (!(im->extra_flags & NOLEGEND)) {
3013 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3014 im->legendwidth = im->ximg;
3015 if (leg_place(im, 0) == -1){
3016 return -1;
3017 }
3018 }
3019 }
3021 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3022 Ymain -= Yxlabel + im->legendheight;
3023 }
3024 else{
3025 Ymain -= Yxlabel;
3026 }
3028 /* reserve space for the title *or* some padding above the graph */
3029 Ymain -= Ytitle;
3031 /* reserve space for padding below the graph */
3032 if (im->extra_flags & NOLEGEND) {
3033 Ymain -= Yspacing;
3034 }
3036 if (im->watermark[0] != '\0') {
3037 Ymain -= Ywatermark;
3038 }
3039 /* limit the remaining height to 0 */
3040 if(Ymain < 1){
3041 Ymain = 1;
3042 }
3043 im->ysize = Ymain;
3044 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
3046 /* The actual size of the image to draw is determined from
3047 ** several sources. The size given on the command line is
3048 ** the graph area but we need more as we have to draw labels
3049 ** and other things outside the graph area.
3050 */
3052 if (elements) {
3053 Xmain = im->xsize; // + Xspacing;
3054 Ymain = im->ysize;
3055 }
3057 im->ximg = Xmain + Xylabel;
3058 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3059 im->ximg += Xspacing;
3060 }
3062 if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3063 im->ximg += im->legendwidth;// + Xspacing;
3064 }
3065 if (im->second_axis_scale != 0){
3066 im->ximg += Xylabel;
3067 }
3069 im->ximg += Xvertical + Xvertical2;
3071 if (!(im->extra_flags & NOLEGEND)) {
3072 if(im->legendposition == NORTH || im->legendposition == SOUTH){
3073 im->legendwidth = im->ximg;
3074 if (leg_place(im, 0) == -1){
3075 return -1;
3076 }
3077 }
3078 }
3080 im->yimg = Ymain + Yxlabel;
3081 if( (im->legendposition == NORTH || im->legendposition == SOUTH) && !(im->extra_flags & NOLEGEND) ){
3082 im->yimg += im->legendheight;
3083 }
3085 /* reserve space for the title *or* some padding above the graph */
3086 if (Ytitle) {
3087 im->yimg += Ytitle;
3088 } else {
3089 im->yimg += 1.5 * Yspacing;
3090 }
3091 /* reserve space for padding below the graph */
3092 if (im->extra_flags & NOLEGEND) {
3093 im->yimg += Yspacing;
3094 }
3096 if (im->watermark[0] != '\0') {
3097 im->yimg += Ywatermark;
3098 }
3099 }
3102 /* In case of putting the legend in west or east position the first
3103 ** legend calculation might lead to wrong positions if some items
3104 ** are not aligned on the left hand side (e.g. centered) as the
3105 ** legendwidth wight have been increased after the item was placed.
3106 ** In this case the positions have to be recalculated.
3107 */
3108 if (!(im->extra_flags & NOLEGEND)) {
3109 if(im->legendposition == WEST || im->legendposition == EAST){
3110 if (leg_place(im, 0) == -1){
3111 return -1;
3112 }
3113 }
3114 }
3116 /* After calculating all dimensions
3117 ** it is now possible to calculate
3118 ** all offsets.
3119 */
3120 switch(im->legendposition){
3121 case NORTH:
3122 im->xOriginTitle = Xvertical + Xylabel + (im->xsize / 2);
3123 im->yOriginTitle = 0;
3125 im->xOriginLegend = 0;
3126 im->yOriginLegend = Ytitle;
3128 im->xOriginLegendY = 0;
3129 im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3131 im->xorigin = Xvertical + Xylabel;
3132 im->yorigin = Ytitle + im->legendheight + Ymain;
3134 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3135 if (im->second_axis_scale != 0){
3136 im->xOriginLegendY2 += Xylabel;
3137 }
3138 im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3140 break;
3142 case WEST:
3143 im->xOriginTitle = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3144 im->yOriginTitle = 0;
3146 im->xOriginLegend = 0;
3147 im->yOriginLegend = Ytitle;
3149 im->xOriginLegendY = im->legendwidth;
3150 im->yOriginLegendY = Ytitle + (Ymain / 2);
3152 im->xorigin = im->legendwidth + Xvertical + Xylabel;
3153 im->yorigin = Ytitle + Ymain;
3155 im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3156 if (im->second_axis_scale != 0){
3157 im->xOriginLegendY2 += Xylabel;
3158 }
3159 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3161 break;
3163 case SOUTH:
3164 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3165 im->yOriginTitle = 0;
3167 im->xOriginLegend = 0;
3168 im->yOriginLegend = Ytitle + Ymain + Yxlabel;
3170 im->xOriginLegendY = 0;
3171 im->yOriginLegendY = Ytitle + (Ymain / 2);
3173 im->xorigin = Xvertical + Xylabel;
3174 im->yorigin = Ytitle + Ymain;
3176 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3177 if (im->second_axis_scale != 0){
3178 im->xOriginLegendY2 += Xylabel;
3179 }
3180 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3182 break;
3184 case EAST:
3185 im->xOriginTitle = Xvertical + Xylabel + im->xsize / 2;
3186 im->yOriginTitle = 0;
3188 im->xOriginLegend = Xvertical + Xylabel + Xmain + Xvertical2;
3189 if (im->second_axis_scale != 0){
3190 im->xOriginLegend += Xylabel;
3191 }
3192 im->yOriginLegend = Ytitle;
3194 im->xOriginLegendY = 0;
3195 im->yOriginLegendY = Ytitle + (Ymain / 2);
3197 im->xorigin = Xvertical + Xylabel;
3198 im->yorigin = Ytitle + Ymain;
3200 im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3201 if (im->second_axis_scale != 0){
3202 im->xOriginLegendY2 += Xylabel;
3203 }
3204 im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3206 if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3207 im->xOriginTitle += Xspacing;
3208 im->xOriginLegend += Xspacing;
3209 im->xOriginLegendY += Xspacing;
3210 im->xorigin += Xspacing;
3211 im->xOriginLegendY2 += Xspacing;
3212 }
3213 break;
3214 }
3216 xtr(im, 0);
3217 ytr(im, DNAN);
3218 return 0;
3219 }
3221 static cairo_status_t cairo_output(
3222 void *closure,
3223 const unsigned char
3224 *data,
3225 unsigned int length)
3226 {
3227 image_desc_t *im = (image_desc_t*)closure;
3229 im->rendered_image =
3230 (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3231 if (im->rendered_image == NULL)
3232 return CAIRO_STATUS_WRITE_ERROR;
3233 memcpy(im->rendered_image + im->rendered_image_size, data, length);
3234 im->rendered_image_size += length;
3235 return CAIRO_STATUS_SUCCESS;
3236 }
3238 /* draw that picture thing ... */
3239 int graph_paint(
3240 image_desc_t *im)
3241 {
3242 int i, ii;
3243 int lazy = lazy_check(im);
3244 double areazero = 0.0;
3245 graph_desc_t *lastgdes = NULL;
3246 rrd_infoval_t info;
3248 // PangoFontMap *font_map = pango_cairo_font_map_get_default();
3250 /* pull the data from the rrd files ... */
3251 if (data_fetch(im) == -1)
3252 return -1;
3253 /* evaluate VDEF and CDEF operations ... */
3254 if (data_calc(im) == -1)
3255 return -1;
3256 /* calculate and PRINT and GPRINT definitions. We have to do it at
3257 * this point because it will affect the length of the legends
3258 * if there are no graph elements (i==0) we stop here ...
3259 * if we are lazy, try to quit ...
3260 */
3261 i = print_calc(im);
3262 if (i < 0)
3263 return -1;
3265 /* if we want and can be lazy ... quit now */
3266 if (i == 0)
3267 return 0;
3269 /**************************************************************
3270 *** Calculating sizes and locations became a bit confusing ***
3271 *** so I moved this into a separate function. ***
3272 **************************************************************/
3273 if (graph_size_location(im, i) == -1)
3274 return -1;
3276 info.u_cnt = im->xorigin;
3277 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3278 info.u_cnt = im->yorigin - im->ysize;
3279 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3280 info.u_cnt = im->xsize;
3281 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3282 info.u_cnt = im->ysize;
3283 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3284 info.u_cnt = im->ximg;
3285 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3286 info.u_cnt = im->yimg;
3287 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3288 info.u_cnt = im->start;
3289 grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3290 info.u_cnt = im->end;
3291 grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3293 /* if we want and can be lazy ... quit now */
3294 if (lazy)
3295 return 0;
3297 /* get actual drawing data and find min and max values */
3298 if (data_proc(im) == -1)
3299 return -1;
3300 if (!im->logarithmic) {
3301 si_unit(im);
3302 }
3304 /* identify si magnitude Kilo, Mega Giga ? */
3305 if (!im->rigid && !im->logarithmic)
3306 expand_range(im); /* make sure the upper and lower limit are
3307 sensible values */
3309 info.u_val = im->minval;
3310 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3311 info.u_val = im->maxval;
3312 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3315 if (!calc_horizontal_grid(im))
3316 return -1;
3317 /* reset precalc */
3318 ytr(im, DNAN);
3319 /* if (im->gridfit)
3320 apply_gridfit(im); */
3321 /* the actual graph is created by going through the individual
3322 graph elements and then drawing them */
3323 cairo_surface_destroy(im->surface);
3324 switch (im->imgformat) {
3325 case IF_PNG:
3326 im->surface =
3327 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3328 im->ximg * im->zoom,
3329 im->yimg * im->zoom);
3330 break;
3331 case IF_PDF:
3332 im->gridfit = 0;
3333 im->surface = strlen(im->graphfile)
3334 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3335 im->yimg * im->zoom)
3336 : cairo_pdf_surface_create_for_stream
3337 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3338 break;
3339 case IF_EPS:
3340 im->gridfit = 0;
3341 im->surface = strlen(im->graphfile)
3342 ?
3343 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3344 im->yimg * im->zoom)
3345 : cairo_ps_surface_create_for_stream
3346 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3347 break;
3348 case IF_SVG:
3349 im->gridfit = 0;
3350 im->surface = strlen(im->graphfile)
3351 ?
3352 cairo_svg_surface_create(im->
3353 graphfile,
3354 im->ximg * im->zoom, im->yimg * im->zoom)
3355 : cairo_svg_surface_create_for_stream
3356 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3357 cairo_svg_surface_restrict_to_version
3358 (im->surface, CAIRO_SVG_VERSION_1_1);
3359 break;
3360 };
3361 cairo_destroy(im->cr);
3362 im->cr = cairo_create(im->surface);
3363 cairo_set_antialias(im->cr, im->graph_antialias);
3364 cairo_scale(im->cr, im->zoom, im->zoom);
3365 // pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3366 gfx_new_area(im, 0, 0, 0, im->yimg,
3367 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3368 gfx_add_point(im, im->ximg, 0);
3369 gfx_close_path(im);
3370 gfx_new_area(im, im->xorigin,
3371 im->yorigin,
3372 im->xorigin +
3373 im->xsize, im->yorigin,
3374 im->xorigin +
3375 im->xsize,
3376 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3377 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3378 gfx_close_path(im);
3379 cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3380 im->xsize, im->ysize + 2.0);
3381 cairo_clip(im->cr);
3382 if (im->minval > 0.0)
3383 areazero = im->minval;
3384 if (im->maxval < 0.0)
3385 areazero = im->maxval;
3386 for (i = 0; i < im->gdes_c; i++) {
3387 switch (im->gdes[i].gf) {
3388 case GF_CDEF:
3389 case GF_VDEF:
3390 case GF_DEF:
3391 case GF_PRINT:
3392 case GF_GPRINT:
3393 case GF_COMMENT:
3394 case GF_TEXTALIGN:
3395 case GF_HRULE:
3396 case GF_VRULE:
3397 case GF_XPORT:
3398 case GF_SHIFT:
3399 break;
3400 case GF_TICK:
3401 for (ii = 0; ii < im->xsize; ii++) {
3402 if (!isnan(im->gdes[i].p_data[ii])
3403 && im->gdes[i].p_data[ii] != 0.0) {
3404 if (im->gdes[i].yrule > 0) {
3405 gfx_line(im,
3406 im->xorigin + ii,
3407 im->yorigin + 1.0,
3408 im->xorigin + ii,
3409 im->yorigin -
3410 im->gdes[i].yrule *
3411 im->ysize, 1.0, im->gdes[i].col);
3412 } else if (im->gdes[i].yrule < 0) {
3413 gfx_line(im,
3414 im->xorigin + ii,
3415 im->yorigin - im->ysize - 1.0,
3416 im->xorigin + ii,
3417 im->yorigin - im->ysize -
3418 im->gdes[i].
3419 yrule *
3420 im->ysize, 1.0, im->gdes[i].col);
3421 }
3422 }
3423 }
3424 break;
3425 case GF_LINE:
3426 case GF_AREA:
3427 /* fix data points at oo and -oo */
3428 for (ii = 0; ii < im->xsize; ii++) {
3429 if (isinf(im->gdes[i].p_data[ii])) {
3430 if (im->gdes[i].p_data[ii] > 0) {
3431 im->gdes[i].p_data[ii] = im->maxval;
3432 } else {
3433 im->gdes[i].p_data[ii] = im->minval;
3434 }
3436 }
3437 } /* for */
3439 /* *******************************************************
3440 a ___. (a,t)
3441 | | ___
3442 ____| | | |
3443 | |___|
3444 -------|--t-1--t--------------------------------
3446 if we know the value at time t was a then
3447 we draw a square from t-1 to t with the value a.
3449 ********************************************************* */
3450 if (im->gdes[i].col.alpha != 0.0) {
3451 /* GF_LINE and friend */
3452 if (im->gdes[i].gf == GF_LINE) {
3453 double last_y = 0.0;
3454 int draw_on = 0;
3456 cairo_save(im->cr);
3457 cairo_new_path(im->cr);
3458 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3459 if (im->gdes[i].dash) {
3460 cairo_set_dash(im->cr,
3461 im->gdes[i].p_dashes,
3462 im->gdes[i].ndash, im->gdes[i].offset);
3463 }
3465 for (ii = 1; ii < im->xsize; ii++) {
3466 if (isnan(im->gdes[i].p_data[ii])
3467 || (im->slopemode == 1
3468 && isnan(im->gdes[i].p_data[ii - 1]))) {
3469 draw_on = 0;
3470 continue;
3471 }
3472 if (draw_on == 0) {
3473 last_y = ytr(im, im->gdes[i].p_data[ii]);
3474 if (im->slopemode == 0) {
3475 double x = ii - 1 + im->xorigin;
3476 double y = last_y;
3478 gfx_line_fit(im, &x, &y);
3479 cairo_move_to(im->cr, x, y);
3480 x = ii + im->xorigin;
3481 y = last_y;
3482 gfx_line_fit(im, &x, &y);
3483 cairo_line_to(im->cr, x, y);
3484 } else {
3485 double x = ii - 1 + im->xorigin;
3486 double y =
3487 ytr(im, im->gdes[i].p_data[ii - 1]);
3488 gfx_line_fit(im, &x, &y);
3489 cairo_move_to(im->cr, x, y);
3490 x = ii + im->xorigin;
3491 y = last_y;
3492 gfx_line_fit(im, &x, &y);
3493 cairo_line_to(im->cr, x, y);
3494 }
3495 draw_on = 1;
3496 } else {
3497 double x1 = ii + im->xorigin;
3498 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3500 if (im->slopemode == 0
3501 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3502 double x = ii - 1 + im->xorigin;
3503 double y = y1;
3505 gfx_line_fit(im, &x, &y);
3506 cairo_line_to(im->cr, x, y);
3507 };
3508 last_y = y1;
3509 gfx_line_fit(im, &x1, &y1);
3510 cairo_line_to(im->cr, x1, y1);
3511 };
3512 }
3513 cairo_set_source_rgba(im->cr,
3514 im->gdes[i].
3515 col.red,
3516 im->gdes[i].
3517 col.green,
3518 im->gdes[i].
3519 col.blue, im->gdes[i].col.alpha);
3520 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3521 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3522 cairo_stroke(im->cr);
3523 cairo_restore(im->cr);
3524 } else {
3525 int idxI = -1;
3526 double *foreY =
3527 (double *) malloc(sizeof(double) * im->xsize * 2);
3528 double *foreX =
3529 (double *) malloc(sizeof(double) * im->xsize * 2);
3530 double *backY =
3531 (double *) malloc(sizeof(double) * im->xsize * 2);
3532 double *backX =
3533 (double *) malloc(sizeof(double) * im->xsize * 2);
3534 int drawem = 0;
3536 for (ii = 0; ii <= im->xsize; ii++) {
3537 double ybase, ytop;
3539 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3540 int cntI = 1;
3541 int lastI = 0;
3543 while (cntI < idxI
3544 &&
3545 AlmostEqual2sComplement(foreY
3546 [lastI],
3547 foreY[cntI], 4)
3548 &&
3549 AlmostEqual2sComplement(foreY
3550 [lastI],
3551 foreY
3552 [cntI + 1], 4)) {
3553 cntI++;
3554 }
3555 gfx_new_area(im,
3556 backX[0], backY[0],
3557 foreX[0], foreY[0],
3558 foreX[cntI],
3559 foreY[cntI], im->gdes[i].col);
3560 while (cntI < idxI) {
3561 lastI = cntI;
3562 cntI++;
3563 while (cntI < idxI
3564 &&
3565 AlmostEqual2sComplement(foreY
3566 [lastI],
3567 foreY[cntI], 4)
3568 &&
3569 AlmostEqual2sComplement(foreY
3570 [lastI],
3571 foreY
3572 [cntI
3573 + 1], 4)) {
3574 cntI++;
3575 }
3576 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3577 }
3578 gfx_add_point(im, backX[idxI], backY[idxI]);
3579 while (idxI > 1) {
3580 lastI = idxI;
3581 idxI--;
3582 while (idxI > 1
3583 &&
3584 AlmostEqual2sComplement(backY
3585 [lastI],
3586 backY[idxI], 4)
3587 &&
3588 AlmostEqual2sComplement(backY
3589 [lastI],
3590 backY
3591 [idxI
3592 - 1], 4)) {
3593 idxI--;
3594 }
3595 gfx_add_point(im, backX[idxI], backY[idxI]);
3596 }
3597 idxI = -1;
3598 drawem = 0;
3599 gfx_close_path(im);
3600 }
3601 if (drawem != 0) {
3602 drawem = 0;
3603 idxI = -1;
3604 }
3605 if (ii == im->xsize)
3606 break;
3607 if (im->slopemode == 0 && ii == 0) {
3608 continue;
3609 }
3610 if (isnan(im->gdes[i].p_data[ii])) {
3611 drawem = 1;
3612 continue;
3613 }
3614 ytop = ytr(im, im->gdes[i].p_data[ii]);
3615 if (lastgdes && im->gdes[i].stack) {
3616 ybase = ytr(im, lastgdes->p_data[ii]);
3617 } else {
3618 ybase = ytr(im, areazero);
3619 }
3620 if (ybase == ytop) {
3621 drawem = 1;
3622 continue;
3623 }
3625 if (ybase > ytop) {
3626 double extra = ytop;
3628 ytop = ybase;
3629 ybase = extra;
3630 }
3631 if (im->slopemode == 0) {
3632 backY[++idxI] = ybase - 0.2;
3633 backX[idxI] = ii + im->xorigin - 1;
3634 foreY[idxI] = ytop + 0.2;
3635 foreX[idxI] = ii + im->xorigin - 1;
3636 }
3637 backY[++idxI] = ybase - 0.2;
3638 backX[idxI] = ii + im->xorigin;
3639 foreY[idxI] = ytop + 0.2;
3640 foreX[idxI] = ii + im->xorigin;
3641 }
3642 /* close up any remaining area */
3643 free(foreY);
3644 free(foreX);
3645 free(backY);
3646 free(backX);
3647 } /* else GF_LINE */
3648 }
3649 /* if color != 0x0 */
3650 /* make sure we do not run into trouble when stacking on NaN */
3651 for (ii = 0; ii < im->xsize; ii++) {
3652 if (isnan(im->gdes[i].p_data[ii])) {
3653 if (lastgdes && (im->gdes[i].stack)) {
3654 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3655 } else {
3656 im->gdes[i].p_data[ii] = areazero;
3657 }
3658 }
3659 }
3660 lastgdes = &(im->gdes[i]);
3661 break;
3662 case GF_STACK:
3663 rrd_set_error
3664 ("STACK should already be turned into LINE or AREA here");
3665 return -1;
3666 break;
3667 } /* switch */
3668 }
3669 cairo_reset_clip(im->cr);
3671 /* grid_paint also does the text */
3672 if (!(im->extra_flags & ONLY_GRAPH))
3673 grid_paint(im);
3674 if (!(im->extra_flags & ONLY_GRAPH))
3675 axis_paint(im);
3676 /* the RULES are the last thing to paint ... */
3677 for (i = 0; i < im->gdes_c; i++) {
3679 switch (im->gdes[i].gf) {
3680 case GF_HRULE:
3681 if (im->gdes[i].yrule >= im->minval
3682 && im->gdes[i].yrule <= im->maxval) {
3683 cairo_save(im->cr);
3684 if (im->gdes[i].dash) {
3685 cairo_set_dash(im->cr,
3686 im->gdes[i].p_dashes,
3687 im->gdes[i].ndash, im->gdes[i].offset);
3688 }
3689 gfx_line(im, im->xorigin,
3690 ytr(im, im->gdes[i].yrule),
3691 im->xorigin + im->xsize,
3692 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3693 cairo_stroke(im->cr);
3694 cairo_restore(im->cr);
3695 }
3696 break;
3697 case GF_VRULE:
3698 if (im->gdes[i].xrule >= im->start
3699 && im->gdes[i].xrule <= im->end) {
3700 cairo_save(im->cr);
3701 if (im->gdes[i].dash) {
3702 cairo_set_dash(im->cr,
3703 im->gdes[i].p_dashes,
3704 im->gdes[i].ndash, im->gdes[i].offset);
3705 }
3706 gfx_line(im,
3707 xtr(im, im->gdes[i].xrule),
3708 im->yorigin, xtr(im,
3709 im->
3710 gdes[i].
3711 xrule),
3712 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3713 cairo_stroke(im->cr);
3714 cairo_restore(im->cr);
3715 }
3716 break;
3717 default:
3718 break;
3719 }
3720 }
3723 switch (im->imgformat) {
3724 case IF_PNG:
3725 {
3726 cairo_status_t status;
3728 status = strlen(im->graphfile) ?
3729 cairo_surface_write_to_png(im->surface, im->graphfile)
3730 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3731 im);
3733 if (status != CAIRO_STATUS_SUCCESS) {
3734 rrd_set_error("Could not save png to '%s'", im->graphfile);
3735 return 1;
3736 }
3737 break;
3738 }
3739 default:
3740 if (strlen(im->graphfile)) {
3741 cairo_show_page(im->cr);
3742 } else {
3743 cairo_surface_finish(im->surface);
3744 }
3745 break;
3746 }
3748 return 0;
3749 }
3752 /*****************************************************
3753 * graph stuff
3754 *****************************************************/
3756 int gdes_alloc(
3757 image_desc_t *im)
3758 {
3760 im->gdes_c++;
3761 if ((im->gdes = (graph_desc_t *)
3762 rrd_realloc(im->gdes, (im->gdes_c)
3763 * sizeof(graph_desc_t))) == NULL) {
3764 rrd_set_error("realloc graph_descs");
3765 return -1;
3766 }
3769 im->gdes[im->gdes_c - 1].step = im->step;
3770 im->gdes[im->gdes_c - 1].step_orig = im->step;
3771 im->gdes[im->gdes_c - 1].stack = 0;
3772 im->gdes[im->gdes_c - 1].linewidth = 0;
3773 im->gdes[im->gdes_c - 1].debug = 0;
3774 im->gdes[im->gdes_c - 1].start = im->start;
3775 im->gdes[im->gdes_c - 1].start_orig = im->start;
3776 im->gdes[im->gdes_c - 1].end = im->end;
3777 im->gdes[im->gdes_c - 1].end_orig = im->end;
3778 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3779 im->gdes[im->gdes_c - 1].data = NULL;
3780 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3781 im->gdes[im->gdes_c - 1].data_first = 0;
3782 im->gdes[im->gdes_c - 1].p_data = NULL;
3783 im->gdes[im->gdes_c - 1].rpnp = NULL;
3784 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3785 im->gdes[im->gdes_c - 1].shift = 0.0;
3786 im->gdes[im->gdes_c - 1].dash = 0;
3787 im->gdes[im->gdes_c - 1].ndash = 0;
3788 im->gdes[im->gdes_c - 1].offset = 0;
3789 im->gdes[im->gdes_c - 1].col.red = 0.0;
3790 im->gdes[im->gdes_c - 1].col.green = 0.0;
3791 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3792 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3793 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3794 im->gdes[im->gdes_c - 1].format[0] = '\0';
3795 im->gdes[im->gdes_c - 1].strftm = 0;
3796 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3797 im->gdes[im->gdes_c - 1].ds = -1;
3798 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3799 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3800 im->gdes[im->gdes_c - 1].yrule = DNAN;
3801 im->gdes[im->gdes_c - 1].xrule = 0;
3802 return 0;
3803 }
3805 /* copies input untill the first unescaped colon is found
3806 or until input ends. backslashes have to be escaped as well */
3807 int scan_for_col(
3808 const char *const input,
3809 int len,
3810 char *const output)
3811 {
3812 int inp, outp = 0;
3814 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3815 if (input[inp] == '\\'
3816 && input[inp + 1] != '\0'
3817 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3818 output[outp++] = input[++inp];
3819 } else {
3820 output[outp++] = input[inp];
3821 }
3822 }
3823 output[outp] = '\0';
3824 return inp;
3825 }
3827 /* Now just a wrapper around rrd_graph_v */
3828 int rrd_graph(
3829 int argc,
3830 char **argv,
3831 char ***prdata,
3832 int *xsize,
3833 int *ysize,
3834 FILE * stream,
3835 double *ymin,
3836 double *ymax)
3837 {
3838 int prlines = 0;
3839 rrd_info_t *grinfo = NULL;
3840 rrd_info_t *walker;
3842 grinfo = rrd_graph_v(argc, argv);
3843 if (grinfo == NULL)
3844 return -1;
3845 walker = grinfo;
3846 (*prdata) = NULL;
3847 while (walker) {
3848 if (strcmp(walker->key, "image_info") == 0) {
3849 prlines++;
3850 if (((*prdata) =
3851 (char**)rrd_realloc((*prdata),
3852 (prlines + 1) * sizeof(char *))) == NULL) {
3853 rrd_set_error("realloc prdata");
3854 return 0;
3855 }
3856 /* imginfo goes to position 0 in the prdata array */
3857 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3858 + 2) * sizeof(char));
3859 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3860 (*prdata)[prlines] = NULL;
3861 }
3862 /* skip anything else */
3863 walker = walker->next;
3864 }
3865 walker = grinfo;
3866 *xsize = 0;
3867 *ysize = 0;
3868 *ymin = 0;
3869 *ymax = 0;
3870 while (walker) {
3871 if (strcmp(walker->key, "image_width") == 0) {
3872 *xsize = walker->value.u_cnt;
3873 } else if (strcmp(walker->key, "image_height") == 0) {
3874 *ysize = walker->value.u_cnt;
3875 } else if (strcmp(walker->key, "value_min") == 0) {
3876 *ymin = walker->value.u_val;
3877 } else if (strcmp(walker->key, "value_max") == 0) {
3878 *ymax = walker->value.u_val;
3879 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3880 prlines++;
3881 if (((*prdata) =
3882 (char**)rrd_realloc((*prdata),
3883 (prlines + 1) * sizeof(char *))) == NULL) {
3884 rrd_set_error("realloc prdata");
3885 return 0;
3886 }
3887 (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3888 + 2) * sizeof(char));
3889 (*prdata)[prlines] = NULL;
3890 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3891 } else if (strcmp(walker->key, "image") == 0) {
3892 if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3893 (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3894 rrd_set_error("writing image");
3895 return 0;
3896 }
3897 }
3898 /* skip anything else */
3899 walker = walker->next;
3900 }
3901 rrd_info_free(grinfo);
3902 return 0;
3903 }
3906 /* Some surgery done on this function, it became ridiculously big.
3907 ** Things moved:
3908 ** - initializing now in rrd_graph_init()
3909 ** - options parsing now in rrd_graph_options()
3910 ** - script parsing now in rrd_graph_script()
3911 */
3912 rrd_info_t *rrd_graph_v(
3913 int argc,
3914 char **argv)
3915 {
3916 image_desc_t im;
3917 rrd_info_t *grinfo;
3918 char *old_locale;
3919 rrd_graph_init(&im);
3920 /* a dummy surface so that we can measure text sizes for placements */
3921 old_locale = setlocale(LC_NUMERIC, "C");
3922 rrd_graph_options(argc, argv, &im);
3923 if (rrd_test_error()) {
3924 rrd_info_free(im.grinfo);
3925 im_free(&im);
3926 return NULL;
3927 }
3929 if (optind >= argc) {
3930 rrd_info_free(im.grinfo);
3931 im_free(&im);
3932 rrd_set_error("missing filename");
3933 return NULL;
3934 }
3936 if (strlen(argv[optind]) >= MAXPATH) {
3937 rrd_set_error("filename (including path) too long");
3938 rrd_info_free(im.grinfo);
3939 im_free(&im);
3940 return NULL;
3941 }
3943 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3944 im.graphfile[MAXPATH - 1] = '\0';
3946 if (strcmp(im.graphfile, "-") == 0) {
3947 im.graphfile[0] = '\0';
3948 }
3950 rrd_graph_script(argc, argv, &im, 1);
3951 setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3953 if (rrd_test_error()) {
3954 rrd_info_free(im.grinfo);
3955 im_free(&im);
3956 return NULL;
3957 }
3959 /* Everything is now read and the actual work can start */
3961 if (graph_paint(&im) == -1) {
3962 rrd_info_free(im.grinfo);
3963 im_free(&im);
3964 return NULL;
3965 }
3968 /* The image is generated and needs to be output.
3969 ** Also, if needed, print a line with information about the image.
3970 */
3972 if (im.imginfo) {
3973 rrd_infoval_t info;
3974 char *path;
3975 char *filename;
3977 path = strdup(im.graphfile);
3978 filename = basename(path);
3979 info.u_str =
3980 sprintf_alloc(im.imginfo,
3981 filename,
3982 (long) (im.zoom *
3983 im.ximg), (long) (im.zoom * im.yimg));
3984 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3985 free(info.u_str);
3986 free(path);
3987 }
3988 if (im.rendered_image) {
3989 rrd_infoval_t img;
3991 img.u_blo.size = im.rendered_image_size;
3992 img.u_blo.ptr = im.rendered_image;
3993 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3994 }
3995 grinfo = im.grinfo;
3996 im_free(&im);
3997 return grinfo;
3998 }
4000 static void
4001 rrd_set_font_desc (
4002 image_desc_t *im,int prop,char *font, double size ){
4003 if (font){
4004 strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4005 im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4006 im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4007 };
4008 if (size > 0){
4009 im->text_prop[prop].size = size;
4010 };
4011 if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4012 pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4013 };
4014 }
4016 void rrd_graph_init(
4017 image_desc_t
4018 *im)
4019 {
4020 unsigned int i;
4021 char *deffont = getenv("RRD_DEFAULT_FONT");
4022 static PangoFontMap *fontmap = NULL;
4023 PangoContext *context;
4025 #ifdef HAVE_TZSET
4026 tzset();
4027 #endif
4029 im->base = 1000;
4030 im->daemon_addr = NULL;
4031 im->draw_x_grid = 1;
4032 im->draw_y_grid = 1;
4033 im->draw_3d_border = 2;
4034 im->dynamic_labels = 0;
4035 im->extra_flags = 0;
4036 im->font_options = cairo_font_options_create();
4037 im->forceleftspace = 0;
4038 im->gdes_c = 0;
4039 im->gdes = NULL;
4040 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4041 im->grid_dash_off = 1;
4042 im->grid_dash_on = 1;
4043 im->gridfit = 1;
4044 im->grinfo = (rrd_info_t *) NULL;
4045 im->grinfo_current = (rrd_info_t *) NULL;
4046 im->imgformat = IF_PNG;
4047 im->imginfo = NULL;
4048 im->lazy = 0;
4049 im->legenddirection = TOP_DOWN;
4050 im->legendheight = 0;
4051 im->legendposition = SOUTH;
4052 im->legendwidth = 0;
4053 im->logarithmic = 0;
4054 im->maxval = DNAN;
4055 im->minval = 0;
4056 im->minval = DNAN;
4057 im->prt_c = 0;
4058 im->rigid = 0;
4059 im->rendered_image_size = 0;
4060 im->rendered_image = NULL;
4061 im->slopemode = 0;
4062 im->step = 0;
4063 im->symbol = ' ';
4064 im->tabwidth = 40.0;
4065 im->title[0] = '\0';
4066 im->unitsexponent = 9999;
4067 im->unitslength = 6;
4068 im->viewfactor = 1.0;
4069 im->watermark[0] = '\0';
4070 im->with_markup = 0;
4071 im->ximg = 0;
4072 im->xlab_user.minsec = -1;
4073 im->xorigin = 0;
4074 im->xOriginLegend = 0;
4075 im->xOriginLegendY = 0;
4076 im->xOriginLegendY2 = 0;
4077 im->xOriginTitle = 0;
4078 im->xsize = 400;
4079 im->ygridstep = DNAN;
4080 im->yimg = 0;
4081 im->ylegend[0] = '\0';
4082 im->second_axis_scale = 0; /* 0 disables it */
4083 im->second_axis_shift = 0; /* no shift by default */
4084 im->second_axis_legend[0] = '\0';
4085 im->second_axis_format[0] = '\0';
4086 im->yorigin = 0;
4087 im->yOriginLegend = 0;
4088 im->yOriginLegendY = 0;
4089 im->yOriginLegendY2 = 0;
4090 im->yOriginTitle = 0;
4091 im->ysize = 100;
4092 im->zoom = 1;
4094 im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4095 im->cr = cairo_create(im->surface);
4097 for (i = 0; i < DIM(text_prop); i++) {
4098 im->text_prop[i].size = -1;
4099 rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4100 }
4102 if (fontmap == NULL){
4103 fontmap = pango_cairo_font_map_get_default();
4104 }
4106 context = pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4108 pango_cairo_context_set_resolution(context, 100);
4110 pango_cairo_update_context(im->cr,context);
4112 im->layout = pango_layout_new(context);
4114 // im->layout = pango_cairo_create_layout(im->cr);
4117 cairo_font_options_set_hint_style
4118 (im->font_options, CAIRO_HINT_STYLE_FULL);
4119 cairo_font_options_set_hint_metrics
4120 (im->font_options, CAIRO_HINT_METRICS_ON);
4121 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4125 for (i = 0; i < DIM(graph_col); i++)
4126 im->graph_col[i] = graph_col[i];
4129 }
4132 void rrd_graph_options(
4133 int argc,
4134 char *argv[],
4135 image_desc_t
4136 *im)
4137 {
4138 int stroff;
4139 char *parsetime_error = NULL;
4140 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4141 time_t start_tmp = 0, end_tmp = 0;
4142 long long_tmp;
4143 rrd_time_value_t start_tv, end_tv;
4144 long unsigned int color;
4146 /* defines for long options without a short equivalent. should be bytes,
4147 and may not collide with (the ASCII value of) short options */
4148 #define LONGOPT_UNITS_SI 255
4150 /* *INDENT-OFF* */
4151 struct option long_options[] = {
4152 { "alt-autoscale", no_argument, 0, 'A'},
4153 { "imgformat", required_argument, 0, 'a'},
4154 { "font-smoothing-threshold", required_argument, 0, 'B'},
4155 { "base", required_argument, 0, 'b'},
4156 { "color", required_argument, 0, 'c'},
4157 { "full-size-mode", no_argument, 0, 'D'},
4158 { "daemon", required_argument, 0, 'd'},
4159 { "slope-mode", no_argument, 0, 'E'},
4160 { "end", required_argument, 0, 'e'},
4161 { "force-rules-legend", no_argument, 0, 'F'},
4162 { "imginfo", required_argument, 0, 'f'},
4163 { "graph-render-mode", required_argument, 0, 'G'},
4164 { "no-legend", no_argument, 0, 'g'},
4165 { "height", required_argument, 0, 'h'},
4166 { "no-minor", no_argument, 0, 'I'},
4167 { "interlaced", no_argument, 0, 'i'},
4168 { "alt-autoscale-min", no_argument, 0, 'J'},
4169 { "only-graph", no_argument, 0, 'j'},
4170 { "units-length", required_argument, 0, 'L'},
4171 { "lower-limit", required_argument, 0, 'l'},
4172 { "alt-autoscale-max", no_argument, 0, 'M'},
4173 { "zoom", required_argument, 0, 'm'},
4174 { "no-gridfit", no_argument, 0, 'N'},
4175 { "font", required_argument, 0, 'n'},
4176 { "logarithmic", no_argument, 0, 'o'},
4177 { "pango-markup", no_argument, 0, 'P'},
4178 { "font-render-mode", required_argument, 0, 'R'},
4179 { "rigid", no_argument, 0, 'r'},
4180 { "step", required_argument, 0, 'S'},
4181 { "start", required_argument, 0, 's'},
4182 { "tabwidth", required_argument, 0, 'T'},
4183 { "title", required_argument, 0, 't'},
4184 { "upper-limit", required_argument, 0, 'u'},
4185 { "vertical-label", required_argument, 0, 'v'},
4186 { "watermark", required_argument, 0, 'W'},
4187 { "width", required_argument, 0, 'w'},
4188 { "units-exponent", required_argument, 0, 'X'},
4189 { "x-grid", required_argument, 0, 'x'},
4190 { "alt-y-grid", no_argument, 0, 'Y'},
4191 { "y-grid", required_argument, 0, 'y'},
4192 { "lazy", no_argument, 0, 'z'},
4193 { "units", required_argument, 0, LONGOPT_UNITS_SI},
4194 { "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 */
4195 { "disable-rrdtool-tag",no_argument, 0, 1001},
4196 { "right-axis", required_argument, 0, 1002},
4197 { "right-axis-label", required_argument, 0, 1003},
4198 { "right-axis-format", required_argument, 0, 1004},
4199 { "legend-position", required_argument, 0, 1005},
4200 { "legend-direction", required_argument, 0, 1006},
4201 { "border", required_argument, 0, 1007},
4202 { "grid-dash", required_argument, 0, 1008},
4203 { "dynamic-labels", no_argument, 0, 1009},
4204 { 0, 0, 0, 0}
4205 };
4206 /* *INDENT-ON* */
4208 optind = 0;
4209 opterr = 0; /* initialize getopt */
4210 rrd_parsetime("end-24h", &start_tv);
4211 rrd_parsetime("now", &end_tv);
4212 while (1) {
4213 int option_index = 0;
4214 int opt;
4215 int col_start, col_end;
4217 opt = getopt_long(argc, argv,
4218 "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",
4219 long_options, &option_index);
4220 if (opt == EOF)
4221 break;
4222 switch (opt) {
4223 case 'I':
4224 im->extra_flags |= NOMINOR;
4225 break;
4226 case 'Y':
4227 im->extra_flags |= ALTYGRID;
4228 break;
4229 case 'A':
4230 im->extra_flags |= ALTAUTOSCALE;
4231 break;
4232 case 'J':
4233 im->extra_flags |= ALTAUTOSCALE_MIN;
4234 break;
4235 case 'M':
4236 im->extra_flags |= ALTAUTOSCALE_MAX;
4237 break;
4238 case 'j':
4239 im->extra_flags |= ONLY_GRAPH;
4240 break;
4241 case 'g':
4242 im->extra_flags |= NOLEGEND;
4243 break;
4244 case 1005:
4245 if (strcmp(optarg, "north") == 0) {
4246 im->legendposition = NORTH;
4247 } else if (strcmp(optarg, "west") == 0) {
4248 im->legendposition = WEST;
4249 } else if (strcmp(optarg, "south") == 0) {
4250 im->legendposition = SOUTH;
4251 } else if (strcmp(optarg, "east") == 0) {
4252 im->legendposition = EAST;
4253 } else {
4254 rrd_set_error("unknown legend-position '%s'", optarg);
4255 return;
4256 }
4257 break;
4258 case 1006:
4259 if (strcmp(optarg, "topdown") == 0) {
4260 im->legenddirection = TOP_DOWN;
4261 } else if (strcmp(optarg, "bottomup") == 0) {
4262 im->legenddirection = BOTTOM_UP;
4263 } else {
4264 rrd_set_error("unknown legend-position '%s'", optarg);
4265 return;
4266 }
4267 break;
4268 case 'F':
4269 im->extra_flags |= FORCE_RULES_LEGEND;
4270 break;
4271 case 1001:
4272 im->extra_flags |= NO_RRDTOOL_TAG;
4273 break;
4274 case LONGOPT_UNITS_SI:
4275 if (im->extra_flags & FORCE_UNITS) {
4276 rrd_set_error("--units can only be used once!");
4277 return;
4278 }
4279 if (strcmp(optarg, "si") == 0)
4280 im->extra_flags |= FORCE_UNITS_SI;
4281 else {
4282 rrd_set_error("invalid argument for --units: %s", optarg);
4283 return;
4284 }
4285 break;
4286 case 'X':
4287 im->unitsexponent = atoi(optarg);
4288 break;
4289 case 'L':
4290 im->unitslength = atoi(optarg);
4291 im->forceleftspace = 1;
4292 break;
4293 case 'T':
4294 im->tabwidth = atof(optarg);
4295 break;
4296 case 'S':
4297 im->step = atoi(optarg);
4298 break;
4299 case 'N':
4300 im->gridfit = 0;
4301 break;
4302 case 'P':
4303 im->with_markup = 1;
4304 break;
4305 case 's':
4306 if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4307 rrd_set_error("start time: %s", parsetime_error);
4308 return;
4309 }
4310 break;
4311 case 'e':
4312 if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4313 rrd_set_error("end time: %s", parsetime_error);
4314 return;
4315 }
4316 break;
4317 case 'x':
4318 if (strcmp(optarg, "none") == 0) {
4319 im->draw_x_grid = 0;
4320 break;
4321 };
4322 if (sscanf(optarg,
4323 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4324 scan_gtm,
4325 &im->xlab_user.gridst,
4326 scan_mtm,
4327 &im->xlab_user.mgridst,
4328 scan_ltm,
4329 &im->xlab_user.labst,
4330 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4331 strncpy(im->xlab_form, optarg + stroff,
4332 sizeof(im->xlab_form) - 1);
4333 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4334 if ((int)
4335 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4336 rrd_set_error("unknown keyword %s", scan_gtm);
4337 return;
4338 } else if ((int)
4339 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4340 == -1) {
4341 rrd_set_error("unknown keyword %s", scan_mtm);
4342 return;
4343 } else if ((int)
4344 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4345 rrd_set_error("unknown keyword %s", scan_ltm);
4346 return;
4347 }
4348 im->xlab_user.minsec = 1;
4349 im->xlab_user.stst = im->xlab_form;
4350 } else {
4351 rrd_set_error("invalid x-grid format");
4352 return;
4353 }
4354 break;
4355 case 'y':
4357 if (strcmp(optarg, "none") == 0) {
4358 im->draw_y_grid = 0;
4359 break;
4360 };
4361 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4362 if (im->ygridstep <= 0) {
4363 rrd_set_error("grid step must be > 0");
4364 return;
4365 } else if (im->ylabfact < 1) {
4366 rrd_set_error("label factor must be > 0");
4367 return;
4368 }
4369 } else {
4370 rrd_set_error("invalid y-grid format");
4371 return;
4372 }
4373 break;
4374 case 1007:
4375 im->draw_3d_border = atoi(optarg);
4376 break;
4377 case 1008: /* grid-dash */
4378 if(sscanf(optarg,
4379 "%lf:%lf",
4380 &im->grid_dash_on,
4381 &im->grid_dash_off) != 2) {
4382 rrd_set_error("expected grid-dash format float:float");
4383 return;
4384 }
4385 break;
4386 case 1009: /* enable dynamic labels */
4387 im->dynamic_labels = 1;
4388 break;
4389 case 1002: /* right y axis */
4391 if(sscanf(optarg,
4392 "%lf:%lf",
4393 &im->second_axis_scale,
4394 &im->second_axis_shift) == 2) {
4395 if(im->second_axis_scale==0){
4396 rrd_set_error("the second_axis_scale must not be 0");
4397 return;
4398 }
4399 } else {
4400 rrd_set_error("invalid right-axis format expected scale:shift");
4401 return;
4402 }
4403 break;
4404 case 1003:
4405 strncpy(im->second_axis_legend,optarg,150);
4406 im->second_axis_legend[150]='\0';
4407 break;
4408 case 1004:
4409 if (bad_format(optarg)){
4410 rrd_set_error("use either %le or %lf formats");
4411 return;
4412 }
4413 strncpy(im->second_axis_format,optarg,150);
4414 im->second_axis_format[150]='\0';
4415 break;
4416 case 'v':
4417 strncpy(im->ylegend, optarg, 150);
4418 im->ylegend[150] = '\0';
4419 break;
4420 case 'u':
4421 im->maxval = atof(optarg);
4422 break;
4423 case 'l':
4424 im->minval = atof(optarg);
4425 break;
4426 case 'b':
4427 im->base = atol(optarg);
4428 if (im->base != 1024 && im->base != 1000) {
4429 rrd_set_error
4430 ("the only sensible value for base apart from 1000 is 1024");
4431 return;
4432 }
4433 break;
4434 case 'w':
4435 long_tmp = atol(optarg);
4436 if (long_tmp < 10) {
4437 rrd_set_error("width below 10 pixels");
4438 return;
4439 }
4440 im->xsize = long_tmp;
4441 break;
4442 case 'h':
4443 long_tmp = atol(optarg);
4444 if (long_tmp < 10) {
4445 rrd_set_error("height below 10 pixels");
4446 return;
4447 }
4448 im->ysize = long_tmp;
4449 break;
4450 case 'D':
4451 im->extra_flags |= FULL_SIZE_MODE;
4452 break;
4453 case 'i':
4454 /* interlaced png not supported at the moment */
4455 break;
4456 case 'r':
4457 im->rigid = 1;
4458 break;
4459 case 'f':
4460 im->imginfo = optarg;
4461 break;
4462 case 'a':
4463 if ((int)
4464 (im->imgformat = if_conv(optarg)) == -1) {
4465 rrd_set_error("unsupported graphics format '%s'", optarg);
4466 return;
4467 }
4468 break;
4469 case 'z':
4470 im->lazy = 1;
4471 break;
4472 case 'E':
4473 im->slopemode = 1;
4474 break;
4475 case 'o':
4476 im->logarithmic = 1;
4477 break;
4478 case 'c':
4479 if (sscanf(optarg,
4480 "%10[A-Z]#%n%8lx%n",
4481 col_nam, &col_start, &color, &col_end) == 2) {
4482 int ci;
4483 int col_len = col_end - col_start;
4485 switch (col_len) {
4486 case 3:
4487 color =
4488 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4489 0x011000) |
4490 ((color & 0x00F)
4491 * 0x001100)
4492 | 0x000000FF);
4493 break;
4494 case 4:
4495 color =
4496 (((color & 0xF000) *
4497 0x11000) | ((color & 0x0F00) *
4498 0x01100) | ((color &
4499 0x00F0) *
4500 0x00110) |
4501 ((color & 0x000F) * 0x00011)
4502 );
4503 break;
4504 case 6:
4505 color = (color << 8) + 0xff /* shift left by 8 */ ;
4506 break;
4507 case 8:
4508 break;
4509 default:
4510 rrd_set_error("the color format is #RRGGBB[AA]");
4511 return;
4512 }
4513 if ((ci = grc_conv(col_nam)) != -1) {
4514 im->graph_col[ci] = gfx_hex_to_col(color);
4515 } else {
4516 rrd_set_error("invalid color name '%s'", col_nam);
4517 return;
4518 }
4519 } else {
4520 rrd_set_error("invalid color def format");
4521 return;
4522 }
4523 break;
4524 case 'n':{
4525 char prop[15];
4526 double size = 1;
4527 int end;
4529 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4530 int sindex, propidx;
4532 if ((sindex = text_prop_conv(prop)) != -1) {
4533 for (propidx = sindex;
4534 propidx < TEXT_PROP_LAST; propidx++) {
4535 if (size > 0) {
4536 rrd_set_font_desc(im,propidx,NULL,size);
4537 }
4538 if ((int) strlen(optarg) > end+2) {
4539 if (optarg[end] == ':') {
4540 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4541 } else {
4542 rrd_set_error
4543 ("expected : after font size in '%s'",
4544 optarg);
4545 return;
4546 }
4547 }
4548 /* only run the for loop for DEFAULT (0) for
4549 all others, we break here. woodo programming */
4550 if (propidx == sindex && sindex != 0)
4551 break;
4552 }
4553 } else {
4554 rrd_set_error("invalid fonttag '%s'", prop);
4555 return;
4556 }
4557 } else {
4558 rrd_set_error("invalid text property format");
4559 return;
4560 }
4561 break;
4562 }
4563 case 'm':
4564 im->zoom = atof(optarg);
4565 if (im->zoom <= 0.0) {
4566 rrd_set_error("zoom factor must be > 0");
4567 return;
4568 }
4569 break;
4570 case 't':
4571 strncpy(im->title, optarg, 150);
4572 im->title[150] = '\0';
4573 break;
4574 case 'R':
4575 if (strcmp(optarg, "normal") == 0) {
4576 cairo_font_options_set_antialias
4577 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4578 cairo_font_options_set_hint_style
4579 (im->font_options, CAIRO_HINT_STYLE_FULL);
4580 } else if (strcmp(optarg, "light") == 0) {
4581 cairo_font_options_set_antialias
4582 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4583 cairo_font_options_set_hint_style
4584 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4585 } else if (strcmp(optarg, "mono") == 0) {
4586 cairo_font_options_set_antialias
4587 (im->font_options, CAIRO_ANTIALIAS_NONE);
4588 cairo_font_options_set_hint_style
4589 (im->font_options, CAIRO_HINT_STYLE_FULL);
4590 } else {
4591 rrd_set_error("unknown font-render-mode '%s'", optarg);
4592 return;
4593 }
4594 break;
4595 case 'G':
4596 if (strcmp(optarg, "normal") == 0)
4597 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4598 else if (strcmp(optarg, "mono") == 0)
4599 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4600 else {
4601 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4602 return;
4603 }
4604 break;
4605 case 'B':
4606 /* not supported curently */
4607 break;
4608 case 'W':
4609 strncpy(im->watermark, optarg, 100);
4610 im->watermark[99] = '\0';
4611 break;
4612 case 'd':
4613 {
4614 if (im->daemon_addr != NULL)
4615 {
4616 rrd_set_error ("You cannot specify --daemon "
4617 "more than once.");
4618 return;
4619 }
4621 im->daemon_addr = strdup(optarg);
4622 if (im->daemon_addr == NULL)
4623 {
4624 rrd_set_error("strdup failed");
4625 return;
4626 }
4628 break;
4629 }
4630 case '?':
4631 if (optopt != 0)
4632 rrd_set_error("unknown option '%c'", optopt);
4633 else
4634 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4635 return;
4636 }
4637 } /* while (1) */
4639 { /* try to connect to rrdcached */
4640 int status = rrdc_connect(im->daemon_addr);
4641 if (status != 0) return;
4642 }
4644 pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4645 pango_layout_context_changed(im->layout);
4649 if (im->logarithmic && im->minval <= 0) {
4650 rrd_set_error
4651 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4652 return;
4653 }
4655 if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4656 /* error string is set in rrd_parsetime.c */
4657 return;
4658 }
4660 if (start_tmp < 3600 * 24 * 365 * 10) {
4661 rrd_set_error
4662 ("the first entry to fetch should be after 1980 (%ld)",
4663 start_tmp);
4664 return;
4665 }
4667 if (end_tmp < start_tmp) {
4668 rrd_set_error
4669 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4670 return;
4671 }
4673 im->start = start_tmp;
4674 im->end = end_tmp;
4675 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4676 }
4678 int rrd_graph_color(
4679 image_desc_t
4680 *im,
4681 char *var,
4682 char *err,
4683 int optional)
4684 {
4685 char *color;
4686 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4688 color = strstr(var, "#");
4689 if (color == NULL) {
4690 if (optional == 0) {
4691 rrd_set_error("Found no color in %s", err);
4692 return 0;
4693 }
4694 return 0;
4695 } else {
4696 int n = 0;
4697 char *rest;
4698 long unsigned int col;
4700 rest = strstr(color, ":");
4701 if (rest != NULL)
4702 n = rest - color;
4703 else
4704 n = strlen(color);
4705 switch (n) {
4706 case 7:
4707 sscanf(color, "#%6lx%n", &col, &n);
4708 col = (col << 8) + 0xff /* shift left by 8 */ ;
4709 if (n != 7)
4710 rrd_set_error("Color problem in %s", err);
4711 break;
4712 case 9:
4713 sscanf(color, "#%8lx%n", &col, &n);
4714 if (n == 9)
4715 break;
4716 default:
4717 rrd_set_error("Color problem in %s", err);
4718 }
4719 if (rrd_test_error())
4720 return 0;
4721 gdp->col = gfx_hex_to_col(col);
4722 return n;
4723 }
4724 }
4727 int bad_format(
4728 char *fmt)
4729 {
4730 char *ptr;
4731 int n = 0;
4733 ptr = fmt;
4734 while (*ptr != '\0')
4735 if (*ptr++ == '%') {
4737 /* line cannot end with percent char */
4738 if (*ptr == '\0')
4739 return 1;
4740 /* '%s', '%S' and '%%' are allowed */
4741 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4742 ptr++;
4743 /* %c is allowed (but use only with vdef!) */
4744 else if (*ptr == 'c') {
4745 ptr++;
4746 n = 1;
4747 }
4749 /* or else '% 6.2lf' and such are allowed */
4750 else {
4751 /* optional padding character */
4752 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4753 ptr++;
4754 /* This should take care of 'm.n' with all three optional */
4755 while (*ptr >= '0' && *ptr <= '9')
4756 ptr++;
4757 if (*ptr == '.')
4758 ptr++;
4759 while (*ptr >= '0' && *ptr <= '9')
4760 ptr++;
4761 /* Either 'le', 'lf' or 'lg' must follow here */
4762 if (*ptr++ != 'l')
4763 return 1;
4764 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4765 ptr++;
4766 else
4767 return 1;
4768 n++;
4769 }
4770 }
4772 return (n != 1);
4773 }
4776 int vdef_parse(
4777 struct graph_desc_t
4778 *gdes,
4779 const char *const str)
4780 {
4781 /* A VDEF currently is either "func" or "param,func"
4782 * so the parsing is rather simple. Change if needed.
4783 */
4784 double param;
4785 char func[30];
4786 int n;
4788 n = 0;
4789 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4790 if (n == (int) strlen(str)) { /* matched */
4791 ;
4792 } else {
4793 n = 0;
4794 sscanf(str, "%29[A-Z]%n", func, &n);
4795 if (n == (int) strlen(str)) { /* matched */
4796 param = DNAN;
4797 } else {
4798 rrd_set_error
4799 ("Unknown function string '%s' in VDEF '%s'",
4800 str, gdes->vname);
4801 return -1;
4802 }
4803 }
4804 if (!strcmp("PERCENT", func))
4805 gdes->vf.op = VDEF_PERCENT;
4806 else if (!strcmp("PERCENTNAN", func))
4807 gdes->vf.op = VDEF_PERCENTNAN;
4808 else if (!strcmp("MAXIMUM", func))
4809 gdes->vf.op = VDEF_MAXIMUM;
4810 else if (!strcmp("AVERAGE", func))
4811 gdes->vf.op = VDEF_AVERAGE;
4812 else if (!strcmp("STDEV", func))
4813 gdes->vf.op = VDEF_STDEV;
4814 else if (!strcmp("MINIMUM", func))
4815 gdes->vf.op = VDEF_MINIMUM;
4816 else if (!strcmp("TOTAL", func))
4817 gdes->vf.op = VDEF_TOTAL;
4818 else if (!strcmp("FIRST", func))
4819 gdes->vf.op = VDEF_FIRST;
4820 else if (!strcmp("LAST", func))
4821 gdes->vf.op = VDEF_LAST;
4822 else if (!strcmp("LSLSLOPE", func))
4823 gdes->vf.op = VDEF_LSLSLOPE;
4824 else if (!strcmp("LSLINT", func))
4825 gdes->vf.op = VDEF_LSLINT;
4826 else if (!strcmp("LSLCORREL", func))
4827 gdes->vf.op = VDEF_LSLCORREL;
4828 else {
4829 rrd_set_error
4830 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4831 return -1;
4832 };
4833 switch (gdes->vf.op) {
4834 case VDEF_PERCENT:
4835 case VDEF_PERCENTNAN:
4836 if (isnan(param)) { /* no parameter given */
4837 rrd_set_error
4838 ("Function '%s' needs parameter in VDEF '%s'\n",
4839 func, gdes->vname);
4840 return -1;
4841 };
4842 if (param >= 0.0 && param <= 100.0) {
4843 gdes->vf.param = param;
4844 gdes->vf.val = DNAN; /* undefined */
4845 gdes->vf.when = 0; /* undefined */
4846 } else {
4847 rrd_set_error
4848 ("Parameter '%f' out of range in VDEF '%s'\n",
4849 param, gdes->vname);
4850 return -1;
4851 };
4852 break;
4853 case VDEF_MAXIMUM:
4854 case VDEF_AVERAGE:
4855 case VDEF_STDEV:
4856 case VDEF_MINIMUM:
4857 case VDEF_TOTAL:
4858 case VDEF_FIRST:
4859 case VDEF_LAST:
4860 case VDEF_LSLSLOPE:
4861 case VDEF_LSLINT:
4862 case VDEF_LSLCORREL:
4863 if (isnan(param)) {
4864 gdes->vf.param = DNAN;
4865 gdes->vf.val = DNAN;
4866 gdes->vf.when = 0;
4867 } else {
4868 rrd_set_error
4869 ("Function '%s' needs no parameter in VDEF '%s'\n",
4870 func, gdes->vname);
4871 return -1;
4872 };
4873 break;
4874 };
4875 return 0;
4876 }
4879 int vdef_calc(
4880 image_desc_t *im,
4881 int gdi)
4882 {
4883 graph_desc_t *src, *dst;
4884 rrd_value_t *data;
4885 long step, steps;
4887 dst = &im->gdes[gdi];
4888 src = &im->gdes[dst->vidx];
4889 data = src->data + src->ds;
4891 steps = (src->end - src->start) / src->step;
4892 #if 0
4893 printf
4894 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4895 src->start, src->end, steps);
4896 #endif
4897 switch (dst->vf.op) {
4898 case VDEF_PERCENT:{
4899 rrd_value_t *array;
4900 int field;
4901 if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4902 rrd_set_error("malloc VDEV_PERCENT");
4903 return -1;
4904 }
4905 for (step = 0; step < steps; step++) {
4906 array[step] = data[step * src->ds_cnt];
4907 }
4908 qsort(array, step, sizeof(double), vdef_percent_compar);
4909 field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4910 dst->vf.val = array[field];
4911 dst->vf.when = 0; /* no time component */
4912 free(array);
4913 #if 0
4914 for (step = 0; step < steps; step++)
4915 printf("DEBUG: %3li:%10.2f %c\n",
4916 step, array[step], step == field ? '*' : ' ');
4917 #endif
4918 }
4919 break;
4920 case VDEF_PERCENTNAN:{
4921 rrd_value_t *array;
4922 int field;
4923 /* count number of "valid" values */
4924 int nancount=0;
4925 for (step = 0; step < steps; step++) {
4926 if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4927 }
4928 /* and allocate it */
4929 if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4930 rrd_set_error("malloc VDEV_PERCENT");
4931 return -1;
4932 }
4933 /* and fill it in */
4934 field=0;
4935 for (step = 0; step < steps; step++) {
4936 if (!isnan(data[step * src->ds_cnt])) {
4937 array[field] = data[step * src->ds_cnt];
4938 field++;
4939 }
4940 }
4941 qsort(array, nancount, sizeof(double), vdef_percent_compar);
4942 field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4943 dst->vf.val = array[field];
4944 dst->vf.when = 0; /* no time component */
4945 free(array);
4946 }
4947 break;
4948 case VDEF_MAXIMUM:
4949 step = 0;
4950 while (step != steps && isnan(data[step * src->ds_cnt]))
4951 step++;
4952 if (step == steps) {
4953 dst->vf.val = DNAN;
4954 dst->vf.when = 0;
4955 } else {
4956 dst->vf.val = data[step * src->ds_cnt];
4957 dst->vf.when = src->start + (step + 1) * src->step;
4958 }
4959 while (step != steps) {
4960 if (finite(data[step * src->ds_cnt])) {
4961 if (data[step * src->ds_cnt] > dst->vf.val) {
4962 dst->vf.val = data[step * src->ds_cnt];
4963 dst->vf.when = src->start + (step + 1) * src->step;
4964 }
4965 }
4966 step++;
4967 }
4968 break;
4969 case VDEF_TOTAL:
4970 case VDEF_STDEV:
4971 case VDEF_AVERAGE:{
4972 int cnt = 0;
4973 double sum = 0.0;
4974 double average = 0.0;
4976 for (step = 0; step < steps; step++) {
4977 if (finite(data[step * src->ds_cnt])) {
4978 sum += data[step * src->ds_cnt];
4979 cnt++;
4980 };
4981 }
4982 if (cnt) {
4983 if (dst->vf.op == VDEF_TOTAL) {
4984 dst->vf.val = sum * src->step;
4985 dst->vf.when = 0; /* no time component */
4986 } else if (dst->vf.op == VDEF_AVERAGE) {
4987 dst->vf.val = sum / cnt;
4988 dst->vf.when = 0; /* no time component */
4989 } else {
4990 average = sum / cnt;
4991 sum = 0.0;
4992 for (step = 0; step < steps; step++) {
4993 if (finite(data[step * src->ds_cnt])) {
4994 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4995 };
4996 }
4997 dst->vf.val = pow(sum / cnt, 0.5);
4998 dst->vf.when = 0; /* no time component */
4999 };
5000 } else {
5001 dst->vf.val = DNAN;
5002 dst->vf.when = 0;
5003 }
5004 }
5005 break;
5006 case VDEF_MINIMUM:
5007 step = 0;
5008 while (step != steps && isnan(data[step * src->ds_cnt]))
5009 step++;
5010 if (step == steps) {
5011 dst->vf.val = DNAN;
5012 dst->vf.when = 0;
5013 } else {
5014 dst->vf.val = data[step * src->ds_cnt];
5015 dst->vf.when = src->start + (step + 1) * src->step;
5016 }
5017 while (step != steps) {
5018 if (finite(data[step * src->ds_cnt])) {
5019 if (data[step * src->ds_cnt] < dst->vf.val) {
5020 dst->vf.val = data[step * src->ds_cnt];
5021 dst->vf.when = src->start + (step + 1) * src->step;
5022 }
5023 }
5024 step++;
5025 }
5026 break;
5027 case VDEF_FIRST:
5028 /* The time value returned here is one step before the
5029 * actual time value. This is the start of the first
5030 * non-NaN interval.
5031 */
5032 step = 0;
5033 while (step != steps && isnan(data[step * src->ds_cnt]))
5034 step++;
5035 if (step == steps) { /* all entries were NaN */
5036 dst->vf.val = DNAN;
5037 dst->vf.when = 0;
5038 } else {
5039 dst->vf.val = data[step * src->ds_cnt];
5040 dst->vf.when = src->start + step * src->step;
5041 }
5042 break;
5043 case VDEF_LAST:
5044 /* The time value returned here is the
5045 * actual time value. This is the end of the last
5046 * non-NaN interval.
5047 */
5048 step = steps - 1;
5049 while (step >= 0 && isnan(data[step * src->ds_cnt]))
5050 step--;
5051 if (step < 0) { /* all entries were NaN */
5052 dst->vf.val = DNAN;
5053 dst->vf.when = 0;
5054 } else {
5055 dst->vf.val = data[step * src->ds_cnt];
5056 dst->vf.when = src->start + (step + 1) * src->step;
5057 }
5058 break;
5059 case VDEF_LSLSLOPE:
5060 case VDEF_LSLINT:
5061 case VDEF_LSLCORREL:{
5062 /* Bestfit line by linear least squares method */
5064 int cnt = 0;
5065 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5067 SUMx = 0;
5068 SUMy = 0;
5069 SUMxy = 0;
5070 SUMxx = 0;
5071 SUMyy = 0;
5072 for (step = 0; step < steps; step++) {
5073 if (finite(data[step * src->ds_cnt])) {
5074 cnt++;
5075 SUMx += step;
5076 SUMxx += step * step;
5077 SUMxy += step * data[step * src->ds_cnt];
5078 SUMy += data[step * src->ds_cnt];
5079 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5080 };
5081 }
5083 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5084 y_intercept = (SUMy - slope * SUMx) / cnt;
5085 correl =
5086 (SUMxy -
5087 (SUMx * SUMy) / cnt) /
5088 sqrt((SUMxx -
5089 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5090 if (cnt) {
5091 if (dst->vf.op == VDEF_LSLSLOPE) {
5092 dst->vf.val = slope;
5093 dst->vf.when = 0;
5094 } else if (dst->vf.op == VDEF_LSLINT) {
5095 dst->vf.val = y_intercept;
5096 dst->vf.when = 0;
5097 } else if (dst->vf.op == VDEF_LSLCORREL) {
5098 dst->vf.val = correl;
5099 dst->vf.when = 0;
5100 };
5101 } else {
5102 dst->vf.val = DNAN;
5103 dst->vf.when = 0;
5104 }
5105 }
5106 break;
5107 }
5108 return 0;
5109 }
5111 /* NaN < -INF < finite_values < INF */
5112 int vdef_percent_compar(
5113 const void
5114 *a,
5115 const void
5116 *b)
5117 {
5118 /* Equality is not returned; this doesn't hurt except
5119 * (maybe) for a little performance.
5120 */
5122 /* First catch NaN values. They are smallest */
5123 if (isnan(*(double *) a))
5124 return -1;
5125 if (isnan(*(double *) b))
5126 return 1;
5127 /* NaN doesn't reach this part so INF and -INF are extremes.
5128 * The sign from isinf() is compatible with the sign we return
5129 */
5130 if (isinf(*(double *) a))
5131 return isinf(*(double *) a);
5132 if (isinf(*(double *) b))
5133 return isinf(*(double *) b);
5134 /* If we reach this, both values must be finite */
5135 if (*(double *) a < *(double *) b)
5136 return -1;
5137 else
5138 return 1;
5139 }
5141 void grinfo_push(
5142 image_desc_t *im,
5143 char *key,
5144 rrd_info_type_t type,
5145 rrd_infoval_t value)
5146 {
5147 im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5148 if (im->grinfo == NULL) {
5149 im->grinfo = im->grinfo_current;
5150 }
5151 }