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