Code

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