515421046611b667179f6b68049cc59dc7bce26f
[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     switch (baseint) {
1265     case TMT_SECOND:
1266         tm.tm_sec -= tm.tm_sec % basestep;
1267         break;
1268     case TMT_MINUTE:
1269         tm.tm_sec = 0;
1270         tm.tm_min -= tm.tm_min % basestep;
1271         break;
1272     case TMT_HOUR:
1273         tm.tm_sec = 0;
1274         tm.tm_min = 0;
1275         tm.tm_hour -= tm.tm_hour % basestep;
1276         break;
1277     case TMT_DAY:
1278         /* we do NOT look at the basestep for this ... */
1279         tm.tm_sec = 0;
1280         tm.tm_min = 0;
1281         tm.tm_hour = 0;
1282         break;
1283     case TMT_WEEK:
1284         /* we do NOT look at the basestep for this ... */
1285         tm.tm_sec = 0;
1286         tm.tm_min = 0;
1287         tm.tm_hour = 0;
1288         tm.tm_mday -= tm.tm_wday - 1;   /* -1 because we want the monday */
1289         if (tm.tm_wday == 0)
1290             tm.tm_mday -= 7;    /* we want the *previous* monday */
1291         break;
1292     case TMT_MONTH:
1293         tm.tm_sec = 0;
1294         tm.tm_min = 0;
1295         tm.tm_hour = 0;
1296         tm.tm_mday = 1;
1297         tm.tm_mon -= tm.tm_mon % basestep;
1298         break;
1299
1300     case TMT_YEAR:
1301         tm.tm_sec = 0;
1302         tm.tm_min = 0;
1303         tm.tm_hour = 0;
1304         tm.tm_mday = 1;
1305         tm.tm_mon = 0;
1306         tm.tm_year -= (tm.tm_year + 1900) % basestep;
1307
1308     }
1309     return mktime(&tm);
1310 }
1311
1312 /* identify the point where the next gridline, label ... gets placed */
1313 time_t find_next_time(
1314     time_t current,     /* what is the initial time */
1315     enum tmt_en baseint,    /* what is the basic interval */
1316     long basestep       /* how many if these do we jump a time */
1317     )
1318 {
1319     struct tm tm;
1320     time_t    madetime;
1321
1322     localtime_r(&current, &tm);
1323     do {
1324         switch (baseint) {
1325         case TMT_SECOND:
1326             tm.tm_sec += basestep;
1327             break;
1328         case TMT_MINUTE:
1329             tm.tm_min += basestep;
1330             break;
1331         case TMT_HOUR:
1332             tm.tm_hour += basestep;
1333             break;
1334         case TMT_DAY:
1335             tm.tm_mday += basestep;
1336             break;
1337         case TMT_WEEK:
1338             tm.tm_mday += 7 * basestep;
1339             break;
1340         case TMT_MONTH:
1341             tm.tm_mon += basestep;
1342             break;
1343         case TMT_YEAR:
1344             tm.tm_year += basestep;
1345         }
1346         madetime = mktime(&tm);
1347     } while (madetime == -1);   /* this is necessary to skip impssible times
1348                                    like the daylight saving time skips */
1349     return madetime;
1350
1351 }
1352
1353
1354 /* calculate values required for PRINT and GPRINT functions */
1355
1356 int print_calc(
1357     image_desc_t * im,
1358     char ***prdata)
1359 {
1360     long      i, ii, validsteps;
1361     double    printval;
1362     struct tm tmvdef;
1363     int       graphelement = 0;
1364     long      vidx;
1365     int       max_ii;
1366     double    magfact = -1;
1367     char     *si_symb = "";
1368     char     *percent_s;
1369     int       prlines = 1;
1370
1371     /* wow initializing tmvdef is quite a task :-) */
1372     time_t    now = time(NULL);
1373
1374     localtime_r(&now, &tmvdef);
1375     if (im->imginfo)
1376         prlines++;
1377     for (i = 0; i < im->gdes_c; i++) {
1378         vidx = im->gdes[i].vidx;
1379         switch (im->gdes[i].gf) {
1380         case GF_PRINT:
1381             prlines++;
1382             if (((*prdata) =
1383                  rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1384                 rrd_set_error("realloc prdata");
1385                 return 0;
1386             }
1387         case GF_GPRINT:
1388             /* PRINT and GPRINT can now print VDEF generated values.
1389              * There's no need to do any calculations on them as these
1390              * calculations were already made.
1391              */
1392             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1393                 printval = im->gdes[vidx].vf.val;
1394                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1395             } else {    /* need to calculate max,min,avg etcetera */
1396                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1397                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1398                 printval = DNAN;
1399                 validsteps = 0;
1400                 for (ii = im->gdes[vidx].ds;
1401                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1402                     if (!finite(im->gdes[vidx].data[ii]))
1403                         continue;
1404                     if (isnan(printval)) {
1405                         printval = im->gdes[vidx].data[ii];
1406                         validsteps++;
1407                         continue;
1408                     }
1409
1410                     switch (im->gdes[i].cf) {
1411                     case CF_HWPREDICT:
1412                     case CF_DEVPREDICT:
1413                     case CF_DEVSEASONAL:
1414                     case CF_SEASONAL:
1415                     case CF_AVERAGE:
1416                         validsteps++;
1417                         printval += im->gdes[vidx].data[ii];
1418                         break;
1419                     case CF_MINIMUM:
1420                         printval = min(printval, im->gdes[vidx].data[ii]);
1421                         break;
1422                     case CF_FAILURES:
1423                     case CF_MAXIMUM:
1424                         printval = max(printval, im->gdes[vidx].data[ii]);
1425                         break;
1426                     case CF_LAST:
1427                         printval = im->gdes[vidx].data[ii];
1428                     }
1429                 }
1430                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1431                     if (validsteps > 1) {
1432                         printval = (printval / validsteps);
1433                     }
1434                 }
1435             }           /* prepare printval */
1436
1437             if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1438                 /* Magfact is set to -1 upon entry to print_calc.  If it
1439                  * is still less than 0, then we need to run auto_scale.
1440                  * Otherwise, put the value into the correct units.  If
1441                  * the value is 0, then do not set the symbol or magnification
1442                  * so next the calculation will be performed again. */
1443                 if (magfact < 0.0) {
1444                     auto_scale(im, &printval, &si_symb, &magfact);
1445                     if (printval == 0.0)
1446                         magfact = -1.0;
1447                 } else {
1448                     printval /= magfact;
1449                 }
1450                 *(++percent_s) = 's';
1451             } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1452                 auto_scale(im, &printval, &si_symb, &magfact);
1453             }
1454
1455             if (im->gdes[i].gf == GF_PRINT) {
1456                 (*prdata)[prlines - 2] =
1457                     malloc((FMT_LEG_LEN + 2) * sizeof(char));
1458                 (*prdata)[prlines - 1] = NULL;
1459                 if (im->gdes[i].strftm) {
1460                     strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1461                              im->gdes[i].format, &tmvdef);
1462                 } else {
1463                     if (bad_format(im->gdes[i].format)) {
1464                         rrd_set_error("bad format for PRINT in '%s'",
1465                                       im->gdes[i].format);
1466                         return -1;
1467                     }
1468 #ifdef HAVE_SNPRINTF
1469                     snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1470                              im->gdes[i].format, printval, si_symb);
1471 #else
1472                     sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1473                             printval, si_symb);
1474 #endif
1475                 }
1476             } else {
1477                 /* GF_GPRINT */
1478
1479                 if (im->gdes[i].strftm) {
1480                     strftime(im->gdes[i].legend, FMT_LEG_LEN,
1481                              im->gdes[i].format, &tmvdef);
1482                 } else {
1483                     if (bad_format(im->gdes[i].format)) {
1484                         rrd_set_error("bad format for GPRINT in '%s'",
1485                                       im->gdes[i].format);
1486                         return -1;
1487                     }
1488 #ifdef HAVE_SNPRINTF
1489                     snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1490                              im->gdes[i].format, printval, si_symb);
1491 #else
1492                     sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1493                             si_symb);
1494 #endif
1495                 }
1496                 graphelement = 1;
1497             }
1498             break;
1499         case GF_LINE:
1500         case GF_AREA:
1501         case GF_TICK:
1502             graphelement = 1;
1503             break;
1504         case GF_HRULE:
1505             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1506                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1507             };
1508             graphelement = 1;
1509             break;
1510         case GF_VRULE:
1511             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1512                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1513             };
1514             graphelement = 1;
1515             break;
1516         case GF_COMMENT:
1517         case GF_DEF:
1518         case GF_CDEF:
1519         case GF_VDEF:
1520 #ifdef WITH_PIECHART
1521         case GF_PART:
1522 #endif
1523         case GF_SHIFT:
1524         case GF_XPORT:
1525             break;
1526         case GF_STACK:
1527             rrd_set_error
1528                 ("STACK should already be turned into LINE or AREA here");
1529             return -1;
1530             break;
1531         }
1532     }
1533     return graphelement;
1534 }
1535
1536
1537 /* place legends with color spots */
1538 int leg_place(
1539     image_desc_t * im)
1540 {
1541     /* graph labels */
1542     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1543     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1544     int       fill = 0, fill_last;
1545     int       leg_c = 0;
1546     int       leg_x = border, leg_y = im->yimg;
1547     int       leg_y_prev = im->yimg;
1548     int       leg_cc;
1549     int       glue = 0;
1550     int       i, ii, mark = 0;
1551     char      prt_fctn; /*special printfunctions */
1552     int      *legspace;
1553
1554     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1555         if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1556             rrd_set_error("malloc for legspace");
1557             return -1;
1558         }
1559
1560         for (i = 0; i < im->gdes_c; i++) {
1561             fill_last = fill;
1562
1563             /* hid legends for rules which are not displayed */
1564
1565             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1566                 if (im->gdes[i].gf == GF_HRULE &&
1567                     (im->gdes[i].yrule < im->minval
1568                      || im->gdes[i].yrule > im->maxval))
1569                     im->gdes[i].legend[0] = '\0';
1570
1571                 if (im->gdes[i].gf == GF_VRULE &&
1572                     (im->gdes[i].xrule < im->start
1573                      || im->gdes[i].xrule > im->end))
1574                     im->gdes[i].legend[0] = '\0';
1575             }
1576
1577             leg_cc = strlen(im->gdes[i].legend);
1578
1579             /* is there a controle code ant the end of the legend string ? */
1580             /* and it is not a tab \\t */
1581             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1582                 && im->gdes[i].legend[leg_cc - 1] != 't') {
1583                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1584                 leg_cc -= 2;
1585                 im->gdes[i].legend[leg_cc] = '\0';
1586             } else {
1587                 prt_fctn = '\0';
1588             }
1589             /* only valid control codes */
1590             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1591                 prt_fctn != 'r' &&
1592                 prt_fctn != 'j' &&
1593                 prt_fctn != 'c' &&
1594                 prt_fctn != 's' &&
1595                 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1596                 free(legspace);
1597                 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1598                               im->gdes[i].legend, prt_fctn);
1599                 return -1;
1600
1601             }
1602
1603             /* remove exess space */
1604             if (prt_fctn == 'n') {
1605                 prt_fctn = 'l';
1606             }
1607
1608             while (prt_fctn == 'g' &&
1609                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1610                 leg_cc--;
1611                 im->gdes[i].legend[leg_cc] = '\0';
1612             }
1613             if (leg_cc != 0) {
1614                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1615
1616                 if (fill > 0) {
1617                     /* no interleg space if string ends in \g */
1618                     fill += legspace[i];
1619                 }
1620                 fill += gfx_get_text_width(im->canvas, fill + border,
1621                                            im->text_prop[TEXT_PROP_LEGEND].
1622                                            font,
1623                                            im->text_prop[TEXT_PROP_LEGEND].
1624                                            size, im->tabwidth,
1625                                            im->gdes[i].legend, 0);
1626                 leg_c++;
1627             } else {
1628                 legspace[i] = 0;
1629             }
1630             /* who said there was a special tag ... ? */
1631             if (prt_fctn == 'g') {
1632                 prt_fctn = '\0';
1633             }
1634             if (prt_fctn == '\0') {
1635                 if (i == im->gdes_c - 1)
1636                     prt_fctn = 'l';
1637
1638                 /* is it time to place the legends ? */
1639                 if (fill > im->ximg - 2 * border) {
1640                     if (leg_c > 1) {
1641                         /* go back one */
1642                         i--;
1643                         fill = fill_last;
1644                         leg_c--;
1645                         prt_fctn = 'j';
1646                     } else {
1647                         prt_fctn = 'l';
1648                     }
1649
1650                 }
1651             }
1652
1653
1654             if (prt_fctn != '\0') {
1655                 leg_x = border;
1656                 if (leg_c >= 2 && prt_fctn == 'j') {
1657                     glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1658                 } else {
1659                     glue = 0;
1660                 }
1661                 if (prt_fctn == 'c')
1662                     leg_x = (im->ximg - fill) / 2.0;
1663                 if (prt_fctn == 'r')
1664                     leg_x = im->ximg - fill - border;
1665
1666                 for (ii = mark; ii <= i; ii++) {
1667                     if (im->gdes[ii].legend[0] == '\0')
1668                         continue;   /* skip empty legends */
1669                     im->gdes[ii].leg_x = leg_x;
1670                     im->gdes[ii].leg_y = leg_y;
1671                     leg_x +=
1672                         gfx_get_text_width(im->canvas, leg_x,
1673                                            im->text_prop[TEXT_PROP_LEGEND].
1674                                            font,
1675                                            im->text_prop[TEXT_PROP_LEGEND].
1676                                            size, im->tabwidth,
1677                                            im->gdes[ii].legend, 0)
1678                         + legspace[ii]
1679                         + glue;
1680                 }
1681                 leg_y_prev = leg_y;
1682                 /* only add y space if there was text on the line */
1683                 if (leg_x > border || prt_fctn == 's')
1684                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1685                 if (prt_fctn == 's')
1686                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1687                 fill = 0;
1688                 leg_c = 0;
1689                 mark = ii;
1690             }
1691         }
1692         im->yimg = leg_y_prev;
1693         /* if we did place some legends we have to add vertical space */
1694         if (leg_y != im->yimg) {
1695             im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1696         }
1697         free(legspace);
1698     }
1699     return 0;
1700 }
1701
1702 /* create a grid on the graph. it determines what to do
1703    from the values of xsize, start and end */
1704
1705 /* the xaxis labels are determined from the number of seconds per pixel
1706    in the requested graph */
1707
1708
1709
1710 int calc_horizontal_grid(
1711     image_desc_t * im)
1712 {
1713     double    range;
1714     double    scaledrange;
1715     int       pixel, i;
1716     int       gridind = 0;
1717     int       decimals, fractionals;
1718
1719     im->ygrid_scale.labfact = 2;
1720     range = im->maxval - im->minval;
1721     scaledrange = range / im->magfact;
1722
1723     /* does the scale of this graph make it impossible to put lines
1724        on it? If so, give up. */
1725     if (isnan(scaledrange)) {
1726         return 0;
1727     }
1728
1729     /* find grid spaceing */
1730     pixel = 1;
1731     if (isnan(im->ygridstep)) {
1732         if (im->extra_flags & ALTYGRID) {
1733             /* find the value with max number of digits. Get number of digits */
1734             decimals =
1735                 ceil(log10
1736                      (max(fabs(im->maxval), fabs(im->minval)) *
1737                       im->viewfactor / im->magfact));
1738             if (decimals <= 0)  /* everything is small. make place for zero */
1739                 decimals = 1;
1740
1741             im->ygrid_scale.gridstep =
1742                 pow((double) 10,
1743                     floor(log10(range * im->viewfactor / im->magfact))) /
1744                 im->viewfactor * im->magfact;
1745
1746             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1747                 im->ygrid_scale.gridstep = 0.1;
1748             /* should have at least 5 lines but no more then 15 */
1749             if (range / im->ygrid_scale.gridstep < 5)
1750                 im->ygrid_scale.gridstep /= 10;
1751             if (range / im->ygrid_scale.gridstep > 15)
1752                 im->ygrid_scale.gridstep *= 10;
1753             if (range / im->ygrid_scale.gridstep > 5) {
1754                 im->ygrid_scale.labfact = 1;
1755                 if (range / im->ygrid_scale.gridstep > 8)
1756                     im->ygrid_scale.labfact = 2;
1757             } else {
1758                 im->ygrid_scale.gridstep /= 5;
1759                 im->ygrid_scale.labfact = 5;
1760             }
1761             fractionals =
1762                 floor(log10
1763                       (im->ygrid_scale.gridstep *
1764                        (double) im->ygrid_scale.labfact * im->viewfactor /
1765                        im->magfact));
1766             if (fractionals < 0) {  /* small amplitude. */
1767                 int       len = decimals - fractionals + 1;
1768
1769                 if (im->unitslength < len + 2)
1770                     im->unitslength = len + 2;
1771                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1772                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1773             } else {
1774                 int       len = decimals + 1;
1775
1776                 if (im->unitslength < len + 2)
1777                     im->unitslength = len + 2;
1778                 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1779                         (im->symbol != ' ' ? " %c" : ""));
1780             }
1781         } else {
1782             for (i = 0; ylab[i].grid > 0; i++) {
1783                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1784                 gridind = i;
1785                 if (pixel > 7)
1786                     break;
1787             }
1788
1789             for (i = 0; i < 4; i++) {
1790                 if (pixel * ylab[gridind].lfac[i] >=
1791                     2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1792                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1793                     break;
1794                 }
1795             }
1796
1797             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1798         }
1799     } else {
1800         im->ygrid_scale.gridstep = im->ygridstep;
1801         im->ygrid_scale.labfact = im->ylabfact;
1802     }
1803     return 1;
1804 }
1805
1806 int draw_horizontal_grid(
1807     image_desc_t * im)
1808 {
1809     int       i;
1810     double    scaledstep;
1811     char      graph_label[100];
1812     int       nlabels = 0;
1813     double    X0 = im->xorigin;
1814     double    X1 = im->xorigin + im->xsize;
1815
1816     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1817     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1818     double    MaxY;
1819
1820     scaledstep =
1821         im->ygrid_scale.gridstep / (double) im->magfact *
1822         (double) im->viewfactor;
1823     MaxY = scaledstep * (double) egrid;
1824     for (i = sgrid; i <= egrid; i++) {
1825         double    Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1826         double    YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1827
1828         if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1829             && floor(Y0 + 0.5) <= im->yorigin) {
1830             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1831                with the chosen settings. Add a label if required by settings, or if
1832                there is only one label so far and the next grid line is out of bounds. */
1833             if (i % im->ygrid_scale.labfact == 0
1834                 || (nlabels == 1
1835                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1836                 if (im->symbol == ' ') {
1837                     if (im->extra_flags & ALTYGRID) {
1838                         sprintf(graph_label, im->ygrid_scale.labfmt,
1839                                 scaledstep * (double) i);
1840                     } else {
1841                         if (MaxY < 10) {
1842                             sprintf(graph_label, "%4.1f",
1843                                     scaledstep * (double) i);
1844                         } else {
1845                             sprintf(graph_label, "%4.0f",
1846                                     scaledstep * (double) i);
1847                         }
1848                     }
1849                 } else {
1850                     char      sisym = (i == 0 ? ' ' : im->symbol);
1851
1852                     if (im->extra_flags & ALTYGRID) {
1853                         sprintf(graph_label, im->ygrid_scale.labfmt,
1854                                 scaledstep * (double) i, sisym);
1855                     } else {
1856                         if (MaxY < 10) {
1857                             sprintf(graph_label, "%4.1f %c",
1858                                     scaledstep * (double) i, sisym);
1859                         } else {
1860                             sprintf(graph_label, "%4.0f %c",
1861                                     scaledstep * (double) i, sisym);
1862                         }
1863                     }
1864                 }
1865                 nlabels++;
1866
1867                 gfx_new_text(im->canvas,
1868                              X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1869                              im->graph_col[GRC_FONT],
1870                              im->text_prop[TEXT_PROP_AXIS].font,
1871                              im->text_prop[TEXT_PROP_AXIS].size,
1872                              im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1873                              graph_label);
1874                 gfx_new_dashed_line(im->canvas,
1875                                     X0 - 2, Y0,
1876                                     X1 + 2, Y0,
1877                                     MGRIDWIDTH, im->graph_col[GRC_MGRID],
1878                                     im->grid_dash_on, im->grid_dash_off);
1879
1880             } else if (!(im->extra_flags & NOMINOR)) {
1881                 gfx_new_dashed_line(im->canvas,
1882                                     X0 - 1, Y0,
1883                                     X1 + 1, Y0,
1884                                     GRIDWIDTH, im->graph_col[GRC_GRID],
1885                                     im->grid_dash_on, im->grid_dash_off);
1886
1887             }
1888         }
1889     }
1890     return 1;
1891 }
1892
1893 /* this is frexp for base 10 */
1894 double    frexp10(
1895     double,
1896     double *);
1897 double frexp10(
1898     double x,
1899     double *e)
1900 {
1901     double    mnt;
1902     int       iexp;
1903
1904     iexp = floor(log(fabs(x)) / log(10));
1905     mnt = x / pow(10.0, iexp);
1906     if (mnt >= 10.0) {
1907         iexp++;
1908         mnt = x / pow(10.0, iexp);
1909     }
1910     *e = iexp;
1911     return mnt;
1912 }
1913
1914 static int AlmostEqual2sComplement(
1915     float A,
1916     float B,
1917     int maxUlps)
1918 {
1919
1920     int       aInt = *(int *) &A;
1921     int       bInt = *(int *) &B;
1922     int       intDiff;
1923
1924     /* Make sure maxUlps is non-negative and small enough that the
1925        default NAN won't compare as equal to anything.  */
1926
1927     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1928
1929     /* Make aInt lexicographically ordered as a twos-complement int */
1930
1931     if (aInt < 0)
1932         aInt = 0x80000000l - aInt;
1933
1934     /* Make bInt lexicographically ordered as a twos-complement int */
1935
1936     if (bInt < 0)
1937         bInt = 0x80000000l - bInt;
1938
1939     intDiff = abs(aInt - bInt);
1940
1941     if (intDiff <= maxUlps)
1942         return 1;
1943
1944     return 0;
1945 }
1946
1947 /* logaritmic horizontal grid */
1948 int horizontal_log_grid(
1949     image_desc_t * im)
1950 {
1951     double    yloglab[][10] = {
1952         {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1953         {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1954         {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1955         {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1956         {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1957         {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
1958     };
1959
1960     int       i, j, val_exp, min_exp;
1961     double    nex;      /* number of decades in data */
1962     double    logscale; /* scale in logarithmic space */
1963     int       exfrac = 1;   /* decade spacing */
1964     int       mid = -1; /* row in yloglab for major grid */
1965     double    mspac;    /* smallest major grid spacing (pixels) */
1966     int       flab;     /* first value in yloglab to use */
1967     double    value, tmp, pre_value;
1968     double    X0, X1, Y0;
1969     char      graph_label[100];
1970
1971     nex = log10(im->maxval / im->minval);
1972     logscale = im->ysize / nex;
1973
1974     /* major spacing for data with high dynamic range */
1975     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1976         if (exfrac == 1)
1977             exfrac = 3;
1978         else
1979             exfrac += 3;
1980     }
1981
1982     /* major spacing for less dynamic data */
1983     do {
1984         /* search best row in yloglab */
1985         mid++;
1986         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
1987         mspac = logscale * log10(10.0 / yloglab[mid][i]);
1988     } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
1989              && yloglab[mid][0] > 0);
1990     if (mid)
1991         mid--;
1992
1993     /* find first value in yloglab */
1994     for (flab = 0;
1995          yloglab[mid][flab] < 10
1996          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1997     if (yloglab[mid][flab] == 10.0) {
1998         tmp += 1.0;
1999         flab = 0;
2000     }
2001     val_exp = tmp;
2002     if (val_exp % exfrac)
2003         val_exp += abs(-val_exp % exfrac);
2004
2005     X0 = im->xorigin;
2006     X1 = im->xorigin + im->xsize;
2007
2008     /* draw grid */
2009     pre_value = DNAN;
2010     while (1) {
2011
2012         value = yloglab[mid][flab] * pow(10.0, val_exp);
2013         if (AlmostEqual2sComplement(value, pre_value, 4))
2014             break;      /* it seems we are not converging */
2015
2016         pre_value = value;
2017
2018         Y0 = ytr(im, value);
2019         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2020             break;
2021
2022         /* major grid line */
2023         gfx_new_dashed_line(im->canvas,
2024                             X0 - 2, Y0,
2025                             X1 + 2, Y0,
2026                             MGRIDWIDTH, im->graph_col[GRC_MGRID],
2027                             im->grid_dash_on, im->grid_dash_off);
2028
2029         /* label */
2030         if (im->extra_flags & FORCE_UNITS_SI) {
2031             int       scale;
2032             double    pvalue;
2033             char      symbol;
2034
2035             scale = floor(val_exp / 3.0);
2036             if (value >= 1.0)
2037                 pvalue = pow(10.0, val_exp % 3);
2038             else
2039                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2040             pvalue *= yloglab[mid][flab];
2041
2042             if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2043                 ((scale + si_symbcenter) >= 0))
2044                 symbol = si_symbol[scale + si_symbcenter];
2045             else
2046                 symbol = '?';
2047
2048             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2049         } else
2050             sprintf(graph_label, "%3.0e", value);
2051         gfx_new_text(im->canvas,
2052                      X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2053                      im->graph_col[GRC_FONT],
2054                      im->text_prop[TEXT_PROP_AXIS].font,
2055                      im->text_prop[TEXT_PROP_AXIS].size,
2056                      im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
2057                      graph_label);
2058
2059         /* minor grid */
2060         if (mid < 4 && exfrac == 1) {
2061             /* find first and last minor line behind current major line
2062              * i is the first line and j tha last */
2063             if (flab == 0) {
2064                 min_exp = val_exp - 1;
2065                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2066                 i = yloglab[mid][i - 1] + 1;
2067                 j = 10;
2068             } else {
2069                 min_exp = val_exp;
2070                 i = yloglab[mid][flab - 1] + 1;
2071                 j = yloglab[mid][flab];
2072             }
2073
2074             /* draw minor lines below current major line */
2075             for (; i < j; i++) {
2076
2077                 value = i * pow(10.0, min_exp);
2078                 if (value < im->minval)
2079                     continue;
2080
2081                 Y0 = ytr(im, value);
2082                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2083                     break;
2084
2085                 /* draw lines */
2086                 gfx_new_dashed_line(im->canvas,
2087                                     X0 - 1, Y0,
2088                                     X1 + 1, Y0,
2089                                     GRIDWIDTH, im->graph_col[GRC_GRID],
2090                                     im->grid_dash_on, im->grid_dash_off);
2091             }
2092         } else if (exfrac > 1) {
2093             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2094                 value = pow(10.0, i);
2095                 if (value < im->minval)
2096                     continue;
2097
2098                 Y0 = ytr(im, value);
2099                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2100                     break;
2101
2102                 /* draw lines */
2103                 gfx_new_dashed_line(im->canvas,
2104                                     X0 - 1, Y0,
2105                                     X1 + 1, Y0,
2106                                     GRIDWIDTH, im->graph_col[GRC_GRID],
2107                                     im->grid_dash_on, im->grid_dash_off);
2108             }
2109         }
2110
2111         /* next decade */
2112         if (yloglab[mid][++flab] == 10.0) {
2113             flab = 0;
2114             val_exp += exfrac;
2115         }
2116     }
2117
2118     /* draw minor lines after highest major line */
2119     if (mid < 4 && exfrac == 1) {
2120         /* find first and last minor line below current major line
2121          * i is the first line and j tha last */
2122         if (flab == 0) {
2123             min_exp = val_exp - 1;
2124             for (i = 1; yloglab[mid][i] < 10.0; i++);
2125             i = yloglab[mid][i - 1] + 1;
2126             j = 10;
2127         } else {
2128             min_exp = val_exp;
2129             i = yloglab[mid][flab - 1] + 1;
2130             j = yloglab[mid][flab];
2131         }
2132
2133         /* draw minor lines below current major line */
2134         for (; i < j; i++) {
2135
2136             value = i * pow(10.0, min_exp);
2137             if (value < im->minval)
2138                 continue;
2139
2140             Y0 = ytr(im, value);
2141             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2142                 break;
2143
2144             /* draw lines */
2145             gfx_new_dashed_line(im->canvas,
2146                                 X0 - 1, Y0,
2147                                 X1 + 1, Y0,
2148                                 GRIDWIDTH, im->graph_col[GRC_GRID],
2149                                 im->grid_dash_on, im->grid_dash_off);
2150         }
2151     }
2152     /* fancy minor gridlines */
2153     else if (exfrac > 1) {
2154         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2155             value = pow(10.0, i);
2156             if (value < im->minval)
2157                 continue;
2158
2159             Y0 = ytr(im, value);
2160             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2161                 break;
2162
2163             /* draw lines */
2164             gfx_new_dashed_line(im->canvas,
2165                                 X0 - 1, Y0,
2166                                 X1 + 1, Y0,
2167                                 GRIDWIDTH, im->graph_col[GRC_GRID],
2168                                 im->grid_dash_on, im->grid_dash_off);
2169         }
2170     }
2171
2172     return 1;
2173 }
2174
2175
2176 void vertical_grid(
2177     image_desc_t * im)
2178 {
2179     int       xlab_sel; /* which sort of label and grid ? */
2180     time_t    ti, tilab, timajor;
2181     long      factor;
2182     char      graph_label[100];
2183     double    X0, Y0, Y1;   /* points for filled graph and more */
2184     struct tm tm;
2185
2186     /* the type of time grid is determined by finding
2187        the number of seconds per pixel in the graph */
2188
2189
2190     if (im->xlab_user.minsec == -1) {
2191         factor = (im->end - im->start) / im->xsize;
2192         xlab_sel = 0;
2193         while (xlab[xlab_sel + 1].minsec != -1
2194                && xlab[xlab_sel + 1].minsec <= factor) {
2195             xlab_sel++;
2196         }               /* pick the last one */
2197         while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2198                && xlab[xlab_sel].length > (im->end - im->start)) {
2199             xlab_sel--;
2200         }               /* go back to the smallest size */
2201         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2202         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2203         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2204         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2205         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2206         im->xlab_user.labst = xlab[xlab_sel].labst;
2207         im->xlab_user.precis = xlab[xlab_sel].precis;
2208         im->xlab_user.stst = xlab[xlab_sel].stst;
2209     }
2210
2211     /* y coords are the same for every line ... */
2212     Y0 = im->yorigin;
2213     Y1 = im->yorigin - im->ysize;
2214
2215
2216     /* paint the minor grid */
2217     if (!(im->extra_flags & NOMINOR)) {
2218         for (ti = find_first_time(im->start,
2219                                   im->xlab_user.gridtm,
2220                                   im->xlab_user.gridst),
2221              timajor = find_first_time(im->start,
2222                                        im->xlab_user.mgridtm,
2223                                        im->xlab_user.mgridst);
2224              ti < im->end;
2225              ti =
2226              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2227             ) {
2228             /* are we inside the graph ? */
2229             if (ti < im->start || ti > im->end)
2230                 continue;
2231             while (timajor < ti) {
2232                 timajor = find_next_time(timajor,
2233                                          im->xlab_user.mgridtm,
2234                                          im->xlab_user.mgridst);
2235             }
2236             if (ti == timajor)
2237                 continue;   /* skip as falls on major grid line */
2238             X0 = xtr(im, ti);
2239             gfx_new_dashed_line(im->canvas, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2240                                 im->graph_col[GRC_GRID],
2241                                 im->grid_dash_on, im->grid_dash_off);
2242
2243         }
2244     }
2245
2246     /* paint the major grid */
2247     for (ti = find_first_time(im->start,
2248                               im->xlab_user.mgridtm,
2249                               im->xlab_user.mgridst);
2250          ti < im->end;
2251          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2252         ) {
2253         /* are we inside the graph ? */
2254         if (ti < im->start || ti > im->end)
2255             continue;
2256         X0 = xtr(im, ti);
2257         gfx_new_dashed_line(im->canvas, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2258                             im->graph_col[GRC_MGRID],
2259                             im->grid_dash_on, im->grid_dash_off);
2260
2261     }
2262     /* paint the labels below the graph */
2263     for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2264                               im->xlab_user.labtm,
2265                               im->xlab_user.labst);
2266          ti <= im->end - im->xlab_user.precis / 2;
2267          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2268         ) {
2269         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2270         /* are we inside the graph ? */
2271         if (tilab < im->start || tilab > im->end)
2272             continue;
2273
2274 #if HAVE_STRFTIME
2275         localtime_r(&tilab, &tm);
2276         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2277 #else
2278 # error "your libc has no strftime I guess we'll abort the exercise here."
2279 #endif
2280         gfx_new_text(im->canvas,
2281                      xtr(im, tilab),
2282                      Y0 + im->text_prop[TEXT_PROP_AXIS].size * 1.4 + 5,
2283                      im->graph_col[GRC_FONT],
2284                      im->text_prop[TEXT_PROP_AXIS].font,
2285                      im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2286                      GFX_H_CENTER, GFX_V_BOTTOM, graph_label);
2287
2288     }
2289
2290 }
2291
2292
2293 void axis_paint(
2294     image_desc_t * im)
2295 {
2296     /* draw x and y axis */
2297     /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2298        im->xorigin+im->xsize,im->yorigin-im->ysize,
2299        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2300
2301        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2302        im->xorigin+im->xsize,im->yorigin-im->ysize,
2303        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2304
2305     gfx_new_line(im->canvas, im->xorigin - 4, im->yorigin,
2306                  im->xorigin + im->xsize + 4, im->yorigin,
2307                  MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2308
2309     gfx_new_line(im->canvas, im->xorigin, im->yorigin + 4,
2310                  im->xorigin, im->yorigin - im->ysize - 4,
2311                  MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2312
2313
2314     /* arrow for X and Y axis direction */
2315     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 */
2316                  im->graph_col[GRC_ARROW]);
2317
2318     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 */
2319                  im->graph_col[GRC_ARROW]);
2320
2321 }
2322
2323 void grid_paint(
2324     image_desc_t * im)
2325 {
2326     long      i;
2327     int       res = 0;
2328     double    X0, Y0;   /* points for filled graph and more */
2329     gfx_node_t *node;
2330
2331     /* draw 3d border */
2332     node = gfx_new_area(im->canvas, 0, im->yimg,
2333                         2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2334     gfx_add_point(node, im->ximg - 2, 2);
2335     gfx_add_point(node, im->ximg, 0);
2336     gfx_add_point(node, 0, 0);
2337 /*    gfx_add_point( node , 0,im->yimg ); */
2338
2339     node = gfx_new_area(im->canvas, 2, im->yimg - 2,
2340                         im->ximg - 2, im->yimg - 2,
2341                         im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2342     gfx_add_point(node, im->ximg, 0);
2343     gfx_add_point(node, im->ximg, im->yimg);
2344     gfx_add_point(node, 0, im->yimg);
2345 /*    gfx_add_point( node , 0,im->yimg ); */
2346
2347
2348     if (im->draw_x_grid == 1)
2349         vertical_grid(im);
2350
2351     if (im->draw_y_grid == 1) {
2352         if (im->logarithmic) {
2353             res = horizontal_log_grid(im);
2354         } else {
2355             res = draw_horizontal_grid(im);
2356         }
2357
2358         /* dont draw horizontal grid if there is no min and max val */
2359         if (!res) {
2360             char     *nodata = "No Data found";
2361
2362             gfx_new_text(im->canvas, im->ximg / 2,
2363                          (2 * im->yorigin - im->ysize) / 2,
2364                          im->graph_col[GRC_FONT],
2365                          im->text_prop[TEXT_PROP_AXIS].font,
2366                          im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2367                          0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2368         }
2369     }
2370
2371     /* yaxis unit description */
2372     gfx_new_text(im->canvas,
2373                  10, (im->yorigin - im->ysize / 2),
2374                  im->graph_col[GRC_FONT],
2375                  im->text_prop[TEXT_PROP_UNIT].font,
2376                  im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2377                  RRDGRAPH_YLEGEND_ANGLE,
2378                  GFX_H_LEFT, GFX_V_CENTER, im->ylegend);
2379
2380     /* graph title */
2381     gfx_new_text(im->canvas,
2382                  im->ximg / 2, im->text_prop[TEXT_PROP_TITLE].size * 1.3 + 4,
2383                  im->graph_col[GRC_FONT],
2384                  im->text_prop[TEXT_PROP_TITLE].font,
2385                  im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2386                  GFX_H_CENTER, GFX_V_CENTER, im->title);
2387     /* rrdtool 'logo' */
2388     gfx_new_text(im->canvas,
2389                  im->ximg - 7, 7,
2390                  (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2391                  im->text_prop[TEXT_PROP_AXIS].font,
2392                  5.5, im->tabwidth, 270,
2393                  GFX_H_RIGHT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2394
2395     /* graph watermark */
2396     if (im->watermark[0] != '\0') {
2397         gfx_new_text(im->canvas,
2398                      im->ximg / 2, im->yimg - 6,
2399                      (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2400                      im->text_prop[TEXT_PROP_AXIS].font,
2401                      5.5, im->tabwidth, 0,
2402                      GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2403     }
2404
2405     /* graph labels */
2406     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2407         for (i = 0; i < im->gdes_c; i++) {
2408             if (im->gdes[i].legend[0] == '\0')
2409                 continue;
2410
2411             /* im->gdes[i].leg_y is the bottom of the legend */
2412             X0 = im->gdes[i].leg_x;
2413             Y0 = im->gdes[i].leg_y;
2414             gfx_new_text(im->canvas, X0, Y0,
2415                          im->graph_col[GRC_FONT],
2416                          im->text_prop[TEXT_PROP_LEGEND].font,
2417                          im->text_prop[TEXT_PROP_LEGEND].size,
2418                          im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2419                          im->gdes[i].legend);
2420             /* The legend for GRAPH items starts with "M " to have
2421                enough space for the box */
2422             if (im->gdes[i].gf != GF_PRINT &&
2423                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2424                 int       boxH, boxV;
2425
2426                 boxH = gfx_get_text_width(im->canvas, 0,
2427                                           im->text_prop[TEXT_PROP_LEGEND].
2428                                           font,
2429                                           im->text_prop[TEXT_PROP_LEGEND].
2430                                           size, im->tabwidth, "o", 0) * 1.2;
2431                 boxV = boxH * 1.1;
2432
2433                 /* make sure transparent colors show up the same way as in the graph */
2434                 node = gfx_new_area(im->canvas,
2435                                     X0, Y0 - boxV,
2436                                     X0, Y0,
2437                                     X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2438                 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2439
2440                 node = gfx_new_area(im->canvas,
2441                                     X0, Y0 - boxV,
2442                                     X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2443                 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2444                 node = gfx_new_line(im->canvas,
2445                                     X0, Y0 - boxV,
2446                                     X0, Y0, 1.0, im->graph_col[GRC_FRAME]);
2447                 gfx_add_point(node, X0 + boxH, Y0);
2448                 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2449                 gfx_close_path(node);
2450             }
2451         }
2452     }
2453 }
2454
2455
2456 /*****************************************************
2457  * lazy check make sure we rely need to create this graph
2458  *****************************************************/
2459
2460 int lazy_check(
2461     image_desc_t * im)
2462 {
2463     FILE     *fd = NULL;
2464     int       size = 1;
2465     struct stat imgstat;
2466
2467     if (im->lazy == 0)
2468         return 0;       /* no lazy option */
2469     if (stat(im->graphfile, &imgstat) != 0)
2470         return 0;       /* can't stat */
2471     /* one pixel in the existing graph is more then what we would
2472        change here ... */
2473     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2474         return 0;
2475     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2476         return 0;       /* the file does not exist */
2477     switch (im->canvas->imgformat) {
2478     case IF_PNG:
2479         size = PngSize(fd, &(im->ximg), &(im->yimg));
2480         break;
2481     default:
2482         size = 1;
2483     }
2484     fclose(fd);
2485     return size;
2486 }
2487
2488 #ifdef WITH_PIECHART
2489 void pie_part(
2490     image_desc_t * im,
2491     gfx_color_t color,
2492     double PieCenterX,
2493     double PieCenterY,
2494     double Radius,
2495     double startangle,
2496     double endangle)
2497 {
2498     gfx_node_t *node;
2499     double    angle;
2500     double    step = M_PI / 50; /* Number of iterations for the circle;
2501                                  ** 10 is definitely too low, more than
2502                                  ** 50 seems to be overkill
2503                                  */
2504
2505     /* Strange but true: we have to work clockwise or else
2506      ** anti aliasing nor transparency don't work.
2507      **
2508      ** This test is here to make sure we do it right, also
2509      ** this makes the for...next loop more easy to implement.
2510      ** The return will occur if the user enters a negative number
2511      ** (which shouldn't be done according to the specs) or if the
2512      ** programmers do something wrong (which, as we all know, never
2513      ** happens anyway :)
2514      */
2515     if (endangle < startangle)
2516         return;
2517
2518     /* Hidden feature: Radius decreases each full circle */
2519     angle = startangle;
2520     while (angle >= 2 * M_PI) {
2521         angle -= 2 * M_PI;
2522         Radius *= 0.8;
2523     }
2524
2525     node = gfx_new_area(im->canvas,
2526                         PieCenterX + sin(startangle) * Radius,
2527                         PieCenterY - cos(startangle) * Radius,
2528                         PieCenterX,
2529                         PieCenterY,
2530                         PieCenterX + sin(endangle) * Radius,
2531                         PieCenterY - cos(endangle) * Radius, color);
2532     for (angle = endangle; angle - startangle >= step; angle -= step) {
2533         gfx_add_point(node,
2534                       PieCenterX + sin(angle) * Radius,
2535                       PieCenterY - cos(angle) * Radius);
2536     }
2537 }
2538
2539 #endif
2540
2541 int graph_size_location(
2542     image_desc_t * im,
2543     int elements
2544 #ifdef WITH_PIECHART
2545     ,
2546     int piechart
2547 #endif
2548     )
2549 {
2550     /* The actual size of the image to draw is determined from
2551      ** several sources.  The size given on the command line is
2552      ** the graph area but we need more as we have to draw labels
2553      ** and other things outside the graph area
2554      */
2555
2556     /* +-+-------------------------------------------+
2557      ** |l|.................title.....................|
2558      ** |e+--+-------------------------------+--------+
2559      ** |b| b|                               |        |
2560      ** |a| a|                               |  pie   |
2561      ** |l| l|          main graph area      | chart  |
2562      ** |.| .|                               |  area  |
2563      ** |t| y|                               |        |
2564      ** |r+--+-------------------------------+--------+
2565      ** |e|  | x-axis labels                 |        |
2566      ** |v+--+-------------------------------+--------+
2567      ** | |..............legends......................|
2568      ** +-+-------------------------------------------+
2569      ** |                 watermark                   |
2570      ** +---------------------------------------------+
2571      */
2572     int       Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2573 #ifdef WITH_PIECHART
2574         Xpie = 0, Ypie = 0,
2575 #endif
2576         Yxlabel = 0,
2577 #if 0
2578         Xlegend = 0, Ylegend = 0,
2579 #endif
2580         Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2581
2582     if (im->extra_flags & ONLY_GRAPH) {
2583         im->xorigin = 0;
2584         im->ximg = im->xsize;
2585         im->yimg = im->ysize;
2586         im->yorigin = im->ysize;
2587         ytr(im, DNAN);
2588         return 0;
2589     }
2590
2591     if (im->ylegend[0] != '\0') {
2592         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2593     }
2594
2595
2596     if (im->title[0] != '\0') {
2597         /* The title is placed "inbetween" two text lines so it
2598          ** automatically has some vertical spacing.  The horizontal
2599          ** spacing is added here, on each side.
2600          */
2601         /* don't care for the with of the title
2602            Xtitle = gfx_get_text_width(im->canvas, 0,
2603            im->text_prop[TEXT_PROP_TITLE].font,
2604            im->text_prop[TEXT_PROP_TITLE].size,
2605            im->tabwidth,
2606            im->title, 0) + 2*Xspacing; */
2607         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2608     }
2609
2610     if (elements) {
2611         Xmain = im->xsize;
2612         Ymain = im->ysize;
2613         if (im->draw_x_grid) {
2614             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2615         }
2616         if (im->draw_y_grid || im->forceleftspace) {
2617             Xylabel = gfx_get_text_width(im->canvas, 0,
2618                                          im->text_prop[TEXT_PROP_AXIS].font,
2619                                          im->text_prop[TEXT_PROP_AXIS].size,
2620                                          im->tabwidth,
2621                                          "0", 0) * im->unitslength;
2622         }
2623     }
2624 #ifdef WITH_PIECHART
2625     if (piechart) {
2626         im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2627         Xpie = im->piesize;
2628         Ypie = im->piesize;
2629     }
2630 #endif
2631
2632     /* Now calculate the total size.  Insert some spacing where
2633        desired.  im->xorigin and im->yorigin need to correspond
2634        with the lower left corner of the main graph area or, if
2635        this one is not set, the imaginary box surrounding the
2636        pie chart area. */
2637
2638     /* The legend width cannot yet be determined, as a result we
2639      ** have problems adjusting the image to it.  For now, we just
2640      ** forget about it at all; the legend will have to fit in the
2641      ** size already allocated.
2642      */
2643     im->ximg = Xylabel + Xmain + 2 * Xspacing;
2644
2645 #ifdef WITH_PIECHART
2646     im->ximg += Xpie;
2647 #endif
2648
2649     if (Xmain)
2650         im->ximg += Xspacing;
2651 #ifdef WITH_PIECHART
2652     if (Xpie)
2653         im->ximg += Xspacing;
2654 #endif
2655
2656     im->xorigin = Xspacing + Xylabel;
2657
2658     /* the length of the title should not influence with width of the graph
2659        if (Xtitle > im->ximg) im->ximg = Xtitle; */
2660
2661     if (Xvertical) {    /* unit description */
2662         im->ximg += Xvertical;
2663         im->xorigin += Xvertical;
2664     }
2665     xtr(im, 0);
2666
2667     /* The vertical size is interesting... we need to compare
2668      ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2669      ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2670      ** in order to start even thinking about Ylegend or Ywatermark.
2671      **
2672      ** Do it in three portions: First calculate the inner part,
2673      ** then do the legend, then adjust the total height of the img,
2674      ** adding space for a watermark if one exists;
2675      */
2676
2677     /* reserve space for main and/or pie */
2678
2679     im->yimg = Ymain + Yxlabel;
2680
2681 #ifdef WITH_PIECHART
2682     if (im->yimg < Ypie)
2683         im->yimg = Ypie;
2684 #endif
2685
2686     im->yorigin = im->yimg - Yxlabel;
2687
2688     /* reserve space for the title *or* some padding above the graph */
2689     if (Ytitle) {
2690         im->yimg += Ytitle;
2691         im->yorigin += Ytitle;
2692     } else {
2693         im->yimg += 1.5 * Yspacing;
2694         im->yorigin += 1.5 * Yspacing;
2695     }
2696     /* reserve space for padding below the graph */
2697     im->yimg += Yspacing;
2698
2699     /* Determine where to place the legends onto the image.
2700      ** Adjust im->yimg to match the space requirements.
2701      */
2702     if (leg_place(im) == -1)
2703         return -1;
2704
2705     if (im->watermark[0] != '\0') {
2706         im->yimg += Ywatermark;
2707     }
2708 #if 0
2709     if (Xlegend > im->ximg) {
2710         im->ximg = Xlegend;
2711         /* reposition Pie */
2712     }
2713 #endif
2714
2715 #ifdef WITH_PIECHART
2716     /* The pie is placed in the upper right hand corner,
2717      ** just below the title (if any) and with sufficient
2718      ** padding.
2719      */
2720     if (elements) {
2721         im->pie_x = im->ximg - Xspacing - Xpie / 2;
2722         im->pie_y = im->yorigin - Ymain + Ypie / 2;
2723     } else {
2724         im->pie_x = im->ximg / 2;
2725         im->pie_y = im->yorigin - Ypie / 2;
2726     }
2727 #endif
2728
2729     ytr(im, DNAN);
2730     return 0;
2731 }
2732
2733 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2734 /* yes we are loosing precision by doing tos with floats instead of doubles
2735    but it seems more stable this way. */
2736
2737
2738 /* draw that picture thing ... */
2739 int graph_paint(
2740     image_desc_t * im,
2741     char ***calcpr)
2742 {
2743     int       i, ii;
2744     int       lazy = lazy_check(im);
2745
2746 #ifdef WITH_PIECHART
2747     int       piechart = 0;
2748     double    PieStart = 0.0;
2749 #endif
2750     FILE     *fo;
2751     gfx_node_t *node;
2752
2753     double    areazero = 0.0;
2754     graph_desc_t *lastgdes = NULL;
2755
2756     /* if we are lazy and there is nothing to PRINT ... quit now */
2757     if (lazy && im->prt_c == 0)
2758         return 0;
2759
2760     /* pull the data from the rrd files ... */
2761
2762     if (data_fetch(im) == -1)
2763         return -1;
2764
2765     /* evaluate VDEF and CDEF operations ... */
2766     if (data_calc(im) == -1)
2767         return -1;
2768
2769 #ifdef WITH_PIECHART
2770     /* check if we need to draw a piechart */
2771     for (i = 0; i < im->gdes_c; i++) {
2772         if (im->gdes[i].gf == GF_PART) {
2773             piechart = 1;
2774             break;
2775         }
2776     }
2777 #endif
2778
2779     /* calculate and PRINT and GPRINT definitions. We have to do it at
2780      * this point because it will affect the length of the legends
2781      * if there are no graph elements we stop here ... 
2782      * if we are lazy, try to quit ... 
2783      */
2784     i = print_calc(im, calcpr);
2785     if (i < 0)
2786         return -1;
2787     if (((i == 0)
2788 #ifdef WITH_PIECHART
2789          && (piechart == 0)
2790 #endif
2791         ) || lazy)
2792         return 0;
2793
2794 #ifdef WITH_PIECHART
2795     /* If there's only the pie chart to draw, signal this */
2796     if (i == 0)
2797         piechart = 2;
2798 #endif
2799
2800     /* get actual drawing data and find min and max values */
2801     if (data_proc(im) == -1)
2802         return -1;
2803
2804     if (!im->logarithmic) {
2805         si_unit(im);
2806     }
2807     /* identify si magnitude Kilo, Mega Giga ? */
2808     if (!im->rigid && !im->logarithmic)
2809         expand_range(im);   /* make sure the upper and lower limit are
2810                                sensible values */
2811
2812     if (!calc_horizontal_grid(im))
2813         return -1;
2814
2815     if (im->gridfit)
2816         apply_gridfit(im);
2817
2818
2819 /**************************************************************
2820  *** Calculating sizes and locations became a bit confusing ***
2821  *** so I moved this into a separate function.              ***
2822  **************************************************************/
2823     if (graph_size_location(im, i
2824 #ifdef WITH_PIECHART
2825                             , piechart
2826 #endif
2827         ) == -1)
2828         return -1;
2829
2830     /* the actual graph is created by going through the individual
2831        graph elements and then drawing them */
2832
2833     node = gfx_new_area(im->canvas,
2834                         0, 0,
2835                         0, im->yimg,
2836                         im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2837
2838     gfx_add_point(node, im->ximg, 0);
2839
2840 #ifdef WITH_PIECHART
2841     if (piechart != 2) {
2842 #endif
2843         node = gfx_new_area(im->canvas,
2844                             im->xorigin, im->yorigin,
2845                             im->xorigin + im->xsize, im->yorigin,
2846                             im->xorigin + im->xsize, im->yorigin - im->ysize,
2847                             im->graph_col[GRC_CANVAS]);
2848
2849         gfx_add_point(node, im->xorigin, im->yorigin - im->ysize);
2850
2851         if (im->minval > 0.0)
2852             areazero = im->minval;
2853         if (im->maxval < 0.0)
2854             areazero = im->maxval;
2855 #ifdef WITH_PIECHART
2856     }
2857 #endif
2858
2859 #ifdef WITH_PIECHART
2860     if (piechart) {
2861         pie_part(im, im->graph_col[GRC_CANVAS], im->pie_x, im->pie_y,
2862                  im->piesize * 0.5, 0, 2 * M_PI);
2863     }
2864 #endif
2865
2866     for (i = 0; i < im->gdes_c; i++) {
2867         switch (im->gdes[i].gf) {
2868         case GF_CDEF:
2869         case GF_VDEF:
2870         case GF_DEF:
2871         case GF_PRINT:
2872         case GF_GPRINT:
2873         case GF_COMMENT:
2874         case GF_HRULE:
2875         case GF_VRULE:
2876         case GF_XPORT:
2877         case GF_SHIFT:
2878             break;
2879         case GF_TICK:
2880             for (ii = 0; ii < im->xsize; ii++) {
2881                 if (!isnan(im->gdes[i].p_data[ii]) &&
2882                     im->gdes[i].p_data[ii] != 0.0) {
2883                     if (im->gdes[i].yrule > 0) {
2884                         gfx_new_line(im->canvas,
2885                                      im->xorigin + ii, im->yorigin,
2886                                      im->xorigin + ii,
2887                                      im->yorigin -
2888                                      im->gdes[i].yrule * im->ysize, 1.0,
2889                                      im->gdes[i].col);
2890                     } else if (im->gdes[i].yrule < 0) {
2891                         gfx_new_line(im->canvas,
2892                                      im->xorigin + ii,
2893                                      im->yorigin - im->ysize,
2894                                      im->xorigin + ii,
2895                                      im->yorigin - (1 -
2896                                                     im->gdes[i].yrule) *
2897                                      im->ysize, 1.0, im->gdes[i].col);
2898
2899                     }
2900                 }
2901             }
2902             break;
2903         case GF_LINE:
2904         case GF_AREA:
2905             /* fix data points at oo and -oo */
2906             for (ii = 0; ii < im->xsize; ii++) {
2907                 if (isinf(im->gdes[i].p_data[ii])) {
2908                     if (im->gdes[i].p_data[ii] > 0) {
2909                         im->gdes[i].p_data[ii] = im->maxval;
2910                     } else {
2911                         im->gdes[i].p_data[ii] = im->minval;
2912                     }
2913
2914                 }
2915             }           /* for */
2916
2917             /* *******************************************************
2918                a           ___. (a,t) 
2919                |   |    ___
2920                ____|   |   |   |
2921                |       |___|
2922                -------|--t-1--t--------------------------------      
2923
2924                if we know the value at time t was a then 
2925                we draw a square from t-1 to t with the value a.
2926
2927                ********************************************************* */
2928             if (im->gdes[i].col != 0x0) {
2929                 /* GF_LINE and friend */
2930                 if (im->gdes[i].gf == GF_LINE) {
2931                     double    last_y = 0.0;
2932
2933                     node = NULL;
2934                     for (ii = 1; ii < im->xsize; ii++) {
2935                         if (isnan(im->gdes[i].p_data[ii])
2936                             || (im->slopemode == 1
2937                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
2938                             node = NULL;
2939                             continue;
2940                         }
2941                         if (node == NULL) {
2942                             last_y = ytr(im, im->gdes[i].p_data[ii]);
2943                             if (im->slopemode == 0) {
2944                                 node = gfx_new_line(im->canvas,
2945                                                     ii - 1 + im->xorigin,
2946                                                     last_y, ii + im->xorigin,
2947                                                     last_y,
2948                                                     im->gdes[i].linewidth,
2949                                                     im->gdes[i].col);
2950                             } else {
2951                                 node = gfx_new_line(im->canvas,
2952                                                     ii - 1 + im->xorigin,
2953                                                     ytr(im,
2954                                                         im->gdes[i].
2955                                                         p_data[ii - 1]),
2956                                                     ii + im->xorigin, last_y,
2957                                                     im->gdes[i].linewidth,
2958                                                     im->gdes[i].col);
2959                             }
2960                         } else {
2961                             double    new_y = ytr(im, im->gdes[i].p_data[ii]);
2962
2963                             if (im->slopemode == 0
2964                                 && !AlmostEqual2sComplement(new_y, last_y,
2965                                                             4)) {
2966                                 gfx_add_point(node, ii - 1 + im->xorigin,
2967                                               new_y);
2968                             };
2969                             last_y = new_y;
2970                             gfx_add_point(node, ii + im->xorigin, new_y);
2971                         };
2972
2973                     }
2974                 } else {
2975                     int       idxI = -1;
2976                     double   *foreY = malloc(sizeof(double) * im->xsize * 2);
2977                     double   *foreX = malloc(sizeof(double) * im->xsize * 2);
2978                     double   *backY = malloc(sizeof(double) * im->xsize * 2);
2979                     double   *backX = malloc(sizeof(double) * im->xsize * 2);
2980                     int       drawem = 0;
2981
2982                     for (ii = 0; ii <= im->xsize; ii++) {
2983                         double    ybase, ytop;
2984
2985                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
2986                             int       cntI = 1;
2987                             int       lastI = 0;
2988
2989                             while (cntI < idxI
2990                                    && AlmostEqual2sComplement(foreY[lastI],
2991                                                               foreY[cntI], 4)
2992                                    && AlmostEqual2sComplement(foreY[lastI],
2993                                                               foreY[cntI + 1],
2994                                                               4)) {
2995                                 cntI++;
2996                             }
2997                             node = gfx_new_area(im->canvas,
2998                                                 backX[0], backY[0],
2999                                                 foreX[0], foreY[0],
3000                                                 foreX[cntI], foreY[cntI],
3001                                                 im->gdes[i].col);
3002                             while (cntI < idxI) {
3003                                 lastI = cntI;
3004                                 cntI++;
3005                                 while (cntI < idxI
3006                                        &&
3007                                        AlmostEqual2sComplement(foreY[lastI],
3008                                                                foreY[cntI], 4)
3009                                        &&
3010                                        AlmostEqual2sComplement(foreY[lastI],
3011                                                                foreY[cntI +
3012                                                                      1], 4)) {
3013                                     cntI++;
3014                                 }
3015                                 gfx_add_point(node, foreX[cntI], foreY[cntI]);
3016                             }
3017                             gfx_add_point(node, backX[idxI], backY[idxI]);
3018                             while (idxI > 1) {
3019                                 lastI = idxI;
3020                                 idxI--;
3021                                 while (idxI > 1
3022                                        &&
3023                                        AlmostEqual2sComplement(backY[lastI],
3024                                                                backY[idxI], 4)
3025                                        &&
3026                                        AlmostEqual2sComplement(backY[lastI],
3027                                                                backY[idxI -
3028                                                                      1], 4)) {
3029                                     idxI--;
3030                                 }
3031                                 gfx_add_point(node, backX[idxI], backY[idxI]);
3032                             }
3033                             idxI = -1;
3034                             drawem = 0;
3035                         }
3036                         if (drawem != 0) {
3037                             drawem = 0;
3038                             idxI = -1;
3039                         }
3040                         if (ii == im->xsize)
3041                             break;
3042
3043                         /* keep things simple for now, just draw these bars
3044                            do not try to build a big and complex area */
3045
3046
3047                         if (im->slopemode == 0 && ii == 0) {
3048                             continue;
3049                         }
3050                         if (isnan(im->gdes[i].p_data[ii])) {
3051                             drawem = 1;
3052                             continue;
3053                         }
3054                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3055                         if (lastgdes && im->gdes[i].stack) {
3056                             ybase = ytr(im, lastgdes->p_data[ii]);
3057                         } else {
3058                             ybase = ytr(im, areazero);
3059                         }
3060                         if (ybase == ytop) {
3061                             drawem = 1;
3062                             continue;
3063                         }
3064                         /* every area has to be wound clock-wise,
3065                            so we have to make sur base remains base  */
3066                         if (ybase > ytop) {
3067                             double    extra = ytop;
3068
3069                             ytop = ybase;
3070                             ybase = extra;
3071                         }
3072                         if (im->slopemode == 0) {
3073                             backY[++idxI] = ybase - 0.2;
3074                             backX[idxI] = ii + im->xorigin - 1;
3075                             foreY[idxI] = ytop + 0.2;
3076                             foreX[idxI] = ii + im->xorigin - 1;
3077                         }
3078                         backY[++idxI] = ybase - 0.2;
3079                         backX[idxI] = ii + im->xorigin;
3080                         foreY[idxI] = ytop + 0.2;
3081                         foreX[idxI] = ii + im->xorigin;
3082                     }
3083                     /* close up any remaining area */
3084                     free(foreY);
3085                     free(foreX);
3086                     free(backY);
3087                     free(backX);
3088                 }       /* else GF_LINE */
3089             }
3090             /* if color != 0x0 */
3091             /* make sure we do not run into trouble when stacking on NaN */
3092             for (ii = 0; ii < im->xsize; ii++) {
3093                 if (isnan(im->gdes[i].p_data[ii])) {
3094                     if (lastgdes && (im->gdes[i].stack)) {
3095                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3096                     } else {
3097                         im->gdes[i].p_data[ii] = areazero;
3098                     }
3099                 }
3100             }
3101             lastgdes = &(im->gdes[i]);
3102             break;
3103 #ifdef WITH_PIECHART
3104         case GF_PART:
3105             if (isnan(im->gdes[i].yrule))   /* fetch variable */
3106                 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
3107
3108             if (finite(im->gdes[i].yrule)) {    /* even the fetched var can be NaN */
3109                 pie_part(im, im->gdes[i].col,
3110                          im->pie_x, im->pie_y, im->piesize * 0.4,
3111                          M_PI * 2.0 * PieStart / 100.0,
3112                          M_PI * 2.0 * (PieStart + im->gdes[i].yrule) / 100.0);
3113                 PieStart += im->gdes[i].yrule;
3114             }
3115             break;
3116 #endif
3117         case GF_STACK:
3118             rrd_set_error
3119                 ("STACK should already be turned into LINE or AREA here");
3120             return -1;
3121             break;
3122
3123         }               /* switch */
3124     }
3125 #ifdef WITH_PIECHART
3126     if (piechart == 2) {
3127         im->draw_x_grid = 0;
3128         im->draw_y_grid = 0;
3129     }
3130 #endif
3131
3132
3133     /* grid_paint also does the text */
3134     if (!(im->extra_flags & ONLY_GRAPH))
3135         grid_paint(im);
3136
3137
3138     if (!(im->extra_flags & ONLY_GRAPH))
3139         axis_paint(im);
3140
3141     /* the RULES are the last thing to paint ... */
3142     for (i = 0; i < im->gdes_c; i++) {
3143
3144         switch (im->gdes[i].gf) {
3145         case GF_HRULE:
3146             if (im->gdes[i].yrule >= im->minval
3147                 && im->gdes[i].yrule <= im->maxval)
3148                 gfx_new_line(im->canvas,
3149                              im->xorigin, ytr(im, im->gdes[i].yrule),
3150                              im->xorigin + im->xsize, ytr(im,
3151                                                           im->gdes[i].yrule),
3152                              1.0, im->gdes[i].col);
3153             break;
3154         case GF_VRULE:
3155             if (im->gdes[i].xrule >= im->start
3156                 && im->gdes[i].xrule <= im->end)
3157                 gfx_new_line(im->canvas,
3158                              xtr(im, im->gdes[i].xrule), im->yorigin,
3159                              xtr(im, im->gdes[i].xrule),
3160                              im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3161             break;
3162         default:
3163             break;
3164         }
3165     }
3166
3167
3168     if (strcmp(im->graphfile, "-") == 0) {
3169         fo = im->graphhandle ? im->graphhandle : stdout;
3170 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3171         /* Change translation mode for stdout to BINARY */
3172         _setmode(_fileno(fo), O_BINARY);
3173 #endif
3174     } else {
3175         if ((fo = fopen(im->graphfile, "wb")) == NULL) {
3176             rrd_set_error("Opening '%s' for write: %s", im->graphfile,
3177                           rrd_strerror(errno));
3178             return (-1);
3179         }
3180     }
3181     gfx_render(im->canvas, im->ximg, im->yimg, 0x00000000, fo);
3182     if (strcmp(im->graphfile, "-") != 0)
3183         fclose(fo);
3184     return 0;
3185 }
3186
3187
3188 /*****************************************************
3189  * graph stuff 
3190  *****************************************************/
3191
3192 int gdes_alloc(
3193     image_desc_t * im)
3194 {
3195
3196     im->gdes_c++;
3197     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3198                                                  * sizeof(graph_desc_t))) ==
3199         NULL) {
3200         rrd_set_error("realloc graph_descs");
3201         return -1;
3202     }
3203
3204
3205     im->gdes[im->gdes_c - 1].step = im->step;
3206     im->gdes[im->gdes_c - 1].step_orig = im->step;
3207     im->gdes[im->gdes_c - 1].stack = 0;
3208     im->gdes[im->gdes_c - 1].linewidth = 0;
3209     im->gdes[im->gdes_c - 1].debug = 0;
3210     im->gdes[im->gdes_c - 1].start = im->start;
3211     im->gdes[im->gdes_c - 1].start_orig = im->start;
3212     im->gdes[im->gdes_c - 1].end = im->end;
3213     im->gdes[im->gdes_c - 1].end_orig = im->end;
3214     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3215     im->gdes[im->gdes_c - 1].data = NULL;
3216     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3217     im->gdes[im->gdes_c - 1].data_first = 0;
3218     im->gdes[im->gdes_c - 1].p_data = NULL;
3219     im->gdes[im->gdes_c - 1].rpnp = NULL;
3220     im->gdes[im->gdes_c - 1].shift = 0;
3221     im->gdes[im->gdes_c - 1].col = 0x0;
3222     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3223     im->gdes[im->gdes_c - 1].format[0] = '\0';
3224     im->gdes[im->gdes_c - 1].strftm = 0;
3225     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3226     im->gdes[im->gdes_c - 1].ds = -1;
3227     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3228     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3229     im->gdes[im->gdes_c - 1].p_data = NULL;
3230     im->gdes[im->gdes_c - 1].yrule = DNAN;
3231     im->gdes[im->gdes_c - 1].xrule = 0;
3232     return 0;
3233 }
3234
3235 /* copies input untill the first unescaped colon is found
3236    or until input ends. backslashes have to be escaped as well */
3237 int scan_for_col(
3238     const char *const input,
3239     int len,
3240     char *const output)
3241 {
3242     int       inp, outp = 0;
3243
3244     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3245         if (input[inp] == '\\' &&
3246             input[inp + 1] != '\0' &&
3247             (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3248             output[outp++] = input[++inp];
3249         } else {
3250             output[outp++] = input[inp];
3251         }
3252     }
3253     output[outp] = '\0';
3254     return inp;
3255 }
3256
3257 /* Some surgery done on this function, it became ridiculously big.
3258 ** Things moved:
3259 ** - initializing     now in rrd_graph_init()
3260 ** - options parsing  now in rrd_graph_options()
3261 ** - script parsing   now in rrd_graph_script()
3262 */
3263 int rrd_graph(
3264     int argc,
3265     char **argv,
3266     char ***prdata,
3267     int *xsize,
3268     int *ysize,
3269     FILE * stream,
3270     double *ymin,
3271     double *ymax)
3272 {
3273     image_desc_t im;
3274
3275     rrd_graph_init(&im);
3276     im.graphhandle = stream;
3277
3278     rrd_graph_options(argc, argv, &im);
3279     if (rrd_test_error()) {
3280         im_free(&im);
3281         return -1;
3282     }
3283
3284     if (strlen(argv[optind]) >= MAXPATH) {
3285         rrd_set_error("filename (including path) too long");
3286         im_free(&im);
3287         return -1;
3288     }
3289     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3290     im.graphfile[MAXPATH - 1] = '\0';
3291
3292     rrd_graph_script(argc, argv, &im, 1);
3293     if (rrd_test_error()) {
3294         im_free(&im);
3295         return -1;
3296     }
3297
3298     /* Everything is now read and the actual work can start */
3299
3300     (*prdata) = NULL;
3301     if (graph_paint(&im, prdata) == -1) {
3302         im_free(&im);
3303         return -1;
3304     }
3305
3306     /* The image is generated and needs to be output.
3307      ** Also, if needed, print a line with information about the image.
3308      */
3309
3310     *xsize = im.ximg;
3311     *ysize = im.yimg;
3312     *ymin = im.minval;
3313     *ymax = im.maxval;
3314     if (im.imginfo) {
3315         char     *filename;
3316
3317         if (!(*prdata)) {
3318             /* maybe prdata is not allocated yet ... lets do it now */
3319             if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3320                 rrd_set_error("malloc imginfo");
3321                 return -1;
3322             };
3323         }
3324         if (((*prdata)[0] =
3325              malloc((strlen(im.imginfo) + 200 +
3326                      strlen(im.graphfile)) * sizeof(char)))
3327             == NULL) {
3328             rrd_set_error("malloc imginfo");
3329             return -1;
3330         }
3331         filename = im.graphfile + strlen(im.graphfile);
3332         while (filename > im.graphfile) {
3333             if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3334                 break;
3335             filename--;
3336         }
3337
3338         sprintf((*prdata)[0], im.imginfo, filename,
3339                 (long) (im.canvas->zoom * im.ximg),
3340                 (long) (im.canvas->zoom * im.yimg));
3341     }
3342     im_free(&im);
3343     return 0;
3344 }
3345
3346 void rrd_graph_init(
3347     image_desc_t * im)
3348 {
3349     unsigned int i;
3350
3351 #ifdef HAVE_TZSET
3352     tzset();
3353 #endif
3354 #ifdef HAVE_SETLOCALE
3355     setlocale(LC_TIME, "");
3356 #ifdef HAVE_MBSTOWCS
3357     setlocale(LC_CTYPE, "");
3358 #endif
3359 #endif
3360     im->yorigin = 0;
3361     im->xorigin = 0;
3362     im->minval = 0;
3363     im->xlab_user.minsec = -1;
3364     im->ximg = 0;
3365     im->yimg = 0;
3366     im->xsize = 400;
3367     im->ysize = 100;
3368     im->step = 0;
3369     im->ylegend[0] = '\0';
3370     im->title[0] = '\0';
3371     im->watermark[0] = '\0';
3372     im->minval = DNAN;
3373     im->maxval = DNAN;
3374     im->unitsexponent = 9999;
3375     im->unitslength = 6;
3376     im->forceleftspace = 0;
3377     im->symbol = ' ';
3378     im->viewfactor = 1.0;
3379     im->extra_flags = 0;
3380     im->rigid = 0;
3381     im->gridfit = 1;
3382     im->imginfo = NULL;
3383     im->lazy = 0;
3384     im->slopemode = 0;
3385     im->logarithmic = 0;
3386     im->ygridstep = DNAN;
3387     im->draw_x_grid = 1;
3388     im->draw_y_grid = 1;
3389     im->base = 1000;
3390     im->prt_c = 0;
3391     im->gdes_c = 0;
3392     im->gdes = NULL;
3393     im->canvas = gfx_new_canvas();
3394     im->grid_dash_on = 1;
3395     im->grid_dash_off = 1;
3396     im->tabwidth = 40.0;
3397
3398     for (i = 0; i < DIM(graph_col); i++)
3399         im->graph_col[i] = graph_col[i];
3400
3401 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3402     {
3403         char     *windir;
3404         char      rrd_win_default_font[1000];
3405
3406         windir = getenv("windir");
3407         /* %windir% is something like D:\windows or C:\winnt */
3408         if (windir != NULL) {
3409             strncpy(rrd_win_default_font, windir, 500);
3410             rrd_win_default_font[500] = '\0';
3411             strcat(rrd_win_default_font, "\\fonts\\");
3412             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3413             for (i = 0; i < DIM(text_prop); i++) {
3414                 strncpy(text_prop[i].font, rrd_win_default_font,
3415                         sizeof(text_prop[i].font) - 1);
3416                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3417             }
3418         }
3419     }
3420 #endif
3421     {
3422         char     *deffont;
3423
3424         deffont = getenv("RRD_DEFAULT_FONT");
3425         if (deffont != NULL) {
3426             for (i = 0; i < DIM(text_prop); i++) {
3427                 strncpy(text_prop[i].font, deffont,
3428                         sizeof(text_prop[i].font) - 1);
3429                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3430             }
3431         }
3432     }
3433     for (i = 0; i < DIM(text_prop); i++) {
3434         im->text_prop[i].size = text_prop[i].size;
3435         strcpy(im->text_prop[i].font, text_prop[i].font);
3436     }
3437 }
3438
3439 void rrd_graph_options(
3440     int argc,
3441     char *argv[],
3442     image_desc_t * im)
3443 {
3444     int       stroff;
3445     char     *parsetime_error = NULL;
3446     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3447     time_t    start_tmp = 0, end_tmp = 0;
3448     long      long_tmp;
3449     struct rrd_time_value start_tv, end_tv;
3450     gfx_color_t color;
3451
3452     optind = 0;
3453     opterr = 0;         /* initialize getopt */
3454
3455     parsetime("end-24h", &start_tv);
3456     parsetime("now", &end_tv);
3457
3458     /* defines for long options without a short equivalent. should be bytes,
3459        and may not collide with (the ASCII value of) short options */
3460 #define LONGOPT_UNITS_SI 255
3461
3462     while (1) {
3463         static struct option long_options[] = {
3464             {"start", required_argument, 0, 's'},
3465             {"end", required_argument, 0, 'e'},
3466             {"x-grid", required_argument, 0, 'x'},
3467             {"y-grid", required_argument, 0, 'y'},
3468             {"vertical-label", required_argument, 0, 'v'},
3469             {"width", required_argument, 0, 'w'},
3470             {"height", required_argument, 0, 'h'},
3471             {"interlaced", no_argument, 0, 'i'},
3472             {"upper-limit", required_argument, 0, 'u'},
3473             {"lower-limit", required_argument, 0, 'l'},
3474             {"rigid", no_argument, 0, 'r'},
3475             {"base", required_argument, 0, 'b'},
3476             {"logarithmic", no_argument, 0, 'o'},
3477             {"color", required_argument, 0, 'c'},
3478             {"font", required_argument, 0, 'n'},
3479             {"title", required_argument, 0, 't'},
3480             {"imginfo", required_argument, 0, 'f'},
3481             {"imgformat", required_argument, 0, 'a'},
3482             {"lazy", no_argument, 0, 'z'},
3483             {"zoom", required_argument, 0, 'm'},
3484             {"no-legend", no_argument, 0, 'g'},
3485             {"force-rules-legend", no_argument, 0, 'F'},
3486             {"only-graph", no_argument, 0, 'j'},
3487             {"alt-y-grid", no_argument, 0, 'Y'},
3488             {"no-minor", no_argument, 0, 'I'},
3489             {"slope-mode", no_argument, 0, 'E'},
3490             {"alt-autoscale", no_argument, 0, 'A'},
3491             {"alt-autoscale-min", no_argument, 0, 'J'},
3492             {"alt-autoscale-max", no_argument, 0, 'M'},
3493             {"no-gridfit", no_argument, 0, 'N'},
3494             {"units-exponent", required_argument, 0, 'X'},
3495             {"units-length", required_argument, 0, 'L'},
3496             {"units", required_argument, 0, LONGOPT_UNITS_SI},
3497             {"step", required_argument, 0, 'S'},
3498             {"tabwidth", required_argument, 0, 'T'},
3499             {"font-render-mode", required_argument, 0, 'R'},
3500             {"font-smoothing-threshold", required_argument, 0, 'B'},
3501             {"watermark", required_argument, 0, 'W'},
3502             {"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 */
3503             {0, 0, 0, 0}
3504         };
3505         int       option_index = 0;
3506         int       opt;
3507         int       col_start, col_end;
3508
3509         opt = getopt_long(argc, argv,
3510                           "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3511                           long_options, &option_index);
3512
3513         if (opt == EOF)
3514             break;
3515
3516         switch (opt) {
3517         case 'I':
3518             im->extra_flags |= NOMINOR;
3519             break;
3520         case 'Y':
3521             im->extra_flags |= ALTYGRID;
3522             break;
3523         case 'A':
3524             im->extra_flags |= ALTAUTOSCALE;
3525             break;
3526         case 'J':
3527             im->extra_flags |= ALTAUTOSCALE_MIN;
3528             break;
3529         case 'M':
3530             im->extra_flags |= ALTAUTOSCALE_MAX;
3531             break;
3532         case 'j':
3533             im->extra_flags |= ONLY_GRAPH;
3534             break;
3535         case 'g':
3536             im->extra_flags |= NOLEGEND;
3537             break;
3538         case 'F':
3539             im->extra_flags |= FORCE_RULES_LEGEND;
3540             break;
3541         case LONGOPT_UNITS_SI:
3542             if (im->extra_flags & FORCE_UNITS) {
3543                 rrd_set_error("--units can only be used once!");
3544                 return;
3545             }
3546             if (strcmp(optarg, "si") == 0)
3547                 im->extra_flags |= FORCE_UNITS_SI;
3548             else {
3549                 rrd_set_error("invalid argument for --units: %s", optarg);
3550                 return;
3551             }
3552             break;
3553         case 'X':
3554             im->unitsexponent = atoi(optarg);
3555             break;
3556         case 'L':
3557             im->unitslength = atoi(optarg);
3558             im->forceleftspace = 1;
3559             break;
3560         case 'T':
3561             im->tabwidth = atof(optarg);
3562             break;
3563         case 'S':
3564             im->step = atoi(optarg);
3565             break;
3566         case 'N':
3567             im->gridfit = 0;
3568             break;
3569         case 's':
3570             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3571                 rrd_set_error("start time: %s", parsetime_error);
3572                 return;
3573             }
3574             break;
3575         case 'e':
3576             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3577                 rrd_set_error("end time: %s", parsetime_error);
3578                 return;
3579             }
3580             break;
3581         case 'x':
3582             if (strcmp(optarg, "none") == 0) {
3583                 im->draw_x_grid = 0;
3584                 break;
3585             };
3586
3587             if (sscanf(optarg,
3588                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3589                        scan_gtm,
3590                        &im->xlab_user.gridst,
3591                        scan_mtm,
3592                        &im->xlab_user.mgridst,
3593                        scan_ltm,
3594                        &im->xlab_user.labst,
3595                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3596                 strncpy(im->xlab_form, optarg + stroff,
3597                         sizeof(im->xlab_form) - 1);
3598                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3599                 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3600                     rrd_set_error("unknown keyword %s", scan_gtm);
3601                     return;
3602                 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3603                            == -1) {
3604                     rrd_set_error("unknown keyword %s", scan_mtm);
3605                     return;
3606                 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3607                            -1) {
3608                     rrd_set_error("unknown keyword %s", scan_ltm);
3609                     return;
3610                 }
3611                 im->xlab_user.minsec = 1;
3612                 im->xlab_user.stst = im->xlab_form;
3613             } else {
3614                 rrd_set_error("invalid x-grid format");
3615                 return;
3616             }
3617             break;
3618         case 'y':
3619
3620             if (strcmp(optarg, "none") == 0) {
3621                 im->draw_y_grid = 0;
3622                 break;
3623             };
3624
3625             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3626                 if (im->ygridstep <= 0) {
3627                     rrd_set_error("grid step must be > 0");
3628                     return;
3629                 } else if (im->ylabfact < 1) {
3630                     rrd_set_error("label factor must be > 0");
3631                     return;
3632                 }
3633             } else {
3634                 rrd_set_error("invalid y-grid format");
3635                 return;
3636             }
3637             break;
3638         case 'v':
3639             strncpy(im->ylegend, optarg, 150);
3640             im->ylegend[150] = '\0';
3641             break;
3642         case 'u':
3643             im->maxval = atof(optarg);
3644             break;
3645         case 'l':
3646             im->minval = atof(optarg);
3647             break;
3648         case 'b':
3649             im->base = atol(optarg);
3650             if (im->base != 1024 && im->base != 1000) {
3651                 rrd_set_error
3652                     ("the only sensible value for base apart from 1000 is 1024");
3653                 return;
3654             }
3655             break;
3656         case 'w':
3657             long_tmp = atol(optarg);
3658             if (long_tmp < 10) {
3659                 rrd_set_error("width below 10 pixels");
3660                 return;
3661             }
3662             im->xsize = long_tmp;
3663             break;
3664         case 'h':
3665             long_tmp = atol(optarg);
3666             if (long_tmp < 10) {
3667                 rrd_set_error("height below 10 pixels");
3668                 return;
3669             }
3670             im->ysize = long_tmp;
3671             break;
3672         case 'i':
3673             im->canvas->interlaced = 1;
3674             break;
3675         case 'r':
3676             im->rigid = 1;
3677             break;
3678         case 'f':
3679             im->imginfo = optarg;
3680             break;
3681         case 'a':
3682             if ((int) (im->canvas->imgformat = if_conv(optarg)) == -1) {
3683                 rrd_set_error("unsupported graphics format '%s'", optarg);
3684                 return;
3685             }
3686             break;
3687         case 'z':
3688             im->lazy = 1;
3689             break;
3690         case 'E':
3691             im->slopemode = 1;
3692             break;
3693
3694         case 'o':
3695             im->logarithmic = 1;
3696             break;
3697         case 'c':
3698             if (sscanf(optarg,
3699                        "%10[A-Z]#%n%8lx%n",
3700                        col_nam, &col_start, &color, &col_end) == 2) {
3701                 int       ci;
3702                 int       col_len = col_end - col_start;
3703
3704                 switch (col_len) {
3705                 case 3:
3706                     color = (((color & 0xF00) * 0x110000) |
3707                              ((color & 0x0F0) * 0x011000) |
3708                              ((color & 0x00F) * 0x001100) | 0x000000FF);
3709                     break;
3710                 case 4:
3711                     color = (((color & 0xF000) * 0x11000) |
3712                              ((color & 0x0F00) * 0x01100) |
3713                              ((color & 0x00F0) * 0x00110) |
3714                              ((color & 0x000F) * 0x00011)
3715                         );
3716                     break;
3717                 case 6:
3718                     color = (color << 8) + 0xff /* shift left by 8 */ ;
3719                     break;
3720                 case 8:
3721                     break;
3722                 default:
3723                     rrd_set_error("the color format is #RRGGBB[AA]");
3724                     return;
3725                 }
3726                 if ((ci = grc_conv(col_nam)) != -1) {
3727                     im->graph_col[ci] = color;
3728                 } else {
3729                     rrd_set_error("invalid color name '%s'", col_nam);
3730                     return;
3731                 }
3732             } else {
3733                 rrd_set_error("invalid color def format");
3734                 return;
3735             }
3736             break;
3737         case 'n':{
3738             char      prop[15];
3739             double    size = 1;
3740             char      font[1024] = "";
3741
3742             if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3743                 int       sindex, propidx;
3744
3745                 if ((sindex = text_prop_conv(prop)) != -1) {
3746                     for (propidx = sindex; propidx < TEXT_PROP_LAST;
3747                          propidx++) {
3748                         if (size > 0) {
3749                             im->text_prop[propidx].size = size;
3750                         }
3751                         if (strlen(font) > 0) {
3752                             strcpy(im->text_prop[propidx].font, font);
3753                         }
3754                         if (propidx == sindex && sindex != 0)
3755                             break;
3756                     }
3757                 } else {
3758                     rrd_set_error("invalid fonttag '%s'", prop);
3759                     return;
3760                 }
3761             } else {
3762                 rrd_set_error("invalid text property format");
3763                 return;
3764             }
3765             break;
3766         }
3767         case 'm':
3768             im->canvas->zoom = atof(optarg);
3769             if (im->canvas->zoom <= 0.0) {
3770                 rrd_set_error("zoom factor must be > 0");
3771                 return;
3772             }
3773             break;
3774         case 't':
3775             strncpy(im->title, optarg, 150);
3776             im->title[150] = '\0';
3777             break;
3778
3779         case 'R':
3780             if (strcmp(optarg, "normal") == 0)
3781                 im->canvas->aa_type = AA_NORMAL;
3782             else if (strcmp(optarg, "light") == 0)
3783                 im->canvas->aa_type = AA_LIGHT;
3784             else if (strcmp(optarg, "mono") == 0)
3785                 im->canvas->aa_type = AA_NONE;
3786             else {
3787                 rrd_set_error("unknown font-render-mode '%s'", optarg);
3788                 return;
3789             }
3790             break;
3791
3792         case 'B':
3793             im->canvas->font_aa_threshold = atof(optarg);
3794             break;
3795
3796         case 'W':
3797             strncpy(im->watermark, optarg, 100);
3798             im->watermark[99] = '\0';
3799             break;
3800
3801         case '?':
3802             if (optopt != 0)
3803                 rrd_set_error("unknown option '%c'", optopt);
3804             else
3805                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3806             return;
3807         }
3808     }
3809
3810     if (optind >= argc) {
3811         rrd_set_error("missing filename");
3812         return;
3813     }
3814
3815     if (im->logarithmic == 1 && im->minval <= 0) {
3816         rrd_set_error
3817             ("for a logarithmic yaxis you must specify a lower-limit > 0");
3818         return;
3819     }
3820
3821     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3822         /* error string is set in parsetime.c */
3823         return;
3824     }
3825
3826     if (start_tmp < 3600 * 24 * 365 * 10) {
3827         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3828                       start_tmp);
3829         return;
3830     }
3831
3832     if (end_tmp < start_tmp) {
3833         rrd_set_error("start (%ld) should be less than end (%ld)",
3834                       start_tmp, end_tmp);
3835         return;
3836     }
3837
3838     im->start = start_tmp;
3839     im->end = end_tmp;
3840     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3841 }
3842
3843 int rrd_graph_color(
3844     image_desc_t * im,
3845     char *var,
3846     char *err,
3847     int optional)
3848 {
3849     char     *color;
3850     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
3851
3852     color = strstr(var, "#");
3853     if (color == NULL) {
3854         if (optional == 0) {
3855             rrd_set_error("Found no color in %s", err);
3856             return 0;
3857         }
3858         return 0;
3859     } else {
3860         int       n = 0;
3861         char     *rest;
3862         gfx_color_t col;
3863
3864         rest = strstr(color, ":");
3865         if (rest != NULL)
3866             n = rest - color;
3867         else
3868             n = strlen(color);
3869
3870         switch (n) {
3871         case 7:
3872             sscanf(color, "#%6lx%n", &col, &n);
3873             col = (col << 8) + 0xff /* shift left by 8 */ ;
3874             if (n != 7)
3875                 rrd_set_error("Color problem in %s", err);
3876             break;
3877         case 9:
3878             sscanf(color, "#%8lx%n", &col, &n);
3879             if (n == 9)
3880                 break;
3881         default:
3882             rrd_set_error("Color problem in %s", err);
3883         }
3884         if (rrd_test_error())
3885             return 0;
3886         gdp->col = col;
3887         return n;
3888     }
3889 }
3890
3891
3892 int bad_format(
3893     char *fmt)
3894 {
3895     char     *ptr;
3896     int       n = 0;
3897
3898     ptr = fmt;
3899     while (*ptr != '\0')
3900         if (*ptr++ == '%') {
3901
3902             /* line cannot end with percent char */
3903             if (*ptr == '\0')
3904                 return 1;
3905
3906             /* '%s', '%S' and '%%' are allowed */
3907             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
3908                 ptr++;
3909
3910             /* %c is allowed (but use only with vdef!) */
3911             else if (*ptr == 'c') {
3912                 ptr++;
3913                 n = 1;
3914             }
3915
3916             /* or else '% 6.2lf' and such are allowed */
3917             else {
3918                 /* optional padding character */
3919                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
3920                     ptr++;
3921
3922                 /* This should take care of 'm.n' with all three optional */
3923                 while (*ptr >= '0' && *ptr <= '9')
3924                     ptr++;
3925                 if (*ptr == '.')
3926                     ptr++;
3927                 while (*ptr >= '0' && *ptr <= '9')
3928                     ptr++;
3929
3930                 /* Either 'le', 'lf' or 'lg' must follow here */
3931                 if (*ptr++ != 'l')
3932                     return 1;
3933                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
3934                     ptr++;
3935                 else
3936                     return 1;
3937                 n++;
3938             }
3939         }
3940
3941     return (n != 1);
3942 }
3943
3944
3945 int vdef_parse(
3946     gdes,
3947     str)
3948     struct graph_desc_t *gdes;
3949     const char *const str;
3950 {
3951     /* A VDEF currently is either "func" or "param,func"
3952      * so the parsing is rather simple.  Change if needed.
3953      */
3954     double    param;
3955     char      func[30];
3956     int       n;
3957
3958     n = 0;
3959     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
3960     if (n == (int) strlen(str)) {   /* matched */
3961         ;
3962     } else {
3963         n = 0;
3964         sscanf(str, "%29[A-Z]%n", func, &n);
3965         if (n == (int) strlen(str)) {   /* matched */
3966             param = DNAN;
3967         } else {
3968             rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
3969                           gdes->vname);
3970             return -1;
3971         }
3972     }
3973     if (!strcmp("PERCENT", func))
3974         gdes->vf.op = VDEF_PERCENT;
3975     else if (!strcmp("MAXIMUM", func))
3976         gdes->vf.op = VDEF_MAXIMUM;
3977     else if (!strcmp("AVERAGE", func))
3978         gdes->vf.op = VDEF_AVERAGE;
3979     else if (!strcmp("MINIMUM", func))
3980         gdes->vf.op = VDEF_MINIMUM;
3981     else if (!strcmp("TOTAL", func))
3982         gdes->vf.op = VDEF_TOTAL;
3983     else if (!strcmp("FIRST", func))
3984         gdes->vf.op = VDEF_FIRST;
3985     else if (!strcmp("LAST", func))
3986         gdes->vf.op = VDEF_LAST;
3987     else if (!strcmp("LSLSLOPE", func))
3988         gdes->vf.op = VDEF_LSLSLOPE;
3989     else if (!strcmp("LSLINT", func))
3990         gdes->vf.op = VDEF_LSLINT;
3991     else if (!strcmp("LSLCORREL", func))
3992         gdes->vf.op = VDEF_LSLCORREL;
3993     else {
3994         rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
3995                       gdes->vname);
3996         return -1;
3997     };
3998
3999     switch (gdes->vf.op) {
4000     case VDEF_PERCENT:
4001         if (isnan(param)) { /* no parameter given */
4002             rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4003                           func, gdes->vname);
4004             return -1;
4005         };
4006         if (param >= 0.0 && param <= 100.0) {
4007             gdes->vf.param = param;
4008             gdes->vf.val = DNAN;    /* undefined */
4009             gdes->vf.when = 0;  /* undefined */
4010         } else {
4011             rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4012                           gdes->vname);
4013             return -1;
4014         };
4015         break;
4016     case VDEF_MAXIMUM:
4017     case VDEF_AVERAGE:
4018     case VDEF_MINIMUM:
4019     case VDEF_TOTAL:
4020     case VDEF_FIRST:
4021     case VDEF_LAST:
4022     case VDEF_LSLSLOPE:
4023     case VDEF_LSLINT:
4024     case VDEF_LSLCORREL:
4025         if (isnan(param)) {
4026             gdes->vf.param = DNAN;
4027             gdes->vf.val = DNAN;
4028             gdes->vf.when = 0;
4029         } else {
4030             rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4031                           func, gdes->vname);
4032             return -1;
4033         };
4034         break;
4035     };
4036     return 0;
4037 }
4038
4039
4040 int vdef_calc(
4041     im,
4042     gdi)
4043     image_desc_t *im;
4044     int gdi;
4045 {
4046     graph_desc_t *src, *dst;
4047     rrd_value_t *data;
4048     long      step, steps;
4049
4050     dst = &im->gdes[gdi];
4051     src = &im->gdes[dst->vidx];
4052     data = src->data + src->ds;
4053     steps = (src->end - src->start) / src->step;
4054
4055 #if 0
4056     printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4057            src->end, steps);
4058 #endif
4059
4060     switch (dst->vf.op) {
4061     case VDEF_PERCENT:{
4062         rrd_value_t *array;
4063         int       field;
4064
4065
4066         if ((array = malloc(steps * sizeof(double))) == NULL) {
4067             rrd_set_error("malloc VDEV_PERCENT");
4068             return -1;
4069         }
4070         for (step = 0; step < steps; step++) {
4071             array[step] = data[step * src->ds_cnt];
4072         }
4073         qsort(array, step, sizeof(double), vdef_percent_compar);
4074
4075         field = (steps - 1) * dst->vf.param / 100;
4076         dst->vf.val = array[field];
4077         dst->vf.when = 0;   /* no time component */
4078         free(array);
4079 #if 0
4080         for (step = 0; step < steps; step++)
4081             printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4082                    step == field ? '*' : ' ');
4083 #endif
4084     }
4085         break;
4086     case VDEF_MAXIMUM:
4087         step = 0;
4088         while (step != steps && isnan(data[step * src->ds_cnt]))
4089             step++;
4090         if (step == steps) {
4091             dst->vf.val = DNAN;
4092             dst->vf.when = 0;
4093         } else {
4094             dst->vf.val = data[step * src->ds_cnt];
4095             dst->vf.when = src->start + (step + 1) * src->step;
4096         }
4097         while (step != steps) {
4098             if (finite(data[step * src->ds_cnt])) {
4099                 if (data[step * src->ds_cnt] > dst->vf.val) {
4100                     dst->vf.val = data[step * src->ds_cnt];
4101                     dst->vf.when = src->start + (step + 1) * src->step;
4102                 }
4103             }
4104             step++;
4105         }
4106         break;
4107     case VDEF_TOTAL:
4108     case VDEF_AVERAGE:{
4109         int       cnt = 0;
4110         double    sum = 0.0;
4111
4112         for (step = 0; step < steps; step++) {
4113             if (finite(data[step * src->ds_cnt])) {
4114                 sum += data[step * src->ds_cnt];
4115                 cnt++;
4116             };
4117         }
4118         if (cnt) {
4119             if (dst->vf.op == VDEF_TOTAL) {
4120                 dst->vf.val = sum * src->step;
4121                 dst->vf.when = 0;   /* no time component */
4122             } else {
4123                 dst->vf.val = sum / cnt;
4124                 dst->vf.when = 0;   /* no time component */
4125             };
4126         } else {
4127             dst->vf.val = DNAN;
4128             dst->vf.when = 0;
4129         }
4130     }
4131         break;
4132     case VDEF_MINIMUM:
4133         step = 0;
4134         while (step != steps && isnan(data[step * src->ds_cnt]))
4135             step++;
4136         if (step == steps) {
4137             dst->vf.val = DNAN;
4138             dst->vf.when = 0;
4139         } else {
4140             dst->vf.val = data[step * src->ds_cnt];
4141             dst->vf.when = src->start + (step + 1) * src->step;
4142         }
4143         while (step != steps) {
4144             if (finite(data[step * src->ds_cnt])) {
4145                 if (data[step * src->ds_cnt] < dst->vf.val) {
4146                     dst->vf.val = data[step * src->ds_cnt];
4147                     dst->vf.when = src->start + (step + 1) * src->step;
4148                 }
4149             }
4150             step++;
4151         }
4152         break;
4153     case VDEF_FIRST:
4154         /* The time value returned here is one step before the
4155          * actual time value.  This is the start of the first
4156          * non-NaN interval.
4157          */
4158         step = 0;
4159         while (step != steps && isnan(data[step * src->ds_cnt]))
4160             step++;
4161         if (step == steps) {    /* all entries were NaN */
4162             dst->vf.val = DNAN;
4163             dst->vf.when = 0;
4164         } else {
4165             dst->vf.val = data[step * src->ds_cnt];
4166             dst->vf.when = src->start + step * src->step;
4167         }
4168         break;
4169     case VDEF_LAST:
4170         /* The time value returned here is the
4171          * actual time value.  This is the end of the last
4172          * non-NaN interval.
4173          */
4174         step = steps - 1;
4175         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4176             step--;
4177         if (step < 0) { /* 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 + 1) * src->step;
4183         }
4184         break;
4185     case VDEF_LSLSLOPE:
4186     case VDEF_LSLINT:
4187     case VDEF_LSLCORREL:{
4188         /* Bestfit line by linear least squares method */
4189
4190         int       cnt = 0;
4191         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4192
4193         SUMx = 0;
4194         SUMy = 0;
4195         SUMxy = 0;
4196         SUMxx = 0;
4197         SUMyy = 0;
4198
4199         for (step = 0; step < steps; step++) {
4200             if (finite(data[step * src->ds_cnt])) {
4201                 cnt++;
4202                 SUMx += step;
4203                 SUMxx += step * step;
4204                 SUMxy += step * data[step * src->ds_cnt];
4205                 SUMy += data[step * src->ds_cnt];
4206                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4207             };
4208         }
4209
4210         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4211         y_intercept = (SUMy - slope * SUMx) / cnt;
4212         correl =
4213             (SUMxy -
4214              (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4215                                           (SUMx * SUMx) / cnt) * (SUMyy -
4216                                                                   (SUMy *
4217                                                                    SUMy) /
4218                                                                   cnt));
4219
4220         if (cnt) {
4221             if (dst->vf.op == VDEF_LSLSLOPE) {
4222                 dst->vf.val = slope;
4223                 dst->vf.when = 0;
4224             } else if (dst->vf.op == VDEF_LSLINT) {
4225                 dst->vf.val = y_intercept;
4226                 dst->vf.when = 0;
4227             } else if (dst->vf.op == VDEF_LSLCORREL) {
4228                 dst->vf.val = correl;
4229                 dst->vf.when = 0;
4230             };
4231
4232         } else {
4233             dst->vf.val = DNAN;
4234             dst->vf.when = 0;
4235         }
4236     }
4237         break;
4238     }
4239     return 0;
4240 }
4241
4242 /* NaN < -INF < finite_values < INF */
4243 int vdef_percent_compar(
4244     a,
4245     b)
4246     const void *a, *b;
4247 {
4248     /* Equality is not returned; this doesn't hurt except
4249      * (maybe) for a little performance.
4250      */
4251
4252     /* First catch NaN values. They are smallest */
4253     if (isnan(*(double *) a))
4254         return -1;
4255     if (isnan(*(double *) b))
4256         return 1;
4257
4258     /* NaN doesn't reach this part so INF and -INF are extremes.
4259      * The sign from isinf() is compatible with the sign we return
4260      */
4261     if (isinf(*(double *) a))
4262         return isinf(*(double *) a);
4263     if (isinf(*(double *) b))
4264         return isinf(*(double *) b);
4265
4266     /* If we reach this, both values must be finite */
4267     if (*(double *) a < *(double *) b)
4268         return -1;
4269     else
4270         return 1;
4271 }