Code

prepare for the release of rrdtool-1.3.2
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.3.2  Copyright by Tobi Oetiker, 1997-2008
3  ****************************************************************************
4  * rrd__graph.c  produce graphs from data in rrdfiles
5  ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
28 #include "rrd_graph.h"
30 /* some constant definitions */
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
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 };
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 };
100 /* sensible y label intervals ...*/
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 };
144 gfx_color_t graph_col[] =   /* default colors */
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 };
159 /* #define DEBUG */
161 #ifdef DEBUG
162 # define DPRINT(x)    (void)(printf x, printf("\n"))
163 #else
164 # define DPRINT(x)
165 #endif
168 /* initialize with xtr(im,0); */
169 int xtr(
170     image_desc_t *im,
171     time_t mytime)
173     static double pixie;
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));
182 /* translate data values into y coordinates */
183 double ytr(
184     image_desc_t *im,
185     double value)
187     static double pixie;
188     double    yval;
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;
211 /* conversion function for symbolic entry names */
214 #define conv_if(VV,VVV) \
215    if (strcmp(#VV, string) == 0) return VVV ;
217 enum gf_en gf_conv(
218     char *string)
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);
237     return (-1);
240 enum gfx_if_en if_conv(
241     char *string)
244     conv_if(PNG, IF_PNG);
245     conv_if(SVG, IF_SVG);
246     conv_if(EPS, IF_EPS);
247     conv_if(PDF, IF_PDF);
249     return (-1);
252 enum tmt_en tmt_conv(
253     char *string)
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);
266 enum grc_en grc_conv(
267     char *string)
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);
281     return -1;
284 enum text_prop_en text_prop_conv(
285     char *string)
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;
298 #undef conv_if
300 int im_free(
301     image_desc_t *im)
303     unsigned long i, ii;
304     cairo_status_t status = 0;
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);
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);
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     }
337     if (im->layout) {
338         g_object_unref (im->layout);
339     }
341     if (im->surface)
342         cairo_surface_destroy(im->surface);
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;
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)
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 */
374     int       symbcenter = 6;
375     int       sindex;
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     }
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;
410 /* find SI magnitude symbol for the numbers on the y-axis*/
411 void si_unit(
412     image_desc_t *im    /* image description */
413     )
416     double    digits, viewdigits = 0;
418     digits =
419         floor(log(max(fabs(im->minval), fabs(im->maxval))) /
420               log((double) im->base));
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     }
429     im->magfact = pow((double) im->base, digits);
431 #ifdef DEBUG
432     printf("digits %6.3f  im->magfact %6.3f\n", digits, im->magfact);
433 #endif
435     im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
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 = '?';
444 /*  move min and max values around to become sensible */
446 void expand_range(
447     image_desc_t *im)
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     };
459     double    scaled_min, scaled_max;
460     double    adj;
461     int       i;
465 #ifdef DEBUG
466     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
467            im->minval, im->maxval, im->magfact);
468 #endif
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;
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;
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);
514                 if (-sensiblevalues[i - 1] <= scaled_min &&
515                     -sensiblevalues[i] >= scaled_min)
516                     im->minval = -sensiblevalues[i - 1] * (im->magfact);
518                 if (sensiblevalues[i - 1] >= scaled_max &&
519                     sensiblevalues[i] <= scaled_max)
520                     im->maxval = sensiblevalues[i - 1] * (im->magfact);
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     }
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
542 void apply_gridfit(
543     image_desc_t *im)
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);
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;
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;
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;
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;
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     }
617 /* reduce data reimplementation by Alex */
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;
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;
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
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);
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
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
697 /* Sanity check: row_cnt should be multiple of reduce_factor */
698 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
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     }
707     /* Now combine reduce_factor intervals at a time
708      ** into one interval for the destination.
709      */
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;
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
792 /* get the data required for the graphs from the 
793    relevant rrds ... */
795 int data_fetch(
796     image_desc_t *im)
798     int       i, ii;
799     int       skip;
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;
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 */
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;
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         }
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         }
872     }
873     return 0;
876 /* evaluate the expressions in the CDEF functions */
878 /*************************************************************
879  * CDEF stuff 
880  *************************************************************/
882 long find_var_wrapper(
883     void *arg1,
884     char *key)
886     return find_var((image_desc_t *) arg1, key);
889 /* find gdes containing var*/
890 long find_var(
891     image_desc_t *im,
892     char *key)
894     long      ii;
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;
906 /* find the largest common denominator for all the numbers
907    in the 0 terminated num array */
908 long lcd(
909     long *num)
911     long      rest;
912     int       i;
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];
926 /* run the rpn calculator on all the VDEF and CDEF arguments */
927 int data_calc(
928     image_desc_t *im)
931     int       gdi;
932     int       dataidx;
933     long     *steparray, rpi;
934     int       stepcnt;
935     time_t    now;
936     rpnstack_t rpnstack;
938     rpnstack_init(&rpnstack);
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];
950             /* remove current shift */
951             vdp->start -= vdp->shift;
952             vdp->end -= vdp->shift;
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;
961             /* normalize shift to multiple of consolidated step */
962             vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
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;
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;
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) */
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                         };
1028                         steparray[stepcnt - 1] = im->gdes[ptr].step;
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                          */
1036                         if (im->gdes[gdi].start < im->gdes[ptr].start)
1037                             im->gdes[gdi].start = im->gdes[ptr].start;
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;
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;
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 */
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;
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             }
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             }
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;
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;
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. */
1128 static int AlmostEqual2sComplement(
1129     float A,
1130     float B,
1131     int maxUlps)
1134     int       aInt = *(int *) &A;
1135     int       bInt = *(int *) &B;
1136     int       intDiff;
1138     /* Make sure maxUlps is non-negative and small enough that the
1139        default NAN won't compare as equal to anything.  */
1141     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1143     /* Make aInt lexicographically ordered as a twos-complement int */
1145     if (aInt < 0)
1146         aInt = 0x80000000l - aInt;
1148     /* Make bInt lexicographically ordered as a twos-complement int */
1150     if (bInt < 0)
1151         bInt = 0x80000000l - bInt;
1153     intDiff = abs(aInt - bInt);
1155     if (intDiff <= maxUlps)
1156         return 1;
1158     return 0;
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)
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;
1172     unsigned long gr_time;
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     }
1186     for (i = 0; i < im->xsize; i++) {   /* for each pixel */
1187         long      vidx;
1189         gr_time = im->start + pixstep * i;  /* time of the current step */
1190         paintval = 0.0;
1192         for (ii = 0; ii < im->gdes_c; ii++) {
1193             double    value;
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                 };
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     }
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 ... */
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     }
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     }
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     }
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;
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;
1321 /* identify the point where the first gridline, label ... gets placed */
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     )
1329     struct tm tm;
1331     localtime_r(&start, &tm);
1333     switch (baseint) {
1334     case TMT_SECOND:
1335         tm.       tm_sec -= tm.tm_sec % basestep;
1337         break;
1338     case TMT_MINUTE:
1339         tm.       tm_sec = 0;
1340         tm.       tm_min -= tm.tm_min % basestep;
1342         break;
1343     case TMT_HOUR:
1344         tm.       tm_sec = 0;
1345         tm.       tm_min = 0;
1346         tm.       tm_hour -= tm.tm_hour % basestep;
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;
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 */
1363         if (tm.tm_wday == 0)
1364             tm.       tm_mday -= 7; /* we want the *previous* monday */
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;
1374         break;
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;
1385     }
1386     return mktime(&tm);
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     )
1396     struct tm tm;
1397     time_t    madetime;
1399     localtime_r(&current, &tm);
1401     do {
1402         switch (baseint) {
1403         case TMT_SECOND:
1404             tm.       tm_sec += basestep;
1406             break;
1407         case TMT_MINUTE:
1408             tm.       tm_min += basestep;
1410             break;
1411         case TMT_HOUR:
1412             tm.       tm_hour += basestep;
1414             break;
1415         case TMT_DAY:
1416             tm.       tm_mday += basestep;
1418             break;
1419         case TMT_WEEK:
1420             tm.       tm_mday += 7 * basestep;
1422             break;
1423         case TMT_MONTH:
1424             tm.       tm_mon += basestep;
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;
1438 /* calculate values required for PRINT and GPRINT functions */
1440 int print_calc(
1441     image_desc_t *im)
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;
1454     /* wow initializing tmvdef is quite a task :-) */
1455     time_t    now = time(NULL);
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                     }
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 */
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             }
1531             if (im->gdes[i].gf == GF_PRINT) {
1532                 rrd_infoval_t prline;
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 */
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;
1614 /* place legends with color spots */
1615 int leg_place(
1616     image_desc_t *im,
1617     int *gY)
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;
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         }
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             }
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             }
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             }
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             }
1700             if (leg_cc != 0) {
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             }
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             }
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         }
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;
1818 /* create a grid on the graph. it determines what to do
1819    from the values of xsize, start and end */
1821 /* the xaxis labels are determined from the number of seconds per pixel
1822    in the requested graph */
1824 int calc_horizontal_grid(
1825     image_desc_t
1826     *im)
1828     double    range;
1829     double    scaledrange;
1830     int       pixel, i;
1831     int       gridind = 0;
1832     int       decimals, fractionals;
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     }
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;
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;
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             }
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             }
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;
1922 int draw_horizontal_grid(
1923     image_desc_t
1924     *im)
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;
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));
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);
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;
2026 /* this is frexp for base 10 */
2027 double    frexp10(
2028     double,
2029     double *);
2030 double frexp10(
2031     double x,
2032     double *e)
2034     double    mnt;
2035     int       iexp;
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;
2048 /* logaritmic horizontal grid */
2049 int horizontal_log_grid(
2050     image_desc_t
2051     *im)
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];
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     }
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) {
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;
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             }
2191             /* draw minor lines below current major line */
2192             for (; i < j; i++) {
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         }
2236         /* next decade */
2237         if (yloglab[mid][++flab] == 10.0) {
2238             flab = 0;
2239             val_exp += exfrac;
2240         }
2241     }
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         }
2258         /* draw minor lines below current major line */
2259         for (; i < j; i++) {
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     }
2303     return 1;
2307 void vertical_grid(
2308     image_desc_t *im)
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;
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     }
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     }
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     }
2450 void axis_paint(
2451     image_desc_t *im)
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]);
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]); */
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);
2480 void grid_paint(
2481     image_desc_t *im)
2483     long      i;
2484     int       res = 0;
2485     double    X0, Y0;   /* points for filled graph and more */
2486     struct gfx_color_t water_color;
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         }
2511         /* dont draw horizontal grid if there is no min and max val */
2512         if (!res) {
2513             char     *nodata = "No Data found";
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     }
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     }
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;
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     }
2647 /*****************************************************
2648  * lazy check make sure we rely need to create this graph
2649  *****************************************************/
2651 int lazy_check(
2652     image_desc_t *im)
2654     FILE     *fd = NULL;
2655     int       size = 1;
2656     struct stat imgstat;
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;
2682 int graph_size_location(
2683     image_desc_t
2684     *im,
2685     int elements)
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      */
2693     int       Xvertical = 0, Ytitle =
2694         0, Xylabel = 0, Xmain = 0, Ymain =
2695         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
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     }
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      */
2726     if (im->ylegend[0] != '\0') {
2727         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2728     }
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     }
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     }
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         }
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         }
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         }
2815         im->ysize = Ymain;
2816     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
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          */
2824         if (im->ylegend[0] != '\0') {
2825             Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2826         }
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         }
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. */
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     }
2901     ytr(im, DNAN);
2902     return 0;
2905 static cairo_status_t cairo_output(
2906     void *closure,
2907     const unsigned char
2908     *data,
2909     unsigned int length)
2911     image_desc_t *im = closure;
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;
2922 /* draw that picture thing ... */
2923 int graph_paint(
2924     image_desc_t *im)
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;
2932 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
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;
2957     if ((i == 0) || lazy)
2958         return 0;
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;
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);
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     }
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 */
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);
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                     }
3118                 }
3119             }           /* for */
3121             /* *******************************************************
3122                a           ___. (a,t) 
3123                |   |    ___
3124                ____|   |   |   |
3125                |       |___|
3126                -------|--t-1--t--------------------------------      
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.
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;
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                     }
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;
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]);
3182                             if (im->slopemode == 0
3183                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3184                                 double    x = ii - 1 + im->xorigin;
3185                                 double    y = y1;
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;
3218                     for (ii = 0; ii <= im->xsize; ii++) {
3219                         double    ybase, ytop;
3221                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3222                             int       cntI = 1;
3223                             int       lastI = 0;
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                         }
3307                         if (ybase > ytop) {
3308                             double    extra = ytop;
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);
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++) {
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     }
3405     switch (im->imgformat) {
3406     case IF_PNG:
3407     {
3408         cairo_status_t status;
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);
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     }
3430     return 0;
3434 /*****************************************************
3435  * graph stuff 
3436  *****************************************************/
3438 int gdes_alloc(
3439     image_desc_t *im)
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     }
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;
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)
3494     int       inp, outp = 0;
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;
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)
3520     int       prlines = 0;
3521     rrd_info_t *grinfo = NULL;
3522     rrd_info_t *walker;
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;
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)
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     }
3607     if (optind >= argc) {
3608         rrd_info_free(im.grinfo);
3609         im_free(&im);
3610         rrd_set_error("missing filename");
3611         return NULL;
3612     }
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     }
3621     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3622     im.graphfile[MAXPATH - 1] = '\0';
3624     if (strcmp(im.graphfile, "-") == 0) {
3625         im.graphfile[0] = '\0';
3626     }
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     }
3635     /* Everything is now read and the actual work can start */
3637     if (graph_paint(&im) == -1) {
3638         rrd_info_free(im.grinfo);
3639         im_free(&im);
3640         return NULL;
3641     }
3644     /* The image is generated and needs to be output.
3645      ** Also, if needed, print a line with information about the image.
3646      */
3648     if (im.imginfo) {
3649         rrd_infoval_t info;
3650         char     *filename;
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;
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;
3678 static void 
3679 rrd_set_font_desc (
3680     image_desc_t *im,int prop,char *font, double size ){
3681     strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);        
3682     im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';   
3683     im->text_prop[prop].size = size;
3684     im->text_prop[prop].font_desc =   pango_font_description_from_string( font );
3685     pango_font_description_set_size(im->text_prop[prop].font_desc, size * PANGO_SCALE);
3688 void rrd_graph_init(
3689     image_desc_t
3690     *im)
3692     unsigned int i;
3693     char     *deffont = getenv("RRD_DEFAULT_FONT");
3694     static PangoFontMap *fontmap = NULL;
3695     PangoContext *context;
3697 #ifdef HAVE_TZSET
3698     tzset();
3699 #endif
3700 #ifdef HAVE_SETLOCALE
3701     setlocale(LC_TIME, "");
3702 #ifdef HAVE_MBSTOWCS
3703     setlocale(LC_CTYPE, "");
3704 #endif
3705 #endif
3706     im->base = 1000;
3707     im->draw_x_grid = 1;
3708     im->draw_y_grid = 1;
3709     im->extra_flags = 0;
3710     im->font_options = cairo_font_options_create();
3711     im->forceleftspace = 0;
3712     im->gdes_c = 0;
3713     im->gdes = NULL;
3714     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3715     im->grid_dash_off = 1;
3716     im->grid_dash_on = 1;
3717     im->gridfit = 1;
3718     im->grinfo = (rrd_info_t *) NULL;
3719     im->grinfo_current = (rrd_info_t *) NULL;
3720     im->imgformat = IF_PNG;
3721     im->imginfo = NULL;
3722     im->lazy = 0;
3723     im->logarithmic = 0;
3724     im->maxval = DNAN;
3725     im->minval = 0;
3726     im->minval = DNAN;
3727     im->prt_c = 0;
3728     im->rigid = 0;
3729     im->rendered_image_size = 0;
3730     im->rendered_image = NULL;
3731     im->slopemode = 0;
3732     im->step = 0;
3733     im->symbol = ' ';
3734     im->tabwidth = 40.0;
3735     im->title[0] = '\0';
3736     im->unitsexponent = 9999;
3737     im->unitslength = 6;
3738     im->viewfactor = 1.0;
3739     im->watermark[0] = '\0';
3740     im->with_markup = 0;
3741     im->ximg = 0;
3742     im->xlab_user.minsec = -1;
3743     im->xorigin = 0;
3744     im->xsize = 400;
3745     im->ygridstep = DNAN;
3746     im->yimg = 0;
3747     im->ylegend[0] = '\0';
3748     im->yorigin = 0;
3749     im->ysize = 100;
3750     im->zoom = 1;
3752     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);     
3753     im->cr = cairo_create(im->surface);
3755     for (i = 0; i < DIM(text_prop); i++) {
3756         im->text_prop[i].size = -1;
3757         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3758     }
3760     if (fontmap == NULL){
3761         fontmap = pango_cairo_font_map_get_default();
3762     }
3764     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3766     pango_cairo_context_set_resolution(context, 100);
3768     pango_cairo_update_context(im->cr,context);
3770     im->layout = pango_layout_new(context);
3772 //  im->layout = pango_cairo_create_layout(im->cr);
3775     cairo_font_options_set_hint_style
3776         (im->font_options, CAIRO_HINT_STYLE_FULL);
3777     cairo_font_options_set_hint_metrics
3778         (im->font_options, CAIRO_HINT_METRICS_ON);
3779     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3783     for (i = 0; i < DIM(graph_col); i++)
3784         im->graph_col[i] = graph_col[i];
3790 void rrd_graph_options(
3791     int argc,
3792     char *argv[],
3793     image_desc_t
3794     *im)
3796     int       stroff;
3797     char     *parsetime_error = NULL;
3798     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3799     time_t    start_tmp = 0, end_tmp = 0;
3800     long      long_tmp;
3801     rrd_time_value_t start_tv, end_tv;
3802     long unsigned int color;
3803     char     *old_locale = "";
3805     /* defines for long options without a short equivalent. should be bytes,
3806        and may not collide with (the ASCII value of) short options */
3807 #define LONGOPT_UNITS_SI 255
3809 /* *INDENT-OFF* */
3810     struct option long_options[] = {
3811         { "start",              required_argument, 0, 's'}, 
3812         { "end",                required_argument, 0, 'e'},
3813         { "x-grid",             required_argument, 0, 'x'},
3814         { "y-grid",             required_argument, 0, 'y'},
3815         { "vertical-label",     required_argument, 0, 'v'},
3816         { "width",              required_argument, 0, 'w'},
3817         { "height",             required_argument, 0, 'h'},
3818         { "full-size-mode",     no_argument,       0, 'D'},
3819         { "interlaced",         no_argument,       0, 'i'},
3820         { "upper-limit",        required_argument, 0, 'u'},
3821         { "lower-limit",        required_argument, 0, 'l'},
3822         { "rigid",              no_argument,       0, 'r'},
3823         { "base",               required_argument, 0, 'b'},
3824         { "logarithmic",        no_argument,       0, 'o'},
3825         { "color",              required_argument, 0, 'c'},
3826         { "font",               required_argument, 0, 'n'},
3827         { "title",              required_argument, 0, 't'},
3828         { "imginfo",            required_argument, 0, 'f'},
3829         { "imgformat",          required_argument, 0, 'a'},
3830         { "lazy",               no_argument,       0, 'z'},
3831         { "zoom",               required_argument, 0, 'm'},
3832         { "no-legend",          no_argument,       0, 'g'},
3833         { "force-rules-legend", no_argument,       0, 'F'},
3834         { "only-graph",         no_argument,       0, 'j'},
3835         { "alt-y-grid",         no_argument,       0, 'Y'},
3836         { "no-minor",           no_argument,       0, 'I'}, 
3837         { "slope-mode",         no_argument,       0, 'E'},
3838         { "alt-autoscale",      no_argument,       0, 'A'},
3839         { "alt-autoscale-min",  no_argument,       0, 'J'},
3840         { "alt-autoscale-max",  no_argument,       0, 'M'},
3841         { "no-gridfit",         no_argument,       0, 'N'},
3842         { "units-exponent",     required_argument, 0, 'X'},
3843         { "units-length",       required_argument, 0, 'L'},
3844         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
3845         { "step",               required_argument, 0, 'S'},
3846         { "tabwidth",           required_argument, 0, 'T'},
3847         { "font-render-mode",   required_argument, 0, 'R'},
3848         { "graph-render-mode",  required_argument, 0, 'G'},
3849         { "font-smoothing-threshold", required_argument, 0, 'B'},
3850         { "watermark",          required_argument, 0, 'W'},
3851         { "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 */
3852         { "pango-markup",       no_argument,       0, 'P'},
3853         {  0, 0, 0, 0}
3854 };
3855 /* *INDENT-ON* */
3857     optind = 0;
3858     opterr = 0;         /* initialize getopt */
3859     rrd_parsetime("end-24h", &start_tv);
3860     rrd_parsetime("now", &end_tv);
3861     while (1) {
3862         int       option_index = 0;
3863         int       opt;
3864         int       col_start, col_end;
3866         opt = getopt_long(argc, argv,
3867                           "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",
3868                           long_options, &option_index);
3869         if (opt == EOF)
3870             break;
3871         switch (opt) {
3872         case 'I':
3873             im->extra_flags |= NOMINOR;
3874             break;
3875         case 'Y':
3876             im->extra_flags |= ALTYGRID;
3877             break;
3878         case 'A':
3879             im->extra_flags |= ALTAUTOSCALE;
3880             break;
3881         case 'J':
3882             im->extra_flags |= ALTAUTOSCALE_MIN;
3883             break;
3884         case 'M':
3885             im->extra_flags |= ALTAUTOSCALE_MAX;
3886             break;
3887         case 'j':
3888             im->extra_flags |= ONLY_GRAPH;
3889             break;
3890         case 'g':
3891             im->extra_flags |= NOLEGEND;
3892             break;
3893         case 'F':
3894             im->extra_flags |= FORCE_RULES_LEGEND;
3895             break;
3896         case LONGOPT_UNITS_SI:
3897             if (im->extra_flags & FORCE_UNITS) {
3898                 rrd_set_error("--units can only be used once!");
3899                 setlocale(LC_NUMERIC, old_locale);
3900                 return;
3901             }
3902             if (strcmp(optarg, "si") == 0)
3903                 im->extra_flags |= FORCE_UNITS_SI;
3904             else {
3905                 rrd_set_error("invalid argument for --units: %s", optarg);
3906                 return;
3907             }
3908             break;
3909         case 'X':
3910             im->unitsexponent = atoi(optarg);
3911             break;
3912         case 'L':
3913             im->unitslength = atoi(optarg);
3914             im->forceleftspace = 1;
3915             break;
3916         case 'T':
3917             old_locale = setlocale(LC_NUMERIC, "C");
3918             im->tabwidth = atof(optarg);
3919             setlocale(LC_NUMERIC, old_locale);
3920             break;
3921         case 'S':
3922             old_locale = setlocale(LC_NUMERIC, "C");
3923             im->step = atoi(optarg);
3924             setlocale(LC_NUMERIC, old_locale);
3925             break;
3926         case 'N':
3927             im->gridfit = 0;
3928             break;
3929         case 'P':
3930             im->with_markup = 1;
3931             break;
3932         case 's':
3933             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
3934                 rrd_set_error("start time: %s", parsetime_error);
3935                 return;
3936             }
3937             break;
3938         case 'e':
3939             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
3940                 rrd_set_error("end time: %s", parsetime_error);
3941                 return;
3942             }
3943             break;
3944         case 'x':
3945             if (strcmp(optarg, "none") == 0) {
3946                 im->draw_x_grid = 0;
3947                 break;
3948             };
3949             if (sscanf(optarg,
3950                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3951                        scan_gtm,
3952                        &im->xlab_user.gridst,
3953                        scan_mtm,
3954                        &im->xlab_user.mgridst,
3955                        scan_ltm,
3956                        &im->xlab_user.labst,
3957                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3958                 strncpy(im->xlab_form, optarg + stroff,
3959                         sizeof(im->xlab_form) - 1);
3960                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3961                 if ((int)
3962                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3963                     rrd_set_error("unknown keyword %s", scan_gtm);
3964                     return;
3965                 } else if ((int)
3966                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3967                            == -1) {
3968                     rrd_set_error("unknown keyword %s", scan_mtm);
3969                     return;
3970                 } else if ((int)
3971                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
3972                     rrd_set_error("unknown keyword %s", scan_ltm);
3973                     return;
3974                 }
3975                 im->xlab_user.minsec = 1;
3976                 im->xlab_user.stst = im->xlab_form;
3977             } else {
3978                 rrd_set_error("invalid x-grid format");
3979                 return;
3980             }
3981             break;
3982         case 'y':
3984             if (strcmp(optarg, "none") == 0) {
3985                 im->draw_y_grid = 0;
3986                 break;
3987             };
3988             old_locale = setlocale(LC_NUMERIC, "C");
3989             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3990                 setlocale(LC_NUMERIC, old_locale);
3991                 if (im->ygridstep <= 0) {
3992                     rrd_set_error("grid step must be > 0");
3993                     return;
3994                 } else if (im->ylabfact < 1) {
3995                     rrd_set_error("label factor must be > 0");
3996                     return;
3997                 }
3998             } else {
3999                 setlocale(LC_NUMERIC, old_locale);
4000                 rrd_set_error("invalid y-grid format");
4001                 return;
4002             }
4003             break;
4004         case 'v':
4005             strncpy(im->ylegend, optarg, 150);
4006             im->ylegend[150] = '\0';
4007             break;
4008         case 'u':
4009             old_locale = setlocale(LC_NUMERIC, "C");
4010             im->maxval = atof(optarg);
4011             setlocale(LC_NUMERIC, old_locale);
4012             break;
4013         case 'l':
4014             old_locale = setlocale(LC_NUMERIC, "C");
4015             im->minval = atof(optarg);
4016             setlocale(LC_NUMERIC, old_locale);
4017             break;
4018         case 'b':
4019             im->base = atol(optarg);
4020             if (im->base != 1024 && im->base != 1000) {
4021                 rrd_set_error
4022                     ("the only sensible value for base apart from 1000 is 1024");
4023                 return;
4024             }
4025             break;
4026         case 'w':
4027             long_tmp = atol(optarg);
4028             if (long_tmp < 10) {
4029                 rrd_set_error("width below 10 pixels");
4030                 return;
4031             }
4032             im->xsize = long_tmp;
4033             break;
4034         case 'h':
4035             long_tmp = atol(optarg);
4036             if (long_tmp < 10) {
4037                 rrd_set_error("height below 10 pixels");
4038                 return;
4039             }
4040             im->ysize = long_tmp;
4041             break;
4042         case 'D':
4043             im->extra_flags |= FULL_SIZE_MODE;
4044             break;
4045         case 'i':
4046             /* interlaced png not supported at the moment */
4047             break;
4048         case 'r':
4049             im->rigid = 1;
4050             break;
4051         case 'f':
4052             im->imginfo = optarg;
4053             break;
4054         case 'a':
4055             if ((int)
4056                 (im->imgformat = if_conv(optarg)) == -1) {
4057                 rrd_set_error("unsupported graphics format '%s'", optarg);
4058                 return;
4059             }
4060             break;
4061         case 'z':
4062             im->lazy = 1;
4063             break;
4064         case 'E':
4065             im->slopemode = 1;
4066             break;
4067         case 'o':
4068             im->logarithmic = 1;
4069             break;
4070         case 'c':
4071             if (sscanf(optarg,
4072                        "%10[A-Z]#%n%8lx%n",
4073                        col_nam, &col_start, &color, &col_end) == 2) {
4074                 int       ci;
4075                 int       col_len = col_end - col_start;
4077                 switch (col_len) {
4078                 case 3:
4079                     color =
4080                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4081                                                          0x011000) |
4082                          ((color & 0x00F)
4083                           * 0x001100)
4084                          | 0x000000FF);
4085                     break;
4086                 case 4:
4087                     color =
4088                         (((color & 0xF000) *
4089                           0x11000) | ((color & 0x0F00) *
4090                                       0x01100) | ((color &
4091                                                    0x00F0) *
4092                                                   0x00110) |
4093                          ((color & 0x000F) * 0x00011)
4094                         );
4095                     break;
4096                 case 6:
4097                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4098                     break;
4099                 case 8:
4100                     break;
4101                 default:
4102                     rrd_set_error("the color format is #RRGGBB[AA]");
4103                     return;
4104                 }
4105                 if ((ci = grc_conv(col_nam)) != -1) {
4106                     im->graph_col[ci] = gfx_hex_to_col(color);
4107                 } else {
4108                     rrd_set_error("invalid color name '%s'", col_nam);
4109                     return;
4110                 }
4111             } else {
4112                 rrd_set_error("invalid color def format");
4113                 return;
4114             }
4115             break;
4116         case 'n':{
4117             char      prop[15];
4118             double    size = 1;
4119             int       end;
4121             old_locale = setlocale(LC_NUMERIC, "C");
4122             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4123                 int       sindex, propidx;
4125                 setlocale(LC_NUMERIC, old_locale);
4126                 if ((sindex = text_prop_conv(prop)) != -1) {
4127                     for (propidx = sindex;
4128                          propidx < TEXT_PROP_LAST; propidx++) {
4129                         if (size > 0) {
4130                             rrd_set_font_desc(im,propidx,NULL,size);   
4131                         }
4132                         if ((int) strlen(optarg) > end) {
4133                             if (optarg[end] == ':') {
4134                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);   
4135                             } else {
4136                                 rrd_set_error
4137                                     ("expected : after font size in '%s'",
4138                                      optarg);
4139                                 return;
4140                             }
4141                         }
4142                         /* only run the for loop for DEFAULT (0) for
4143                            all others, we break here. woodo programming */
4144                         if (propidx == sindex && sindex != 0)
4145                             break;
4146                     }
4147                 } else {
4148                     rrd_set_error("invalid fonttag '%s'", prop);
4149                     return;
4150                 }
4151             } else {
4152                 setlocale(LC_NUMERIC, old_locale);
4153                 rrd_set_error("invalid text property format");
4154                 return;
4155             }
4156             break;
4157         }
4158         case 'm':
4159             old_locale = setlocale(LC_NUMERIC, "C");
4160             im->zoom = atof(optarg);
4161             setlocale(LC_NUMERIC, old_locale);
4162             if (im->zoom <= 0.0) {
4163                 rrd_set_error("zoom factor must be > 0");
4164                 return;
4165             }
4166             break;
4167         case 't':
4168             strncpy(im->title, optarg, 150);
4169             im->title[150] = '\0';
4170             break;
4171         case 'R':
4172             if (strcmp(optarg, "normal") == 0) {
4173                 cairo_font_options_set_antialias
4174                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4175                 cairo_font_options_set_hint_style
4176                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4177             } else if (strcmp(optarg, "light") == 0) {
4178                 cairo_font_options_set_antialias
4179                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4180                 cairo_font_options_set_hint_style
4181                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4182             } else if (strcmp(optarg, "mono") == 0) {
4183                 cairo_font_options_set_antialias
4184                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4185                 cairo_font_options_set_hint_style
4186                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4187             } else {
4188                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4189                 return;
4190             }
4191             break;
4192         case 'G':
4193             if (strcmp(optarg, "normal") == 0)
4194                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4195             else if (strcmp(optarg, "mono") == 0)
4196                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4197             else {
4198                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4199                 return;
4200             }
4201             break;
4202         case 'B':
4203             /* not supported curently */
4204             break;
4205         case 'W':
4206             strncpy(im->watermark, optarg, 100);
4207             im->watermark[99] = '\0';
4208             break;
4209         case '?':
4210             if (optopt != 0)
4211                 rrd_set_error("unknown option '%c'", optopt);
4212             else
4213                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4214             return;
4215         }
4216     }
4217     
4218     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4219     pango_layout_context_changed(im->layout);
4223     if (im->logarithmic && im->minval <= 0) {
4224         rrd_set_error
4225             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4226         return;
4227     }
4229     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4230         /* error string is set in rrd_parsetime.c */
4231         return;
4232     }
4234     if (start_tmp < 3600 * 24 * 365 * 10) {
4235         rrd_set_error
4236             ("the first entry to fetch should be after 1980 (%ld)",
4237              start_tmp);
4238         return;
4239     }
4241     if (end_tmp < start_tmp) {
4242         rrd_set_error
4243             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4244         return;
4245     }
4247     im->start = start_tmp;
4248     im->end = end_tmp;
4249     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4252 int rrd_graph_color(
4253     image_desc_t
4254     *im,
4255     char *var,
4256     char *err,
4257     int optional)
4259     char     *color;
4260     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4262     color = strstr(var, "#");
4263     if (color == NULL) {
4264         if (optional == 0) {
4265             rrd_set_error("Found no color in %s", err);
4266             return 0;
4267         }
4268         return 0;
4269     } else {
4270         int       n = 0;
4271         char     *rest;
4272         long unsigned int col;
4274         rest = strstr(color, ":");
4275         if (rest != NULL)
4276             n = rest - color;
4277         else
4278             n = strlen(color);
4279         switch (n) {
4280         case 7:
4281             sscanf(color, "#%6lx%n", &col, &n);
4282             col = (col << 8) + 0xff /* shift left by 8 */ ;
4283             if (n != 7)
4284                 rrd_set_error("Color problem in %s", err);
4285             break;
4286         case 9:
4287             sscanf(color, "#%8lx%n", &col, &n);
4288             if (n == 9)
4289                 break;
4290         default:
4291             rrd_set_error("Color problem in %s", err);
4292         }
4293         if (rrd_test_error())
4294             return 0;
4295         gdp->col = gfx_hex_to_col(col);
4296         return n;
4297     }
4301 int bad_format(
4302     char *fmt)
4304     char     *ptr;
4305     int       n = 0;
4307     ptr = fmt;
4308     while (*ptr != '\0')
4309         if (*ptr++ == '%') {
4311             /* line cannot end with percent char */
4312             if (*ptr == '\0')
4313                 return 1;
4314             /* '%s', '%S' and '%%' are allowed */
4315             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4316                 ptr++;
4317             /* %c is allowed (but use only with vdef!) */
4318             else if (*ptr == 'c') {
4319                 ptr++;
4320                 n = 1;
4321             }
4323             /* or else '% 6.2lf' and such are allowed */
4324             else {
4325                 /* optional padding character */
4326                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4327                     ptr++;
4328                 /* This should take care of 'm.n' with all three optional */
4329                 while (*ptr >= '0' && *ptr <= '9')
4330                     ptr++;
4331                 if (*ptr == '.')
4332                     ptr++;
4333                 while (*ptr >= '0' && *ptr <= '9')
4334                     ptr++;
4335                 /* Either 'le', 'lf' or 'lg' must follow here */
4336                 if (*ptr++ != 'l')
4337                     return 1;
4338                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4339                     ptr++;
4340                 else
4341                     return 1;
4342                 n++;
4343             }
4344         }
4346     return (n != 1);
4350 int vdef_parse(
4351     struct graph_desc_t
4352     *gdes,
4353     const char *const str)
4355     /* A VDEF currently is either "func" or "param,func"
4356      * so the parsing is rather simple.  Change if needed.
4357      */
4358     double    param;
4359     char      func[30];
4360     int       n;
4361     char     *old_locale;
4363     n = 0;
4364     old_locale = setlocale(LC_NUMERIC, "C");
4365     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4366     setlocale(LC_NUMERIC, old_locale);
4367     if (n == (int) strlen(str)) {   /* matched */
4368         ;
4369     } else {
4370         n = 0;
4371         sscanf(str, "%29[A-Z]%n", func, &n);
4372         if (n == (int) strlen(str)) {   /* matched */
4373             param = DNAN;
4374         } else {
4375             rrd_set_error
4376                 ("Unknown function string '%s' in VDEF '%s'",
4377                  str, gdes->vname);
4378             return -1;
4379         }
4380     }
4381     if (!strcmp("PERCENT", func))
4382         gdes->vf.op = VDEF_PERCENT;
4383     else if (!strcmp("MAXIMUM", func))
4384         gdes->vf.op = VDEF_MAXIMUM;
4385     else if (!strcmp("AVERAGE", func))
4386         gdes->vf.op = VDEF_AVERAGE;
4387     else if (!strcmp("STDEV", func))
4388         gdes->vf.op = VDEF_STDEV;
4389     else if (!strcmp("MINIMUM", func))
4390         gdes->vf.op = VDEF_MINIMUM;
4391     else if (!strcmp("TOTAL", func))
4392         gdes->vf.op = VDEF_TOTAL;
4393     else if (!strcmp("FIRST", func))
4394         gdes->vf.op = VDEF_FIRST;
4395     else if (!strcmp("LAST", func))
4396         gdes->vf.op = VDEF_LAST;
4397     else if (!strcmp("LSLSLOPE", func))
4398         gdes->vf.op = VDEF_LSLSLOPE;
4399     else if (!strcmp("LSLINT", func))
4400         gdes->vf.op = VDEF_LSLINT;
4401     else if (!strcmp("LSLCORREL", func))
4402         gdes->vf.op = VDEF_LSLCORREL;
4403     else {
4404         rrd_set_error
4405             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4406         return -1;
4407     };
4408     switch (gdes->vf.op) {
4409     case VDEF_PERCENT:
4410         if (isnan(param)) { /* no parameter given */
4411             rrd_set_error
4412                 ("Function '%s' needs parameter in VDEF '%s'\n",
4413                  func, gdes->vname);
4414             return -1;
4415         };
4416         if (param >= 0.0 && param <= 100.0) {
4417             gdes->vf.param = param;
4418             gdes->vf.val = DNAN;    /* undefined */
4419             gdes->vf.when = 0;  /* undefined */
4420         } else {
4421             rrd_set_error
4422                 ("Parameter '%f' out of range in VDEF '%s'\n",
4423                  param, gdes->vname);
4424             return -1;
4425         };
4426         break;
4427     case VDEF_MAXIMUM:
4428     case VDEF_AVERAGE:
4429     case VDEF_STDEV:
4430     case VDEF_MINIMUM:
4431     case VDEF_TOTAL:
4432     case VDEF_FIRST:
4433     case VDEF_LAST:
4434     case VDEF_LSLSLOPE:
4435     case VDEF_LSLINT:
4436     case VDEF_LSLCORREL:
4437         if (isnan(param)) {
4438             gdes->vf.param = DNAN;
4439             gdes->vf.val = DNAN;
4440             gdes->vf.when = 0;
4441         } else {
4442             rrd_set_error
4443                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4444                  func, gdes->vname);
4445             return -1;
4446         };
4447         break;
4448     };
4449     return 0;
4453 int vdef_calc(
4454     image_desc_t *im,
4455     int gdi)
4457     graph_desc_t *src, *dst;
4458     rrd_value_t *data;
4459     long      step, steps;
4460     unsigned long end;
4462     dst = &im->gdes[gdi];
4463     src = &im->gdes[dst->vidx];
4464     data = src->data + src->ds;
4465     end =
4466         src->end_orig % (long) src->step ==
4467         0 ? src->end_orig : (src->end_orig + (long) src->step -
4468                              src->end_orig % (long) src->step);
4470     steps = (end - src->start) / src->step;
4471 #if 0
4472     printf
4473         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4474          src->start, src->end_orig, steps);
4475 #endif
4476     switch (dst->vf.op) {
4477     case VDEF_PERCENT:{
4478         rrd_value_t *array;
4479         int       field;
4480         if ((array = malloc(steps * sizeof(double))) == NULL) {
4481             rrd_set_error("malloc VDEV_PERCENT");
4482             return -1;
4483         }
4484         for (step = 0; step < steps; step++) {
4485             array[step] = data[step * src->ds_cnt];
4486         }
4487         qsort(array, step, sizeof(double), vdef_percent_compar);
4488         field = (steps - 1) * dst->vf.param / 100;
4489         dst->vf.val = array[field];
4490         dst->vf.when = 0;   /* no time component */
4491         free(array);
4492 #if 0
4493         for (step = 0; step < steps; step++)
4494             printf("DEBUG: %3li:%10.2f %c\n",
4495                    step, array[step], step == field ? '*' : ' ');
4496 #endif
4497     }
4498         break;
4499     case VDEF_MAXIMUM:
4500         step = 0;
4501         while (step != steps && isnan(data[step * src->ds_cnt]))
4502             step++;
4503         if (step == steps) {
4504             dst->vf.val = DNAN;
4505             dst->vf.when = 0;
4506         } else {
4507             dst->vf.val = data[step * src->ds_cnt];
4508             dst->vf.when = src->start + (step + 1) * src->step;
4509         }
4510         while (step != steps) {
4511             if (finite(data[step * src->ds_cnt])) {
4512                 if (data[step * src->ds_cnt] > dst->vf.val) {
4513                     dst->vf.val = data[step * src->ds_cnt];
4514                     dst->vf.when = src->start + (step + 1) * src->step;
4515                 }
4516             }
4517             step++;
4518         }
4519         break;
4520     case VDEF_TOTAL:
4521     case VDEF_STDEV:
4522     case VDEF_AVERAGE:{
4523         int       cnt = 0;
4524         double    sum = 0.0;
4525         double    average = 0.0;
4527         for (step = 0; step < steps; step++) {
4528             if (finite(data[step * src->ds_cnt])) {
4529                 sum += data[step * src->ds_cnt];
4530                 cnt++;
4531             };
4532         }
4533         if (cnt) {
4534             if (dst->vf.op == VDEF_TOTAL) {
4535                 dst->vf.val = sum * src->step;
4536                 dst->vf.when = 0;   /* no time component */
4537             } else if (dst->vf.op == VDEF_AVERAGE) {
4538                 dst->vf.val = sum / cnt;
4539                 dst->vf.when = 0;   /* no time component */
4540             } else {
4541                 average = sum / cnt;
4542                 sum = 0.0;
4543                 for (step = 0; step < steps; step++) {
4544                     if (finite(data[step * src->ds_cnt])) {
4545                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
4546                     };
4547                 }
4548                 dst->vf.val = pow(sum / cnt, 0.5);
4549                 dst->vf.when = 0;   /* no time component */
4550             };
4551         } else {
4552             dst->vf.val = DNAN;
4553             dst->vf.when = 0;
4554         }
4555     }
4556         break;
4557     case VDEF_MINIMUM:
4558         step = 0;
4559         while (step != steps && isnan(data[step * src->ds_cnt]))
4560             step++;
4561         if (step == steps) {
4562             dst->vf.val = DNAN;
4563             dst->vf.when = 0;
4564         } else {
4565             dst->vf.val = data[step * src->ds_cnt];
4566             dst->vf.when = src->start + (step + 1) * src->step;
4567         }
4568         while (step != steps) {
4569             if (finite(data[step * src->ds_cnt])) {
4570                 if (data[step * src->ds_cnt] < dst->vf.val) {
4571                     dst->vf.val = data[step * src->ds_cnt];
4572                     dst->vf.when = src->start + (step + 1) * src->step;
4573                 }
4574             }
4575             step++;
4576         }
4577         break;
4578     case VDEF_FIRST:
4579         /* The time value returned here is one step before the
4580          * actual time value.  This is the start of the first
4581          * non-NaN interval.
4582          */
4583         step = 0;
4584         while (step != steps && isnan(data[step * src->ds_cnt]))
4585             step++;
4586         if (step == steps) {    /* all entries were NaN */
4587             dst->vf.val = DNAN;
4588             dst->vf.when = 0;
4589         } else {
4590             dst->vf.val = data[step * src->ds_cnt];
4591             dst->vf.when = src->start + step * src->step;
4592         }
4593         break;
4594     case VDEF_LAST:
4595         /* The time value returned here is the
4596          * actual time value.  This is the end of the last
4597          * non-NaN interval.
4598          */
4599         step = steps - 1;
4600         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4601             step--;
4602         if (step < 0) { /* all entries were NaN */
4603             dst->vf.val = DNAN;
4604             dst->vf.when = 0;
4605         } else {
4606             dst->vf.val = data[step * src->ds_cnt];
4607             dst->vf.when = src->start + (step + 1) * src->step;
4608         }
4609         break;
4610     case VDEF_LSLSLOPE:
4611     case VDEF_LSLINT:
4612     case VDEF_LSLCORREL:{
4613         /* Bestfit line by linear least squares method */
4615         int       cnt = 0;
4616         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4618         SUMx = 0;
4619         SUMy = 0;
4620         SUMxy = 0;
4621         SUMxx = 0;
4622         SUMyy = 0;
4623         for (step = 0; step < steps; step++) {
4624             if (finite(data[step * src->ds_cnt])) {
4625                 cnt++;
4626                 SUMx += step;
4627                 SUMxx += step * step;
4628                 SUMxy += step * data[step * src->ds_cnt];
4629                 SUMy += data[step * src->ds_cnt];
4630                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4631             };
4632         }
4634         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4635         y_intercept = (SUMy - slope * SUMx) / cnt;
4636         correl =
4637             (SUMxy -
4638              (SUMx * SUMy) / cnt) /
4639             sqrt((SUMxx -
4640                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4641         if (cnt) {
4642             if (dst->vf.op == VDEF_LSLSLOPE) {
4643                 dst->vf.val = slope;
4644                 dst->vf.when = 0;
4645             } else if (dst->vf.op == VDEF_LSLINT) {
4646                 dst->vf.val = y_intercept;
4647                 dst->vf.when = 0;
4648             } else if (dst->vf.op == VDEF_LSLCORREL) {
4649                 dst->vf.val = correl;
4650                 dst->vf.when = 0;
4651             };
4652         } else {
4653             dst->vf.val = DNAN;
4654             dst->vf.when = 0;
4655         }
4656     }
4657         break;
4658     }
4659     return 0;
4662 /* NaN < -INF < finite_values < INF */
4663 int vdef_percent_compar(
4664     const void
4665     *a,
4666     const void
4667     *b)
4669     /* Equality is not returned; this doesn't hurt except
4670      * (maybe) for a little performance.
4671      */
4673     /* First catch NaN values. They are smallest */
4674     if (isnan(*(double *) a))
4675         return -1;
4676     if (isnan(*(double *) b))
4677         return 1;
4678     /* NaN doesn't reach this part so INF and -INF are extremes.
4679      * The sign from isinf() is compatible with the sign we return
4680      */
4681     if (isinf(*(double *) a))
4682         return isinf(*(double *) a);
4683     if (isinf(*(double *) b))
4684         return isinf(*(double *) b);
4685     /* If we reach this, both values must be finite */
4686     if (*(double *) a < *(double *) b)
4687         return -1;
4688     else
4689         return 1;
4692 void grinfo_push(
4693     image_desc_t *im,
4694     char *key,
4695     rrd_info_type_t type,
4696     rrd_infoval_t value)
4698     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4699     if (im->grinfo == NULL) {
4700         im->grinfo = im->grinfo_current;
4701     }