Merge branch 'master' into ff/rrdd
[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         info.u_cnt = im->ximg;
2965         grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2966         info.u_cnt = im->yimg;
2967         grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2968         return 0;
2969     }
2970     /* pull the data from the rrd files ... */
2971     if (data_fetch(im) == -1)
2972         return -1;
2973     /* evaluate VDEF and CDEF operations ... */
2974     if (data_calc(im) == -1)
2975         return -1;
2976     /* calculate and PRINT and GPRINT definitions. We have to do it at
2977      * this point because it will affect the length of the legends
2978      * if there are no graph elements (i==0) we stop here ... 
2979      * if we are lazy, try to quit ... 
2980      */
2981     i = print_calc(im); 
2982     if (i < 0)
2983         return -1;
2984
2985     if ((i == 0) || lazy)
2986         return 0;
2987
2988 /**************************************************************
2989  *** Calculating sizes and locations became a bit confusing ***
2990  *** so I moved this into a separate function.              ***
2991  **************************************************************/
2992     if (graph_size_location(im, i) == -1)
2993         return -1;
2994
2995     info.u_cnt = im->xorigin;
2996     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2997     info.u_cnt = im->yorigin - im->ysize;
2998     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2999     info.u_cnt = im->xsize;
3000     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3001     info.u_cnt = im->ysize;
3002     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3003     info.u_cnt = im->ximg;
3004     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3005     info.u_cnt = im->yimg;
3006     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3007
3008     /* get actual drawing data and find min and max values */
3009     if (data_proc(im) == -1)
3010         return -1;
3011     if (!im->logarithmic) {
3012         si_unit(im);
3013     }
3014
3015     /* identify si magnitude Kilo, Mega Giga ? */
3016     if (!im->rigid && !im->logarithmic)
3017         expand_range(im);   /* make sure the upper and lower limit are
3018                                sensible values */
3019
3020     info.u_val = im->minval;
3021     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3022     info.u_val = im->maxval;
3023     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3024
3025     if (!calc_horizontal_grid(im))
3026         return -1;
3027     /* reset precalc */
3028     ytr(im, DNAN);
3029 /*   if (im->gridfit)
3030      apply_gridfit(im); */
3031     /* the actual graph is created by going through the individual
3032        graph elements and then drawing them */
3033     cairo_surface_destroy(im->surface);
3034     switch (im->imgformat) {
3035     case IF_PNG:
3036         im->surface =
3037             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3038                                        im->ximg * im->zoom,
3039                                        im->yimg * im->zoom);
3040         break;
3041     case IF_PDF:
3042         im->gridfit = 0;
3043         im->surface = strlen(im->graphfile)
3044             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3045                                        im->yimg * im->zoom)
3046             : cairo_pdf_surface_create_for_stream
3047             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3048         break;
3049     case IF_EPS:
3050         im->gridfit = 0;
3051         im->surface = strlen(im->graphfile)
3052             ?
3053             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3054                                     im->yimg * im->zoom)
3055             : cairo_ps_surface_create_for_stream
3056             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3057         break;
3058     case IF_SVG:
3059         im->gridfit = 0;
3060         im->surface = strlen(im->graphfile)
3061             ?
3062             cairo_svg_surface_create(im->
3063                                      graphfile,
3064                                      im->ximg * im->zoom, im->yimg * im->zoom)
3065             : cairo_svg_surface_create_for_stream
3066             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3067         cairo_svg_surface_restrict_to_version
3068             (im->surface, CAIRO_SVG_VERSION_1_1);
3069         break;
3070     };
3071     im->cr = cairo_create(im->surface);
3072     cairo_set_antialias(im->cr, im->graph_antialias);
3073     cairo_scale(im->cr, im->zoom, im->zoom);
3074     pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3075     gfx_new_area(im, 0, 0, 0, im->yimg,
3076                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3077     gfx_add_point(im, im->ximg, 0);
3078     gfx_close_path(im);
3079     gfx_new_area(im, im->xorigin,
3080                  im->yorigin,
3081                  im->xorigin +
3082                  im->xsize, im->yorigin,
3083                  im->xorigin +
3084                  im->xsize,
3085                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3086     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3087     gfx_close_path(im);
3088     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3089                     im->xsize, im->ysize + 2.0);
3090     cairo_clip(im->cr);
3091     if (im->minval > 0.0)
3092         areazero = im->minval;
3093     if (im->maxval < 0.0)
3094         areazero = im->maxval;
3095     for (i = 0; i < im->gdes_c; i++) {
3096         switch (im->gdes[i].gf) {
3097         case GF_CDEF:
3098         case GF_VDEF:
3099         case GF_DEF:
3100         case GF_PRINT:
3101         case GF_GPRINT:
3102         case GF_COMMENT:
3103         case GF_TEXTALIGN:
3104         case GF_HRULE:
3105         case GF_VRULE:
3106         case GF_XPORT:
3107         case GF_SHIFT:
3108             break;
3109         case GF_TICK:
3110             for (ii = 0; ii < im->xsize; ii++) {
3111                 if (!isnan(im->gdes[i].p_data[ii])
3112                     && im->gdes[i].p_data[ii] != 0.0) {
3113                     if (im->gdes[i].yrule > 0) {
3114                         gfx_line(im,
3115                                  im->xorigin + ii,
3116                                  im->yorigin,
3117                                  im->xorigin + ii,
3118                                  im->yorigin -
3119                                  im->gdes[i].yrule *
3120                                  im->ysize, 1.0, im->gdes[i].col);
3121                     } else if (im->gdes[i].yrule < 0) {
3122                         gfx_line(im,
3123                                  im->xorigin + ii,
3124                                  im->yorigin - im->ysize,
3125                                  im->xorigin + ii,
3126                                  im->yorigin - (1 -
3127                                                 im->gdes[i].
3128                                                 yrule) *
3129                                  im->ysize, 1.0, im->gdes[i].col);
3130                     }
3131                 }
3132             }
3133             break;
3134         case GF_LINE:
3135         case GF_AREA:
3136             /* fix data points at oo and -oo */
3137             for (ii = 0; ii < im->xsize; ii++) {
3138                 if (isinf(im->gdes[i].p_data[ii])) {
3139                     if (im->gdes[i].p_data[ii] > 0) {
3140                         im->gdes[i].p_data[ii] = im->maxval;
3141                     } else {
3142                         im->gdes[i].p_data[ii] = im->minval;
3143                     }
3144
3145                 }
3146             }           /* for */
3147
3148             /* *******************************************************
3149                a           ___. (a,t) 
3150                |   |    ___
3151                ____|   |   |   |
3152                |       |___|
3153                -------|--t-1--t--------------------------------      
3154
3155                if we know the value at time t was a then 
3156                we draw a square from t-1 to t with the value a.
3157
3158                ********************************************************* */
3159             if (im->gdes[i].col.alpha != 0.0) {
3160                 /* GF_LINE and friend */
3161                 if (im->gdes[i].gf == GF_LINE) {
3162                     double    last_y = 0.0;
3163                     int       draw_on = 0;
3164
3165                     cairo_save(im->cr);
3166                     cairo_new_path(im->cr);
3167                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3168                     if (im->gdes[i].dash) {
3169                         cairo_set_dash(im->cr,
3170                                        im->gdes[i].p_dashes,
3171                                        im->gdes[i].ndash, im->gdes[i].offset);
3172                     }
3173
3174                     for (ii = 1; ii < im->xsize; ii++) {
3175                         if (isnan(im->gdes[i].p_data[ii])
3176                             || (im->slopemode == 1
3177                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3178                             draw_on = 0;
3179                             continue;
3180                         }
3181                         if (draw_on == 0) {
3182                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3183                             if (im->slopemode == 0) {
3184                                 double    x = ii - 1 + im->xorigin;
3185                                 double    y = last_y;
3186
3187                                 gfx_line_fit(im, &x, &y);
3188                                 cairo_move_to(im->cr, x, y);
3189                                 x = ii + im->xorigin;
3190                                 y = last_y;
3191                                 gfx_line_fit(im, &x, &y);
3192                                 cairo_line_to(im->cr, x, y);
3193                             } else {
3194                                 double    x = ii - 1 + im->xorigin;
3195                                 double    y =
3196                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3197                                 gfx_line_fit(im, &x, &y);
3198                                 cairo_move_to(im->cr, x, y);
3199                                 x = ii + im->xorigin;
3200                                 y = last_y;
3201                                 gfx_line_fit(im, &x, &y);
3202                                 cairo_line_to(im->cr, x, y);
3203                             }
3204                             draw_on = 1;
3205                         } else {
3206                             double    x1 = ii + im->xorigin;
3207                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3208
3209                             if (im->slopemode == 0
3210                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3211                                 double    x = ii - 1 + im->xorigin;
3212                                 double    y = y1;
3213
3214                                 gfx_line_fit(im, &x, &y);
3215                                 cairo_line_to(im->cr, x, y);
3216                             };
3217                             last_y = y1;
3218                             gfx_line_fit(im, &x1, &y1);
3219                             cairo_line_to(im->cr, x1, y1);
3220                         };
3221                     }
3222                     cairo_set_source_rgba(im->cr,
3223                                           im->gdes[i].
3224                                           col.red,
3225                                           im->gdes[i].
3226                                           col.green,
3227                                           im->gdes[i].
3228                                           col.blue, im->gdes[i].col.alpha);
3229                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3230                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3231                     cairo_stroke(im->cr);
3232                     cairo_restore(im->cr);
3233                 } else {
3234                     int       idxI = -1;
3235                     double   *foreY =
3236                         (double *) malloc(sizeof(double) * im->xsize * 2);
3237                     double   *foreX =
3238                         (double *) malloc(sizeof(double) * im->xsize * 2);
3239                     double   *backY =
3240                         (double *) malloc(sizeof(double) * im->xsize * 2);
3241                     double   *backX =
3242                         (double *) malloc(sizeof(double) * im->xsize * 2);
3243                     int       drawem = 0;
3244
3245                     for (ii = 0; ii <= im->xsize; ii++) {
3246                         double    ybase, ytop;
3247
3248                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3249                             int       cntI = 1;
3250                             int       lastI = 0;
3251
3252                             while (cntI < idxI
3253                                    &&
3254                                    AlmostEqual2sComplement(foreY
3255                                                            [lastI],
3256                                                            foreY[cntI], 4)
3257                                    &&
3258                                    AlmostEqual2sComplement(foreY
3259                                                            [lastI],
3260                                                            foreY
3261                                                            [cntI + 1], 4)) {
3262                                 cntI++;
3263                             }
3264                             gfx_new_area(im,
3265                                          backX[0], backY[0],
3266                                          foreX[0], foreY[0],
3267                                          foreX[cntI],
3268                                          foreY[cntI], im->gdes[i].col);
3269                             while (cntI < idxI) {
3270                                 lastI = cntI;
3271                                 cntI++;
3272                                 while (cntI < idxI
3273                                        &&
3274                                        AlmostEqual2sComplement(foreY
3275                                                                [lastI],
3276                                                                foreY[cntI], 4)
3277                                        &&
3278                                        AlmostEqual2sComplement(foreY
3279                                                                [lastI],
3280                                                                foreY
3281                                                                [cntI
3282                                                                 + 1], 4)) {
3283                                     cntI++;
3284                                 }
3285                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3286                             }
3287                             gfx_add_point(im, backX[idxI], backY[idxI]);
3288                             while (idxI > 1) {
3289                                 lastI = idxI;
3290                                 idxI--;
3291                                 while (idxI > 1
3292                                        &&
3293                                        AlmostEqual2sComplement(backY
3294                                                                [lastI],
3295                                                                backY[idxI], 4)
3296                                        &&
3297                                        AlmostEqual2sComplement(backY
3298                                                                [lastI],
3299                                                                backY
3300                                                                [idxI
3301                                                                 - 1], 4)) {
3302                                     idxI--;
3303                                 }
3304                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3305                             }
3306                             idxI = -1;
3307                             drawem = 0;
3308                             gfx_close_path(im);
3309                         }
3310                         if (drawem != 0) {
3311                             drawem = 0;
3312                             idxI = -1;
3313                         }
3314                         if (ii == im->xsize)
3315                             break;
3316                         if (im->slopemode == 0 && ii == 0) {
3317                             continue;
3318                         }
3319                         if (isnan(im->gdes[i].p_data[ii])) {
3320                             drawem = 1;
3321                             continue;
3322                         }
3323                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3324                         if (lastgdes && im->gdes[i].stack) {
3325                             ybase = ytr(im, lastgdes->p_data[ii]);
3326                         } else {
3327                             ybase = ytr(im, areazero);
3328                         }
3329                         if (ybase == ytop) {
3330                             drawem = 1;
3331                             continue;
3332                         }
3333
3334                         if (ybase > ytop) {
3335                             double    extra = ytop;
3336
3337                             ytop = ybase;
3338                             ybase = extra;
3339                         }
3340                         if (im->slopemode == 0) {
3341                             backY[++idxI] = ybase - 0.2;
3342                             backX[idxI] = ii + im->xorigin - 1;
3343                             foreY[idxI] = ytop + 0.2;
3344                             foreX[idxI] = ii + im->xorigin - 1;
3345                         }
3346                         backY[++idxI] = ybase - 0.2;
3347                         backX[idxI] = ii + im->xorigin;
3348                         foreY[idxI] = ytop + 0.2;
3349                         foreX[idxI] = ii + im->xorigin;
3350                     }
3351                     /* close up any remaining area */
3352                     free(foreY);
3353                     free(foreX);
3354                     free(backY);
3355                     free(backX);
3356                 }       /* else GF_LINE */
3357             }
3358             /* if color != 0x0 */
3359             /* make sure we do not run into trouble when stacking on NaN */
3360             for (ii = 0; ii < im->xsize; ii++) {
3361                 if (isnan(im->gdes[i].p_data[ii])) {
3362                     if (lastgdes && (im->gdes[i].stack)) {
3363                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3364                     } else {
3365                         im->gdes[i].p_data[ii] = areazero;
3366                     }
3367                 }
3368             }
3369             lastgdes = &(im->gdes[i]);
3370             break;
3371         case GF_STACK:
3372             rrd_set_error
3373                 ("STACK should already be turned into LINE or AREA here");
3374             return -1;
3375             break;
3376         }               /* switch */
3377     }
3378     cairo_reset_clip(im->cr);
3379
3380     /* grid_paint also does the text */
3381     if (!(im->extra_flags & ONLY_GRAPH))
3382         grid_paint(im);
3383     if (!(im->extra_flags & ONLY_GRAPH))
3384         axis_paint(im);
3385     /* the RULES are the last thing to paint ... */
3386     for (i = 0; i < im->gdes_c; i++) {
3387
3388         switch (im->gdes[i].gf) {
3389         case GF_HRULE:
3390             if (im->gdes[i].yrule >= im->minval
3391                 && im->gdes[i].yrule <= im->maxval) {
3392                 cairo_save(im->cr);
3393                 if (im->gdes[i].dash) {
3394                     cairo_set_dash(im->cr,
3395                                    im->gdes[i].p_dashes,
3396                                    im->gdes[i].ndash, im->gdes[i].offset);
3397                 }
3398                 gfx_line(im, im->xorigin,
3399                          ytr(im, im->gdes[i].yrule),
3400                          im->xorigin + im->xsize,
3401                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3402                 cairo_stroke(im->cr);
3403                 cairo_restore(im->cr);
3404             }
3405             break;
3406         case GF_VRULE:
3407             if (im->gdes[i].xrule >= im->start
3408                 && im->gdes[i].xrule <= im->end) {
3409                 cairo_save(im->cr);
3410                 if (im->gdes[i].dash) {
3411                     cairo_set_dash(im->cr,
3412                                    im->gdes[i].p_dashes,
3413                                    im->gdes[i].ndash, im->gdes[i].offset);
3414                 }
3415                 gfx_line(im,
3416                          xtr(im, im->gdes[i].xrule),
3417                          im->yorigin, xtr(im,
3418                                           im->
3419                                           gdes[i].
3420                                           xrule),
3421                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3422                 cairo_stroke(im->cr);
3423                 cairo_restore(im->cr);
3424             }
3425             break;
3426         default:
3427             break;
3428         }
3429     }
3430
3431
3432     switch (im->imgformat) {
3433     case IF_PNG:
3434     {
3435         cairo_status_t status;
3436
3437         status = strlen(im->graphfile) ?
3438             cairo_surface_write_to_png(im->surface, im->graphfile)
3439             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3440                                                 im);
3441
3442         if (status != CAIRO_STATUS_SUCCESS) {
3443             rrd_set_error("Could not save png to '%s'", im->graphfile);
3444             return 1;
3445         }
3446         break;
3447     }
3448     default:
3449         if (strlen(im->graphfile)) {
3450             cairo_show_page(im->cr);
3451         } else {
3452             cairo_surface_finish(im->surface);
3453         }
3454         break;
3455     }
3456
3457     return 0;
3458 }
3459
3460
3461 /*****************************************************
3462  * graph stuff 
3463  *****************************************************/
3464
3465 int gdes_alloc(
3466     image_desc_t *im)
3467 {
3468
3469     im->gdes_c++;
3470     if ((im->gdes = (graph_desc_t *)
3471          rrd_realloc(im->gdes, (im->gdes_c)
3472                      * sizeof(graph_desc_t))) == NULL) {
3473         rrd_set_error("realloc graph_descs");
3474         return -1;
3475     }
3476
3477
3478     im->gdes[im->gdes_c - 1].step = im->step;
3479     im->gdes[im->gdes_c - 1].step_orig = im->step;
3480     im->gdes[im->gdes_c - 1].stack = 0;
3481     im->gdes[im->gdes_c - 1].linewidth = 0;
3482     im->gdes[im->gdes_c - 1].debug = 0;
3483     im->gdes[im->gdes_c - 1].start = im->start;
3484     im->gdes[im->gdes_c - 1].start_orig = im->start;
3485     im->gdes[im->gdes_c - 1].end = im->end;
3486     im->gdes[im->gdes_c - 1].end_orig = im->end;
3487     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3488     im->gdes[im->gdes_c - 1].data = NULL;
3489     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3490     im->gdes[im->gdes_c - 1].data_first = 0;
3491     im->gdes[im->gdes_c - 1].p_data = NULL;
3492     im->gdes[im->gdes_c - 1].rpnp = NULL;
3493     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3494     im->gdes[im->gdes_c - 1].shift = 0.0;
3495     im->gdes[im->gdes_c - 1].dash = 0;
3496     im->gdes[im->gdes_c - 1].ndash = 0;
3497     im->gdes[im->gdes_c - 1].offset = 0;
3498     im->gdes[im->gdes_c - 1].col.red = 0.0;
3499     im->gdes[im->gdes_c - 1].col.green = 0.0;
3500     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3501     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3502     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3503     im->gdes[im->gdes_c - 1].format[0] = '\0';
3504     im->gdes[im->gdes_c - 1].strftm = 0;
3505     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3506     im->gdes[im->gdes_c - 1].ds = -1;
3507     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3508     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3509     im->gdes[im->gdes_c - 1].yrule = DNAN;
3510     im->gdes[im->gdes_c - 1].xrule = 0;
3511     return 0;
3512 }
3513
3514 /* copies input untill the first unescaped colon is found
3515    or until input ends. backslashes have to be escaped as well */
3516 int scan_for_col(
3517     const char *const input,
3518     int len,
3519     char *const output)
3520 {
3521     int       inp, outp = 0;
3522
3523     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3524         if (input[inp] == '\\'
3525             && input[inp + 1] != '\0'
3526             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3527             output[outp++] = input[++inp];
3528         } else {
3529             output[outp++] = input[inp];
3530         }
3531     }
3532     output[outp] = '\0';
3533     return inp;
3534 }
3535
3536 /* Now just a wrapper around rrd_graph_v */
3537 int rrd_graph(
3538     int argc,
3539     char **argv,
3540     char ***prdata,
3541     int *xsize,
3542     int *ysize,
3543     FILE * stream,
3544     double *ymin,
3545     double *ymax)
3546 {
3547     int       prlines = 0;
3548     rrd_info_t *grinfo = NULL;
3549     rrd_info_t *walker;
3550
3551     grinfo = rrd_graph_v(argc, argv);
3552     if (grinfo == NULL)
3553         return -1;
3554     walker = grinfo;
3555     (*prdata) = NULL;
3556     while (walker) {
3557         if (strcmp(walker->key, "image_info") == 0) {
3558             prlines++;
3559             if (((*prdata) =
3560                  rrd_realloc((*prdata),
3561                              (prlines + 1) * sizeof(char *))) == NULL) {
3562                 rrd_set_error("realloc prdata");
3563                 return 0;
3564             }
3565             /* imginfo goes to position 0 in the prdata array */
3566             (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3567                                              + 2) * sizeof(char));
3568             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3569             (*prdata)[prlines] = NULL;
3570         }
3571         /* skip anything else */
3572         walker = walker->next;
3573     }
3574     walker = grinfo;
3575     *xsize = 0;
3576     *ysize = 0;
3577     *ymin = 0;
3578     *ymax = 0;
3579     while (walker) {        
3580         if (strcmp(walker->key, "image_width") == 0) {
3581             *xsize = walker->value.u_int;
3582         } else if (strcmp(walker->key, "image_height") == 0) {
3583             *ysize = walker->value.u_int;
3584         } else if (strcmp(walker->key, "value_min") == 0) {
3585             *ymin = walker->value.u_val;
3586         } else if (strcmp(walker->key, "value_max") == 0) {
3587             *ymax = walker->value.u_val;
3588         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3589             prlines++;
3590             if (((*prdata) =
3591                  rrd_realloc((*prdata),
3592                              (prlines + 1) * sizeof(char *))) == NULL) {
3593                 rrd_set_error("realloc prdata");
3594                 return 0;
3595             }
3596             (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3597                                              + 2) * sizeof(char));
3598             (*prdata)[prlines] = NULL;
3599             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3600         } else if (strcmp(walker->key, "image") == 0) {
3601             fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3602                    (stream ? stream : stdout));
3603         }
3604         /* skip anything else */
3605         walker = walker->next;
3606     }
3607     rrd_info_free(grinfo);
3608     return 0;
3609 }
3610
3611
3612 /* Some surgery done on this function, it became ridiculously big.
3613 ** Things moved:
3614 ** - initializing     now in rrd_graph_init()
3615 ** - options parsing  now in rrd_graph_options()
3616 ** - script parsing   now in rrd_graph_script()
3617 */
3618 rrd_info_t *rrd_graph_v(
3619     int argc,
3620     char **argv)
3621 {
3622     image_desc_t im;
3623     rrd_info_t *grinfo;
3624
3625     rrd_graph_init(&im);
3626     /* a dummy surface so that we can measure text sizes for placements */
3627     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3628     im.cr = cairo_create(im.surface);
3629     rrd_graph_options(argc, argv, &im);
3630     if (rrd_test_error()) {
3631         rrd_info_free(im.grinfo);
3632         im_free(&im);
3633         return NULL;
3634     }
3635
3636     if (optind >= argc) {
3637         rrd_info_free(im.grinfo);
3638         im_free(&im);
3639         rrd_set_error("missing filename");
3640         return NULL;
3641     }
3642
3643     if (strlen(argv[optind]) >= MAXPATH) {
3644         rrd_set_error("filename (including path) too long");
3645         rrd_info_free(im.grinfo);
3646         im_free(&im);
3647         return NULL;
3648     }
3649
3650     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3651     im.graphfile[MAXPATH - 1] = '\0';
3652
3653     if (strcmp(im.graphfile, "-") == 0) {
3654         im.graphfile[0] = '\0';
3655     }
3656
3657     rrd_graph_script(argc, argv, &im, 1);
3658     if (rrd_test_error()) {
3659         rrd_info_free(im.grinfo);
3660         im_free(&im);
3661         return NULL;
3662     }
3663
3664     /* Everything is now read and the actual work can start */
3665
3666     if (graph_paint(&im) == -1) {
3667         rrd_info_free(im.grinfo);
3668         im_free(&im);
3669         return NULL;
3670     }
3671
3672
3673     /* The image is generated and needs to be output.
3674      ** Also, if needed, print a line with information about the image.
3675      */
3676
3677     if (im.imginfo) {
3678         rrd_infoval_t info;
3679
3680         info.u_str =
3681             sprintf_alloc(im.imginfo,
3682                           im.graphfile,
3683                           (long) (im.zoom *
3684                                   im.ximg), (long) (im.zoom * im.yimg));
3685         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3686         free(info.u_str);
3687     }
3688     if (im.rendered_image) {
3689         rrd_infoval_t img;
3690
3691         img.u_blo.size = im.rendered_image_size;
3692         img.u_blo.ptr = im.rendered_image;
3693         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3694     }
3695     grinfo = im.grinfo;
3696     im_free(&im);
3697     return grinfo;
3698 }
3699
3700 void rrd_graph_init(
3701     image_desc_t
3702     *im)
3703 {
3704     unsigned int i;
3705
3706 #ifdef HAVE_TZSET
3707     tzset();
3708 #endif
3709 #ifdef HAVE_SETLOCALE
3710     setlocale(LC_TIME, "");
3711 #ifdef HAVE_MBSTOWCS
3712     setlocale(LC_CTYPE, "");
3713 #endif
3714 #endif
3715     im->base = 1000;
3716     im->cr = NULL;
3717     im->draw_x_grid = 1;
3718     im->draw_y_grid = 1;
3719     im->extra_flags = 0;
3720     im->font_options = cairo_font_options_create();
3721     im->forceleftspace = 0;
3722     im->gdes_c = 0;
3723     im->gdes = NULL;
3724     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3725     im->grid_dash_off = 1;
3726     im->grid_dash_on = 1;
3727     im->gridfit = 1;
3728     im->grinfo = (rrd_info_t *) NULL;
3729     im->grinfo_current = (rrd_info_t *) NULL;
3730     im->imgformat = IF_PNG;
3731     im->imginfo = NULL;
3732     im->use_rrdcached = 0;
3733     im->lazy = 0;
3734     im->logarithmic = 0;
3735     im->maxval = DNAN;
3736     im->minval = 0;
3737     im->minval = DNAN;
3738     im->prt_c = 0;
3739     im->rigid = 0;
3740     im->rendered_image_size = 0;
3741     im->rendered_image = NULL;
3742     im->slopemode = 0;
3743     im->step = 0;
3744     im->surface = NULL;
3745     im->symbol = ' ';
3746     im->tabwidth = 40.0;
3747     im->title[0] = '\0';
3748     im->unitsexponent = 9999;
3749     im->unitslength = 6;
3750     im->viewfactor = 1.0;
3751     im->watermark[0] = '\0';
3752     im->with_markup = 0;
3753     im->ximg = 0;
3754     im->xlab_user.minsec = -1;
3755     im->xorigin = 0;
3756     im->xsize = 400;
3757     im->ygridstep = DNAN;
3758     im->yimg = 0;
3759     im->ylegend[0] = '\0';
3760     im->yorigin = 0;
3761     im->ysize = 100;
3762     im->zoom = 1;
3763     cairo_font_options_set_hint_style
3764         (im->font_options, CAIRO_HINT_STYLE_FULL);
3765     cairo_font_options_set_hint_metrics
3766         (im->font_options, CAIRO_HINT_METRICS_ON);
3767     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3768     for (i = 0; i < DIM(graph_col); i++)
3769         im->graph_col[i] = graph_col[i];
3770     {
3771         char     *deffont;
3772
3773         deffont = getenv("RRD_DEFAULT_FONT");
3774         if (deffont != NULL) {
3775             for (i = 0; i < DIM(text_prop); i++) {
3776                 strncpy(text_prop[i].font, deffont,
3777                         sizeof(text_prop[i].font) - 1);
3778                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3779             }
3780         }
3781     }
3782     for (i = 0; i < DIM(text_prop); i++) {
3783         im->text_prop[i].size = text_prop[i].size;
3784         strcpy(im->text_prop[i].font, text_prop[i].font);
3785     }
3786 }
3787
3788 void rrd_graph_options(
3789     int argc,
3790     char *argv[],
3791     image_desc_t
3792     *im)
3793 {
3794     int       stroff;
3795     char     *parsetime_error = NULL;
3796     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3797     time_t    start_tmp = 0, end_tmp = 0;
3798     long      long_tmp;
3799     rrd_time_value_t start_tv, end_tv;
3800     long unsigned int color;
3801     char     *old_locale = "";
3802
3803     /* defines for long options without a short equivalent. should be bytes,
3804        and may not collide with (the ASCII value of) short options */
3805 #define LONGOPT_UNITS_SI 255
3806
3807 /* *INDENT-OFF* */
3808     struct option long_options[] = {
3809         { "start",              required_argument, 0, 's'}, 
3810         { "end",                required_argument, 0, 'e'},
3811         { "x-grid",             required_argument, 0, 'x'},
3812         { "y-grid",             required_argument, 0, 'y'},
3813         { "vertical-label",     required_argument, 0, 'v'},
3814         { "width",              required_argument, 0, 'w'},
3815         { "height",             required_argument, 0, 'h'},
3816         { "full-size-mode",     no_argument,       0, 'D'},
3817         { "interlaced",         no_argument,       0, 'i'},
3818         { "upper-limit",        required_argument, 0, 'u'},
3819         { "lower-limit",        required_argument, 0, 'l'},
3820         { "rigid",              no_argument,       0, 'r'},
3821         { "base",               required_argument, 0, 'b'},
3822         { "logarithmic",        no_argument,       0, 'o'},
3823         { "color",              required_argument, 0, 'c'},
3824         { "font",               required_argument, 0, 'n'},
3825         { "title",              required_argument, 0, 't'},
3826         { "imginfo",            required_argument, 0, 'f'},
3827         { "imgformat",          required_argument, 0, 'a'},
3828         { "lazy",               no_argument,       0, 'z'},
3829         { "zoom",               required_argument, 0, 'm'},
3830         { "no-legend",          no_argument,       0, 'g'},
3831         { "force-rules-legend", no_argument,       0, 'F'},
3832         { "only-graph",         no_argument,       0, 'j'},
3833         { "alt-y-grid",         no_argument,       0, 'Y'},
3834         { "no-minor",           no_argument,       0, 'I'}, 
3835         { "slope-mode",         no_argument,       0, 'E'},
3836         { "alt-autoscale",      no_argument,       0, 'A'},
3837         { "alt-autoscale-min",  no_argument,       0, 'J'},
3838         { "alt-autoscale-max",  no_argument,       0, 'M'},
3839         { "no-gridfit",         no_argument,       0, 'N'},
3840         { "units-exponent",     required_argument, 0, 'X'},
3841         { "units-length",       required_argument, 0, 'L'},
3842         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
3843         { "step",               required_argument, 0, 'S'},
3844         { "tabwidth",           required_argument, 0, 'T'},
3845         { "font-render-mode",   required_argument, 0, 'R'},
3846         { "graph-render-mode",  required_argument, 0, 'G'},
3847         { "font-smoothing-threshold", required_argument, 0, 'B'},
3848         { "watermark",          required_argument, 0, 'W'},
3849         { "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 */
3850         { "pango-markup",       no_argument,       0, 'P'},
3851         { "daemon",             required_argument, 0, 'd'},
3852         {  0, 0, 0, 0}
3853 };
3854 /* *INDENT-ON* */
3855
3856     optind = 0;
3857     opterr = 0;         /* initialize getopt */
3858     rrd_parsetime("end-24h", &start_tv);
3859     rrd_parsetime("now", &end_tv);
3860     while (1) {
3861         int       option_index = 0;
3862         int       opt;
3863         int       col_start, col_end;
3864
3865         opt = getopt_long(argc, argv,
3866                           "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:",
3867                           long_options, &option_index);
3868         if (opt == EOF)
3869             break;
3870         switch (opt) {
3871         case 'I':
3872             im->extra_flags |= NOMINOR;
3873             break;
3874         case 'Y':
3875             im->extra_flags |= ALTYGRID;
3876             break;
3877         case 'A':
3878             im->extra_flags |= ALTAUTOSCALE;
3879             break;
3880         case 'J':
3881             im->extra_flags |= ALTAUTOSCALE_MIN;
3882             break;
3883         case 'M':
3884             im->extra_flags |= ALTAUTOSCALE_MAX;
3885             break;
3886         case 'j':
3887             im->extra_flags |= ONLY_GRAPH;
3888             break;
3889         case 'g':
3890             im->extra_flags |= NOLEGEND;
3891             break;
3892         case 'F':
3893             im->extra_flags |= FORCE_RULES_LEGEND;
3894             break;
3895         case LONGOPT_UNITS_SI:
3896             if (im->extra_flags & FORCE_UNITS) {
3897                 rrd_set_error("--units can only be used once!");
3898                 setlocale(LC_NUMERIC, old_locale);
3899                 return;
3900             }
3901             if (strcmp(optarg, "si") == 0)
3902                 im->extra_flags |= FORCE_UNITS_SI;
3903             else {
3904                 rrd_set_error("invalid argument for --units: %s", optarg);
3905                 return;
3906             }
3907             break;
3908         case 'X':
3909             im->unitsexponent = atoi(optarg);
3910             break;
3911         case 'L':
3912             im->unitslength = atoi(optarg);
3913             im->forceleftspace = 1;
3914             break;
3915         case 'T':
3916             old_locale = setlocale(LC_NUMERIC, "C");
3917             im->tabwidth = atof(optarg);
3918             setlocale(LC_NUMERIC, old_locale);
3919             break;
3920         case 'S':
3921             old_locale = setlocale(LC_NUMERIC, "C");
3922             im->step = atoi(optarg);
3923             setlocale(LC_NUMERIC, old_locale);
3924             break;
3925         case 'N':
3926             im->gridfit = 0;
3927             break;
3928         case 'P':
3929             im->with_markup = 1;
3930             break;
3931         case 's':
3932             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3933                 rrd_set_error("start time: %s", parsetime_error);
3934                 return;
3935             }
3936             break;
3937         case 'e':
3938             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3939                 rrd_set_error("end time: %s", parsetime_error);
3940                 return;
3941             }
3942             break;
3943         case 'x':
3944             if (strcmp(optarg, "none") == 0) {
3945                 im->draw_x_grid = 0;
3946                 break;
3947             };
3948             if (sscanf(optarg,
3949                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3950                        scan_gtm,
3951                        &im->xlab_user.gridst,
3952                        scan_mtm,
3953                        &im->xlab_user.mgridst,
3954                        scan_ltm,
3955                        &im->xlab_user.labst,
3956                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3957                 strncpy(im->xlab_form, optarg + stroff,
3958                         sizeof(im->xlab_form) - 1);
3959                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3960                 if ((int)
3961                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3962                     rrd_set_error("unknown keyword %s", scan_gtm);
3963                     return;
3964                 } else if ((int)
3965                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3966                            == -1) {
3967                     rrd_set_error("unknown keyword %s", scan_mtm);
3968                     return;
3969                 } else if ((int)
3970                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
3971                     rrd_set_error("unknown keyword %s", scan_ltm);
3972                     return;
3973                 }
3974                 im->xlab_user.minsec = 1;
3975                 im->xlab_user.stst = im->xlab_form;
3976             } else {
3977                 rrd_set_error("invalid x-grid format");
3978                 return;
3979             }
3980             break;
3981         case 'y':
3982
3983             if (strcmp(optarg, "none") == 0) {
3984                 im->draw_y_grid = 0;
3985                 break;
3986             };
3987             old_locale = setlocale(LC_NUMERIC, "C");
3988             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3989                 setlocale(LC_NUMERIC, old_locale);
3990                 if (im->ygridstep <= 0) {
3991                     rrd_set_error("grid step must be > 0");
3992                     return;
3993                 } else if (im->ylabfact < 1) {
3994                     rrd_set_error("label factor must be > 0");
3995                     return;
3996                 }
3997             } else {
3998                 setlocale(LC_NUMERIC, old_locale);
3999                 rrd_set_error("invalid y-grid format");
4000                 return;
4001             }
4002             break;
4003         case 'v':
4004             strncpy(im->ylegend, optarg, 150);
4005             im->ylegend[150] = '\0';
4006             break;
4007         case 'u':
4008             old_locale = setlocale(LC_NUMERIC, "C");
4009             im->maxval = atof(optarg);
4010             setlocale(LC_NUMERIC, old_locale);
4011             break;
4012         case 'l':
4013             old_locale = setlocale(LC_NUMERIC, "C");
4014             im->minval = atof(optarg);
4015             setlocale(LC_NUMERIC, old_locale);
4016             break;
4017         case 'b':
4018             im->base = atol(optarg);
4019             if (im->base != 1024 && im->base != 1000) {
4020                 rrd_set_error
4021                     ("the only sensible value for base apart from 1000 is 1024");
4022                 return;
4023             }
4024             break;
4025         case 'w':
4026             long_tmp = atol(optarg);
4027             if (long_tmp < 10) {
4028                 rrd_set_error("width below 10 pixels");
4029                 return;
4030             }
4031             im->xsize = long_tmp;
4032             break;
4033         case 'h':
4034             long_tmp = atol(optarg);
4035             if (long_tmp < 10) {
4036                 rrd_set_error("height below 10 pixels");
4037                 return;
4038             }
4039             im->ysize = long_tmp;
4040             break;
4041         case 'D':
4042             im->extra_flags |= FULL_SIZE_MODE;
4043             break;
4044         case 'i':
4045             /* interlaced png not supported at the moment */
4046             break;
4047         case 'r':
4048             im->rigid = 1;
4049             break;
4050         case 'f':
4051             im->imginfo = optarg;
4052             break;
4053         case 'a':
4054             if ((int)
4055                 (im->imgformat = if_conv(optarg)) == -1) {
4056                 rrd_set_error("unsupported graphics format '%s'", optarg);
4057                 return;
4058             }
4059             break;
4060         case 'z':
4061             im->lazy = 1;
4062             break;
4063         case 'E':
4064             im->slopemode = 1;
4065             break;
4066         case 'o':
4067             im->logarithmic = 1;
4068             break;
4069         case 'c':
4070             if (sscanf(optarg,
4071                        "%10[A-Z]#%n%8lx%n",
4072                        col_nam, &col_start, &color, &col_end) == 2) {
4073                 int       ci;
4074                 int       col_len = col_end - col_start;
4075
4076                 switch (col_len) {
4077                 case 3:
4078                     color =
4079                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4080                                                          0x011000) |
4081                          ((color & 0x00F)
4082                           * 0x001100)
4083                          | 0x000000FF);
4084                     break;
4085                 case 4:
4086                     color =
4087                         (((color & 0xF000) *
4088                           0x11000) | ((color & 0x0F00) *
4089                                       0x01100) | ((color &
4090                                                    0x00F0) *
4091                                                   0x00110) |
4092                          ((color & 0x000F) * 0x00011)
4093                         );
4094                     break;
4095                 case 6:
4096                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4097                     break;
4098                 case 8:
4099                     break;
4100                 default:
4101                     rrd_set_error("the color format is #RRGGBB[AA]");
4102                     return;
4103                 }
4104                 if ((ci = grc_conv(col_nam)) != -1) {
4105                     im->graph_col[ci] = gfx_hex_to_col(color);
4106                 } else {
4107                     rrd_set_error("invalid color name '%s'", col_nam);
4108                     return;
4109                 }
4110             } else {
4111                 rrd_set_error("invalid color def format");
4112                 return;
4113             }
4114             break;
4115         case 'n':{
4116             char      prop[15];
4117             double    size = 1;
4118             int       end;
4119
4120             old_locale = setlocale(LC_NUMERIC, "C");
4121             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4122                 int       sindex, propidx;
4123
4124                 setlocale(LC_NUMERIC, old_locale);
4125                 if ((sindex = text_prop_conv(prop)) != -1) {
4126                     for (propidx = sindex;
4127                          propidx < TEXT_PROP_LAST; propidx++) {
4128                         if (size > 0) {
4129                             im->text_prop[propidx].size = size;
4130                         }
4131                         if ((int) strlen(optarg) > end) {
4132                             if (optarg[end] == ':') {
4133                                 strncpy(im->text_prop[propidx].font,
4134                                         optarg + end + 1, 255);
4135                                 im->text_prop[propidx].font[255] = '\0';
4136                             } else {
4137                                 rrd_set_error
4138                                     ("expected : after font size in '%s'",
4139                                      optarg);
4140                                 return;
4141                             }
4142                         }
4143                         /* only run the for loop for DEFAULT (0) for
4144                            all others, we break here. woodo programming */
4145                         if (propidx == sindex && sindex != 0)
4146                             break;
4147                     }
4148                 } else {
4149                     rrd_set_error("invalid fonttag '%s'", prop);
4150                     return;
4151                 }
4152             } else {
4153                 setlocale(LC_NUMERIC, old_locale);
4154                 rrd_set_error("invalid text property format");
4155                 return;
4156             }
4157             break;
4158         }
4159         case 'm':
4160             old_locale = setlocale(LC_NUMERIC, "C");
4161             im->zoom = atof(optarg);
4162             setlocale(LC_NUMERIC, old_locale);
4163             if (im->zoom <= 0.0) {
4164                 rrd_set_error("zoom factor must be > 0");
4165                 return;
4166             }
4167             break;
4168         case 't':
4169             strncpy(im->title, optarg, 150);
4170             im->title[150] = '\0';
4171             break;
4172         case 'R':
4173             if (strcmp(optarg, "normal") == 0) {
4174                 cairo_font_options_set_antialias
4175                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4176                 cairo_font_options_set_hint_style
4177                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4178             } else if (strcmp(optarg, "light") == 0) {
4179                 cairo_font_options_set_antialias
4180                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4181                 cairo_font_options_set_hint_style
4182                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4183             } else if (strcmp(optarg, "mono") == 0) {
4184                 cairo_font_options_set_antialias
4185                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4186                 cairo_font_options_set_hint_style
4187                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4188             } else {
4189                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4190                 return;
4191             }
4192             break;
4193         case 'G':
4194             if (strcmp(optarg, "normal") == 0)
4195                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4196             else if (strcmp(optarg, "mono") == 0)
4197                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4198             else {
4199                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4200                 return;
4201             }
4202             break;
4203         case 'B':
4204             /* not supported curently */
4205             break;
4206         case 'W':
4207             strncpy(im->watermark, optarg, 100);
4208             im->watermark[99] = '\0';
4209             break;
4210         case 'd':
4211         {
4212             int status;
4213             if (im->use_rrdcached)
4214             {
4215                 rrd_set_error ("You cannot specify --daemon "
4216                         "more than once.");
4217                 return;
4218             }
4219             status = rrdc_connect (optarg);
4220             if (status != 0)
4221             {
4222                 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4223                         optarg, status);
4224                 return;
4225             }
4226             im->use_rrdcached = 1;
4227             break;
4228         }
4229         case '?':
4230             if (optopt != 0)
4231                 rrd_set_error("unknown option '%c'", optopt);
4232             else
4233                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4234             return;
4235         }
4236     } /* while (1) */
4237
4238     if (im->use_rrdcached == 0)
4239     {
4240         char *temp;
4241
4242         temp = getenv (ENV_RRDCACHED_ADDRESS);
4243         if (temp != NULL)
4244         {
4245             int status;
4246
4247             status = rrdc_connect (temp);
4248             if (status != 0)
4249             {
4250                 rrd_set_error ("rrdc_connect(%s) failed with status %i.",
4251                         temp, status);
4252                 return;
4253             }
4254             im->use_rrdcached = 1;
4255         }
4256     }
4257
4258     if (im->logarithmic && im->minval <= 0) {
4259         rrd_set_error
4260             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4261         return;
4262     }
4263
4264     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4265         /* error string is set in rrd_parsetime.c */
4266         return;
4267     }
4268
4269     if (start_tmp < 3600 * 24 * 365 * 10) {
4270         rrd_set_error
4271             ("the first entry to fetch should be after 1980 (%ld)",
4272              start_tmp);
4273         return;
4274     }
4275
4276     if (end_tmp < start_tmp) {
4277         rrd_set_error
4278             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4279         return;
4280     }
4281
4282     im->start = start_tmp;
4283     im->end = end_tmp;
4284     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4285 }
4286
4287 int rrd_graph_color(
4288     image_desc_t
4289     *im,
4290     char *var,
4291     char *err,
4292     int optional)
4293 {
4294     char     *color;
4295     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4296
4297     color = strstr(var, "#");
4298     if (color == NULL) {
4299         if (optional == 0) {
4300             rrd_set_error("Found no color in %s", err);
4301             return 0;
4302         }
4303         return 0;
4304     } else {
4305         int       n = 0;
4306         char     *rest;
4307         long unsigned int col;
4308
4309         rest = strstr(color, ":");
4310         if (rest != NULL)
4311             n = rest - color;
4312         else
4313             n = strlen(color);
4314         switch (n) {
4315         case 7:
4316             sscanf(color, "#%6lx%n", &col, &n);
4317             col = (col << 8) + 0xff /* shift left by 8 */ ;
4318             if (n != 7)
4319                 rrd_set_error("Color problem in %s", err);
4320             break;
4321         case 9:
4322             sscanf(color, "#%8lx%n", &col, &n);
4323             if (n == 9)
4324                 break;
4325         default:
4326             rrd_set_error("Color problem in %s", err);
4327         }
4328         if (rrd_test_error())
4329             return 0;
4330         gdp->col = gfx_hex_to_col(col);
4331         return n;
4332     }
4333 }
4334
4335
4336 int bad_format(
4337     char *fmt)
4338 {
4339     char     *ptr;
4340     int       n = 0;
4341
4342     ptr = fmt;
4343     while (*ptr != '\0')
4344         if (*ptr++ == '%') {
4345
4346             /* line cannot end with percent char */
4347             if (*ptr == '\0')
4348                 return 1;
4349             /* '%s', '%S' and '%%' are allowed */
4350             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4351                 ptr++;
4352             /* %c is allowed (but use only with vdef!) */
4353             else if (*ptr == 'c') {
4354                 ptr++;
4355                 n = 1;
4356             }
4357
4358             /* or else '% 6.2lf' and such are allowed */
4359             else {
4360                 /* optional padding character */
4361                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4362                     ptr++;
4363                 /* This should take care of 'm.n' with all three optional */
4364                 while (*ptr >= '0' && *ptr <= '9')
4365                     ptr++;
4366                 if (*ptr == '.')
4367                     ptr++;
4368                 while (*ptr >= '0' && *ptr <= '9')
4369                     ptr++;
4370                 /* Either 'le', 'lf' or 'lg' must follow here */
4371                 if (*ptr++ != 'l')
4372                     return 1;
4373                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4374                     ptr++;
4375                 else
4376                     return 1;
4377                 n++;
4378             }
4379         }
4380
4381     return (n != 1);
4382 }
4383
4384
4385 int vdef_parse(
4386     struct graph_desc_t
4387     *gdes,
4388     const char *const str)
4389 {
4390     /* A VDEF currently is either "func" or "param,func"
4391      * so the parsing is rather simple.  Change if needed.
4392      */
4393     double    param;
4394     char      func[30];
4395     int       n;
4396     char     *old_locale;
4397
4398     n = 0;
4399     old_locale = setlocale(LC_NUMERIC, "C");
4400     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4401     setlocale(LC_NUMERIC, old_locale);
4402     if (n == (int) strlen(str)) {   /* matched */
4403         ;
4404     } else {
4405         n = 0;
4406         sscanf(str, "%29[A-Z]%n", func, &n);
4407         if (n == (int) strlen(str)) {   /* matched */
4408             param = DNAN;
4409         } else {
4410             rrd_set_error
4411                 ("Unknown function string '%s' in VDEF '%s'",
4412                  str, gdes->vname);
4413             return -1;
4414         }
4415     }
4416     if (!strcmp("PERCENT", func))
4417         gdes->vf.op = VDEF_PERCENT;
4418     else if (!strcmp("MAXIMUM", func))
4419         gdes->vf.op = VDEF_MAXIMUM;
4420     else if (!strcmp("AVERAGE", func))
4421         gdes->vf.op = VDEF_AVERAGE;
4422     else if (!strcmp("STDEV", func))
4423         gdes->vf.op = VDEF_STDEV;
4424     else if (!strcmp("MINIMUM", func))
4425         gdes->vf.op = VDEF_MINIMUM;
4426     else if (!strcmp("TOTAL", func))
4427         gdes->vf.op = VDEF_TOTAL;
4428     else if (!strcmp("FIRST", func))
4429         gdes->vf.op = VDEF_FIRST;
4430     else if (!strcmp("LAST", func))
4431         gdes->vf.op = VDEF_LAST;
4432     else if (!strcmp("LSLSLOPE", func))
4433         gdes->vf.op = VDEF_LSLSLOPE;
4434     else if (!strcmp("LSLINT", func))
4435         gdes->vf.op = VDEF_LSLINT;
4436     else if (!strcmp("LSLCORREL", func))
4437         gdes->vf.op = VDEF_LSLCORREL;
4438     else {
4439         rrd_set_error
4440             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4441         return -1;
4442     };
4443     switch (gdes->vf.op) {
4444     case VDEF_PERCENT:
4445         if (isnan(param)) { /* no parameter given */
4446             rrd_set_error
4447                 ("Function '%s' needs parameter in VDEF '%s'\n",
4448                  func, gdes->vname);
4449             return -1;
4450         };
4451         if (param >= 0.0 && param <= 100.0) {
4452             gdes->vf.param = param;
4453             gdes->vf.val = DNAN;    /* undefined */
4454             gdes->vf.when = 0;  /* undefined */
4455         } else {
4456             rrd_set_error
4457                 ("Parameter '%f' out of range in VDEF '%s'\n",
4458                  param, gdes->vname);
4459             return -1;
4460         };
4461         break;
4462     case VDEF_MAXIMUM:
4463     case VDEF_AVERAGE:
4464     case VDEF_STDEV:
4465     case VDEF_MINIMUM:
4466     case VDEF_TOTAL:
4467     case VDEF_FIRST:
4468     case VDEF_LAST:
4469     case VDEF_LSLSLOPE:
4470     case VDEF_LSLINT:
4471     case VDEF_LSLCORREL:
4472         if (isnan(param)) {
4473             gdes->vf.param = DNAN;
4474             gdes->vf.val = DNAN;
4475             gdes->vf.when = 0;
4476         } else {
4477             rrd_set_error
4478                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4479                  func, gdes->vname);
4480             return -1;
4481         };
4482         break;
4483     };
4484     return 0;
4485 }
4486
4487
4488 int vdef_calc(
4489     image_desc_t *im,
4490     int gdi)
4491 {
4492     graph_desc_t *src, *dst;
4493     rrd_value_t *data;
4494     long      step, steps;
4495     unsigned long end;
4496
4497     dst = &im->gdes[gdi];
4498     src = &im->gdes[dst->vidx];
4499     data = src->data + src->ds;
4500     end =
4501         src->end_orig % (long)src->step ==
4502         0 ? src->end_orig : (src->end_orig + (long)src->step -
4503                              src->end_orig % (long)src->step);
4504
4505     steps = (end - src->start) / src->step;
4506 #if 0
4507     printf
4508         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4509          src->start, src->end_orig, steps);
4510 #endif
4511     switch (dst->vf.op) {
4512     case VDEF_PERCENT:{
4513         rrd_value_t *array;
4514         int       field;
4515         if ((array = malloc(steps * sizeof(double))) == NULL) {
4516             rrd_set_error("malloc VDEV_PERCENT");
4517             return -1;
4518         }
4519         for (step = 0; step < steps; step++) {
4520             array[step] = data[step * src->ds_cnt];
4521         }
4522         qsort(array, step, sizeof(double), vdef_percent_compar);
4523         field = (steps - 1) * dst->vf.param / 100;
4524         dst->vf.val = array[field];
4525         dst->vf.when = 0;   /* no time component */
4526         free(array);
4527 #if 0
4528         for (step = 0; step < steps; step++)
4529             printf("DEBUG: %3li:%10.2f %c\n",
4530                    step, array[step], step == field ? '*' : ' ');
4531 #endif
4532     }
4533         break;
4534     case VDEF_MAXIMUM:
4535         step = 0;
4536         while (step != steps && isnan(data[step * src->ds_cnt]))
4537             step++;
4538         if (step == steps) {
4539             dst->vf.val = DNAN;
4540             dst->vf.when = 0;
4541         } else {
4542             dst->vf.val = data[step * src->ds_cnt];
4543             dst->vf.when = src->start + (step + 1) * src->step;
4544         }
4545         while (step != steps) {
4546             if (finite(data[step * src->ds_cnt])) {
4547                 if (data[step * src->ds_cnt] > dst->vf.val) {
4548                     dst->vf.val = data[step * src->ds_cnt];
4549                     dst->vf.when = src->start + (step + 1) * src->step;
4550                 }
4551             }
4552             step++;
4553         }
4554         break;
4555     case VDEF_TOTAL:
4556     case VDEF_STDEV:
4557     case VDEF_AVERAGE:{
4558         int       cnt = 0;
4559         double    sum = 0.0;
4560         double    average = 0.0;
4561
4562         for (step = 0; step < steps; step++) {
4563             if (finite(data[step * src->ds_cnt])) {
4564                 sum += data[step * src->ds_cnt];
4565                 cnt++;
4566             };
4567         }
4568         if (cnt) {
4569             if (dst->vf.op == VDEF_TOTAL) {
4570                 dst->vf.val = sum * src->step;
4571                 dst->vf.when = 0;   /* no time component */
4572             } else if (dst->vf.op == VDEF_AVERAGE) {
4573                 dst->vf.val = sum / cnt;
4574                 dst->vf.when = 0;   /* no time component */
4575             } else {
4576                 average = sum / cnt;
4577                 sum = 0.0;
4578                 for (step = 0; step < steps; step++) {
4579                     if (finite(data[step * src->ds_cnt])) {
4580                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
4581                     };
4582                 }
4583                 dst->vf.val = pow(sum / cnt, 0.5);
4584                 dst->vf.when = 0;   /* no time component */
4585             };
4586         } else {
4587             dst->vf.val = DNAN;
4588             dst->vf.when = 0;
4589         }
4590     }
4591         break;
4592     case VDEF_MINIMUM:
4593         step = 0;
4594         while (step != steps && isnan(data[step * src->ds_cnt]))
4595             step++;
4596         if (step == steps) {
4597             dst->vf.val = DNAN;
4598             dst->vf.when = 0;
4599         } else {
4600             dst->vf.val = data[step * src->ds_cnt];
4601             dst->vf.when = src->start + (step + 1) * src->step;
4602         }
4603         while (step != steps) {
4604             if (finite(data[step * src->ds_cnt])) {
4605                 if (data[step * src->ds_cnt] < dst->vf.val) {
4606                     dst->vf.val = data[step * src->ds_cnt];
4607                     dst->vf.when = src->start + (step + 1) * src->step;
4608                 }
4609             }
4610             step++;
4611         }
4612         break;
4613     case VDEF_FIRST:
4614         /* The time value returned here is one step before the
4615          * actual time value.  This is the start of the first
4616          * non-NaN interval.
4617          */
4618         step = 0;
4619         while (step != steps && isnan(data[step * src->ds_cnt]))
4620             step++;
4621         if (step == steps) {    /* all entries were NaN */
4622             dst->vf.val = DNAN;
4623             dst->vf.when = 0;
4624         } else {
4625             dst->vf.val = data[step * src->ds_cnt];
4626             dst->vf.when = src->start + step * src->step;
4627         }
4628         break;
4629     case VDEF_LAST:
4630         /* The time value returned here is the
4631          * actual time value.  This is the end of the last
4632          * non-NaN interval.
4633          */
4634         step = steps - 1;
4635         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4636             step--;
4637         if (step < 0) { /* all entries were NaN */
4638             dst->vf.val = DNAN;
4639             dst->vf.when = 0;
4640         } else {
4641             dst->vf.val = data[step * src->ds_cnt];
4642             dst->vf.when = src->start + (step + 1) * src->step;
4643         }
4644         break;
4645     case VDEF_LSLSLOPE:
4646     case VDEF_LSLINT:
4647     case VDEF_LSLCORREL:{
4648         /* Bestfit line by linear least squares method */
4649
4650         int       cnt = 0;
4651         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4652
4653         SUMx = 0;
4654         SUMy = 0;
4655         SUMxy = 0;
4656         SUMxx = 0;
4657         SUMyy = 0;
4658         for (step = 0; step < steps; step++) {
4659             if (finite(data[step * src->ds_cnt])) {
4660                 cnt++;
4661                 SUMx += step;
4662                 SUMxx += step * step;
4663                 SUMxy += step * data[step * src->ds_cnt];
4664                 SUMy += data[step * src->ds_cnt];
4665                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4666             };
4667         }
4668
4669         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4670         y_intercept = (SUMy - slope * SUMx) / cnt;
4671         correl =
4672             (SUMxy -
4673              (SUMx * SUMy) / cnt) /
4674             sqrt((SUMxx -
4675                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4676         if (cnt) {
4677             if (dst->vf.op == VDEF_LSLSLOPE) {
4678                 dst->vf.val = slope;
4679                 dst->vf.when = 0;
4680             } else if (dst->vf.op == VDEF_LSLINT) {
4681                 dst->vf.val = y_intercept;
4682                 dst->vf.when = 0;
4683             } else if (dst->vf.op == VDEF_LSLCORREL) {
4684                 dst->vf.val = correl;
4685                 dst->vf.when = 0;
4686             };
4687         } else {
4688             dst->vf.val = DNAN;
4689             dst->vf.when = 0;
4690         }
4691     }
4692         break;
4693     }
4694     return 0;
4695 }
4696
4697 /* NaN < -INF < finite_values < INF */
4698 int vdef_percent_compar(
4699     const void
4700     *a,
4701     const void
4702     *b)
4703 {
4704     /* Equality is not returned; this doesn't hurt except
4705      * (maybe) for a little performance.
4706      */
4707
4708     /* First catch NaN values. They are smallest */
4709     if (isnan(*(double *) a))
4710         return -1;
4711     if (isnan(*(double *) b))
4712         return 1;
4713     /* NaN doesn't reach this part so INF and -INF are extremes.
4714      * The sign from isinf() is compatible with the sign we return
4715      */
4716     if (isinf(*(double *) a))
4717         return isinf(*(double *) a);
4718     if (isinf(*(double *) b))
4719         return isinf(*(double *) b);
4720     /* If we reach this, both values must be finite */
4721     if (*(double *) a < *(double *) b)
4722         return -1;
4723     else
4724         return 1;
4725 }
4726
4727 void grinfo_push(
4728     image_desc_t *im,
4729     char *key,
4730     rrd_info_type_t type,
4731     rrd_infoval_t value)
4732 {
4733     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4734     if (im->grinfo == NULL) {
4735         im->grinfo = im->grinfo_current;
4736     }
4737 }