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