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