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