Code

prepare for the release of rrdtool-1.2.99907080300
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.2.99907080300  Copyright by Tobi Oetiker, 1997-2007
3  ****************************************************************************
4  * rrd__graph.c  produce graphs from data in rrdfiles
5  ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
37 #endif
39 text_prop_t text_prop[] = {
40     {8.0, RRD_DEFAULT_FONT}
41     ,                   /* default */
42     {9.0, RRD_DEFAULT_FONT}
43     ,                   /* title */
44     {7.0, RRD_DEFAULT_FONT}
45     ,                   /* axis */
46     {8.0, RRD_DEFAULT_FONT}
47     ,                   /* unit */
48     {8.0, RRD_DEFAULT_FONT} /* legend */
49 };
51 xlab_t    xlab[] = {
52     {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
53     ,
54     {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
55     ,
56     {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
57     ,
58     {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
59     ,
60     {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
61     ,
62     {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
63     ,
64     {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 4, 0, "%a %H:%M"}
65     ,
66     {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
67     ,
68     {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
69     ,
70     /*{300,             0,   TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly */
71     {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
72     ,
73     {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
74     ,
75     {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
76     ,
77     {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
78     ,
79     {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
80     ,
81     {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
82      "Week %V"}
83     ,
84     {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
85      "%b"}
86     ,
87     {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
88      "%b"}
89     ,
90     {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
91     ,
92     {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
93      365 * 24 * 3600, "%y"}
94     ,
95     {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
96 };
98 /* sensible y label intervals ...*/
100 ylab_t    ylab[] = {
101     {0.1, {1, 2, 5, 10}
102      }
103     ,
104     {0.2, {1, 5, 10, 20}
105      }
106     ,
107     {0.5, {1, 2, 4, 10}
108      }
109     ,
110     {1.0, {1, 2, 5, 10}
111      }
112     ,
113     {2.0, {1, 5, 10, 20}
114      }
115     ,
116     {5.0, {1, 2, 4, 10}
117      }
118     ,
119     {10.0, {1, 2, 5, 10}
120      }
121     ,
122     {20.0, {1, 5, 10, 20}
123      }
124     ,
125     {50.0, {1, 2, 4, 10}
126      }
127     ,
128     {100.0, {1, 2, 5, 10}
129      }
130     ,
131     {200.0, {1, 5, 10, 20}
132      }
133     ,
134     {500.0, {1, 2, 4, 10}
135      }
136     ,
137     {0.0, {0, 0, 0, 0}
138      }
139 };
142 gfx_color_t graph_col[] =   /* default colors */
144     {1.00, 1.00, 1.00, 1.00},   /* canvas     */
145     {0.95, 0.95, 0.95, 1.00},   /* background */
146     {0.81, 0.81, 0.81, 1.00},   /* shade A    */
147     {0.62, 0.62, 0.62, 1.00},   /* shade B    */
148     {0.56, 0.56, 0.56, 0.75},   /* grid       */
149     {0.87, 0.31, 0.31, 0.60},   /* major grid */
150     {0.00, 0.00, 0.00, 1.00},   /* font       */
151     {0.50, 0.12, 0.12, 1.00},   /* arrow      */
152     {0.12, 0.12, 0.12, 1.00},   /* axis       */
153     {0.00, 0.00, 0.00, 1.00}    /* frame      */
154 };
157 /* #define DEBUG */
159 #ifdef DEBUG
160 # define DPRINT(x)    (void)(printf x, printf("\n"))
161 #else
162 # define DPRINT(x)
163 #endif
166 /* initialize with xtr(im,0); */
167 int xtr(
168     image_desc_t *im,
169     time_t mytime)
171     static double pixie;
173     if (mytime == 0) {
174         pixie = (double) im->xsize / (double) (im->end - im->start);
175         return im->xorigin;
176     }
177     return (int) ((double) im->xorigin + pixie * (mytime - im->start));
180 /* translate data values into y coordinates */
181 double ytr(
182     image_desc_t *im,
183     double value)
185     static double pixie;
186     double    yval;
188     if (isnan(value)) {
189         if (!im->logarithmic)
190             pixie = (double) im->ysize / (im->maxval - im->minval);
191         else
192             pixie =
193                 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
194         yval = im->yorigin;
195     } else if (!im->logarithmic) {
196         yval = im->yorigin - pixie * (value - im->minval);
197     } else {
198         if (value < im->minval) {
199             yval = im->yorigin;
200         } else {
201             yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
202         }
203     }
204     /* make sure we don't return anything too unreasonable. GD lib can
205        get terribly slow when drawing lines outside its scope. This is 
206        especially problematic in connection with the rigid option */
207     if (!im->rigid) {
208         /* keep yval as-is */
209     } else if (yval > im->yorigin) {
210         yval = im->yorigin + 0.00001;
211     } else if (yval < im->yorigin - im->ysize) {
212         yval = im->yorigin - im->ysize - 0.00001;
213     }
214     return yval;
219 /* conversion function for symbolic entry names */
222 #define conv_if(VV,VVV) \
223    if (strcmp(#VV, string) == 0) return VVV ;
225 enum gf_en gf_conv(
226     char *string)
229     conv_if(PRINT, GF_PRINT);
230     conv_if(GPRINT, GF_GPRINT);
231     conv_if(COMMENT, GF_COMMENT);
232     conv_if(HRULE, GF_HRULE);
233     conv_if(VRULE, GF_VRULE);
234     conv_if(LINE, GF_LINE);
235     conv_if(AREA, GF_AREA);
236     conv_if(STACK, GF_STACK);
237     conv_if(TICK, GF_TICK);
238     conv_if(TEXTALIGN, GF_TEXTALIGN);
239     conv_if(DEF, GF_DEF);
240     conv_if(CDEF, GF_CDEF);
241     conv_if(VDEF, GF_VDEF);
242     conv_if(XPORT, GF_XPORT);
243     conv_if(SHIFT, GF_SHIFT);
245     return (-1);
248 enum gfx_if_en if_conv(
249     char *string)
252     conv_if(PNG, IF_PNG);
253     conv_if(SVG, IF_SVG);
254     conv_if(EPS, IF_EPS);
255     conv_if(PDF, IF_PDF);
257     return (-1);
260 enum tmt_en tmt_conv(
261     char *string)
264     conv_if(SECOND, TMT_SECOND);
265     conv_if(MINUTE, TMT_MINUTE);
266     conv_if(HOUR, TMT_HOUR);
267     conv_if(DAY, TMT_DAY);
268     conv_if(WEEK, TMT_WEEK);
269     conv_if(MONTH, TMT_MONTH);
270     conv_if(YEAR, TMT_YEAR);
271     return (-1);
274 enum grc_en grc_conv(
275     char *string)
278     conv_if(BACK, GRC_BACK);
279     conv_if(CANVAS, GRC_CANVAS);
280     conv_if(SHADEA, GRC_SHADEA);
281     conv_if(SHADEB, GRC_SHADEB);
282     conv_if(GRID, GRC_GRID);
283     conv_if(MGRID, GRC_MGRID);
284     conv_if(FONT, GRC_FONT);
285     conv_if(ARROW, GRC_ARROW);
286     conv_if(AXIS, GRC_AXIS);
287     conv_if(FRAME, GRC_FRAME);
289     return -1;
292 enum text_prop_en text_prop_conv(
293     char *string)
296     conv_if(DEFAULT, TEXT_PROP_DEFAULT);
297     conv_if(TITLE, TEXT_PROP_TITLE);
298     conv_if(AXIS, TEXT_PROP_AXIS);
299     conv_if(UNIT, TEXT_PROP_UNIT);
300     conv_if(LEGEND, TEXT_PROP_LEGEND);
301     return -1;
305 #undef conv_if
307 int im_free(
308     image_desc_t *im)
310     unsigned long i, ii;
311     cairo_status_t status = 0;
313     if (im == NULL)
314         return 0;
315     for (i = 0; i < (unsigned) im->gdes_c; i++) {
316         if (im->gdes[i].data_first) {
317             /* careful here, because a single pointer can occur several times */
318             free(im->gdes[i].data);
319             if (im->gdes[i].ds_namv) {
320                 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
321                     free(im->gdes[i].ds_namv[ii]);
322                 free(im->gdes[i].ds_namv);
323             }
324         }
325         free(im->gdes[i].p_data);
326         free(im->gdes[i].rpnp);
327     }
328     free(im->gdes);
329     if (im->font_options)
330         cairo_font_options_destroy(im->font_options);
332     if (im->cr) {
333         status = cairo_status(im->cr);
334         cairo_destroy(im->cr);
335     }
336     if (im->surface)
337         cairo_surface_destroy(im->surface);
338     if (status)
339         fprintf(stderr, "OOPS: Cairo has issuesm it can't even die: %s\n",
340                 cairo_status_to_string(status));
342     return 0;
345 /* find SI magnitude symbol for the given number*/
346 void auto_scale(
347     image_desc_t *im,   /* image description */
348     double *value,
349     char **symb_ptr,
350     double *magfact)
353     char     *symbol[] = { "a", /* 10e-18 Atto */
354         "f",            /* 10e-15 Femto */
355         "p",            /* 10e-12 Pico */
356         "n",            /* 10e-9  Nano */
357         "u",            /* 10e-6  Micro */
358         "m",            /* 10e-3  Milli */
359         " ",            /* Base */
360         "k",            /* 10e3   Kilo */
361         "M",            /* 10e6   Mega */
362         "G",            /* 10e9   Giga */
363         "T",            /* 10e12  Tera */
364         "P",            /* 10e15  Peta */
365         "E"
366     };                  /* 10e18  Exa */
368     int       symbcenter = 6;
369     int       sindex;
371     if (*value == 0.0 || isnan(*value)) {
372         sindex = 0;
373         *magfact = 1.0;
374     } else {
375         sindex = floor(log(fabs(*value)) / log((double) im->base));
376         *magfact = pow((double) im->base, (double) sindex);
377         (*value) /= (*magfact);
378     }
379     if (sindex <= symbcenter && sindex >= -symbcenter) {
380         (*symb_ptr) = symbol[sindex + symbcenter];
381     } else {
382         (*symb_ptr) = "?";
383     }
387 static char si_symbol[] = {
388     'a',                /* 10e-18 Atto */
389     'f',                /* 10e-15 Femto */
390     'p',                /* 10e-12 Pico */
391     'n',                /* 10e-9  Nano */
392     'u',                /* 10e-6  Micro */
393     'm',                /* 10e-3  Milli */
394     ' ',                /* Base */
395     'k',                /* 10e3   Kilo */
396     'M',                /* 10e6   Mega */
397     'G',                /* 10e9   Giga */
398     'T',                /* 10e12  Tera */
399     'P',                /* 10e15  Peta */
400     'E',                /* 10e18  Exa */
401 };
402 static const int si_symbcenter = 6;
404 /* find SI magnitude symbol for the numbers on the y-axis*/
405 void si_unit(
406     image_desc_t *im    /* image description */
407     )
410     double    digits, viewdigits = 0;
412     digits =
413         floor(log(max(fabs(im->minval), fabs(im->maxval))) /
414               log((double) im->base));
416     if (im->unitsexponent != 9999) {
417         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
418         viewdigits = floor(im->unitsexponent / 3);
419     } else {
420         viewdigits = digits;
421     }
423     im->magfact = pow((double) im->base, digits);
425 #ifdef DEBUG
426     printf("digits %6.3f  im->magfact %6.3f\n", digits, im->magfact);
427 #endif
429     im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
431     if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
432         ((viewdigits + si_symbcenter) >= 0))
433         im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
434     else
435         im->symbol = '?';
438 /*  move min and max values around to become sensible */
440 void expand_range(
441     image_desc_t *im)
443     double    sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
444         600.0, 500.0, 400.0, 300.0, 250.0,
445         200.0, 125.0, 100.0, 90.0, 80.0,
446         75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
447         25.0, 20.0, 10.0, 9.0, 8.0,
448         7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
449         2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
450         0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
451     };
453     double    scaled_min, scaled_max;
454     double    adj;
455     int       i;
459 #ifdef DEBUG
460     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
461            im->minval, im->maxval, im->magfact);
462 #endif
464     if (isnan(im->ygridstep)) {
465         if (im->extra_flags & ALTAUTOSCALE) {
466             /* measure the amplitude of the function. Make sure that
467                graph boundaries are slightly higher then max/min vals
468                so we can see amplitude on the graph */
469             double    delt, fact;
471             delt = im->maxval - im->minval;
472             adj = delt * 0.1;
473             fact = 2.0 * pow(10.0,
474                              floor(log10
475                                    (max(fabs(im->minval), fabs(im->maxval)) /
476                                     im->magfact)) - 2);
477             if (delt < fact) {
478                 adj = (fact - delt) * 0.55;
479 #ifdef DEBUG
480                 printf
481                     ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
482                      im->minval, im->maxval, delt, fact, adj);
483 #endif
484             }
485             im->minval -= adj;
486             im->maxval += adj;
487         } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
488             /* measure the amplitude of the function. Make sure that
489                graph boundaries are slightly lower than min vals
490                so we can see amplitude on the graph */
491             adj = (im->maxval - im->minval) * 0.1;
492             im->minval -= adj;
493         } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
494             /* measure the amplitude of the function. Make sure that
495                graph boundaries are slightly higher than max vals
496                so we can see amplitude on the graph */
497             adj = (im->maxval - im->minval) * 0.1;
498             im->maxval += adj;
499         } else {
500             scaled_min = im->minval / im->magfact;
501             scaled_max = im->maxval / im->magfact;
503             for (i = 1; sensiblevalues[i] > 0; i++) {
504                 if (sensiblevalues[i - 1] >= scaled_min &&
505                     sensiblevalues[i] <= scaled_min)
506                     im->minval = sensiblevalues[i] * (im->magfact);
508                 if (-sensiblevalues[i - 1] <= scaled_min &&
509                     -sensiblevalues[i] >= scaled_min)
510                     im->minval = -sensiblevalues[i - 1] * (im->magfact);
512                 if (sensiblevalues[i - 1] >= scaled_max &&
513                     sensiblevalues[i] <= scaled_max)
514                     im->maxval = sensiblevalues[i - 1] * (im->magfact);
516                 if (-sensiblevalues[i - 1] <= scaled_max &&
517                     -sensiblevalues[i] >= scaled_max)
518                     im->maxval = -sensiblevalues[i] * (im->magfact);
519             }
520         }
521     } else {
522         /* adjust min and max to the grid definition if there is one */
523         im->minval = (double) im->ylabfact * im->ygridstep *
524             floor(im->minval / ((double) im->ylabfact * im->ygridstep));
525         im->maxval = (double) im->ylabfact * im->ygridstep *
526             ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
527     }
529 #ifdef DEBUG
530     fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
531             im->minval, im->maxval, im->magfact);
532 #endif
536 void apply_gridfit(
537     image_desc_t *im)
539     if (isnan(im->minval) || isnan(im->maxval))
540         return;
541     ytr(im, DNAN);
542     if (im->logarithmic) {
543         double    ya, yb, ypix, ypixfrac;
544         double    log10_range = log10(im->maxval) - log10(im->minval);
546         ya = pow((double) 10, floor(log10(im->minval)));
547         while (ya < im->minval)
548             ya *= 10;
549         if (ya > im->maxval)
550             return;     /* don't have y=10^x gridline */
551         yb = ya * 10;
552         if (yb <= im->maxval) {
553             /* we have at least 2 y=10^x gridlines.
554                Make sure distance between them in pixels
555                are an integer by expanding im->maxval */
556             double    y_pixel_delta = ytr(im, ya) - ytr(im, yb);
557             double    factor = y_pixel_delta / floor(y_pixel_delta);
558             double    new_log10_range = factor * log10_range;
559             double    new_ymax_log10 = log10(im->minval) + new_log10_range;
561             im->maxval = pow(10, new_ymax_log10);
562             ytr(im, DNAN);  /* reset precalc */
563             log10_range = log10(im->maxval) - log10(im->minval);
564         }
565         /* make sure first y=10^x gridline is located on 
566            integer pixel position by moving scale slightly 
567            downwards (sub-pixel movement) */
568         ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
569         ypixfrac = ypix - floor(ypix);
570         if (ypixfrac > 0 && ypixfrac < 1) {
571             double    yfrac = ypixfrac / im->ysize;
573             im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
574             im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
575             ytr(im, DNAN);  /* reset precalc */
576         }
577     } else {
578         /* Make sure we have an integer pixel distance between
579            each minor gridline */
580         double    ypos1 = ytr(im, im->minval);
581         double    ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
582         double    y_pixel_delta = ypos1 - ypos2;
583         double    factor = y_pixel_delta / floor(y_pixel_delta);
584         double    new_range = factor * (im->maxval - im->minval);
585         double    gridstep = im->ygrid_scale.gridstep;
586         double    minor_y, minor_y_px, minor_y_px_frac;
588         if (im->maxval > 0.0)
589             im->maxval = im->minval + new_range;
590         else
591             im->minval = im->maxval - new_range;
592         ytr(im, DNAN);  /* reset precalc */
593         /* make sure first minor gridline is on integer pixel y coord */
594         minor_y = gridstep * floor(im->minval / gridstep);
595         while (minor_y < im->minval)
596             minor_y += gridstep;
597         minor_y_px = ytr(im, minor_y) + im->ysize;  /* ensure > 0 by adding ysize */
598         minor_y_px_frac = minor_y_px - floor(minor_y_px);
599         if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
600             double    yfrac = minor_y_px_frac / im->ysize;
601             double    range = im->maxval - im->minval;
603             im->minval = im->minval - yfrac * range;
604             im->maxval = im->maxval - yfrac * range;
605             ytr(im, DNAN);  /* reset precalc */
606         }
607         calc_horizontal_grid(im);   /* recalc with changed im->maxval */
608     }
611 /* reduce data reimplementation by Alex */
613 void reduce_data(
614     enum cf_en cf,      /* which consolidation function ? */
615     unsigned long cur_step, /* step the data currently is in */
616     time_t *start,      /* start, end and step as requested ... */
617     time_t *end,        /* ... by the application will be   ... */
618     unsigned long *step,    /* ... adjusted to represent reality    */
619     unsigned long *ds_cnt,  /* number of data sources in file */
620     rrd_value_t **data)
621 {                       /* two dimensional array containing the data */
622     int       i, reduce_factor = ceil((double) (*step) / (double) cur_step);
623     unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
624         0;
625     rrd_value_t *srcptr, *dstptr;
627     (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
628     dstptr = *data;
629     srcptr = *data;
630     row_cnt = ((*end) - (*start)) / cur_step;
632 #ifdef DEBUG
633 #define DEBUG_REDUCE
634 #endif
635 #ifdef DEBUG_REDUCE
636     printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
637            row_cnt, reduce_factor, *start, *end, cur_step);
638     for (col = 0; col < row_cnt; col++) {
639         printf("time %10lu: ", *start + (col + 1) * cur_step);
640         for (i = 0; i < *ds_cnt; i++)
641             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
642         printf("\n");
643     }
644 #endif
646     /* We have to combine [reduce_factor] rows of the source
647      ** into one row for the destination.  Doing this we also
648      ** need to take care to combine the correct rows.  First
649      ** alter the start and end time so that they are multiples
650      ** of the new step time.  We cannot reduce the amount of
651      ** time so we have to move the end towards the future and
652      ** the start towards the past.
653      */
654     end_offset = (*end) % (*step);
655     start_offset = (*start) % (*step);
657     /* If there is a start offset (which cannot be more than
658      ** one destination row), skip the appropriate number of
659      ** source rows and one destination row.  The appropriate
660      ** number is what we do know (start_offset/cur_step) of
661      ** the new interval (*step/cur_step aka reduce_factor).
662      */
663 #ifdef DEBUG_REDUCE
664     printf("start_offset: %lu  end_offset: %lu\n", start_offset, end_offset);
665     printf("row_cnt before:  %lu\n", row_cnt);
666 #endif
667     if (start_offset) {
668         (*start) = (*start) - start_offset;
669         skiprows = reduce_factor - start_offset / cur_step;
670         srcptr += skiprows * *ds_cnt;
671         for (col = 0; col < (*ds_cnt); col++)
672             *dstptr++ = DNAN;
673         row_cnt -= skiprows;
674     }
675 #ifdef DEBUG_REDUCE
676     printf("row_cnt between: %lu\n", row_cnt);
677 #endif
679     /* At the end we have some rows that are not going to be
680      ** used, the amount is end_offset/cur_step
681      */
682     if (end_offset) {
683         (*end) = (*end) - end_offset + (*step);
684         skiprows = end_offset / cur_step;
685         row_cnt -= skiprows;
686     }
687 #ifdef DEBUG_REDUCE
688     printf("row_cnt after:   %lu\n", row_cnt);
689 #endif
691 /* Sanity check: row_cnt should be multiple of reduce_factor */
692 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
694     if (row_cnt % reduce_factor) {
695         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
696                row_cnt, reduce_factor);
697         printf("BUG in reduce_data()\n");
698         exit(1);
699     }
701     /* Now combine reduce_factor intervals at a time
702      ** into one interval for the destination.
703      */
705     for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
706         for (col = 0; col < (*ds_cnt); col++) {
707             rrd_value_t newval = DNAN;
708             unsigned long validval = 0;
710             for (i = 0; i < reduce_factor; i++) {
711                 if (isnan(srcptr[i * (*ds_cnt) + col])) {
712                     continue;
713                 }
714                 validval++;
715                 if (isnan(newval))
716                     newval = srcptr[i * (*ds_cnt) + col];
717                 else {
718                     switch (cf) {
719                     case CF_HWPREDICT:
720                     case CF_MHWPREDICT:
721                     case CF_DEVSEASONAL:
722                     case CF_DEVPREDICT:
723                     case CF_SEASONAL:
724                     case CF_AVERAGE:
725                         newval += srcptr[i * (*ds_cnt) + col];
726                         break;
727                     case CF_MINIMUM:
728                         newval = min(newval, srcptr[i * (*ds_cnt) + col]);
729                         break;
730                     case CF_FAILURES:
731                         /* an interval contains a failure if any subintervals contained a failure */
732                     case CF_MAXIMUM:
733                         newval = max(newval, srcptr[i * (*ds_cnt) + col]);
734                         break;
735                     case CF_LAST:
736                         newval = srcptr[i * (*ds_cnt) + col];
737                         break;
738                     }
739                 }
740             }
741             if (validval == 0) {
742                 newval = DNAN;
743             } else {
744                 switch (cf) {
745                 case CF_HWPREDICT:
746                 case CF_MHWPREDICT:
747                 case CF_DEVSEASONAL:
748                 case CF_DEVPREDICT:
749                 case CF_SEASONAL:
750                 case CF_AVERAGE:
751                     newval /= validval;
752                     break;
753                 case CF_MINIMUM:
754                 case CF_FAILURES:
755                 case CF_MAXIMUM:
756                 case CF_LAST:
757                     break;
758                 }
759             }
760             *dstptr++ = newval;
761         }
762         srcptr += (*ds_cnt) * reduce_factor;
763         row_cnt -= reduce_factor;
764     }
765     /* If we had to alter the endtime, we didn't have enough
766      ** source rows to fill the last row. Fill it with NaN.
767      */
768     if (end_offset)
769         for (col = 0; col < (*ds_cnt); col++)
770             *dstptr++ = DNAN;
771 #ifdef DEBUG_REDUCE
772     row_cnt = ((*end) - (*start)) / *step;
773     srcptr = *data;
774     printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
775            row_cnt, *start, *end, *step);
776     for (col = 0; col < row_cnt; col++) {
777         printf("time %10lu: ", *start + (col + 1) * (*step));
778         for (i = 0; i < *ds_cnt; i++)
779             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
780         printf("\n");
781     }
782 #endif
786 /* get the data required for the graphs from the 
787    relevant rrds ... */
789 int data_fetch(
790     image_desc_t *im)
792     int       i, ii;
793     int       skip;
795     /* pull the data from the rrd files ... */
796     for (i = 0; i < (int) im->gdes_c; i++) {
797         /* only GF_DEF elements fetch data */
798         if (im->gdes[i].gf != GF_DEF)
799             continue;
801         skip = 0;
802         /* do we have it already ? */
803         for (ii = 0; ii < i; ii++) {
804             if (im->gdes[ii].gf != GF_DEF)
805                 continue;
806             if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
807                 && (im->gdes[i].cf == im->gdes[ii].cf)
808                 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
809                 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
810                 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
811                 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
812                 /* OK, the data is already there.
813                  ** Just copy the header portion
814                  */
815                 im->gdes[i].start = im->gdes[ii].start;
816                 im->gdes[i].end = im->gdes[ii].end;
817                 im->gdes[i].step = im->gdes[ii].step;
818                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
819                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
820                 im->gdes[i].data = im->gdes[ii].data;
821                 im->gdes[i].data_first = 0;
822                 skip = 1;
823             }
824             if (skip)
825                 break;
826         }
827         if (!skip) {
828             unsigned long ft_step = im->gdes[i].step;   /* ft_step will record what we got from fetch */
830             if ((rrd_fetch_fn(im->gdes[i].rrd,
831                               im->gdes[i].cf,
832                               &im->gdes[i].start,
833                               &im->gdes[i].end,
834                               &ft_step,
835                               &im->gdes[i].ds_cnt,
836                               &im->gdes[i].ds_namv,
837                               &im->gdes[i].data)) == -1) {
838                 return -1;
839             }
840             im->gdes[i].data_first = 1;
842             if (ft_step < im->gdes[i].step) {
843                 reduce_data(im->gdes[i].cf_reduce,
844                             ft_step,
845                             &im->gdes[i].start,
846                             &im->gdes[i].end,
847                             &im->gdes[i].step,
848                             &im->gdes[i].ds_cnt, &im->gdes[i].data);
849             } else {
850                 im->gdes[i].step = ft_step;
851             }
852         }
854         /* lets see if the required data source is really there */
855         for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
856             if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
857                 im->gdes[i].ds = ii;
858             }
859         }
860         if (im->gdes[i].ds == -1) {
861             rrd_set_error("No DS called '%s' in '%s'",
862                           im->gdes[i].ds_nam, im->gdes[i].rrd);
863             return -1;
864         }
866     }
867     return 0;
870 /* evaluate the expressions in the CDEF functions */
872 /*************************************************************
873  * CDEF stuff 
874  *************************************************************/
876 long find_var_wrapper(
877     void *arg1,
878     char *key)
880     return find_var((image_desc_t *) arg1, key);
883 /* find gdes containing var*/
884 long find_var(
885     image_desc_t *im,
886     char *key)
888     long      ii;
890     for (ii = 0; ii < im->gdes_c - 1; ii++) {
891         if ((im->gdes[ii].gf == GF_DEF
892              || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
893             && (strcmp(im->gdes[ii].vname, key) == 0)) {
894             return ii;
895         }
896     }
897     return -1;
900 /* find the largest common denominator for all the numbers
901    in the 0 terminated num array */
902 long lcd(
903     long *num)
905     long      rest;
906     int       i;
908     for (i = 0; num[i + 1] != 0; i++) {
909         do {
910             rest = num[i] % num[i + 1];
911             num[i] = num[i + 1];
912             num[i + 1] = rest;
913         } while (rest != 0);
914         num[i + 1] = num[i];
915     }
916 /*    return i==0?num[i]:num[i-1]; */
917     return num[i];
920 /* run the rpn calculator on all the VDEF and CDEF arguments */
921 int data_calc(
922     image_desc_t *im)
925     int       gdi;
926     int       dataidx;
927     long     *steparray, rpi;
928     int       stepcnt;
929     time_t    now;
930     rpnstack_t rpnstack;
932     rpnstack_init(&rpnstack);
934     for (gdi = 0; gdi < im->gdes_c; gdi++) {
935         /* Look for GF_VDEF and GF_CDEF in the same loop,
936          * so CDEFs can use VDEFs and vice versa
937          */
938         switch (im->gdes[gdi].gf) {
939         case GF_XPORT:
940             break;
941         case GF_SHIFT:{
942             graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
944             /* remove current shift */
945             vdp->start -= vdp->shift;
946             vdp->end -= vdp->shift;
948             /* vdef */
949             if (im->gdes[gdi].shidx >= 0)
950                 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
951             /* constant */
952             else
953                 vdp->shift = im->gdes[gdi].shval;
955             /* normalize shift to multiple of consolidated step */
956             vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
958             /* apply shift */
959             vdp->start += vdp->shift;
960             vdp->end += vdp->shift;
961             break;
962         }
963         case GF_VDEF:
964             /* A VDEF has no DS.  This also signals other parts
965              * of rrdtool that this is a VDEF value, not a CDEF.
966              */
967             im->gdes[gdi].ds_cnt = 0;
968             if (vdef_calc(im, gdi)) {
969                 rrd_set_error("Error processing VDEF '%s'",
970                               im->gdes[gdi].vname);
971                 rpnstack_free(&rpnstack);
972                 return -1;
973             }
974             break;
975         case GF_CDEF:
976             im->gdes[gdi].ds_cnt = 1;
977             im->gdes[gdi].ds = 0;
978             im->gdes[gdi].data_first = 1;
979             im->gdes[gdi].start = 0;
980             im->gdes[gdi].end = 0;
981             steparray = NULL;
982             stepcnt = 0;
983             dataidx = -1;
985             /* Find the variables in the expression.
986              * - VDEF variables are substituted by their values
987              *   and the opcode is changed into OP_NUMBER.
988              * - CDEF variables are analized for their step size,
989              *   the lowest common denominator of all the step
990              *   sizes of the data sources involved is calculated
991              *   and the resulting number is the step size for the
992              *   resulting data source.
993              */
994             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
995                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
996                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
997                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
999                     if (im->gdes[ptr].ds_cnt == 0) {    /* this is a VDEF data source */
1000 #if 0
1001                         printf
1002                             ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1003                              im->gdes[gdi].vname, im->gdes[ptr].vname);
1004                         printf("DEBUG: value from vdef is %f\n",
1005                                im->gdes[ptr].vf.val);
1006 #endif
1007                         im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1008                         im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1009                     } else {    /* normal variables and PREF(variables) */
1011                         /* add one entry to the array that keeps track of the step sizes of the
1012                          * data sources going into the CDEF. */
1013                         if ((steparray =
1014                              rrd_realloc(steparray,
1015                                          (++stepcnt +
1016                                           1) * sizeof(*steparray))) == NULL) {
1017                             rrd_set_error("realloc steparray");
1018                             rpnstack_free(&rpnstack);
1019                             return -1;
1020                         };
1022                         steparray[stepcnt - 1] = im->gdes[ptr].step;
1024                         /* adjust start and end of cdef (gdi) so
1025                          * that it runs from the latest start point
1026                          * to the earliest endpoint of any of the
1027                          * rras involved (ptr)
1028                          */
1030                         if (im->gdes[gdi].start < im->gdes[ptr].start)
1031                             im->gdes[gdi].start = im->gdes[ptr].start;
1033                         if (im->gdes[gdi].end == 0 ||
1034                             im->gdes[gdi].end > im->gdes[ptr].end)
1035                             im->gdes[gdi].end = im->gdes[ptr].end;
1037                         /* store pointer to the first element of
1038                          * the rra providing data for variable,
1039                          * further save step size and data source
1040                          * count of this rra
1041                          */
1042                         im->gdes[gdi].rpnp[rpi].data =
1043                             im->gdes[ptr].data + im->gdes[ptr].ds;
1044                         im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1045                         im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1047                         /* backoff the *.data ptr; this is done so
1048                          * rpncalc() function doesn't have to treat
1049                          * the first case differently
1050                          */
1051                     }   /* if ds_cnt != 0 */
1052                 }       /* if OP_VARIABLE */
1053             }           /* loop through all rpi */
1055             /* move the data pointers to the correct period */
1056             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1057                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1058                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1059                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1060                     long      diff =
1061                         im->gdes[gdi].start - im->gdes[ptr].start;
1063                     if (diff > 0)
1064                         im->gdes[gdi].rpnp[rpi].data +=
1065                             (diff / im->gdes[ptr].step) *
1066                             im->gdes[ptr].ds_cnt;
1067                 }
1068             }
1070             if (steparray == NULL) {
1071                 rrd_set_error("rpn expressions without DEF"
1072                               " or CDEF variables are not supported");
1073                 rpnstack_free(&rpnstack);
1074                 return -1;
1075             }
1076             steparray[stepcnt] = 0;
1077             /* Now find the resulting step.  All steps in all
1078              * used RRAs have to be visited
1079              */
1080             im->gdes[gdi].step = lcd(steparray);
1081             free(steparray);
1082             if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1083                                                im->gdes[gdi].start)
1084                                               / im->gdes[gdi].step)
1085                                              * sizeof(double))) == NULL) {
1086                 rrd_set_error("malloc im->gdes[gdi].data");
1087                 rpnstack_free(&rpnstack);
1088                 return -1;
1089             }
1091             /* Step through the new cdef results array and
1092              * calculate the values
1093              */
1094             for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1095                  now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1096                 rpnp_t   *rpnp = im->gdes[gdi].rpnp;
1098                 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1099                  * in this case we are advancing by timesteps;
1100                  * we use the fact that time_t is a synonym for long
1101                  */
1102                 if (rpn_calc(rpnp, &rpnstack, (long) now,
1103                              im->gdes[gdi].data, ++dataidx) == -1) {
1104                     /* rpn_calc sets the error string */
1105                     rpnstack_free(&rpnstack);
1106                     return -1;
1107                 }
1108             }           /* enumerate over time steps within a CDEF */
1109             break;
1110         default:
1111             continue;
1112         }
1113     }                   /* enumerate over CDEFs */
1114     rpnstack_free(&rpnstack);
1115     return 0;
1118 /* massage data so, that we get one value for each x coordinate in the graph */
1119 int data_proc(
1120     image_desc_t *im)
1122     long      i, ii;
1123     double    pixstep = (double) (im->end - im->start)
1124         / (double) im->xsize;   /* how much time 
1125                                    passes in one pixel */
1126     double    paintval;
1127     double    minval = DNAN, maxval = DNAN;
1129     unsigned long gr_time;
1131     /* memory for the processed data */
1132     for (i = 0; i < im->gdes_c; i++) {
1133         if ((im->gdes[i].gf == GF_LINE) ||
1134             (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1135             if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1136                                              * sizeof(rrd_value_t))) == NULL) {
1137                 rrd_set_error("malloc data_proc");
1138                 return -1;
1139             }
1140         }
1141     }
1143     for (i = 0; i < im->xsize; i++) {   /* for each pixel */
1144         long      vidx;
1146         gr_time = im->start + pixstep * i;  /* time of the current step */
1147         paintval = 0.0;
1149         for (ii = 0; ii < im->gdes_c; ii++) {
1150             double    value;
1152             switch (im->gdes[ii].gf) {
1153             case GF_LINE:
1154             case GF_AREA:
1155             case GF_TICK:
1156                 if (!im->gdes[ii].stack)
1157                     paintval = 0.0;
1158                 value = im->gdes[ii].yrule;
1159                 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1160                     /* The time of the data doesn't necessarily match
1161                      ** the time of the graph. Beware.
1162                      */
1163                     vidx = im->gdes[ii].vidx;
1164                     if (im->gdes[vidx].gf == GF_VDEF) {
1165                         value = im->gdes[vidx].vf.val;
1166                     } else
1167                         if (((long int) gr_time >=
1168                              (long int) im->gdes[vidx].start)
1169                             && ((long int) gr_time <=
1170                                 (long int) im->gdes[vidx].end)) {
1171                         value = im->gdes[vidx].data[(unsigned long)
1172                                                     floor((double)
1173                                                           (gr_time -
1174                                                            im->gdes[vidx].
1175                                                            start)
1176                                                           /
1177                                                           im->gdes[vidx].step)
1178                                                     * im->gdes[vidx].ds_cnt +
1179                                                     im->gdes[vidx].ds];
1180                     } else {
1181                         value = DNAN;
1182                     }
1183                 };
1185                 if (!isnan(value)) {
1186                     paintval += value;
1187                     im->gdes[ii].p_data[i] = paintval;
1188                     /* GF_TICK: the data values are not
1189                      ** relevant for min and max
1190                      */
1191                     if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1192                         if ((isnan(minval) || paintval < minval) &&
1193                             !(im->logarithmic && paintval <= 0.0))
1194                             minval = paintval;
1195                         if (isnan(maxval) || paintval > maxval)
1196                             maxval = paintval;
1197                     }
1198                 } else {
1199                     im->gdes[ii].p_data[i] = DNAN;
1200                 }
1201                 break;
1202             case GF_STACK:
1203                 rrd_set_error
1204                     ("STACK should already be turned into LINE or AREA here");
1205                 return -1;
1206                 break;
1207             default:
1208                 break;
1209             }
1210         }
1211     }
1213     /* if min or max have not been asigned a value this is because
1214        there was no data in the graph ... this is not good ...
1215        lets set these to dummy values then ... */
1217     if (im->logarithmic) {
1218         if (isnan(minval))
1219             minval = 0.2;
1220         if (isnan(maxval))
1221             maxval = 5.1;
1222     } else {
1223         if (isnan(minval))
1224             minval = 0.0;
1225         if (isnan(maxval))
1226             maxval = 1.0;
1227     }
1229     /* adjust min and max values */
1230     if (isnan(im->minval)
1231         /* don't adjust low-end with log scale *//* why not? */
1232         || ((!im->rigid) && im->minval > minval)
1233         ) {
1234         if (im->logarithmic)
1235             im->minval = minval * 0.5;
1236         else
1237             im->minval = minval;
1238     }
1239     if (isnan(im->maxval)
1240         || (!im->rigid && im->maxval < maxval)
1241         ) {
1242         if (im->logarithmic)
1243             im->maxval = maxval * 2.0;
1244         else
1245             im->maxval = maxval;
1246     }
1247     /* make sure min is smaller than max */
1248     if (im->minval > im->maxval) {
1249         im->minval = 0.99 * im->maxval;
1250     }
1252     /* make sure min and max are not equal */
1253     if (im->minval == im->maxval) {
1254         im->maxval *= 1.01;
1255         if (!im->logarithmic) {
1256             im->minval *= 0.99;
1257         }
1258         /* make sure min and max are not both zero */
1259         if (im->maxval == 0.0) {
1260             im->maxval = 1.0;
1261         }
1262     }
1263     return 0;
1268 /* identify the point where the first gridline, label ... gets placed */
1270 time_t find_first_time(
1271     time_t start,       /* what is the initial time */
1272     enum tmt_en baseint,    /* what is the basic interval */
1273     long basestep       /* how many if these do we jump a time */
1274     )
1276     struct tm tm;
1278     localtime_r(&start, &tm);
1280     switch (baseint) {
1281     case TMT_SECOND:
1282         tm.       tm_sec -= tm.tm_sec % basestep;
1284         break;
1285     case TMT_MINUTE:
1286         tm.       tm_sec = 0;
1287         tm.       tm_min -= tm.tm_min % basestep;
1289         break;
1290     case TMT_HOUR:
1291         tm.       tm_sec = 0;
1292         tm.       tm_min = 0;
1293         tm.       tm_hour -= tm.tm_hour % basestep;
1295         break;
1296     case TMT_DAY:
1297         /* we do NOT look at the basestep for this ... */
1298         tm.       tm_sec = 0;
1299         tm.       tm_min = 0;
1300         tm.       tm_hour = 0;
1302         break;
1303     case TMT_WEEK:
1304         /* we do NOT look at the basestep for this ... */
1305         tm.       tm_sec = 0;
1306         tm.       tm_min = 0;
1307         tm.       tm_hour = 0;
1308         tm.       tm_mday -= tm.tm_wday - 1;    /* -1 because we want the monday */
1310         if (tm.tm_wday == 0)
1311             tm.       tm_mday -= 7; /* we want the *previous* monday */
1313         break;
1314     case TMT_MONTH:
1315         tm.       tm_sec = 0;
1316         tm.       tm_min = 0;
1317         tm.       tm_hour = 0;
1318         tm.       tm_mday = 1;
1319         tm.       tm_mon -= tm.tm_mon % basestep;
1321         break;
1323     case TMT_YEAR:
1324         tm.       tm_sec = 0;
1325         tm.       tm_min = 0;
1326         tm.       tm_hour = 0;
1327         tm.       tm_mday = 1;
1328         tm.       tm_mon = 0;
1329         tm.       tm_year -= (
1330     tm.tm_year + 1900) %basestep;
1332     }
1333     return mktime(&tm);
1336 /* identify the point where the next gridline, label ... gets placed */
1337 time_t find_next_time(
1338     time_t current,     /* what is the initial time */
1339     enum tmt_en baseint,    /* what is the basic interval */
1340     long basestep       /* how many if these do we jump a time */
1341     )
1343     struct tm tm;
1344     time_t    madetime;
1346     localtime_r(&current, &tm);
1348     do {
1349         switch (baseint) {
1350         case TMT_SECOND:
1351             tm.       tm_sec += basestep;
1353             break;
1354         case TMT_MINUTE:
1355             tm.       tm_min += basestep;
1357             break;
1358         case TMT_HOUR:
1359             tm.       tm_hour += basestep;
1361             break;
1362         case TMT_DAY:
1363             tm.       tm_mday += basestep;
1365             break;
1366         case TMT_WEEK:
1367             tm.       tm_mday += 7 * basestep;
1369             break;
1370         case TMT_MONTH:
1371             tm.       tm_mon += basestep;
1373             break;
1374         case TMT_YEAR:
1375             tm.       tm_year += basestep;
1376         }
1377         madetime = mktime(&tm);
1378     } while (madetime == -1);   /* this is necessary to skip impssible times
1379                                    like the daylight saving time skips */
1380     return madetime;
1385 /* calculate values required for PRINT and GPRINT functions */
1387 int print_calc(
1388     image_desc_t *im,
1389     char ***prdata)
1391     long      i, ii, validsteps;
1392     double    printval;
1393     struct tm tmvdef;
1394     int       graphelement = 0;
1395     long      vidx;
1396     int       max_ii;
1397     double    magfact = -1;
1398     char     *si_symb = "";
1399     char     *percent_s;
1400     int       prlines = 1;
1402     /* wow initializing tmvdef is quite a task :-) */
1403     time_t    now = time(NULL);
1405     localtime_r(&now, &tmvdef);
1406     if (im->imginfo)
1407         prlines++;
1408     for (i = 0; i < im->gdes_c; i++) {
1409         vidx = im->gdes[i].vidx;
1410         switch (im->gdes[i].gf) {
1411         case GF_PRINT:
1412             prlines++;
1413             if (((*prdata) =
1414                  rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1415                 rrd_set_error("realloc prdata");
1416                 return 0;
1417             }
1418         case GF_GPRINT:
1419             /* PRINT and GPRINT can now print VDEF generated values.
1420              * There's no need to do any calculations on them as these
1421              * calculations were already made.
1422              */
1423             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1424                 printval = im->gdes[vidx].vf.val;
1425                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1426             } else {    /* need to calculate max,min,avg etcetera */
1427                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1428                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1429                 printval = DNAN;
1430                 validsteps = 0;
1431                 for (ii = im->gdes[vidx].ds;
1432                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1433                     if (!finite(im->gdes[vidx].data[ii]))
1434                         continue;
1435                     if (isnan(printval)) {
1436                         printval = im->gdes[vidx].data[ii];
1437                         validsteps++;
1438                         continue;
1439                     }
1441                     switch (im->gdes[i].cf) {
1442                     case CF_HWPREDICT:
1443                     case CF_MHWPREDICT:
1444                     case CF_DEVPREDICT:
1445                     case CF_DEVSEASONAL:
1446                     case CF_SEASONAL:
1447                     case CF_AVERAGE:
1448                         validsteps++;
1449                         printval += im->gdes[vidx].data[ii];
1450                         break;
1451                     case CF_MINIMUM:
1452                         printval = min(printval, im->gdes[vidx].data[ii]);
1453                         break;
1454                     case CF_FAILURES:
1455                     case CF_MAXIMUM:
1456                         printval = max(printval, im->gdes[vidx].data[ii]);
1457                         break;
1458                     case CF_LAST:
1459                         printval = im->gdes[vidx].data[ii];
1460                     }
1461                 }
1462                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1463                     if (validsteps > 1) {
1464                         printval = (printval / validsteps);
1465                     }
1466                 }
1467             }           /* prepare printval */
1469             if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1470                 /* Magfact is set to -1 upon entry to print_calc.  If it
1471                  * is still less than 0, then we need to run auto_scale.
1472                  * Otherwise, put the value into the correct units.  If
1473                  * the value is 0, then do not set the symbol or magnification
1474                  * so next the calculation will be performed again. */
1475                 if (magfact < 0.0) {
1476                     auto_scale(im, &printval, &si_symb, &magfact);
1477                     if (printval == 0.0)
1478                         magfact = -1.0;
1479                 } else {
1480                     printval /= magfact;
1481                 }
1482                 *(++percent_s) = 's';
1483             } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1484                 auto_scale(im, &printval, &si_symb, &magfact);
1485             }
1487             if (im->gdes[i].gf == GF_PRINT) {
1488                 (*prdata)[prlines - 2] =
1489                     malloc((FMT_LEG_LEN + 2) * sizeof(char));
1490                 (*prdata)[prlines - 1] = NULL;
1491                 if (im->gdes[i].strftm) {
1492                     strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1493                              im->gdes[i].format, &tmvdef);
1494                 } else {
1495                     if (bad_format(im->gdes[i].format)) {
1496                         rrd_set_error("bad format for PRINT in '%s'",
1497                                       im->gdes[i].format);
1498                         return -1;
1499                     }
1500 #ifdef HAVE_SNPRINTF
1501                     snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1502                              im->gdes[i].format, printval, si_symb);
1503 #else
1504                     sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1505                             printval, si_symb);
1506 #endif
1507                 }
1508             } else {
1509                 /* GF_GPRINT */
1511                 if (im->gdes[i].strftm) {
1512                     strftime(im->gdes[i].legend, FMT_LEG_LEN,
1513                              im->gdes[i].format, &tmvdef);
1514                 } else {
1515                     if (bad_format(im->gdes[i].format)) {
1516                         rrd_set_error("bad format for GPRINT in '%s'",
1517                                       im->gdes[i].format);
1518                         return -1;
1519                     }
1520 #ifdef HAVE_SNPRINTF
1521                     snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1522                              im->gdes[i].format, printval, si_symb);
1523 #else
1524                     sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1525                             si_symb);
1526 #endif
1527                 }
1528                 graphelement = 1;
1529             }
1530             break;
1531         case GF_LINE:
1532         case GF_AREA:
1533         case GF_TICK:
1534             graphelement = 1;
1535             break;
1536         case GF_HRULE:
1537             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1538                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1539             };
1540             graphelement = 1;
1541             break;
1542         case GF_VRULE:
1543             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1544                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1545             };
1546             graphelement = 1;
1547             break;
1548         case GF_COMMENT:
1549         case GF_TEXTALIGN:
1550         case GF_DEF:
1551         case GF_CDEF:
1552         case GF_VDEF:
1553 #ifdef WITH_PIECHART
1554         case GF_PART:
1555 #endif
1556         case GF_SHIFT:
1557         case GF_XPORT:
1558             break;
1559         case GF_STACK:
1560             rrd_set_error
1561                 ("STACK should already be turned into LINE or AREA here");
1562             return -1;
1563             break;
1564         }
1565     }
1566     return graphelement;
1570 /* place legends with color spots */
1571 int leg_place(
1572     image_desc_t *im,
1573     int *gY)
1575     /* graph labels */
1576     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1577     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1578     int       fill = 0, fill_last;
1579     int       leg_c = 0;
1580     int       leg_x = border, leg_y = im->yimg;
1581     int       leg_y_prev = im->yimg;
1582     int       leg_cc;
1583     int       glue = 0;
1584     int       i, ii, mark = 0;
1585     char      prt_fctn; /*special printfunctions */
1586     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1587     int      *legspace;
1589     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1590         if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1591             rrd_set_error("malloc for legspace");
1592             return -1;
1593         }
1595         if (im->extra_flags & FULL_SIZE_MODE)
1596             leg_y = leg_y_prev =
1597                 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1599         for (i = 0; i < im->gdes_c; i++) {
1600             fill_last = fill;
1602             /* hide legends for rules which are not displayed */
1604             if (im->gdes[i].gf == GF_TEXTALIGN) {
1605                 default_txtalign = im->gdes[i].txtalign;
1606             }
1608             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1609                 if (im->gdes[i].gf == GF_HRULE &&
1610                     (im->gdes[i].yrule < im->minval
1611                      || im->gdes[i].yrule > im->maxval))
1612                     im->gdes[i].legend[0] = '\0';
1614                 if (im->gdes[i].gf == GF_VRULE &&
1615                     (im->gdes[i].xrule < im->start
1616                      || im->gdes[i].xrule > im->end))
1617                     im->gdes[i].legend[0] = '\0';
1618             }
1620             leg_cc = strlen(im->gdes[i].legend);
1622             /* is there a controle code ant the end of the legend string ? */
1623             /* and it is not a tab \\t */
1624             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1625                 && im->gdes[i].legend[leg_cc - 1] != 't') {
1626                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1627                 leg_cc -= 2;
1628                 im->gdes[i].legend[leg_cc] = '\0';
1629             } else {
1630                 prt_fctn = '\0';
1631             }
1632             /* only valid control codes */
1633             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1634                 prt_fctn != 'r' &&
1635                 prt_fctn != 'j' &&
1636                 prt_fctn != 'c' &&
1637                 prt_fctn != 's' &&
1638                 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1639                 free(legspace);
1640                 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1641                               im->gdes[i].legend, prt_fctn);
1642                 return -1;
1644             }
1645             /* \n -> \l */
1646             if (prt_fctn == 'n') {
1647                 prt_fctn = 'l';
1648             }
1650             /* remove exess space from the end of the legend for \g */
1651             while (prt_fctn == 'g' &&
1652                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1653                 leg_cc--;
1654                 im->gdes[i].legend[leg_cc] = '\0';
1655             }
1657             if (leg_cc != 0) {
1659                 /* no interleg space if string ends in \g */
1660                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1662                 if (fill > 0) {
1663                     fill += legspace[i];
1664                 }
1665                 fill += gfx_get_text_width(im, fill + border,
1666                                            im->text_prop[TEXT_PROP_LEGEND].
1667                                            font,
1668                                            im->text_prop[TEXT_PROP_LEGEND].
1669                                            size, im->tabwidth,
1670                                            im->gdes[i].legend);
1671                 leg_c++;
1672             } else {
1673                 legspace[i] = 0;
1674             }
1675             /* who said there was a special tag ... ? */
1676             if (prt_fctn == 'g') {
1677                 prt_fctn = '\0';
1678             }
1680             if (prt_fctn == '\0') {
1681                 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1682                     /* just one legend item is left right or center */
1683                     switch (default_txtalign) {
1684                     case TXA_RIGHT:
1685                         prt_fctn = 'r';
1686                         break;
1687                     case TXA_CENTER:
1688                         prt_fctn = 'c';
1689                         break;
1690                     case TXA_JUSTIFIED:
1691                         prt_fctn = 'j';
1692                         break;
1693                     default:
1694                         prt_fctn = 'l';
1695                         break;
1696                     }
1697                 }
1698                 /* is it time to place the legends ? */
1699                 if (fill > im->ximg - 2 * border) {
1700                     if (leg_c > 1) {
1701                         /* go back one */
1702                         i--;
1703                         fill = fill_last;
1704                         leg_c--;
1705                     }
1706                 }
1707                 if (leg_c == 1 && prt_fctn == 'j') {
1708                     prt_fctn = 'l';
1709                 }
1710             }
1713             if (prt_fctn != '\0') {
1714                 leg_x = border;
1715                 if (leg_c >= 2 && prt_fctn == 'j') {
1716                     glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1717                 } else {
1718                     glue = 0;
1719                 }
1720                 if (prt_fctn == 'c')
1721                     leg_x = (im->ximg - fill) / 2.0;
1722                 if (prt_fctn == 'r')
1723                     leg_x = im->ximg - fill - border;
1725                 for (ii = mark; ii <= i; ii++) {
1726                     if (im->gdes[ii].legend[0] == '\0')
1727                         continue;   /* skip empty legends */
1728                     im->gdes[ii].leg_x = leg_x;
1729                     im->gdes[ii].leg_y = leg_y;
1730                     leg_x +=
1731                         gfx_get_text_width(im, leg_x,
1732                                            im->text_prop[TEXT_PROP_LEGEND].
1733                                            font,
1734                                            im->text_prop[TEXT_PROP_LEGEND].
1735                                            size, im->tabwidth,
1736                                            im->gdes[ii].legend)
1737                         + legspace[ii]
1738                         + glue;
1739                 }
1740                 leg_y_prev = leg_y;
1741                 if (im->extra_flags & FULL_SIZE_MODE) {
1742                     /* only add y space if there was text on the line */
1743                     if (leg_x > border || prt_fctn == 's')
1744                         leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1745                     if (prt_fctn == 's')
1746                         leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1747                 } else {
1748                     if (leg_x > border || prt_fctn == 's')
1749                         leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1750                     if (prt_fctn == 's')
1751                         leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1752                 }
1753                 fill = 0;
1754                 leg_c = 0;
1755                 mark = ii;
1756             }
1757         }
1759         if (im->extra_flags & FULL_SIZE_MODE) {
1760             if (leg_y != leg_y_prev) {
1761                 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1762                 im->yorigin =
1763                     leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1764             }
1765         } else {
1766             im->yimg = leg_y_prev;
1767             /* if we did place some legends we have to add vertical space */
1768             if (leg_y != im->yimg)
1769                 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1770         }
1771         free(legspace);
1772     }
1773     return 0;
1776 /* create a grid on the graph. it determines what to do
1777    from the values of xsize, start and end */
1779 /* the xaxis labels are determined from the number of seconds per pixel
1780    in the requested graph */
1784 int calc_horizontal_grid(
1785     image_desc_t *im)
1787     double    range;
1788     double    scaledrange;
1789     int       pixel, i;
1790     int       gridind = 0;
1791     int       decimals, fractionals;
1793     im->ygrid_scale.labfact = 2;
1794     range = im->maxval - im->minval;
1795     scaledrange = range / im->magfact;
1797     /* does the scale of this graph make it impossible to put lines
1798        on it? If so, give up. */
1799     if (isnan(scaledrange)) {
1800         return 0;
1801     }
1803     /* find grid spaceing */
1804     pixel = 1;
1805     if (isnan(im->ygridstep)) {
1806         if (im->extra_flags & ALTYGRID) {
1807             /* find the value with max number of digits. Get number of digits */
1808             decimals =
1809                 ceil(log10
1810                      (max(fabs(im->maxval), fabs(im->minval)) *
1811                       im->viewfactor / im->magfact));
1812             if (decimals <= 0)  /* everything is small. make place for zero */
1813                 decimals = 1;
1815             im->ygrid_scale.gridstep =
1816                 pow((double) 10,
1817                     floor(log10(range * im->viewfactor / im->magfact))) /
1818                 im->viewfactor * im->magfact;
1820             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1821                 im->ygrid_scale.gridstep = 0.1;
1822             /* should have at least 5 lines but no more then 15 */
1823             if (range / im->ygrid_scale.gridstep < 5)
1824                 im->ygrid_scale.gridstep /= 10;
1825             if (range / im->ygrid_scale.gridstep > 15)
1826                 im->ygrid_scale.gridstep *= 10;
1827             if (range / im->ygrid_scale.gridstep > 5) {
1828                 im->ygrid_scale.labfact = 1;
1829                 if (range / im->ygrid_scale.gridstep > 8)
1830                     im->ygrid_scale.labfact = 2;
1831             } else {
1832                 im->ygrid_scale.gridstep /= 5;
1833                 im->ygrid_scale.labfact = 5;
1834             }
1835             fractionals =
1836                 floor(log10
1837                       (im->ygrid_scale.gridstep *
1838                        (double) im->ygrid_scale.labfact * im->viewfactor /
1839                        im->magfact));
1840             if (fractionals < 0) {  /* small amplitude. */
1841                 int       len = decimals - fractionals + 1;
1843                 if (im->unitslength < len + 2)
1844                     im->unitslength = len + 2;
1845                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1846                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1847             } else {
1848                 int       len = decimals + 1;
1850                 if (im->unitslength < len + 2)
1851                     im->unitslength = len + 2;
1852                 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1853                         (im->symbol != ' ' ? " %c" : ""));
1854             }
1855         } else {
1856             for (i = 0; ylab[i].grid > 0; i++) {
1857                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1858                 gridind = i;
1859                 if (pixel > 7)
1860                     break;
1861             }
1863             for (i = 0; i < 4; i++) {
1864                 if (pixel * ylab[gridind].lfac[i] >=
1865                     2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1866                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1867                     break;
1868                 }
1869             }
1871             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1872         }
1873     } else {
1874         im->ygrid_scale.gridstep = im->ygridstep;
1875         im->ygrid_scale.labfact = im->ylabfact;
1876     }
1877     return 1;
1880 int draw_horizontal_grid(
1881     image_desc_t *im)
1883     int       i;
1884     double    scaledstep;
1885     char      graph_label[100];
1886     int       nlabels = 0;
1887     double    X0 = im->xorigin;
1888     double    X1 = im->xorigin + im->xsize;
1890     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1891     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1892     double    MaxY;
1894     scaledstep =
1895         im->ygrid_scale.gridstep / (double) im->magfact *
1896         (double) im->viewfactor;
1897     MaxY = scaledstep * (double) egrid;
1898     for (i = sgrid; i <= egrid; i++) {
1899         double    Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1900         double    YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1902         if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1903             && floor(Y0 + 0.5) <= im->yorigin) {
1904             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1905                with the chosen settings. Add a label if required by settings, or if
1906                there is only one label so far and the next grid line is out of bounds. */
1907             if (i % im->ygrid_scale.labfact == 0
1908                 || (nlabels == 1
1909                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1910                 if (im->symbol == ' ') {
1911                     if (im->extra_flags & ALTYGRID) {
1912                         sprintf(graph_label, im->ygrid_scale.labfmt,
1913                                 scaledstep * (double) i);
1914                     } else {
1915                         if (MaxY < 10) {
1916                             sprintf(graph_label, "%4.1f",
1917                                     scaledstep * (double) i);
1918                         } else {
1919                             sprintf(graph_label, "%4.0f",
1920                                     scaledstep * (double) i);
1921                         }
1922                     }
1923                 } else {
1924                     char      sisym = (i == 0 ? ' ' : im->symbol);
1926                     if (im->extra_flags & ALTYGRID) {
1927                         sprintf(graph_label, im->ygrid_scale.labfmt,
1928                                 scaledstep * (double) i, sisym);
1929                     } else {
1930                         if (MaxY < 10) {
1931                             sprintf(graph_label, "%4.1f %c",
1932                                     scaledstep * (double) i, sisym);
1933                         } else {
1934                             sprintf(graph_label, "%4.0f %c",
1935                                     scaledstep * (double) i, sisym);
1936                         }
1937                     }
1938                 }
1939                 nlabels++;
1941                 gfx_text(im,
1942                          X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1943                          im->graph_col[GRC_FONT],
1944                          im->text_prop[TEXT_PROP_AXIS].font,
1945                          im->text_prop[TEXT_PROP_AXIS].size,
1946                          im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1947                          graph_label);
1948                 gfx_line(im,
1949                          X0 - 2, Y0,
1950                          X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1951                 gfx_line(im,
1952                          X1, Y0,
1953                          X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1954                 gfx_dashed_line(im,
1955                                 X0 - 2, Y0,
1956                                 X1 + 2, Y0,
1957                                 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1958                                 im->grid_dash_on, im->grid_dash_off);
1960             } else if (!(im->extra_flags & NOMINOR)) {
1961                 gfx_line(im,
1962                          X0 - 2, Y0,
1963                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1964                 gfx_line(im,
1965                          X1, Y0,
1966                          X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
1967                 gfx_dashed_line(im,
1968                                 X0 - 1, Y0,
1969                                 X1 + 1, Y0,
1970                                 GRIDWIDTH, im->graph_col[GRC_GRID],
1971                                 im->grid_dash_on, im->grid_dash_off);
1973             }
1974         }
1975     }
1976     return 1;
1979 /* this is frexp for base 10 */
1980 double    frexp10(
1981     double,
1982     double *);
1983 double frexp10(
1984     double x,
1985     double *e)
1987     double    mnt;
1988     int       iexp;
1990     iexp = floor(log(fabs(x)) / log(10));
1991     mnt = x / pow(10.0, iexp);
1992     if (mnt >= 10.0) {
1993         iexp++;
1994         mnt = x / pow(10.0, iexp);
1995     }
1996     *e = iexp;
1997     return mnt;
2000 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2001 /* yes we are loosing precision by doing tos with floats instead of doubles
2002    but it seems more stable this way. */
2004 static int AlmostEqual2sComplement(
2005     float A,
2006     float B,
2007     int maxUlps)
2010     int       aInt = *(int *) &A;
2011     int       bInt = *(int *) &B;
2012     int       intDiff;
2014     /* Make sure maxUlps is non-negative and small enough that the
2015        default NAN won't compare as equal to anything.  */
2017     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2019     /* Make aInt lexicographically ordered as a twos-complement int */
2021     if (aInt < 0)
2022         aInt = 0x80000000l - aInt;
2024     /* Make bInt lexicographically ordered as a twos-complement int */
2026     if (bInt < 0)
2027         bInt = 0x80000000l - bInt;
2029     intDiff = abs(aInt - bInt);
2031     if (intDiff <= maxUlps)
2032         return 1;
2034     return 0;
2037 /* logaritmic horizontal grid */
2038 int horizontal_log_grid(
2039     image_desc_t *im)
2041     double    yloglab[][10] = {
2042         {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2043         {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2044         {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2045         {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2046         {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2047         {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2048     };
2050     int       i, j, val_exp, min_exp;
2051     double    nex;      /* number of decades in data */
2052     double    logscale; /* scale in logarithmic space */
2053     int       exfrac = 1;   /* decade spacing */
2054     int       mid = -1; /* row in yloglab for major grid */
2055     double    mspac;    /* smallest major grid spacing (pixels) */
2056     int       flab;     /* first value in yloglab to use */
2057     double    value, tmp, pre_value;
2058     double    X0, X1, Y0;
2059     char      graph_label[100];
2061     nex = log10(im->maxval / im->minval);
2062     logscale = im->ysize / nex;
2064     /* major spacing for data with high dynamic range */
2065     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2066         if (exfrac == 1)
2067             exfrac = 3;
2068         else
2069             exfrac += 3;
2070     }
2072     /* major spacing for less dynamic data */
2073     do {
2074         /* search best row in yloglab */
2075         mid++;
2076         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2077         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2078     } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2079              && yloglab[mid][0] > 0);
2080     if (mid)
2081         mid--;
2083     /* find first value in yloglab */
2084     for (flab = 0;
2085          yloglab[mid][flab] < 10
2086          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2087     if (yloglab[mid][flab] == 10.0) {
2088         tmp += 1.0;
2089         flab = 0;
2090     }
2091     val_exp = tmp;
2092     if (val_exp % exfrac)
2093         val_exp += abs(-val_exp % exfrac);
2095     X0 = im->xorigin;
2096     X1 = im->xorigin + im->xsize;
2098     /* draw grid */
2099     pre_value = DNAN;
2100     while (1) {
2102         value = yloglab[mid][flab] * pow(10.0, val_exp);
2103         if (AlmostEqual2sComplement(value, pre_value, 4))
2104             break;      /* it seems we are not converging */
2106         pre_value = value;
2108         Y0 = ytr(im, value);
2109         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2110             break;
2112         /* major grid line */
2114         gfx_line(im,
2115                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2116         gfx_line(im,
2117                  X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2120         gfx_dashed_line(im,
2121                         X0 - 2, Y0,
2122                         X1 + 2, Y0,
2123                         MGRIDWIDTH, im->graph_col[GRC_MGRID],
2124                         im->grid_dash_on, im->grid_dash_off);
2126         /* label */
2127         if (im->extra_flags & FORCE_UNITS_SI) {
2128             int       scale;
2129             double    pvalue;
2130             char      symbol;
2132             scale = floor(val_exp / 3.0);
2133             if (value >= 1.0)
2134                 pvalue = pow(10.0, val_exp % 3);
2135             else
2136                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2137             pvalue *= yloglab[mid][flab];
2139             if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2140                 ((scale + si_symbcenter) >= 0))
2141                 symbol = si_symbol[scale + si_symbcenter];
2142             else
2143                 symbol = '?';
2145             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2146         } else
2147             sprintf(graph_label, "%3.0e", value);
2148         gfx_text(im,
2149                  X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2150                  im->graph_col[GRC_FONT],
2151                  im->text_prop[TEXT_PROP_AXIS].font,
2152                  im->text_prop[TEXT_PROP_AXIS].size,
2153                  im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2155         /* minor grid */
2156         if (mid < 4 && exfrac == 1) {
2157             /* find first and last minor line behind current major line
2158              * i is the first line and j tha last */
2159             if (flab == 0) {
2160                 min_exp = val_exp - 1;
2161                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2162                 i = yloglab[mid][i - 1] + 1;
2163                 j = 10;
2164             } else {
2165                 min_exp = val_exp;
2166                 i = yloglab[mid][flab - 1] + 1;
2167                 j = yloglab[mid][flab];
2168             }
2170             /* draw minor lines below current major line */
2171             for (; i < j; i++) {
2173                 value = i * pow(10.0, min_exp);
2174                 if (value < im->minval)
2175                     continue;
2177                 Y0 = ytr(im, value);
2178                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2179                     break;
2181                 /* draw lines */
2182                 gfx_line(im,
2183                          X0 - 2, Y0,
2184                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2185                 gfx_line(im,
2186                          X1, Y0,
2187                          X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2188                 gfx_dashed_line(im,
2189                                 X0 - 1, Y0,
2190                                 X1 + 1, Y0,
2191                                 GRIDWIDTH, im->graph_col[GRC_GRID],
2192                                 im->grid_dash_on, im->grid_dash_off);
2193             }
2194         } else if (exfrac > 1) {
2195             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2196                 value = pow(10.0, i);
2197                 if (value < im->minval)
2198                     continue;
2200                 Y0 = ytr(im, value);
2201                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2202                     break;
2204                 /* draw lines */
2205                 gfx_line(im,
2206                          X0 - 2, Y0,
2207                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2208                 gfx_line(im,
2209                          X1, Y0,
2210                          X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2211                 gfx_dashed_line(im,
2212                                 X0 - 1, Y0,
2213                                 X1 + 1, Y0,
2214                                 GRIDWIDTH, im->graph_col[GRC_GRID],
2215                                 im->grid_dash_on, im->grid_dash_off);
2216             }
2217         }
2219         /* next decade */
2220         if (yloglab[mid][++flab] == 10.0) {
2221             flab = 0;
2222             val_exp += exfrac;
2223         }
2224     }
2226     /* draw minor lines after highest major line */
2227     if (mid < 4 && exfrac == 1) {
2228         /* find first and last minor line below current major line
2229          * i is the first line and j tha last */
2230         if (flab == 0) {
2231             min_exp = val_exp - 1;
2232             for (i = 1; yloglab[mid][i] < 10.0; i++);
2233             i = yloglab[mid][i - 1] + 1;
2234             j = 10;
2235         } else {
2236             min_exp = val_exp;
2237             i = yloglab[mid][flab - 1] + 1;
2238             j = yloglab[mid][flab];
2239         }
2241         /* draw minor lines below current major line */
2242         for (; i < j; i++) {
2244             value = i * pow(10.0, min_exp);
2245             if (value < im->minval)
2246                 continue;
2248             Y0 = ytr(im, value);
2249             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2250                 break;
2252             /* draw lines */
2253             gfx_line(im,
2254                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2255             gfx_line(im,
2256                      X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2257             gfx_dashed_line(im,
2258                             X0 - 1, Y0,
2259                             X1 + 1, Y0,
2260                             GRIDWIDTH, im->graph_col[GRC_GRID],
2261                             im->grid_dash_on, im->grid_dash_off);
2262         }
2263     }
2264     /* fancy minor gridlines */
2265     else if (exfrac > 1) {
2266         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2267             value = pow(10.0, i);
2268             if (value < im->minval)
2269                 continue;
2271             Y0 = ytr(im, value);
2272             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2273                 break;
2275             /* draw lines */
2276             gfx_line(im,
2277                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2278             gfx_line(im,
2279                      X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2280             gfx_dashed_line(im,
2281                             X0 - 1, Y0,
2282                             X1 + 1, Y0,
2283                             GRIDWIDTH, im->graph_col[GRC_GRID],
2284                             im->grid_dash_on, im->grid_dash_off);
2285         }
2286     }
2288     return 1;
2292 void vertical_grid(
2293     image_desc_t *im)
2295     int       xlab_sel; /* which sort of label and grid ? */
2296     time_t    ti, tilab, timajor;
2297     long      factor;
2298     char      graph_label[100];
2299     double    X0, Y0, Y1;   /* points for filled graph and more */
2300     struct tm tm;
2302     /* the type of time grid is determined by finding
2303        the number of seconds per pixel in the graph */
2306     if (im->xlab_user.minsec == -1) {
2307         factor = (im->end - im->start) / im->xsize;
2308         xlab_sel = 0;
2309         while (xlab[xlab_sel + 1].minsec != -1
2310                && xlab[xlab_sel + 1].minsec <= factor) {
2311             xlab_sel++;
2312         }               /* pick the last one */
2313         while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2314                && xlab[xlab_sel].length > (im->end - im->start)) {
2315             xlab_sel--;
2316         }               /* go back to the smallest size */
2317         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2318         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2319         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2320         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2321         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2322         im->xlab_user.labst = xlab[xlab_sel].labst;
2323         im->xlab_user.precis = xlab[xlab_sel].precis;
2324         im->xlab_user.stst = xlab[xlab_sel].stst;
2325     }
2327     /* y coords are the same for every line ... */
2328     Y0 = im->yorigin;
2329     Y1 = im->yorigin - im->ysize;
2332     /* paint the minor grid */
2333     if (!(im->extra_flags & NOMINOR)) {
2334         for (ti = find_first_time(im->start,
2335                                   im->xlab_user.gridtm,
2336                                   im->xlab_user.gridst),
2337              timajor = find_first_time(im->start,
2338                                        im->xlab_user.mgridtm,
2339                                        im->xlab_user.mgridst);
2340              ti < im->end;
2341              ti =
2342              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2343             ) {
2344             /* are we inside the graph ? */
2345             if (ti < im->start || ti > im->end)
2346                 continue;
2347             while (timajor < ti) {
2348                 timajor = find_next_time(timajor,
2349                                          im->xlab_user.mgridtm,
2350                                          im->xlab_user.mgridst);
2351             }
2352             if (ti == timajor)
2353                 continue;   /* skip as falls on major grid line */
2354             X0 = xtr(im, ti);
2355             gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2356                      im->graph_col[GRC_GRID]);
2357             gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2358                      im->graph_col[GRC_GRID]);
2359             gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2360                             im->graph_col[GRC_GRID],
2361                             im->grid_dash_on, im->grid_dash_off);
2363         }
2364     }
2366     /* paint the major grid */
2367     for (ti = find_first_time(im->start,
2368                               im->xlab_user.mgridtm,
2369                               im->xlab_user.mgridst);
2370          ti < im->end;
2371          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2372         ) {
2373         /* are we inside the graph ? */
2374         if (ti < im->start || ti > im->end)
2375             continue;
2376         X0 = xtr(im, ti);
2377         gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2378                  im->graph_col[GRC_MGRID]);
2379         gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2380                  im->graph_col[GRC_MGRID]);
2381         gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2382                         im->graph_col[GRC_MGRID],
2383                         im->grid_dash_on, im->grid_dash_off);
2385     }
2386     /* paint the labels below the graph */
2387     for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2388                               im->xlab_user.labtm,
2389                               im->xlab_user.labst);
2390          ti <= im->end - im->xlab_user.precis / 2;
2391          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2392         ) {
2393         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2394         /* are we inside the graph ? */
2395         if (tilab < im->start || tilab > im->end)
2396             continue;
2398 #if HAVE_STRFTIME
2399         localtime_r(&tilab, &tm);
2400         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2401 #else
2402 # error "your libc has no strftime I guess we'll abort the exercise here."
2403 #endif
2404         gfx_text(im,
2405                  xtr(im, tilab),
2406                  Y0 + 3,
2407                  im->graph_col[GRC_FONT],
2408                  im->text_prop[TEXT_PROP_AXIS].font,
2409                  im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2410                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2412     }
2417 void axis_paint(
2418     image_desc_t *im)
2420     /* draw x and y axis */
2421     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2422        im->xorigin+im->xsize,im->yorigin-im->ysize,
2423        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2425        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2426        im->xorigin+im->xsize,im->yorigin-im->ysize,
2427        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2429     gfx_line(im, im->xorigin - 4, im->yorigin,
2430              im->xorigin + im->xsize + 4, im->yorigin,
2431              MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2433     gfx_line(im, im->xorigin, im->yorigin + 4,
2434              im->xorigin, im->yorigin - im->ysize - 4,
2435              MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2438     /* arrow for X and Y axis direction */
2440     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 */
2441                  im->graph_col[GRC_ARROW]);
2442     gfx_close_path(im);
2444     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 */
2445                  im->graph_col[GRC_ARROW]);
2446     gfx_close_path(im);
2451 void grid_paint(
2452     image_desc_t *im)
2454     long      i;
2455     int       res = 0;
2456     double    X0, Y0;   /* points for filled graph and more */
2457     struct gfx_color_t water_color;
2459     /* draw 3d border */
2460     gfx_new_area(im, 0, im->yimg,
2461                  2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2462     gfx_add_point(im, im->ximg - 2, 2);
2463     gfx_add_point(im, im->ximg, 0);
2464     gfx_add_point(im, 0, 0);
2465     gfx_close_path(im);
2467     gfx_new_area(im, 2, im->yimg - 2,
2468                  im->ximg - 2, im->yimg - 2,
2469                  im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2470     gfx_add_point(im, im->ximg, 0);
2471     gfx_add_point(im, im->ximg, im->yimg);
2472     gfx_add_point(im, 0, im->yimg);
2473     gfx_close_path(im);
2476     if (im->draw_x_grid == 1)
2477         vertical_grid(im);
2479     if (im->draw_y_grid == 1) {
2480         if (im->logarithmic) {
2481             res = horizontal_log_grid(im);
2482         } else {
2483             res = draw_horizontal_grid(im);
2484         }
2486         /* dont draw horizontal grid if there is no min and max val */
2487         if (!res) {
2488             char     *nodata = "No Data found";
2490             gfx_text(im, im->ximg / 2,
2491                      (2 * im->yorigin - im->ysize) / 2,
2492                      im->graph_col[GRC_FONT],
2493                      im->text_prop[TEXT_PROP_AXIS].font,
2494                      im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2495                      0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2496         }
2497     }
2499     /* yaxis unit description */
2500     gfx_text(im,
2501              10, (im->yorigin - im->ysize / 2),
2502              im->graph_col[GRC_FONT],
2503              im->text_prop[TEXT_PROP_UNIT].font,
2504              im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2505              RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2507     /* graph title */
2508     gfx_text(im,
2509              im->ximg / 2, 6,
2510              im->graph_col[GRC_FONT],
2511              im->text_prop[TEXT_PROP_TITLE].font,
2512              im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2513              GFX_H_CENTER, GFX_V_TOP, im->title);
2514     /* rrdtool 'logo' */
2515     water_color = im->graph_col[GRC_FONT];
2516     water_color.alpha = 0.3;
2517     gfx_text(im,
2518              im->ximg - 4, 5,
2519              water_color,
2520              im->text_prop[TEXT_PROP_AXIS].font,
2521              5.5, im->tabwidth, -90,
2522              GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2524     /* graph watermark */
2525     if (im->watermark[0] != '\0') {
2526         gfx_text(im,
2527                  im->ximg / 2, im->yimg - 6,
2528                  water_color,
2529                  im->text_prop[TEXT_PROP_AXIS].font,
2530                  5.5, im->tabwidth, 0,
2531                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2532     }
2534     /* graph labels */
2535     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2536         for (i = 0; i < im->gdes_c; i++) {
2537             if (im->gdes[i].legend[0] == '\0')
2538                 continue;
2540             /* im->gdes[i].leg_y is the bottom of the legend */
2541             X0 = im->gdes[i].leg_x;
2542             Y0 = im->gdes[i].leg_y;
2543             gfx_text(im, X0, Y0,
2544                      im->graph_col[GRC_FONT],
2545                      im->text_prop[TEXT_PROP_LEGEND].font,
2546                      im->text_prop[TEXT_PROP_LEGEND].size,
2547                      im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2548                      im->gdes[i].legend);
2549             /* The legend for GRAPH items starts with "M " to have
2550                enough space for the box */
2551             if (im->gdes[i].gf != GF_PRINT &&
2552                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2553                 double    boxH, boxV;
2554                 double    X1, Y1;
2557                 boxH = gfx_get_text_width(im, 0,
2558                                           im->text_prop[TEXT_PROP_LEGEND].
2559                                           font,
2560                                           im->text_prop[TEXT_PROP_LEGEND].
2561                                           size, im->tabwidth, "o") * 1.2;
2562                 boxV = boxH;
2564                 /* shift the box up a bit */
2565                 Y0 -= boxV * 0.4;
2567                 /* make sure transparent colors show up the same way as in the graph */
2569                 gfx_new_area(im,
2570                              X0, Y0 - boxV,
2571                              X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2572                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2573                 gfx_close_path(im);
2575                 gfx_new_area(im,
2576                              X0, Y0 - boxV,
2577                              X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2578                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2579                 gfx_close_path(im);
2581                 cairo_save(im->cr);
2582                 cairo_new_path(im->cr);
2583                 cairo_set_line_width(im->cr, 1.0);
2584                 X1 = X0 + boxH;
2585                 Y1 = Y0 - boxV;
2586                 gfx_line_fit(im, &X0, &Y0);
2587                 gfx_line_fit(im, &X1, &Y1);
2588                 cairo_move_to(im->cr, X0, Y0);
2589                 cairo_line_to(im->cr, X1, Y0);
2590                 cairo_line_to(im->cr, X1, Y1);
2591                 cairo_line_to(im->cr, X0, Y1);
2592                 cairo_close_path(im->cr);
2593                 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2594                                       im->graph_col[GRC_FRAME].green,
2595                                       im->graph_col[GRC_FRAME].blue,
2596                                       im->graph_col[GRC_FRAME].alpha);
2597                 cairo_stroke(im->cr);
2598                 cairo_restore(im->cr);
2599             }
2600         }
2601     }
2605 /*****************************************************
2606  * lazy check make sure we rely need to create this graph
2607  *****************************************************/
2609 int lazy_check(
2610     image_desc_t *im)
2612     FILE     *fd = NULL;
2613     int       size = 1;
2614     struct stat imgstat;
2616     if (im->lazy == 0)
2617         return 0;       /* no lazy option */
2618     if (stat(im->graphfile, &imgstat) != 0)
2619         return 0;       /* can't stat */
2620     /* one pixel in the existing graph is more then what we would
2621        change here ... */
2622     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2623         return 0;
2624     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2625         return 0;       /* the file does not exist */
2626     switch (im->imgformat) {
2627     case IF_PNG:
2628         size = PngSize(fd, &(im->ximg), &(im->yimg));
2629         break;
2630     default:
2631         size = 1;
2632     }
2633     fclose(fd);
2634     return size;
2638 int graph_size_location(
2639     image_desc_t *im,
2640     int elements)
2642     /* The actual size of the image to draw is determined from
2643      ** several sources.  The size given on the command line is
2644      ** the graph area but we need more as we have to draw labels
2645      ** and other things outside the graph area
2646      */
2648     int       Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2649         Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2651     if (im->extra_flags & ONLY_GRAPH) {
2652         im->xorigin = 0;
2653         im->ximg = im->xsize;
2654         im->yimg = im->ysize;
2655         im->yorigin = im->ysize;
2656         ytr(im, DNAN);
2657         return 0;
2658     }
2660     /** +---+--------------------------------------------+
2661      ** | y |...............graph title..................|
2662      ** |   +---+-------------------------------+--------+
2663      ** | a | y |                               |        |
2664      ** | x |   |                               |        |
2665      ** | i | a |                               |  pie   |
2666      ** | s | x |       main graph area         | chart  |
2667      ** |   | i |                               |  area  |
2668      ** | t | s |                               |        |
2669      ** | i |   |                               |        |
2670      ** | t | l |                               |        |
2671      ** | l | b +-------------------------------+--------+
2672      ** | e | l |       x axis labels           |        |
2673      ** +---+---+-------------------------------+--------+
2674      ** |....................legends.....................|
2675      ** +------------------------------------------------+
2676      ** |                   watermark                    |
2677      ** +------------------------------------------------+
2678      */
2680     if (im->ylegend[0] != '\0') {
2681         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2682     }
2684     if (im->title[0] != '\0') {
2685         /* The title is placed "inbetween" two text lines so it
2686          ** automatically has some vertical spacing.  The horizontal
2687          ** spacing is added here, on each side.
2688          */
2689         /* if necessary, reduce the font size of the title until it fits the image width */
2690         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2691     }
2693     if (elements) {
2694         if (im->draw_x_grid) {
2695             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2696         }
2697         if (im->draw_y_grid || im->forceleftspace) {
2698             Xylabel = gfx_get_text_width(im, 0,
2699                                          im->text_prop[TEXT_PROP_AXIS].font,
2700                                          im->text_prop[TEXT_PROP_AXIS].size,
2701                                          im->tabwidth, "0") * im->unitslength;
2702         }
2703     }
2705     if (im->extra_flags & FULL_SIZE_MODE) {
2706         /* The actual size of the image to draw has been determined by the user.
2707          ** The graph area is the space remaining after accounting for the legend,
2708          ** the watermark, the pie chart, the axis labels, and the title.
2709          */
2710         im->xorigin = 0;
2711         im->ximg = im->xsize;
2712         im->yimg = im->ysize;
2713         im->yorigin = im->ysize;
2714         Xmain = im->ximg;
2715         Ymain = im->yimg;
2717         im->yorigin += Ytitle;
2719         /* Now calculate the total size.  Insert some spacing where
2720            desired.  im->xorigin and im->yorigin need to correspond
2721            with the lower left corner of the main graph area or, if
2722            this one is not set, the imaginary box surrounding the
2723            pie chart area. */
2725         /* Initial size calculation for the main graph area */
2726         Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2727         if (Xmain)
2728             Xmain -= Xspacing;  /* put space between main graph area and right edge */
2730         im->xorigin = Xspacing + Xylabel;
2732         /* the length of the title should not influence with width of the graph
2733            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2735         if (Xvertical) {    /* unit description */
2736             Xmain -= Xvertical;
2737             im->xorigin += Xvertical;
2738         }
2739         im->xsize = Xmain;
2740         xtr(im, 0);
2742         /* The vertical size of the image is known in advance.  The main graph area
2743          ** (Ymain) and im->yorigin must be set according to the space requirements
2744          ** of the legend and the axis labels.
2745          */
2747         if (im->extra_flags & NOLEGEND) {
2748             /* set dimensions correctly if using full size mode with no legend */
2749             im->yorigin =
2750                 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2751                 Yspacing;
2752             Ymain = im->yorigin;
2753         } else {
2754             /* Determine where to place the legends onto the image.
2755              ** Set Ymain and adjust im->yorigin to match the space requirements.
2756              */
2757             if (leg_place(im, &Ymain) == -1)
2758                 return -1;
2759         }
2762         /* remove title space *or* some padding above the graph from the main graph area */
2763         if (Ytitle) {
2764             Ymain -= Ytitle;
2765         } else {
2766             Ymain -= 1.5 * Yspacing;
2767         }
2769         /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2770         if (im->watermark[0] != '\0') {
2771             Ymain -= Ywatermark;
2772         }
2774         im->ysize = Ymain;
2776     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
2778         /* The actual size of the image to draw is determined from
2779          ** several sources.  The size given on the command line is
2780          ** the graph area but we need more as we have to draw labels
2781          ** and other things outside the graph area.
2782          */
2784         if (im->ylegend[0] != '\0') {
2785             Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2786         }
2789         if (im->title[0] != '\0') {
2790             /* The title is placed "inbetween" two text lines so it
2791              ** automatically has some vertical spacing.  The horizontal
2792              ** spacing is added here, on each side.
2793              */
2794             /* don't care for the with of the title
2795                Xtitle = gfx_get_text_width(im->canvas, 0,
2796                im->text_prop[TEXT_PROP_TITLE].font,
2797                im->text_prop[TEXT_PROP_TITLE].size,
2798                im->tabwidth,
2799                im->title, 0) + 2*Xspacing; */
2800             Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2801         }
2803         if (elements) {
2804             Xmain = im->xsize;
2805             Ymain = im->ysize;
2806         }
2807         /* Now calculate the total size.  Insert some spacing where
2808            desired.  im->xorigin and im->yorigin need to correspond
2809            with the lower left corner of the main graph area or, if
2810            this one is not set, the imaginary box surrounding the
2811            pie chart area. */
2813         /* The legend width cannot yet be determined, as a result we
2814          ** have problems adjusting the image to it.  For now, we just
2815          ** forget about it at all; the legend will have to fit in the
2816          ** size already allocated.
2817          */
2818         im->ximg = Xylabel + Xmain + 2 * Xspacing;
2820         if (Xmain)
2821             im->ximg += Xspacing;
2823         im->xorigin = Xspacing + Xylabel;
2825         /* the length of the title should not influence with width of the graph
2826            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2828         if (Xvertical) {    /* unit description */
2829             im->ximg += Xvertical;
2830             im->xorigin += Xvertical;
2831         }
2832         xtr(im, 0);
2834         /* The vertical size is interesting... we need to compare
2835          ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2836          ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2837          ** in order to start even thinking about Ylegend or Ywatermark.
2838          **
2839          ** Do it in three portions: First calculate the inner part,
2840          ** then do the legend, then adjust the total height of the img,
2841          ** adding space for a watermark if one exists;
2842          */
2844         /* reserve space for main and/or pie */
2846         im->yimg = Ymain + Yxlabel;
2849         im->yorigin = im->yimg - Yxlabel;
2851         /* reserve space for the title *or* some padding above the graph */
2852         if (Ytitle) {
2853             im->yimg += Ytitle;
2854             im->yorigin += Ytitle;
2855         } else {
2856             im->yimg += 1.5 * Yspacing;
2857             im->yorigin += 1.5 * Yspacing;
2858         }
2859         /* reserve space for padding below the graph */
2860         im->yimg += Yspacing;
2862         /* Determine where to place the legends onto the image.
2863          ** Adjust im->yimg to match the space requirements.
2864          */
2865         if (leg_place(im, 0) == -1)
2866             return -1;
2868         if (im->watermark[0] != '\0') {
2869             im->yimg += Ywatermark;
2870         }
2871     }
2873     ytr(im, DNAN);
2874     return 0;
2879 static cairo_status_t cairo_write_func_filehandle(
2880     void *closure,
2881     const unsigned char *data,
2882     unsigned int length)
2884     if (fwrite(data, length, 1, closure) != 1)
2885         return CAIRO_STATUS_WRITE_ERROR;
2886     return CAIRO_STATUS_SUCCESS;
2889 static cairo_status_t cairo_copy_to_buffer(
2890     void *closure,
2891     const unsigned char *data,
2892     unsigned int length)
2894     image_desc_t *im = closure;
2896     im->rendered_image =
2897         realloc(im->rendered_image, im->rendered_image_size + length);
2898     if (im->rendered_image == NULL) {
2899         return CAIRO_STATUS_WRITE_ERROR;
2900     }
2902     memcpy(im->rendered_image + im->rendered_image_size, data, length);
2904     im->rendered_image_size += length;
2906     return CAIRO_STATUS_SUCCESS;
2909 /* draw that picture thing ... */
2910 int graph_paint(
2911     image_desc_t *im,
2912     char ***calcpr)
2914     int       i, ii;
2915     int       lazy = lazy_check(im);
2917     double    areazero = 0.0;
2918     graph_desc_t *lastgdes = NULL;
2920     PangoFontMap *font_map = pango_cairo_font_map_get_default();
2923     /* if we are lazy and there is nothing to PRINT ... quit now */
2924     if (lazy && im->prt_c == 0)
2925         return 0;
2927     /* pull the data from the rrd files ... */
2929     if (data_fetch(im) == -1)
2930         return -1;
2932     /* evaluate VDEF and CDEF operations ... */
2933     if (data_calc(im) == -1)
2934         return -1;
2937     /* calculate and PRINT and GPRINT definitions. We have to do it at
2938      * this point because it will affect the length of the legends
2939      * if there are no graph elements we stop here ... 
2940      * if we are lazy, try to quit ... 
2941      */
2942     i = print_calc(im, calcpr);
2943     if (i < 0)
2944         return -1;
2945     if ((i == 0) || lazy)
2946         return 0;
2948 /**************************************************************
2949  *** Calculating sizes and locations became a bit confusing ***
2950  *** so I moved this into a separate function.              ***
2951  **************************************************************/
2952     if (graph_size_location(im, i) == -1)
2953         return -1;
2955     /* get actual drawing data and find min and max values */
2956     if (data_proc(im) == -1)
2957         return -1;
2959     if (!im->logarithmic) {
2960         si_unit(im);
2961     }
2962     /* identify si magnitude Kilo, Mega Giga ? */
2963     if (!im->rigid && !im->logarithmic)
2964         expand_range(im);   /* make sure the upper and lower limit are
2965                                sensible values */
2967     if (!calc_horizontal_grid(im))
2968         return -1;
2970     /* reset precalc */
2971     ytr(im, DNAN);
2973 /*   if (im->gridfit)
2974      apply_gridfit(im); */
2977     /* the actual graph is created by going through the individual
2978        graph elements and then drawing them */
2979     cairo_surface_destroy(im->surface);
2981     switch (im->imgformat) {
2982     case IF_PNG:
2983         im->surface =
2984             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2985                                        im->ximg * im->zoom,
2986                                        im->yimg * im->zoom);
2987         break;
2988     case IF_PDF:
2989         im->gridfit = 0;
2990         im->surface = strlen(im->graphfile)
2991             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2992                                        im->yimg * im->zoom)
2993             : cairo_pdf_surface_create_for_stream(&cairo_copy_to_buffer, im,
2994                                                   im->ximg * im->zoom,
2995                                                   im->yimg * im->zoom);
2996         break;
2997     case IF_EPS:
2998         im->gridfit = 0;
2999         im->surface = strlen(im->graphfile)
3000             ? cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3001                                       im->yimg * im->zoom)
3002             : cairo_ps_surface_create_for_stream(&cairo_copy_to_buffer, im,
3003                                                  im->ximg * im->zoom,
3004                                                  im->yimg * im->zoom);
3005         break;
3006     case IF_SVG:
3007         im->gridfit = 0;
3008         im->surface = strlen(im->graphfile)
3009             ? cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
3010                                        im->yimg * im->zoom)
3011             : cairo_svg_surface_create_for_stream(&cairo_copy_to_buffer, im,
3012                                                   im->ximg * im->zoom,
3013                                                   im->yimg * im->zoom);
3014         cairo_svg_surface_restrict_to_version(im->surface,
3015                                               CAIRO_SVG_VERSION_1_1);
3016         break;
3017     };
3018     im->cr = cairo_create(im->surface);
3019     pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3020     cairo_set_antialias(im->cr, im->graph_antialias);
3021     cairo_scale(im->cr, im->zoom, im->zoom);
3023     gfx_new_area(im,
3024                  0, 0,
3025                  0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3027     gfx_add_point(im, im->ximg, 0);
3028     gfx_close_path(im);
3030     gfx_new_area(im,
3031                  im->xorigin, im->yorigin,
3032                  im->xorigin + im->xsize, im->yorigin,
3033                  im->xorigin + im->xsize, im->yorigin - im->ysize,
3034                  im->graph_col[GRC_CANVAS]);
3036     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3037     gfx_close_path(im);
3039     if (im->minval > 0.0)
3040         areazero = im->minval;
3041     if (im->maxval < 0.0)
3042         areazero = im->maxval;
3044     for (i = 0; i < im->gdes_c; i++) {
3045         switch (im->gdes[i].gf) {
3046         case GF_CDEF:
3047         case GF_VDEF:
3048         case GF_DEF:
3049         case GF_PRINT:
3050         case GF_GPRINT:
3051         case GF_COMMENT:
3052         case GF_TEXTALIGN:
3053         case GF_HRULE:
3054         case GF_VRULE:
3055         case GF_XPORT:
3056         case GF_SHIFT:
3057             break;
3058         case GF_TICK:
3059             for (ii = 0; ii < im->xsize; ii++) {
3060                 if (!isnan(im->gdes[i].p_data[ii]) &&
3061                     im->gdes[i].p_data[ii] != 0.0) {
3062                     if (im->gdes[i].yrule > 0) {
3063                         gfx_line(im,
3064                                  im->xorigin + ii, im->yorigin,
3065                                  im->xorigin + ii,
3066                                  im->yorigin -
3067                                  im->gdes[i].yrule * im->ysize, 1.0,
3068                                  im->gdes[i].col);
3069                     } else if (im->gdes[i].yrule < 0) {
3070                         gfx_line(im,
3071                                  im->xorigin + ii,
3072                                  im->yorigin - im->ysize,
3073                                  im->xorigin + ii,
3074                                  im->yorigin - (1 -
3075                                                 im->gdes[i].yrule) *
3076                                  im->ysize, 1.0, im->gdes[i].col);
3078                     }
3079                 }
3080             }
3081             break;
3082         case GF_LINE:
3083         case GF_AREA:
3084             /* fix data points at oo and -oo */
3085             for (ii = 0; ii < im->xsize; ii++) {
3086                 if (isinf(im->gdes[i].p_data[ii])) {
3087                     if (im->gdes[i].p_data[ii] > 0) {
3088                         im->gdes[i].p_data[ii] = im->maxval;
3089                     } else {
3090                         im->gdes[i].p_data[ii] = im->minval;
3091                     }
3093                 }
3094             }           /* for */
3096             /* *******************************************************
3097                a           ___. (a,t) 
3098                |   |    ___
3099                ____|   |   |   |
3100                |       |___|
3101                -------|--t-1--t--------------------------------      
3103                if we know the value at time t was a then 
3104                we draw a square from t-1 to t with the value a.
3106                ********************************************************* */
3107             if (im->gdes[i].col.alpha != 0.0) {
3108                 /* GF_LINE and friend */
3109                 if (im->gdes[i].gf == GF_LINE) {
3110                     double    last_y = 0.0;
3111                     int       draw_on = 0;
3113                     cairo_save(im->cr);
3114                     cairo_new_path(im->cr);
3116                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3117                     for (ii = 1; ii < im->xsize; ii++) {
3118                         if (isnan(im->gdes[i].p_data[ii])
3119                             || (im->slopemode == 1
3120                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3121                             draw_on = 0;
3122                             continue;
3123                         }
3124                         if (draw_on == 0) {
3125                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3126                             if (im->slopemode == 0) {
3127                                 double    x = ii - 1 + im->xorigin;
3128                                 double    y = last_y;
3130                                 gfx_line_fit(im, &x, &y);
3131                                 cairo_move_to(im->cr, x, y);
3132                                 x = ii + im->xorigin;
3133                                 y = last_y;
3134                                 gfx_line_fit(im, &x, &y);
3135                                 cairo_line_to(im->cr, x, y);
3136                             } else {
3137                                 double    x = ii - 1 + im->xorigin;
3138                                 double    y = ytr(im,
3139                                                   im->gdes[i].p_data[ii - 1]);
3141                                 gfx_line_fit(im, &x, &y);
3142                                 cairo_move_to(im->cr, x, y);
3143                                 x = ii + im->xorigin;
3144                                 y = last_y;
3145                                 gfx_line_fit(im, &x, &y);
3146                                 cairo_line_to(im->cr, x, y);
3147                             }
3148                             draw_on = 1;
3149                         } else {
3150                             double    x1 = ii + im->xorigin;
3151                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3153                             if (im->slopemode == 0
3154                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3155                                 double    x = ii - 1 + im->xorigin;
3156                                 double    y = y1;
3158                                 gfx_line_fit(im, &x, &y);
3159                                 cairo_line_to(im->cr, x, y);
3160                             };
3161                             last_y = y1;
3162                             gfx_line_fit(im, &x1, &y1);
3163                             cairo_line_to(im->cr, x1, y1);
3164                         };
3166                     }
3167                     cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3168                                           im->gdes[i].col.green,
3169                                           im->gdes[i].col.blue,
3170                                           im->gdes[i].col.alpha);
3171                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3172                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3173                     cairo_stroke(im->cr);
3174                     cairo_restore(im->cr);
3175                 } else {
3176                     int       idxI = -1;
3177                     double   *foreY = malloc(sizeof(double) * im->xsize * 2);
3178                     double   *foreX = malloc(sizeof(double) * im->xsize * 2);
3179                     double   *backY = malloc(sizeof(double) * im->xsize * 2);
3180                     double   *backX = malloc(sizeof(double) * im->xsize * 2);
3181                     int       drawem = 0;
3183                     for (ii = 0; ii <= im->xsize; ii++) {
3184                         double    ybase, ytop;
3186                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3187                             int       cntI = 1;
3188                             int       lastI = 0;
3190                             while (cntI < idxI
3191                                    && AlmostEqual2sComplement(foreY[lastI],
3192                                                               foreY[cntI], 4)
3193                                    && AlmostEqual2sComplement(foreY[lastI],
3194                                                               foreY[cntI + 1],
3195                                                               4)) {
3196                                 cntI++;
3197                             }
3198                             gfx_new_area(im,
3199                                          backX[0], backY[0],
3200                                          foreX[0], foreY[0],
3201                                          foreX[cntI], foreY[cntI],
3202                                          im->gdes[i].col);
3203                             while (cntI < idxI) {
3204                                 lastI = cntI;
3205                                 cntI++;
3206                                 while (cntI < idxI
3207                                        &&
3208                                        AlmostEqual2sComplement(foreY[lastI],
3209                                                                foreY[cntI], 4)
3210                                        &&
3211                                        AlmostEqual2sComplement(foreY[lastI],
3212                                                                foreY[cntI +
3213                                                                      1], 4)) {
3214                                     cntI++;
3215                                 }
3216                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3217                             }
3218                             gfx_add_point(im, backX[idxI], backY[idxI]);
3219                             while (idxI > 1) {
3220                                 lastI = idxI;
3221                                 idxI--;
3222                                 while (idxI > 1
3223                                        &&
3224                                        AlmostEqual2sComplement(backY[lastI],
3225                                                                backY[idxI], 4)
3226                                        &&
3227                                        AlmostEqual2sComplement(backY[lastI],
3228                                                                backY[idxI -
3229                                                                      1], 4)) {
3230                                     idxI--;
3231                                 }
3232                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3233                             }
3234                             idxI = -1;
3235                             drawem = 0;
3236                             gfx_close_path(im);
3237                         }
3238                         if (drawem != 0) {
3239                             drawem = 0;
3240                             idxI = -1;
3241                         }
3242                         if (ii == im->xsize)
3243                             break;
3245                         if (im->slopemode == 0 && ii == 0) {
3246                             continue;
3247                         }
3248                         if (isnan(im->gdes[i].p_data[ii])) {
3249                             drawem = 1;
3250                             continue;
3251                         }
3252                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3253                         if (lastgdes && im->gdes[i].stack) {
3254                             ybase = ytr(im, lastgdes->p_data[ii]);
3255                         } else {
3256                             ybase = ytr(im, areazero);
3257                         }
3258                         if (ybase == ytop) {
3259                             drawem = 1;
3260                             continue;
3261                         }
3263                         if (ybase > ytop) {
3264                             double    extra = ytop;
3266                             ytop = ybase;
3267                             ybase = extra;
3268                         }
3269                         if (im->slopemode == 0) {
3270                             backY[++idxI] = ybase - 0.2;
3271                             backX[idxI] = ii + im->xorigin - 1;
3272                             foreY[idxI] = ytop + 0.2;
3273                             foreX[idxI] = ii + im->xorigin - 1;
3274                         }
3275                         backY[++idxI] = ybase - 0.2;
3276                         backX[idxI] = ii + im->xorigin;
3277                         foreY[idxI] = ytop + 0.2;
3278                         foreX[idxI] = ii + im->xorigin;
3279                     }
3280                     /* close up any remaining area */
3281                     free(foreY);
3282                     free(foreX);
3283                     free(backY);
3284                     free(backX);
3285                 }       /* else GF_LINE */
3286             }
3287             /* if color != 0x0 */
3288             /* make sure we do not run into trouble when stacking on NaN */
3289             for (ii = 0; ii < im->xsize; ii++) {
3290                 if (isnan(im->gdes[i].p_data[ii])) {
3291                     if (lastgdes && (im->gdes[i].stack)) {
3292                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3293                     } else {
3294                         im->gdes[i].p_data[ii] = areazero;
3295                     }
3296                 }
3297             }
3298             lastgdes = &(im->gdes[i]);
3299             break;
3300         case GF_STACK:
3301             rrd_set_error
3302                 ("STACK should already be turned into LINE or AREA here");
3303             return -1;
3304             break;
3306         }               /* switch */
3307     }
3309     /* grid_paint also does the text */
3310     if (!(im->extra_flags & ONLY_GRAPH))
3311         grid_paint(im);
3314     if (!(im->extra_flags & ONLY_GRAPH))
3315         axis_paint(im);
3317     /* the RULES are the last thing to paint ... */
3318     for (i = 0; i < im->gdes_c; i++) {
3320         switch (im->gdes[i].gf) {
3321         case GF_HRULE:
3322             if (im->gdes[i].yrule >= im->minval
3323                 && im->gdes[i].yrule <= im->maxval)
3324                 gfx_line(im,
3325                          im->xorigin, ytr(im, im->gdes[i].yrule),
3326                          im->xorigin + im->xsize, ytr(im,
3327                                                       im->gdes[i].yrule),
3328                          1.0, im->gdes[i].col);
3329             break;
3330         case GF_VRULE:
3331             if (im->gdes[i].xrule >= im->start
3332                 && im->gdes[i].xrule <= im->end)
3333                 gfx_line(im,
3334                          xtr(im, im->gdes[i].xrule), im->yorigin,
3335                          xtr(im, im->gdes[i].xrule),
3336                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3337             break;
3338         default:
3339             break;
3340         }
3341     }
3344     switch (im->imgformat) {
3345     case IF_PNG:
3346     {
3347         cairo_status_t status;
3349         if (strlen(im->graphfile) == 0) {
3350             status =
3351                 cairo_surface_write_to_png_stream(im->surface,
3352                                                   &cairo_copy_to_buffer, im);
3353         } else if (strcmp(im->graphfile, "-") == 0) {
3354             status =
3355                 cairo_surface_write_to_png_stream(im->surface,
3356                                                   &cairo_write_func_filehandle,
3357                                                   (void *) stdout);
3358         } else {
3359             status = cairo_surface_write_to_png(im->surface, im->graphfile);
3360         }
3362         if (status != CAIRO_STATUS_SUCCESS) {
3363             rrd_set_error("Could not save png to '%s'", im->graphfile);
3364             return 1;
3365         }
3366     }
3367         break;
3368     default:
3369         if (strlen(im->graphfile)) {
3370             cairo_show_page(im->cr);
3371         } else {
3372             cairo_surface_finish(im->surface);
3373         }
3374         break;
3375     }
3376     return 0;
3380 /*****************************************************
3381  * graph stuff 
3382  *****************************************************/
3384 int gdes_alloc(
3385     image_desc_t *im)
3388     im->gdes_c++;
3389     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3390                                                  * sizeof(graph_desc_t))) ==
3391         NULL) {
3392         rrd_set_error("realloc graph_descs");
3393         return -1;
3394     }
3397     im->gdes[im->gdes_c - 1].step = im->step;
3398     im->gdes[im->gdes_c - 1].step_orig = im->step;
3399     im->gdes[im->gdes_c - 1].stack = 0;
3400     im->gdes[im->gdes_c - 1].linewidth = 0;
3401     im->gdes[im->gdes_c - 1].debug = 0;
3402     im->gdes[im->gdes_c - 1].start = im->start;
3403     im->gdes[im->gdes_c - 1].start_orig = im->start;
3404     im->gdes[im->gdes_c - 1].end = im->end;
3405     im->gdes[im->gdes_c - 1].end_orig = im->end;
3406     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3407     im->gdes[im->gdes_c - 1].data = NULL;
3408     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3409     im->gdes[im->gdes_c - 1].data_first = 0;
3410     im->gdes[im->gdes_c - 1].p_data = NULL;
3411     im->gdes[im->gdes_c - 1].rpnp = NULL;
3412     im->gdes[im->gdes_c - 1].shift = 0.0;
3413     im->gdes[im->gdes_c - 1].col.red = 0.0;
3414     im->gdes[im->gdes_c - 1].col.green = 0.0;
3415     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3416     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3417     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3418     im->gdes[im->gdes_c - 1].format[0] = '\0';
3419     im->gdes[im->gdes_c - 1].strftm = 0;
3420     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3421     im->gdes[im->gdes_c - 1].ds = -1;
3422     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3423     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3424     im->gdes[im->gdes_c - 1].p_data = NULL;
3425     im->gdes[im->gdes_c - 1].yrule = DNAN;
3426     im->gdes[im->gdes_c - 1].xrule = 0;
3427     return 0;
3430 /* copies input untill the first unescaped colon is found
3431    or until input ends. backslashes have to be escaped as well */
3432 int scan_for_col(
3433     const char *const input,
3434     int len,
3435     char *const output)
3437     int       inp, outp = 0;
3439     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3440         if (input[inp] == '\\' &&
3441             input[inp + 1] != '\0' &&
3442             (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3443             output[outp++] = input[++inp];
3444         } else {
3445             output[outp++] = input[inp];
3446         }
3447     }
3448     output[outp] = '\0';
3449     return inp;
3452 /* Some surgery done on this function, it became ridiculously big.
3453 ** Things moved:
3454 ** - initializing     now in rrd_graph_init()
3455 ** - options parsing  now in rrd_graph_options()
3456 ** - script parsing   now in rrd_graph_script()
3457 */
3458 int rrd_graph(
3459     int argc,
3460     char **argv,
3461     char ***prdata,
3462     int *xsize,
3463     int *ysize,
3464     FILE * stream,
3465     double *ymin,
3466     double *ymax)
3468     image_desc_t im;
3470     rrd_graph_init(&im);
3472     /* a dummy surface so that we can measure text sizes for placements */
3473     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3474     im.cr = cairo_create(im.surface);
3475     im.graphhandle = stream;
3477     rrd_graph_options(argc, argv, &im);
3478     if (rrd_test_error()) {
3479         im_free(&im);
3480         return -1;
3481     }
3483     if (optind >= argc) {
3484         rrd_set_error("missing filename");
3485         return -1;
3486     }
3488     if (strlen(argv[optind]) >= MAXPATH) {
3489         rrd_set_error("filename (including path) too long");
3490         im_free(&im);
3491         return -1;
3492     }
3494     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3495     im.graphfile[MAXPATH - 1] = '\0';
3497     rrd_graph_script(argc, argv, &im, 1);
3498     if (rrd_test_error()) {
3499         im_free(&im);
3500         return -1;
3501     }
3503     /* Everything is now read and the actual work can start */
3505     (*prdata) = NULL;
3506     if (graph_paint(&im, prdata) == -1) {
3507         im_free(&im);
3508         return -1;
3509     }
3511     /* The image is generated and needs to be output.
3512      ** Also, if needed, print a line with information about the image.
3513      */
3515     *xsize = im.ximg;
3516     *ysize = im.yimg;
3517     *ymin = im.minval;
3518     *ymax = im.maxval;
3519     if (im.imginfo) {
3520         char     *filename;
3522         if (!(*prdata)) {
3523             /* maybe prdata is not allocated yet ... lets do it now */
3524             if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3525                 rrd_set_error("malloc imginfo");
3526                 return -1;
3527             };
3528         }
3529         if (((*prdata)[0] =
3530              malloc((strlen(im.imginfo) + 200 +
3531                      strlen(im.graphfile)) * sizeof(char)))
3532             == NULL) {
3533             rrd_set_error("malloc imginfo");
3534             return -1;
3535         }
3536         filename = im.graphfile + strlen(im.graphfile);
3537         while (filename > im.graphfile) {
3538             if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3539                 break;
3540             filename--;
3541         }
3543         sprintf((*prdata)[0], im.imginfo, filename,
3544                 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3545     }
3546     im_free(&im);
3547     return 0;
3550 /* a simplified version of the above that just creates the graph in memory 
3551    and returns a pointer to it. */
3553 unsigned char *rrd_graph_in_memory(
3554     int argc,
3555     char **argv,
3556     char ***prdata,
3557     int *xsize,
3558     int *ysize,
3559     double *ymin,
3560     double *ymax,
3561     size_t * img_size)
3563     image_desc_t im;
3565     rrd_graph_init(&im);
3567     /* a dummy surface so that we can measure text sizes for placements */
3568     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3569     im.cr = cairo_create(im.surface);
3571     rrd_graph_options(argc, argv, &im);
3572     if (rrd_test_error()) {
3573         im_free(&im);
3574         return NULL;
3575     }
3577     rrd_graph_script(argc, argv, &im, 1);
3578     if (rrd_test_error()) {
3579         im_free(&im);
3580         return NULL;
3581     }
3583     /* Everything is now read and the actual work can start */
3585     /* by not assigning a name to im.graphfile data will be written to
3586        newly allocated memory on im.rendered_image ... */
3588     (*prdata) = NULL;
3589     if (graph_paint(&im, prdata) == -1) {
3590         im_free(&im);
3591         return NULL;
3592     }
3594     *xsize = im.ximg;
3595     *ysize = im.yimg;
3596     *ymin = im.minval;
3597     *ymax = im.maxval;
3598     *img_size = im.rendered_image_size;
3599     im_free(&im);
3601     return im.rendered_image;
3604 void rrd_graph_init(
3605     image_desc_t *im)
3607     unsigned int i;
3609 #ifdef HAVE_TZSET
3610     tzset();
3611 #endif
3612 #ifdef HAVE_SETLOCALE
3613     setlocale(LC_TIME, "");
3614 #ifdef HAVE_MBSTOWCS
3615     setlocale(LC_CTYPE, "");
3616 #endif
3617 #endif
3618     im->yorigin = 0;
3619     im->xorigin = 0;
3620     im->minval = 0;
3621     im->xlab_user.minsec = -1;
3622     im->ximg = 0;
3623     im->yimg = 0;
3624     im->xsize = 400;
3625     im->ysize = 100;
3626     im->rendered_image_size = 0;
3627     im->rendered_image = NULL;
3628     im->step = 0;
3629     im->ylegend[0] = '\0';
3630     im->title[0] = '\0';
3631     im->watermark[0] = '\0';
3632     im->minval = DNAN;
3633     im->maxval = DNAN;
3634     im->unitsexponent = 9999;
3635     im->unitslength = 6;
3636     im->forceleftspace = 0;
3637     im->symbol = ' ';
3638     im->viewfactor = 1.0;
3639     im->imgformat = IF_PNG;
3640     im->graphfile[0] = '\0';
3641     im->cr = NULL;
3642     im->surface = NULL;
3643     im->extra_flags = 0;
3644     im->rigid = 0;
3645     im->gridfit = 1;
3646     im->imginfo = NULL;
3647     im->lazy = 0;
3648     im->slopemode = 0;
3649     im->logarithmic = 0;
3650     im->ygridstep = DNAN;
3651     im->draw_x_grid = 1;
3652     im->draw_y_grid = 1;
3653     im->base = 1000;
3654     im->prt_c = 0;
3655     im->gdes_c = 0;
3656     im->gdes = NULL;
3657     im->grid_dash_on = 1;
3658     im->grid_dash_off = 1;
3659     im->tabwidth = 40.0;
3660     im->zoom = 1;
3661     im->font_options = cairo_font_options_create();
3662     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3664     cairo_font_options_set_hint_style(im->font_options,
3665                                       CAIRO_HINT_STYLE_FULL);
3666     cairo_font_options_set_hint_metrics(im->font_options,
3667                                         CAIRO_HINT_METRICS_ON);
3668     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3671     for (i = 0; i < DIM(graph_col); i++)
3672         im->graph_col[i] = graph_col[i];
3674 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3675     {
3676         char     *windir;
3677         char      rrd_win_default_font[1000];
3679         windir = getenv("windir");
3680         /* %windir% is something like D:\windows or C:\winnt */
3681         if (windir != NULL) {
3682             strncpy(rrd_win_default_font, windir, 500);
3683             rrd_win_default_font[500] = '\0';
3684             strcat(rrd_win_default_font, "\\fonts\\");
3685             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3686             for (i = 0; i < DIM(text_prop); i++) {
3687                 strncpy(text_prop[i].font, rrd_win_default_font,
3688                         sizeof(text_prop[i].font) - 1);
3689                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3690             }
3691         }
3692     }
3693 #endif
3694     {
3695         char     *deffont;
3697         deffont = getenv("RRD_DEFAULT_FONT");
3698         if (deffont != NULL) {
3699             for (i = 0; i < DIM(text_prop); i++) {
3700                 strncpy(text_prop[i].font, deffont,
3701                         sizeof(text_prop[i].font) - 1);
3702                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3703             }
3704         }
3705     }
3706     for (i = 0; i < DIM(text_prop); i++) {
3707         im->text_prop[i].size = text_prop[i].size;
3708         strcpy(im->text_prop[i].font, text_prop[i].font);
3709     }
3712 void rrd_graph_options(
3713     int argc,
3714     char *argv[],
3715     image_desc_t *im)
3717     int       stroff;
3718     char     *parsetime_error = NULL;
3719     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3720     time_t    start_tmp = 0, end_tmp = 0;
3721     long      long_tmp;
3722     struct rrd_time_value start_tv, end_tv;
3723     long unsigned int color;
3724     char     *old_locale = "";
3726     /* defines for long options without a short equivalent. should be bytes,
3727        and may not collide with (the ASCII value of) short options */
3728 #define LONGOPT_UNITS_SI 255
3729     struct option long_options[] = {
3730         {"start", required_argument, 0, 's'},
3731         {"end", required_argument, 0, 'e'},
3732         {"x-grid", required_argument, 0, 'x'},
3733         {"y-grid", required_argument, 0, 'y'},
3734         {"vertical-label", required_argument, 0, 'v'},
3735         {"width", required_argument, 0, 'w'},
3736         {"height", required_argument, 0, 'h'},
3737         {"full-size-mode", no_argument, 0, 'D'},
3738         {"interlaced", no_argument, 0, 'i'},
3739         {"upper-limit", required_argument, 0, 'u'},
3740         {"lower-limit", required_argument, 0, 'l'},
3741         {"rigid", no_argument, 0, 'r'},
3742         {"base", required_argument, 0, 'b'},
3743         {"logarithmic", no_argument, 0, 'o'},
3744         {"color", required_argument, 0, 'c'},
3745         {"font", required_argument, 0, 'n'},
3746         {"title", required_argument, 0, 't'},
3747         {"imginfo", required_argument, 0, 'f'},
3748         {"imgformat", required_argument, 0, 'a'},
3749         {"lazy", no_argument, 0, 'z'},
3750         {"zoom", required_argument, 0, 'm'},
3751         {"no-legend", no_argument, 0, 'g'},
3752         {"force-rules-legend", no_argument, 0, 'F'},
3753         {"only-graph", no_argument, 0, 'j'},
3754         {"alt-y-grid", no_argument, 0, 'Y'},
3755         {"no-minor", no_argument, 0, 'I'},
3756         {"slope-mode", no_argument, 0, 'E'},
3757         {"alt-autoscale", no_argument, 0, 'A'},
3758         {"alt-autoscale-min", no_argument, 0, 'J'},
3759         {"alt-autoscale-max", no_argument, 0, 'M'},
3760         {"no-gridfit", no_argument, 0, 'N'},
3761         {"units-exponent", required_argument, 0, 'X'},
3762         {"units-length", required_argument, 0, 'L'},
3763         {"units", required_argument, 0, LONGOPT_UNITS_SI},
3764         {"step", required_argument, 0, 'S'},
3765         {"tabwidth", required_argument, 0, 'T'},
3766         {"font-render-mode", required_argument, 0, 'R'},
3767         {"graph-render-mode", required_argument, 0, 'G'},
3768         {"font-smoothing-threshold", required_argument, 0, 'B'},
3769         {"watermark", required_argument, 0, 'W'},
3770         {"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 */
3771         {0, 0, 0, 0}
3772     };
3774     optind = 0;
3775     opterr = 0;         /* initialize getopt */
3777     parsetime("end-24h", &start_tv);
3778     parsetime("now", &end_tv);
3780     while (1) {
3781         int       option_index = 0;
3782         int       opt;
3783         int       col_start, col_end;
3785         opt = getopt_long(argc, argv,
3786                           "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:",
3787                           long_options, &option_index);
3789         if (opt == EOF)
3790             break;
3792         switch (opt) {
3793         case 'I':
3794             im->extra_flags |= NOMINOR;
3795             break;
3796         case 'Y':
3797             im->extra_flags |= ALTYGRID;
3798             break;
3799         case 'A':
3800             im->extra_flags |= ALTAUTOSCALE;
3801             break;
3802         case 'J':
3803             im->extra_flags |= ALTAUTOSCALE_MIN;
3804             break;
3805         case 'M':
3806             im->extra_flags |= ALTAUTOSCALE_MAX;
3807             break;
3808         case 'j':
3809             im->extra_flags |= ONLY_GRAPH;
3810             break;
3811         case 'g':
3812             im->extra_flags |= NOLEGEND;
3813             break;
3814         case 'F':
3815             im->extra_flags |= FORCE_RULES_LEGEND;
3816             break;
3817         case LONGOPT_UNITS_SI:
3818             if (im->extra_flags & FORCE_UNITS) {
3819                 rrd_set_error("--units can only be used once!");
3820                 setlocale(LC_NUMERIC, old_locale);
3821                 return;
3822             }
3823             if (strcmp(optarg, "si") == 0)
3824                 im->extra_flags |= FORCE_UNITS_SI;
3825             else {
3826                 rrd_set_error("invalid argument for --units: %s", optarg);
3827                 return;
3828             }
3829             break;
3830         case 'X':
3831             im->unitsexponent = atoi(optarg);
3832             break;
3833         case 'L':
3834             im->unitslength = atoi(optarg);
3835             im->forceleftspace = 1;
3836             break;
3837         case 'T':
3838             old_locale = setlocale(LC_NUMERIC, "C");
3839             im->tabwidth = atof(optarg);
3840             setlocale(LC_NUMERIC, old_locale);
3841             break;
3842         case 'S':
3843             old_locale = setlocale(LC_NUMERIC, "C");
3844             im->step = atoi(optarg);
3845             setlocale(LC_NUMERIC, old_locale);
3846             break;
3847         case 'N':
3848             im->gridfit = 0;
3849             break;
3850         case 's':
3851             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3852                 rrd_set_error("start time: %s", parsetime_error);
3853                 return;
3854             }
3855             break;
3856         case 'e':
3857             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3858                 rrd_set_error("end time: %s", parsetime_error);
3859                 return;
3860             }
3861             break;
3862         case 'x':
3863             if (strcmp(optarg, "none") == 0) {
3864                 im->draw_x_grid = 0;
3865                 break;
3866             };
3868             if (sscanf(optarg,
3869                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3870                        scan_gtm,
3871                        &im->xlab_user.gridst,
3872                        scan_mtm,
3873                        &im->xlab_user.mgridst,
3874                        scan_ltm,
3875                        &im->xlab_user.labst,
3876                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3877                 strncpy(im->xlab_form, optarg + stroff,
3878                         sizeof(im->xlab_form) - 1);
3879                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3880                 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3881                     rrd_set_error("unknown keyword %s", scan_gtm);
3882                     return;
3883                 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3884                            == -1) {
3885                     rrd_set_error("unknown keyword %s", scan_mtm);
3886                     return;
3887                 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3888                            -1) {
3889                     rrd_set_error("unknown keyword %s", scan_ltm);
3890                     return;
3891                 }
3892                 im->xlab_user.minsec = 1;
3893                 im->xlab_user.stst = im->xlab_form;
3894             } else {
3895                 rrd_set_error("invalid x-grid format");
3896                 return;
3897             }
3898             break;
3899         case 'y':
3901             if (strcmp(optarg, "none") == 0) {
3902                 im->draw_y_grid = 0;
3903                 break;
3904             };
3905             old_locale = setlocale(LC_NUMERIC, "C");
3906             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3907                 setlocale(LC_NUMERIC, old_locale);
3908                 if (im->ygridstep <= 0) {
3909                     rrd_set_error("grid step must be > 0");
3910                     return;
3911                 } else if (im->ylabfact < 1) {
3912                     rrd_set_error("label factor must be > 0");
3913                     return;
3914                 }
3915             } else {
3916                 setlocale(LC_NUMERIC, old_locale);
3917                 rrd_set_error("invalid y-grid format");
3918                 return;
3919             }
3920             break;
3921         case 'v':
3922             strncpy(im->ylegend, optarg, 150);
3923             im->ylegend[150] = '\0';
3924             break;
3925         case 'u':
3926             old_locale = setlocale(LC_NUMERIC, "C");
3927             im->maxval = atof(optarg);
3928             setlocale(LC_NUMERIC, old_locale);
3929             break;
3930         case 'l':
3931             old_locale = setlocale(LC_NUMERIC, "C");
3932             im->minval = atof(optarg);
3933             setlocale(LC_NUMERIC, old_locale);
3934             break;
3935         case 'b':
3936             im->base = atol(optarg);
3937             if (im->base != 1024 && im->base != 1000) {
3938                 rrd_set_error
3939                     ("the only sensible value for base apart from 1000 is 1024");
3940                 return;
3941             }
3942             break;
3943         case 'w':
3944             long_tmp = atol(optarg);
3945             if (long_tmp < 10) {
3946                 rrd_set_error("width below 10 pixels");
3947                 return;
3948             }
3949             im->xsize = long_tmp;
3950             break;
3951         case 'h':
3952             long_tmp = atol(optarg);
3953             if (long_tmp < 10) {
3954                 rrd_set_error("height below 10 pixels");
3955                 return;
3956             }
3957             im->ysize = long_tmp;
3958             break;
3959         case 'D':
3960             im->extra_flags |= FULL_SIZE_MODE;
3961             break;
3962         case 'i':
3963             /* interlaced png not supported at the moment */
3964             break;
3965         case 'r':
3966             im->rigid = 1;
3967             break;
3968         case 'f':
3969             im->imginfo = optarg;
3970             break;
3971         case 'a':
3972             if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3973                 rrd_set_error("unsupported graphics format '%s'", optarg);
3974                 return;
3975             }
3976             break;
3977         case 'z':
3978             im->lazy = 1;
3979             break;
3980         case 'E':
3981             im->slopemode = 1;
3982             break;
3984         case 'o':
3985             im->logarithmic = 1;
3986             break;
3987         case 'c':
3988             if (sscanf(optarg,
3989                        "%10[A-Z]#%n%8lx%n",
3990                        col_nam, &col_start, &color, &col_end) == 2) {
3991                 int       ci;
3992                 int       col_len = col_end - col_start;
3994                 switch (col_len) {
3995                 case 3:
3996                     color = (((color & 0xF00) * 0x110000) |
3997                              ((color & 0x0F0) * 0x011000) |
3998                              ((color & 0x00F) * 0x001100) | 0x000000FF);
3999                     break;
4000                 case 4:
4001                     color = (((color & 0xF000) * 0x11000) |
4002                              ((color & 0x0F00) * 0x01100) |
4003                              ((color & 0x00F0) * 0x00110) |
4004                              ((color & 0x000F) * 0x00011)
4005                         );
4006                     break;
4007                 case 6:
4008                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4009                     break;
4010                 case 8:
4011                     break;
4012                 default:
4013                     rrd_set_error("the color format is #RRGGBB[AA]");
4014                     return;
4015                 }
4016                 if ((ci = grc_conv(col_nam)) != -1) {
4017                     im->graph_col[ci] = gfx_hex_to_col(color);
4018                 } else {
4019                     rrd_set_error("invalid color name '%s'", col_nam);
4020                     return;
4021                 }
4022             } else {
4023                 rrd_set_error("invalid color def format");
4024                 return;
4025             }
4026             break;
4027         case 'n':{
4028             char      prop[15];
4029             double    size = 1;
4030             char      font[1024] = "";
4032             old_locale = setlocale(LC_NUMERIC, "C");
4033             if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
4034                 int       sindex, propidx;
4036                 setlocale(LC_NUMERIC, old_locale);
4037                 if ((sindex = text_prop_conv(prop)) != -1) {
4038                     for (propidx = sindex; propidx < TEXT_PROP_LAST;
4039                          propidx++) {
4040                         if (size > 0) {
4041                             im->text_prop[propidx].size = size;
4042                         }
4043                         if (strlen(font) > 0) {
4044                             strcpy(im->text_prop[propidx].font, font);
4045                         }
4046                         if (propidx == sindex && sindex != 0)
4047                             break;
4048                     }
4049                 } else {
4050                     rrd_set_error("invalid fonttag '%s'", prop);
4051                     return;
4052                 }
4053             } else {
4054                 setlocale(LC_NUMERIC, old_locale);
4055                 rrd_set_error("invalid text property format");
4056                 return;
4057             }
4058             break;
4059         }
4060         case 'm':
4061             old_locale = setlocale(LC_NUMERIC, "C");
4062             im->zoom = atof(optarg);
4063             setlocale(LC_NUMERIC, old_locale);
4064             if (im->zoom <= 0.0) {
4065                 rrd_set_error("zoom factor must be > 0");
4066                 return;
4067             }
4068             break;
4069         case 't':
4070             strncpy(im->title, optarg, 150);
4071             im->title[150] = '\0';
4072             break;
4074         case 'R':
4075             if (strcmp(optarg, "normal") == 0) {
4076                 cairo_font_options_set_antialias(im->font_options,
4077                                                  CAIRO_ANTIALIAS_GRAY);
4078                 cairo_font_options_set_hint_style(im->font_options,
4079                                                   CAIRO_HINT_STYLE_FULL);
4080             } else if (strcmp(optarg, "light") == 0) {
4081                 cairo_font_options_set_antialias(im->font_options,
4082                                                  CAIRO_ANTIALIAS_GRAY);
4083                 cairo_font_options_set_hint_style(im->font_options,
4084                                                   CAIRO_HINT_STYLE_SLIGHT);
4085             } else if (strcmp(optarg, "mono") == 0) {
4086                 cairo_font_options_set_antialias(im->font_options,
4087                                                  CAIRO_ANTIALIAS_NONE);
4088                 cairo_font_options_set_hint_style(im->font_options,
4089                                                   CAIRO_HINT_STYLE_FULL);
4090             } else {
4091                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4092                 return;
4093             }
4094             break;
4095         case 'G':
4096             if (strcmp(optarg, "normal") == 0)
4097                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4098             else if (strcmp(optarg, "mono") == 0)
4099                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4100             else {
4101                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4102                 return;
4103             }
4104             break;
4105         case 'B':
4106             /* not supported curently */
4107             break;
4109         case 'W':
4110             strncpy(im->watermark, optarg, 100);
4111             im->watermark[99] = '\0';
4112             break;
4114         case '?':
4115             if (optopt != 0)
4116                 rrd_set_error("unknown option '%c'", optopt);
4117             else
4118                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4119             return;
4120         }
4121     }
4123     if (im->logarithmic == 1 && im->minval <= 0) {
4124         rrd_set_error
4125             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4126         return;
4127     }
4129     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4130         /* error string is set in parsetime.c */
4131         return;
4132     }
4134     if (start_tmp < 3600 * 24 * 365 * 10) {
4135         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4136                       start_tmp);
4137         return;
4138     }
4140     if (end_tmp < start_tmp) {
4141         rrd_set_error("start (%ld) should be less than end (%ld)",
4142                       start_tmp, end_tmp);
4143         return;
4144     }
4146     im->start = start_tmp;
4147     im->end = end_tmp;
4148     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4151 int rrd_graph_color(
4152     image_desc_t *im,
4153     char *var,
4154     char *err,
4155     int optional)
4157     char     *color;
4158     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4160     color = strstr(var, "#");
4161     if (color == NULL) {
4162         if (optional == 0) {
4163             rrd_set_error("Found no color in %s", err);
4164             return 0;
4165         }
4166         return 0;
4167     } else {
4168         int       n = 0;
4169         char     *rest;
4170         long unsigned int col;
4172         rest = strstr(color, ":");
4173         if (rest != NULL)
4174             n = rest - color;
4175         else
4176             n = strlen(color);
4178         switch (n) {
4179         case 7:
4180             sscanf(color, "#%6lx%n", &col, &n);
4181             col = (col << 8) + 0xff /* shift left by 8 */ ;
4182             if (n != 7)
4183                 rrd_set_error("Color problem in %s", err);
4184             break;
4185         case 9:
4186             sscanf(color, "#%8lx%n", &col, &n);
4187             if (n == 9)
4188                 break;
4189         default:
4190             rrd_set_error("Color problem in %s", err);
4191         }
4192         if (rrd_test_error())
4193             return 0;
4194         gdp->col = gfx_hex_to_col(col);
4195         return n;
4196     }
4200 int bad_format(
4201     char *fmt)
4203     char     *ptr;
4204     int       n = 0;
4206     ptr = fmt;
4207     while (*ptr != '\0')
4208         if (*ptr++ == '%') {
4210             /* line cannot end with percent char */
4211             if (*ptr == '\0')
4212                 return 1;
4214             /* '%s', '%S' and '%%' are allowed */
4215             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4216                 ptr++;
4218             /* %c is allowed (but use only with vdef!) */
4219             else if (*ptr == 'c') {
4220                 ptr++;
4221                 n = 1;
4222             }
4224             /* or else '% 6.2lf' and such are allowed */
4225             else {
4226                 /* optional padding character */
4227                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4228                     ptr++;
4230                 /* This should take care of 'm.n' with all three optional */
4231                 while (*ptr >= '0' && *ptr <= '9')
4232                     ptr++;
4233                 if (*ptr == '.')
4234                     ptr++;
4235                 while (*ptr >= '0' && *ptr <= '9')
4236                     ptr++;
4238                 /* Either 'le', 'lf' or 'lg' must follow here */
4239                 if (*ptr++ != 'l')
4240                     return 1;
4241                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4242                     ptr++;
4243                 else
4244                     return 1;
4245                 n++;
4246             }
4247         }
4249     return (n != 1);
4253 int vdef_parse(
4254     struct graph_desc_t *gdes,
4255     const char *const str)
4257     /* A VDEF currently is either "func" or "param,func"
4258      * so the parsing is rather simple.  Change if needed.
4259      */
4260     double    param;
4261     char      func[30];
4262     int       n;
4263     char     *old_locale;
4265     n = 0;
4266     old_locale = setlocale(LC_NUMERIC, "C");
4267     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4268     setlocale(LC_NUMERIC, old_locale);
4269     if (n == (int) strlen(str)) {   /* matched */
4270         ;
4271     } else {
4272         n = 0;
4273         sscanf(str, "%29[A-Z]%n", func, &n);
4274         if (n == (int) strlen(str)) {   /* matched */
4275             param = DNAN;
4276         } else {
4277             rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4278                           gdes->vname);
4279             return -1;
4280         }
4281     }
4282     if (!strcmp("PERCENT", func))
4283         gdes->vf.op = VDEF_PERCENT;
4284     else if (!strcmp("MAXIMUM", func))
4285         gdes->vf.op = VDEF_MAXIMUM;
4286     else if (!strcmp("AVERAGE", func))
4287         gdes->vf.op = VDEF_AVERAGE;
4288     else if (!strcmp("MINIMUM", func))
4289         gdes->vf.op = VDEF_MINIMUM;
4290     else if (!strcmp("TOTAL", func))
4291         gdes->vf.op = VDEF_TOTAL;
4292     else if (!strcmp("FIRST", func))
4293         gdes->vf.op = VDEF_FIRST;
4294     else if (!strcmp("LAST", func))
4295         gdes->vf.op = VDEF_LAST;
4296     else if (!strcmp("LSLSLOPE", func))
4297         gdes->vf.op = VDEF_LSLSLOPE;
4298     else if (!strcmp("LSLINT", func))
4299         gdes->vf.op = VDEF_LSLINT;
4300     else if (!strcmp("LSLCORREL", func))
4301         gdes->vf.op = VDEF_LSLCORREL;
4302     else {
4303         rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4304                       gdes->vname);
4305         return -1;
4306     };
4308     switch (gdes->vf.op) {
4309     case VDEF_PERCENT:
4310         if (isnan(param)) { /* no parameter given */
4311             rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4312                           func, gdes->vname);
4313             return -1;
4314         };
4315         if (param >= 0.0 && param <= 100.0) {
4316             gdes->vf.param = param;
4317             gdes->vf.val = DNAN;    /* undefined */
4318             gdes->vf.when = 0;  /* undefined */
4319         } else {
4320             rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4321                           gdes->vname);
4322             return -1;
4323         };
4324         break;
4325     case VDEF_MAXIMUM:
4326     case VDEF_AVERAGE:
4327     case VDEF_MINIMUM:
4328     case VDEF_TOTAL:
4329     case VDEF_FIRST:
4330     case VDEF_LAST:
4331     case VDEF_LSLSLOPE:
4332     case VDEF_LSLINT:
4333     case VDEF_LSLCORREL:
4334         if (isnan(param)) {
4335             gdes->vf.param = DNAN;
4336             gdes->vf.val = DNAN;
4337             gdes->vf.when = 0;
4338         } else {
4339             rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4340                           func, gdes->vname);
4341             return -1;
4342         };
4343         break;
4344     };
4345     return 0;
4349 int vdef_calc(
4350     image_desc_t *im,
4351     int gdi)
4353     graph_desc_t *src, *dst;
4354     rrd_value_t *data;
4355     long      step, steps;
4357     dst = &im->gdes[gdi];
4358     src = &im->gdes[dst->vidx];
4359     data = src->data + src->ds;
4360     steps = (src->end - src->start) / src->step;
4362 #if 0
4363     printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4364            src->end, steps);
4365 #endif
4367     switch (dst->vf.op) {
4368     case VDEF_PERCENT:{
4369         rrd_value_t *array;
4370         int       field;
4373         if ((array = malloc(steps * sizeof(double))) == NULL) {
4374             rrd_set_error("malloc VDEV_PERCENT");
4375             return -1;
4376         }
4377         for (step = 0; step < steps; step++) {
4378             array[step] = data[step * src->ds_cnt];
4379         }
4380         qsort(array, step, sizeof(double), vdef_percent_compar);
4382         field = (steps - 1) * dst->vf.param / 100;
4383         dst->vf.val = array[field];
4384         dst->vf.when = 0;   /* no time component */
4385         free(array);
4386 #if 0
4387         for (step = 0; step < steps; step++)
4388             printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4389                    step == field ? '*' : ' ');
4390 #endif
4391     }
4392         break;
4393     case VDEF_MAXIMUM:
4394         step = 0;
4395         while (step != steps && isnan(data[step * src->ds_cnt]))
4396             step++;
4397         if (step == steps) {
4398             dst->vf.val = DNAN;
4399             dst->vf.when = 0;
4400         } else {
4401             dst->vf.val = data[step * src->ds_cnt];
4402             dst->vf.when = src->start + (step + 1) * src->step;
4403         }
4404         while (step != steps) {
4405             if (finite(data[step * src->ds_cnt])) {
4406                 if (data[step * src->ds_cnt] > dst->vf.val) {
4407                     dst->vf.val = data[step * src->ds_cnt];
4408                     dst->vf.when = src->start + (step + 1) * src->step;
4409                 }
4410             }
4411             step++;
4412         }
4413         break;
4414     case VDEF_TOTAL:
4415     case VDEF_AVERAGE:{
4416         int       cnt = 0;
4417         double    sum = 0.0;
4419         for (step = 0; step < steps; step++) {
4420             if (finite(data[step * src->ds_cnt])) {
4421                 sum += data[step * src->ds_cnt];
4422                 cnt++;
4423             };
4424         }
4425         if (cnt) {
4426             if (dst->vf.op == VDEF_TOTAL) {
4427                 dst->vf.val = sum * src->step;
4428                 dst->vf.when = 0;   /* no time component */
4429             } else {
4430                 dst->vf.val = sum / cnt;
4431                 dst->vf.when = 0;   /* no time component */
4432             };
4433         } else {
4434             dst->vf.val = DNAN;
4435             dst->vf.when = 0;
4436         }
4437     }
4438         break;
4439     case VDEF_MINIMUM:
4440         step = 0;
4441         while (step != steps && isnan(data[step * src->ds_cnt]))
4442             step++;
4443         if (step == steps) {
4444             dst->vf.val = DNAN;
4445             dst->vf.when = 0;
4446         } else {
4447             dst->vf.val = data[step * src->ds_cnt];
4448             dst->vf.when = src->start + (step + 1) * src->step;
4449         }
4450         while (step != steps) {
4451             if (finite(data[step * src->ds_cnt])) {
4452                 if (data[step * src->ds_cnt] < dst->vf.val) {
4453                     dst->vf.val = data[step * src->ds_cnt];
4454                     dst->vf.when = src->start + (step + 1) * src->step;
4455                 }
4456             }
4457             step++;
4458         }
4459         break;
4460     case VDEF_FIRST:
4461         /* The time value returned here is one step before the
4462          * actual time value.  This is the start of the first
4463          * non-NaN interval.
4464          */
4465         step = 0;
4466         while (step != steps && isnan(data[step * src->ds_cnt]))
4467             step++;
4468         if (step == steps) {    /* all entries were NaN */
4469             dst->vf.val = DNAN;
4470             dst->vf.when = 0;
4471         } else {
4472             dst->vf.val = data[step * src->ds_cnt];
4473             dst->vf.when = src->start + step * src->step;
4474         }
4475         break;
4476     case VDEF_LAST:
4477         /* The time value returned here is the
4478          * actual time value.  This is the end of the last
4479          * non-NaN interval.
4480          */
4481         step = steps - 1;
4482         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4483             step--;
4484         if (step < 0) { /* all entries were NaN */
4485             dst->vf.val = DNAN;
4486             dst->vf.when = 0;
4487         } else {
4488             dst->vf.val = data[step * src->ds_cnt];
4489             dst->vf.when = src->start + (step + 1) * src->step;
4490         }
4491         break;
4492     case VDEF_LSLSLOPE:
4493     case VDEF_LSLINT:
4494     case VDEF_LSLCORREL:{
4495         /* Bestfit line by linear least squares method */
4497         int       cnt = 0;
4498         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4500         SUMx = 0;
4501         SUMy = 0;
4502         SUMxy = 0;
4503         SUMxx = 0;
4504         SUMyy = 0;
4506         for (step = 0; step < steps; step++) {
4507             if (finite(data[step * src->ds_cnt])) {
4508                 cnt++;
4509                 SUMx += step;
4510                 SUMxx += step * step;
4511                 SUMxy += step * data[step * src->ds_cnt];
4512                 SUMy += data[step * src->ds_cnt];
4513                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4514             };
4515         }
4517         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4518         y_intercept = (SUMy - slope * SUMx) / cnt;
4519         correl =
4520             (SUMxy -
4521              (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4522                                           (SUMx * SUMx) / cnt) * (SUMyy -
4523                                                                   (SUMy *
4524                                                                    SUMy) /
4525                                                                   cnt));
4527         if (cnt) {
4528             if (dst->vf.op == VDEF_LSLSLOPE) {
4529                 dst->vf.val = slope;
4530                 dst->vf.when = 0;
4531             } else if (dst->vf.op == VDEF_LSLINT) {
4532                 dst->vf.val = y_intercept;
4533                 dst->vf.when = 0;
4534             } else if (dst->vf.op == VDEF_LSLCORREL) {
4535                 dst->vf.val = correl;
4536                 dst->vf.when = 0;
4537             };
4539         } else {
4540             dst->vf.val = DNAN;
4541             dst->vf.when = 0;
4542         }
4543     }
4544         break;
4545     }
4546     return 0;
4549 /* NaN < -INF < finite_values < INF */
4550 int vdef_percent_compar(
4551     const void *a,
4552     const void *b)
4554     /* Equality is not returned; this doesn't hurt except
4555      * (maybe) for a little performance.
4556      */
4558     /* First catch NaN values. They are smallest */
4559     if (isnan(*(double *) a))
4560         return -1;
4561     if (isnan(*(double *) b))
4562         return 1;
4564     /* NaN doesn't reach this part so INF and -INF are extremes.
4565      * The sign from isinf() is compatible with the sign we return
4566      */
4567     if (isinf(*(double *) a))
4568         return isinf(*(double *) a);
4569     if (isinf(*(double *) b))
4570         return isinf(*(double *) b);
4572     /* If we reach this, both values must be finite */
4573     if (*(double *) a < *(double *) b)
4574         return -1;
4575     else
4576         return 1;