Code

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