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