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