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