VDEF and VRULE are comig along -- Alex van den Bogaerdt <alex@slot.hollandcasino.nl>
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.0.33  Copyright Tobias Oetiker, 1997 - 2000
3  ****************************************************************************
4  * rrd__graph.c  make creates ne rrds
5  ****************************************************************************/
6
7 #include "rrd_tool.h"
8 #include <gd.h>
9 #include <gdlucidan10.h>
10 #include <gdlucidab12.h>
11 #include <sys/stat.h>
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
16 #include "rrd_rpncalc.h"
17
18 #define SmallFont gdLucidaNormal10
19 #define LargeFont gdLucidaBold12
20
21 /* #define DEBUG */
22
23 #ifdef DEBUG
24 # define DPRINT(x)    (void)(printf x, printf("\n"))
25 #else
26 # define DPRINT(x)
27 #endif
28
29 #define DEF_NAM_FMT "%29[_A-Za-z0-9]"
30
31 enum tmt_en {TMT_SECOND=0,TMT_MINUTE,TMT_HOUR,TMT_DAY,
32              TMT_WEEK,TMT_MONTH,TMT_YEAR};
33
34 enum grc_en {GRC_CANVAS=0,GRC_BACK,GRC_SHADEA,GRC_SHADEB,
35              GRC_GRID,GRC_MGRID,GRC_FONT,GRC_FRAME,GRC_ARROW,__GRC_END__};
36
37
38 enum gf_en {GF_PRINT=0,GF_GPRINT,GF_COMMENT,GF_HRULE,GF_VRULE,GF_LINE1,
39             GF_LINE2,GF_LINE3,GF_AREA,GF_STACK,GF_TICK,
40             GF_DEF, GF_CDEF, GF_VDEF};
41
42 enum if_en {IF_GIF=0,IF_PNG=1};
43
44 enum vdef_op_en {
45                  VDEF_MAXIMUM   /* like the MAX in (G)PRINT */
46                 ,VDEF_MINIMUM   /* like the MIN in (G)PRINT */
47                 ,VDEF_AVERAGE   /* like the AVERAGE in (G)PRINT */
48                 ,VDEF_PERCENT   /* Nth percentile */
49                 ,VDEF_FIRST     /* first non-unknown value and time */
50                 ,VDEF_LAST      /* last  non-unknown value and time */
51                 };
52 typedef struct vdef_t {
53     enum vdef_op_en     op;
54     double              param;  /* parameter for function, if applicable */
55     double              val;    /* resulting value */
56     time_t              when;   /* timestamp, if applicable */
57 } vdef_t;
58
59 typedef struct col_trip_t {
60     int red; /* red = -1 is no color */
61     int green;
62     int blue;
63     int i; /* color index assigned in gif image i=-1 is unasigned*/
64 } col_trip_t;
65
66
67 typedef struct xlab_t {
68     long         minsec;       /* minimum sec per pix */
69     enum tmt_en  gridtm;       /* grid interval in what ?*/
70     long         gridst;       /* how many whats per grid*/
71     enum tmt_en  mgridtm;      /* label interval in what ?*/
72     long         mgridst;      /* how many whats per label*/
73     enum tmt_en  labtm;        /* label interval in what ?*/
74     long         labst;        /* how many whats per label*/
75     long         precis;       /* label precision -> label placement*/
76     char         *stst;        /* strftime string*/
77 } xlab_t;
78
79 xlab_t xlab[] = {
80     {0,        TMT_SECOND,30, TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
81     {2,        TMT_MINUTE,1,  TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
82     {5,        TMT_MINUTE,2,  TMT_MINUTE,10, TMT_MINUTE,10,        0,"%H:%M"},
83     {10,       TMT_MINUTE,5,  TMT_MINUTE,20, TMT_MINUTE,20,        0,"%H:%M"},
84     {30,       TMT_MINUTE,10, TMT_HOUR,1,    TMT_HOUR,1,           0,"%H:%M"},
85     {60,       TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,2,           0,"%H:%M"},
86     {180,      TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           0,"%H:%M"},
87     /*{300,      TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly*/
88     {600,      TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%a"},
89     {1800,     TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a"},
90     {3600,     TMT_DAY,1,     TMT_WEEK,1,     TMT_WEEK,1,    7*24*3600,"Week %W"},
91     {3*3600,   TMT_WEEK,1,      TMT_MONTH,1,     TMT_WEEK,2,    7*24*3600,"Week %W"},
92     {6*3600,   TMT_MONTH,1,   TMT_MONTH,1,   TMT_MONTH,1, 30*24*3600,"%b"},
93     {48*3600,  TMT_MONTH,1,   TMT_MONTH,3,   TMT_MONTH,3, 30*24*3600,"%b"},
94     {10*24*3600, TMT_YEAR,1,  TMT_YEAR,1,    TMT_YEAR,1, 365*24*3600,"%y"},
95     {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
96 };
97
98 /* sensible logarithmic y label intervals ...
99    the first element of each row defines the possible starting points on the
100    y axis ... the other specify the */
101
102 double yloglab[][12]= {{ 1e9, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
103                        {  1e3, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
104                        {  1e1, 1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
105                        /* {  1e1, 1,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0 }, */
106                        {  1e1, 1,  2.5,  5,  7.5,  0,  0,  0,  0,  0,  0,  0 },
107                        {  1e1, 1,  2,  4,  6,  8,  0,  0,  0,  0,  0,  0 },
108                        {  1e1, 1,  2,  3,  4,  5,  6,  7,  8,  9,  0,  0 },
109                        {  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }};
110
111 /* sensible y label intervals ...*/
112
113 typedef struct ylab_t {
114     double   grid;    /* grid spacing */
115     int      lfac[4]; /* associated label spacing*/
116 } ylab_t;
117
118 ylab_t ylab[]= {
119     {0.1, {1,2, 5,10}},
120     {0.2, {1,5,10,20}},
121     {0.5, {1,2, 4,10}},
122     {1.0,   {1,2, 5,10}},
123     {2.0,   {1,5,10,20}},
124     {5.0,   {1,2, 4,10}},
125     {10.0,  {1,2, 5,10}},
126     {20.0,  {1,5,10,20}},
127     {50.0,  {1,2, 4,10}},
128     {100.0, {1,2, 5,10}},
129     {200.0, {1,5,10,20}},
130     {500.0, {1,2, 4,10}},
131     {0.0,   {0,0,0,0}}};
132
133
134
135 col_trip_t graph_col[] = { /* default colors */
136     {255,255,255,-1},   /* canvas */
137     {245,245,245,-1},   /* background */
138     {200,200,200,-1},   /* shade A    */
139     {150,150,150,-1},   /* shade B    */
140     {140,140,140,-1},      /* grid */
141     {130,30,30,-1},      /* major grid */
142     {0,0,0,-1},         /* font */      
143     {0,0,0,-1},         /* frame */
144     {255,0,0,-1}        /*arrow*/
145 };
146
147 /* this structure describes the elements which can make up a graph.
148    because they are quite diverse, not all elements will use all the
149    possible parts of the structure. */
150 #ifdef HAVE_SNPRINTF
151 #define FMT_LEG_LEN 200
152 #else
153 #define FMT_LEG_LEN 2000
154 #endif
155
156 typedef  struct graph_desc_t {
157     enum gf_en     gf;         /* graphing function */
158     char           vname[30];  /* name of the variable */
159     long           vidx;       /* gdes reference */
160     char           rrd[255];   /* name of the rrd_file containing data */
161     char           ds_nam[DS_NAM_SIZE]; /* data source name */
162     long           ds;         /* data source number */
163     enum cf_en     cf;         /* consolidation function */
164     col_trip_t     col;        /* graph color */
165     char           format[FMT_LEG_LEN+5]; /* format for PRINT AND GPRINT */
166     char           legend[FMT_LEG_LEN+5]; /* legend*/
167     gdPoint        legloc;     /* location of legend */   
168     double         yrule;      /* value for y rule line and for VDEF */
169     time_t         xrule;      /* time for x rule line and for VDEF */
170     vdef_t         vf;         /* instruction for VDEF function */
171     rpnp_t         *rpnp;     /* instructions for CDEF function */
172
173     /* description of data fetched for the graph element */
174     time_t         start,end; /* timestaps for first and last data element */
175     unsigned long  step;      /* time between samples */
176     unsigned long  ds_cnt; /* how many data sources are there in the fetch */
177     long           data_first; /* first pointer to this data */
178     char           **ds_namv; /* name of datasources  in the fetch. */
179     rrd_value_t    *data; /* the raw data drawn from the rrd */
180     rrd_value_t    *p_data; /* processed data, xsize elments */
181
182 } graph_desc_t;
183
184 #define ALTYGRID          0x01  /* use alternative y grid algorithm */
185 #define ALTAUTOSCALE      0x02  /* use alternative algorithm to find lower and upper bounds */
186 #define ALTAUTOSCALE_MAX  0x04  /* use alternative algorithm to find upper bounds */
187 #define NOLEGEND          0x08  /* use no legend */
188
189 typedef struct image_desc_t {
190
191     /* configuration of graph */
192
193     char           graphfile[MAXPATH]; /* filename for graphic */
194     long           xsize,ysize;    /* graph area size in pixels */
195     col_trip_t     graph_col[__GRC_END__]; /* real colors for the graph */   
196     char           ylegend[200];   /* legend along the yaxis */
197     char           title[200];     /* title for graph */
198     int            draw_x_grid;      /* no x-grid at all */
199     int            draw_y_grid;      /* no x-grid at all */
200     xlab_t         xlab_user;      /* user defined labeling for xaxis */
201     char           xlab_form[200]; /* format for the label on the xaxis */
202
203     double         ygridstep;      /* user defined step for y grid */
204     int            ylabfact;       /* every how many y grid shall a label be written ? */
205
206     time_t         start,end;      /* what time does the graph cover */
207     unsigned long           step;           /* any preference for the default step ? */
208     rrd_value_t    minval,maxval;  /* extreme values in the data */
209     int            rigid;          /* do not expand range even with 
210                                       values outside */
211     char*          imginfo;         /* construct an <IMG ... tag and return 
212                                       as first retval */
213     int            lazy;           /* only update the gif if there is reasonable
214                                       probablility that the existing one is out of date */
215     int            logarithmic;    /* scale the yaxis logarithmic */
216     enum if_en     imgformat;         /* image format */
217     
218     /* status information */
219             
220     long           xorigin,yorigin;/* where is (0,0) of the graph */
221     long           xgif,ygif;      /* total size of the gif */
222     int            interlaced;     /* will the graph be interlaced? */
223     double         magfact;        /* numerical magnitude*/
224     long         base;            /* 1000 or 1024 depending on what we graph */
225     char           symbol;         /* magnitude symbol for y-axis */
226     int            unitsexponent;    /* 10*exponent for units on y-asis */
227     int            extra_flags;    /* flags for boolean options */
228     /* data elements */
229
230     long  prt_c;                  /* number of print elements */
231     long  gdes_c;                  /* number of graphics elements */
232     graph_desc_t   *gdes;          /* points to an array of graph elements */
233
234 } image_desc_t;
235
236 /* Prototypes */
237 int xtr(image_desc_t *,time_t);
238 int ytr(image_desc_t *, double);
239 enum gf_en gf_conv(char *);
240 enum if_en if_conv(char *);
241 enum tmt_en tmt_conv(char *);
242 enum grc_en grc_conv(char *);
243 int im_free(image_desc_t *);
244 void auto_scale( image_desc_t *,  double *, char **, double *);
245 void si_unit( image_desc_t *);
246 void expand_range(image_desc_t *);
247 void reduce_data( enum cf_en,  unsigned long,  time_t *, time_t *,  unsigned long *,  unsigned long *,  rrd_value_t **);
248 int data_fetch( image_desc_t *);
249 long find_var(image_desc_t *, char *);
250 long find_var_wrapper(void *arg1, char *key);
251 long lcd(long *);
252 int data_calc( image_desc_t *);
253 int data_proc( image_desc_t *);
254 time_t find_first_time( time_t,  enum tmt_en,  long);
255 time_t find_next_time( time_t,  enum tmt_en,  long);
256 void gator( gdImagePtr, int, int);
257 int print_calc(image_desc_t *, char ***);
258 int leg_place(image_desc_t *);
259 int horizontal_grid(gdImagePtr, image_desc_t *);
260 int horizontal_log_grid(gdImagePtr, image_desc_t *);
261 void vertical_grid( gdImagePtr, image_desc_t *);
262 void axis_paint( image_desc_t *, gdImagePtr);
263 void grid_paint( image_desc_t *, gdImagePtr);
264 gdImagePtr MkLineBrush(image_desc_t *,long, enum gf_en);
265 int lazy_check(image_desc_t *);
266 int graph_paint(image_desc_t *, char ***);
267 int gdes_alloc(image_desc_t *);
268 int scan_for_col(char *, int, char *);
269 int rrd_graph(int, char **, char ***, int *, int *);
270 int bad_format(char *);
271 int vdef_parse(struct graph_desc_t *,char *);
272 int vdef_calc(image_desc_t *, int);
273 int vdef_percent_compar(const void *,const void *);
274
275 /* translate time values into x coordinates */   
276 /*#define xtr(x) (int)((double)im->xorigin \
277                 + ((double) im->xsize / (double)(im->end - im->start) ) \
278                 * ((double)(x) - im->start)+0.5) */
279 /* initialize with xtr(im,0); */
280 int
281 xtr(image_desc_t *im,time_t mytime){
282     static double pixie;
283     if (mytime==0){
284         pixie = (double) im->xsize / (double)(im->end - im->start);
285         return im->xorigin;
286     }
287     return (int)((double)im->xorigin 
288                  + pixie * ( mytime - im->start ) );
289 }
290
291 /* translate data values into y coordinates */
292
293 /* #define ytr(x) (int)((double)im->yorigin \
294                 - ((double) im->ysize / (im->maxval - im->minval) ) \
295                 * ((double)(x) - im->minval)+0.5) */
296 int
297 ytr(image_desc_t *im, double value){
298     static double pixie;
299     double yval;
300     if (isnan(value)){
301       if(!im->logarithmic)
302         pixie = (double) im->ysize / (im->maxval - im->minval);
303       else 
304         pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
305       yval = im->yorigin;
306     } else if(!im->logarithmic) {
307       yval = im->yorigin - pixie * (value - im->minval) + 0.5;
308     } else {
309       if (value < im->minval) {
310         yval = im->yorigin;
311       } else {
312         yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
313       }
314     }
315     /* make sure we don't return anything too unreasonable. GD lib can
316        get terribly slow when drawing lines outside its scope. This is 
317        especially problematic in connection with the rigid option */
318     if (! im->rigid) {
319       return (int)yval;
320     } else if ((int)yval > im->yorigin) {
321       return im->yorigin+2;
322     } else if ((int) yval < im->yorigin - im->ysize){
323       return im->yorigin - im->ysize - 2;
324     } else {
325       return (int)yval;
326     } 
327 }
328
329    
330     
331
332
333 /* conversion function for symbolic entry names */
334
335
336 #define conv_if(VV,VVV) \
337    if (strcmp(#VV, string) == 0) return VVV ;
338
339 enum gf_en gf_conv(char *string){
340     
341     conv_if(PRINT,GF_PRINT)
342     conv_if(GPRINT,GF_GPRINT)
343     conv_if(COMMENT,GF_COMMENT)
344     conv_if(HRULE,GF_HRULE)
345     conv_if(VRULE,GF_VRULE)
346     conv_if(LINE1,GF_LINE1)
347     conv_if(LINE2,GF_LINE2)
348     conv_if(LINE3,GF_LINE3)
349     conv_if(AREA,GF_AREA)
350     conv_if(STACK,GF_STACK)
351         conv_if(TICK,GF_TICK)
352     conv_if(DEF,GF_DEF)
353     conv_if(CDEF,GF_CDEF)
354     conv_if(VDEF,GF_VDEF)
355     
356     return (-1);
357 }
358
359 enum if_en if_conv(char *string){
360     
361     conv_if(GIF,IF_GIF)
362     conv_if(PNG,IF_PNG)
363
364     return (-1);
365 }
366
367 enum tmt_en tmt_conv(char *string){
368
369     conv_if(SECOND,TMT_SECOND)
370     conv_if(MINUTE,TMT_MINUTE)
371     conv_if(HOUR,TMT_HOUR)
372     conv_if(DAY,TMT_DAY)
373     conv_if(WEEK,TMT_WEEK)
374     conv_if(MONTH,TMT_MONTH)
375     conv_if(YEAR,TMT_YEAR)
376     return (-1);
377 }
378
379 enum grc_en grc_conv(char *string){
380
381     conv_if(BACK,GRC_BACK)
382     conv_if(CANVAS,GRC_CANVAS)
383     conv_if(SHADEA,GRC_SHADEA)
384     conv_if(SHADEB,GRC_SHADEB)
385     conv_if(GRID,GRC_GRID)
386     conv_if(MGRID,GRC_MGRID)
387     conv_if(FONT,GRC_FONT)
388     conv_if(FRAME,GRC_FRAME)
389     conv_if(ARROW,GRC_ARROW)
390
391     return -1;  
392 }
393
394 #undef conv_if
395
396
397
398 int
399 im_free(image_desc_t *im)
400 {
401     long i,ii;
402     if (im == NULL) return 0;
403     for(i=0;i<im->gdes_c;i++){
404       if (im->gdes[i].data_first){
405         /* careful here, because a single pointer can occur several times */
406           free (im->gdes[i].data);
407           if (im->gdes[i].ds_namv){
408               for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
409                   free(im->gdes[i].ds_namv[ii]);
410               free(im->gdes[i].ds_namv);
411           }
412       }
413       free (im->gdes[i].p_data);
414       free (im->gdes[i].rpnp);
415     }
416     free(im->gdes);
417     return 0;
418 }
419
420 /* find SI magnitude symbol for the given number*/
421 void
422 auto_scale(
423            image_desc_t *im,   /* image description */
424            double *value,
425            char **symb_ptr,
426            double *magfact
427            )
428 {
429         
430     char *symbol[] = {"a", /* 10e-18 Ato */
431                       "f", /* 10e-15 Femto */
432                       "p", /* 10e-12 Pico */
433                       "n", /* 10e-9  Nano */
434                       "u", /* 10e-6  Micro */
435                       "m", /* 10e-3  Milli */
436                       " ", /* Base */
437                       "k", /* 10e3   Kilo */
438                       "M", /* 10e6   Mega */
439                       "G", /* 10e9   Giga */
440                       "T", /* 10e12  Terra */
441                       "P", /* 10e15  Peta */
442                       "E"};/* 10e18  Exa */
443
444     int symbcenter = 6;
445     int sindex;  
446
447     if (*value == 0.0 || isnan(*value) ) {
448         sindex = 0;
449         *magfact = 1.0;
450     } else {
451         sindex = floor(log(fabs(*value))/log((double)im->base)); 
452         *magfact = pow((double)im->base, (double)sindex);
453         (*value) /= (*magfact);
454     }
455     if ( sindex <= symbcenter && sindex >= -symbcenter) {
456         (*symb_ptr) = symbol[sindex+symbcenter];
457     }
458     else {
459         (*symb_ptr) = "?";
460     }
461 }
462
463
464 /* find SI magnitude symbol for the numbers on the y-axis*/
465 void 
466 si_unit(
467     image_desc_t *im   /* image description */
468 )
469 {
470
471     char symbol[] = {'a', /* 10e-18 Ato */ 
472                      'f', /* 10e-15 Femto */
473                      'p', /* 10e-12 Pico */
474                      'n', /* 10e-9  Nano */
475                      'u', /* 10e-6  Micro */
476                      'm', /* 10e-3  Milli */
477                      ' ', /* Base */
478                      'k', /* 10e3   Kilo */
479                      'M', /* 10e6   Mega */
480                      'G', /* 10e9   Giga */
481                      'T', /* 10e12  Terra */
482                      'P', /* 10e15  Peta */
483                      'E'};/* 10e18  Exa */
484
485     int   symbcenter = 6;
486     double digits;  
487     
488     if (im->unitsexponent != 9999) {
489         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
490         digits = floor(im->unitsexponent / 3);
491     } else {
492         digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base)); 
493     }
494     im->magfact = pow((double)im->base , digits);
495
496 #ifdef DEBUG
497     printf("digits %6.3f  im->magfact %6.3f\n",digits,im->magfact);
498 #endif
499
500     if ( ((digits+symbcenter) < sizeof(symbol)) &&
501                     ((digits+symbcenter) >= 0) )
502         im->symbol = symbol[(int)digits+symbcenter];
503     else
504         im->symbol = ' ';
505  }
506
507 /*  move min and max values around to become sensible */
508
509 void 
510 expand_range(image_desc_t *im)
511 {
512     double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
513                               600.0,500.0,400.0,300.0,250.0,
514                               200.0,125.0,100.0,90.0,80.0,
515                               75.0,70.0,60.0,50.0,40.0,30.0,
516                               25.0,20.0,10.0,9.0,8.0,
517                               7.0,6.0,5.0,4.0,3.5,3.0,
518                               2.5,2.0,1.8,1.5,1.2,1.0,
519                               0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
520     
521     double scaled_min,scaled_max;  
522     double adj;
523     int i;
524     
525
526     
527 #ifdef DEBUG
528     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
529            im->minval,im->maxval,im->magfact);
530 #endif
531
532     if (isnan(im->ygridstep)){
533         if(im->extra_flags & ALTAUTOSCALE) {
534             /* measure the amplitude of the function. Make sure that
535                graph boundaries are slightly higher then max/min vals
536                so we can see amplitude on the graph */
537               double delt, fact;
538
539               delt = im->maxval - im->minval;
540               adj = delt * 0.1;
541               fact = 2.0 * pow(10.0,
542                     floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
543               if (delt < fact) {
544                 adj = (fact - delt) * 0.55;
545 #ifdef DEBUG
546               printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
547 #endif
548               }
549               im->minval -= adj;
550               im->maxval += adj;
551         }
552         else if(im->extra_flags & ALTAUTOSCALE_MAX) {
553             /* measure the amplitude of the function. Make sure that
554                graph boundaries are slightly higher than max vals
555                so we can see amplitude on the graph */
556               adj = (im->maxval - im->minval) * 0.1;
557               im->maxval += adj;
558         }
559         else {
560             scaled_min = im->minval / im->magfact;
561             scaled_max = im->maxval / im->magfact;
562             
563             for (i=1; sensiblevalues[i] > 0; i++){
564                 if (sensiblevalues[i-1]>=scaled_min &&
565                     sensiblevalues[i]<=scaled_min)      
566                     im->minval = sensiblevalues[i]*(im->magfact);
567                 
568                 if (-sensiblevalues[i-1]<=scaled_min &&
569                 -sensiblevalues[i]>=scaled_min)
570                     im->minval = -sensiblevalues[i-1]*(im->magfact);
571                 
572                 if (sensiblevalues[i-1] >= scaled_max &&
573                     sensiblevalues[i] <= scaled_max)
574                     im->maxval = sensiblevalues[i-1]*(im->magfact);
575                 
576                 if (-sensiblevalues[i-1]<=scaled_max &&
577                     -sensiblevalues[i] >=scaled_max)
578                     im->maxval = -sensiblevalues[i]*(im->magfact);
579             }
580         }
581     } else {
582         /* adjust min and max to the grid definition if there is one */
583         im->minval = (double)im->ylabfact * im->ygridstep * 
584             floor(im->minval / ((double)im->ylabfact * im->ygridstep));
585         im->maxval = (double)im->ylabfact * im->ygridstep * 
586             ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
587     }
588     
589 #ifdef DEBUG
590     fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
591            im->minval,im->maxval,im->magfact);
592 #endif
593 }
594
595     
596 /* reduce data reimplementation by Alex */
597
598 void
599 reduce_data(
600     enum cf_en     cf,         /* which consolidation function ?*/
601     unsigned long  cur_step,   /* step the data currently is in */
602     time_t         *start,     /* start, end and step as requested ... */
603     time_t         *end,       /* ... by the application will be   ... */
604     unsigned long  *step,      /* ... adjusted to represent reality    */
605     unsigned long  *ds_cnt,    /* number of data sources in file */
606     rrd_value_t    **data)     /* two dimensional array containing the data */
607 {
608     int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
609     unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
610     rrd_value_t    *srcptr,*dstptr;
611
612     (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
613     dstptr = *data;
614     srcptr = *data;
615
616     /* We were given one extra row at the beginning of the interval.
617     ** We also need to return one extra row.  The extra interval is
618     ** the one defined by the start time in both cases.  It is not
619     ** used when graphing but maybe we can use it while reducing the
620     ** data.
621     */
622     row_cnt = ((*end)-(*start))/cur_step +1;
623
624     /* alter start and end so that they are multiples of the new steptime.
625     ** End will be shifted towards the future and start will be shifted
626     ** towards the past in order to include the requested interval
627     */ 
628     end_offset = (*end) % (*step);
629     if (end_offset) end_offset = (*step)-end_offset;
630     start_offset = (*start) % (*step);
631     (*end) = (*end)+end_offset;
632     (*start) = (*start)-start_offset;
633
634     /* The first destination row is unknown yet it still needs
635     ** to be present in the returned data.  Skip it.
636     ** Don't make it NaN or we might overwrite the source.
637     */
638     dstptr += (*ds_cnt);
639
640     /* Depending on the amount of extra data needed at the
641     ** start of the destination, three things can happen:
642     ** -1- start_offset == 0:  skip the extra source row
643     ** -2- start_offset == cur_step: do nothing
644     ** -3- start_offset > cur_step: skip some source rows and 
645     **                      fill one destination row with NaN
646     */
647     if (start_offset==0) {
648         srcptr+=(*ds_cnt);
649         row_cnt--;
650     } else if (start_offset!=cur_step) {
651         skiprows=((*step)-start_offset)/cur_step+1;
652         srcptr += ((*ds_cnt)*skiprows);
653         row_cnt-=skiprows;
654         for (col=0;col<(*ds_cnt);col++) *dstptr++=DNAN;
655     }
656
657     /* If we had to alter the endtime, there won't be
658     ** enough data to fill the last row.  This means
659     ** we have to skip some rows at the end
660     */
661     if (end_offset) {
662         skiprows = ((*step)-end_offset)/cur_step;
663         row_cnt-=skiprows;
664     }
665
666
667 /* Sanity check: row_cnt should be multiple of reduce_factor */
668 /* if this gets triggered, something is REALY WRONG ... we die immediately */
669
670     if (row_cnt%reduce_factor) {
671         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
672                                 row_cnt,reduce_factor);
673         printf("BUG in reduce_data()\n");
674         exit(1);
675     }
676
677     /* Now combine reduce_factor intervals at a time
678     ** into one interval for the destination.
679     */
680
681     for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
682         for (col=0;col<(*ds_cnt);col++) {
683             rrd_value_t newval=DNAN;
684             unsigned long validval=0;
685
686             for (i=0;i<reduce_factor;i++) {
687                 if (isnan(srcptr[i*(*ds_cnt)+col])) {
688                     continue;
689                 }
690                 validval++;
691                 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
692                 else {
693                     switch (cf) {
694                         case CF_HWPREDICT:
695                         case CF_DEVSEASONAL:
696                         case CF_DEVPREDICT:
697                         case CF_SEASONAL:
698                         case CF_AVERAGE:
699                             newval += srcptr[i*(*ds_cnt)+col];
700                             break;
701                         case CF_MINIMUM:
702                             newval = min (newval,srcptr[i*(*ds_cnt)+col]);
703                             break;
704                         case CF_FAILURES: 
705                         /* an interval contains a failure if any subintervals contained a failure */
706                         case CF_MAXIMUM:
707                             newval = max (newval,srcptr[i*(*ds_cnt)+col]);
708                             break;
709                         case CF_LAST:
710                             newval = srcptr[i*(*ds_cnt)+col];
711                             break;
712                     }
713                 }
714             }
715             if (validval == 0){newval = DNAN;} else{
716                 switch (cf) {
717                     case CF_HWPREDICT:
718             case CF_DEVSEASONAL:
719                     case CF_DEVPREDICT:
720                     case CF_SEASONAL:
721                     case CF_AVERAGE:                
722                        newval /= validval;
723                         break;
724                     case CF_MINIMUM:
725                     case CF_FAILURES:
726                     case CF_MAXIMUM:
727                     case CF_LAST:
728                         break;
729                 }
730             }
731             *dstptr++=newval;
732         }
733         srcptr+=(*ds_cnt)*reduce_factor;
734         row_cnt-=reduce_factor;
735     }
736
737     /* If we had to alter the endtime, we didn't have enough
738     ** source rows to fill the last row. Fill it with NaN.
739     */
740     if (end_offset!=0) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
741 }
742
743
744 /* get the data required for the graphs from the 
745    relevant rrds ... */
746
747 int
748 data_fetch( image_desc_t *im )
749 {
750     int       i,ii;
751     int skip;
752     /* pull the data from the log files ... */
753     for (i=0;i<im->gdes_c;i++){
754         /* only GF_DEF elements fetch data */
755         if (im->gdes[i].gf != GF_DEF) 
756             continue;
757
758         skip=0;
759         /* do we have it already ?*/
760         for (ii=0;ii<i;ii++){
761             if (im->gdes[ii].gf != GF_DEF) 
762                 continue;
763             if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
764                 && (im->gdes[i].cf == im->gdes[ii].cf)){
765                 /* OK the data it is here already ... 
766                  * we just copy the header portion */
767                 im->gdes[i].start = im->gdes[ii].start;
768                 im->gdes[i].end = im->gdes[ii].end;
769                 im->gdes[i].step = im->gdes[ii].step;
770                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
771                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;             
772                 im->gdes[i].data = im->gdes[ii].data;
773                 im->gdes[i].data_first = 0;
774                 skip=1;
775             }
776             if (skip) 
777                 break;
778         }
779         if (! skip) {
780             unsigned long  ft_step = im->gdes[i].step ;
781             
782             if((rrd_fetch_fn(im->gdes[i].rrd,
783                              im->gdes[i].cf,
784                              &im->gdes[i].start,
785                              &im->gdes[i].end,
786                              &ft_step,
787                              &im->gdes[i].ds_cnt,
788                              &im->gdes[i].ds_namv,
789                              &im->gdes[i].data)) == -1){                
790                 return -1;
791             }
792             im->gdes[i].data_first = 1;     
793         
794             if (ft_step < im->gdes[i].step) {
795                 reduce_data(im->gdes[i].cf,
796                             ft_step,
797                             &im->gdes[i].start,
798                             &im->gdes[i].end,
799                             &im->gdes[i].step,
800                             &im->gdes[i].ds_cnt,
801                             &im->gdes[i].data);
802             } else {
803                 im->gdes[i].step = ft_step;
804             }
805         }
806         
807         /* lets see if the required data source is realy there */
808         for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
809             if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
810                 im->gdes[i].ds=ii; }
811         }
812         if (im->gdes[i].ds== -1){
813             rrd_set_error("No DS called '%s' in '%s'",
814                           im->gdes[i].ds_nam,im->gdes[i].rrd);
815             return -1; 
816         }
817         
818     }
819     return 0;
820 }
821
822 /* evaluate the expressions in the CDEF functions */
823
824 /*************************************************************
825  * CDEF stuff 
826  *************************************************************/
827
828 long
829 find_var_wrapper(void *arg1, char *key)
830 {
831    return find_var((image_desc_t *) arg1, key);
832 }
833
834 /* find gdes containing var*/
835 long
836 find_var(image_desc_t *im, char *key){
837     long ii;
838     for(ii=0;ii<im->gdes_c-1;ii++){
839         if((im->gdes[ii].gf == GF_DEF 
840             || im->gdes[ii].gf == GF_VDEF
841             || im->gdes[ii].gf == GF_CDEF) 
842            && (strcmp(im->gdes[ii].vname,key) == 0)){
843             return ii; 
844         }          
845     }               
846     return -1;
847 }
848
849 /* find the largest common denominator for all the numbers
850    in the 0 terminated num array */
851 long
852 lcd(long *num){
853     long rest;
854     int i;
855     for (i=0;num[i+1]!=0;i++){
856         do { 
857             rest=num[i] % num[i+1];
858             num[i]=num[i+1]; num[i+1]=rest;
859         } while (rest!=0);
860         num[i+1] = num[i];
861     }
862 /*    return i==0?num[i]:num[i-1]; */
863       return num[i];
864 }
865
866 /* run the rpn calculator on all the VDEF and CDEF arguments */
867 int
868 data_calc( image_desc_t *im){
869
870     int       gdi;
871     int       dataidx;
872     long      *steparray, rpi;
873     int       stepcnt;
874     time_t    now;
875     rpnstack_t rpnstack;
876
877     rpnstack_init(&rpnstack);
878
879     for (gdi=0;gdi<im->gdes_c;gdi++){
880         /* Look for GF_VDEF and GF_CDEF in the same loop,
881          * so CDEFs can use VDEFs and vice versa
882          */
883         switch (im->gdes[gdi].gf) {
884             case GF_VDEF:
885                 /* A VDEF has no DS.  This also signals other parts
886                  * of rrdtool that this is a VDEF value, not a CDEF.
887                  */
888                 im->gdes[gdi].ds_cnt = 0;
889                 if (vdef_calc(im,gdi)) {
890                     rrd_set_error("Error processing VDEF '%s'"
891                         ,im->gdes[gdi].vname
892                         );
893                     rpnstack_free(&rpnstack);
894                     return -1;
895                 }
896                 break;
897             case GF_CDEF:
898                 im->gdes[gdi].ds_cnt = 1;
899                 im->gdes[gdi].ds = 0;
900                 im->gdes[gdi].data_first = 1;
901                 im->gdes[gdi].start = 0;
902                 im->gdes[gdi].end = 0;
903                 steparray=NULL;
904                 stepcnt = 0;
905                 dataidx=-1;
906
907                 /* Find the variables in the expression.
908                  * - VDEF variables are substituted by their values
909                  *   and the opcode is changed into OP_NUMBER.
910 ******************
911 * Note to Jake: I cannot oversee the implications for your
912 * COMPUTE DS stuff.  Please check if VDEF and COMPUTE are
913 * compatible (or can be made so).
914 ******************
915                  * - CDEF variables are analized for their step size,
916                  *   the lowest common denominator of all the step
917                  *   sizes of the data sources involved is calculated
918                  *   and the resulting number is the step size for the
919                  *   resulting data source.
920                  */
921                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
922                     if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
923                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
924                         if (im->gdes[ptr].ds_cnt == 0) {
925 #if 0
926 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
927         im->gdes[gdi].vname,
928         im->gdes[ptr].vname);
929 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
930 #endif
931                             im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
932                             im->gdes[gdi].rpnp[rpi].op  = OP_NUMBER;
933                         } else {
934                             if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
935                                 rrd_set_error("realloc steparray");
936                                 rpnstack_free(&rpnstack);
937                                 return -1;
938                             };
939
940                             steparray[stepcnt-1] = im->gdes[ptr].step;
941
942                             /* adjust start and end of cdef (gdi) so
943                              * that it runs from the latest start point
944                              * to the earliest endpoint of any of the
945                              * rras involved (ptr)
946                              */
947                             if(im->gdes[gdi].start < im->gdes[ptr].start)
948                                 im->gdes[gdi].start = im->gdes[ptr].start;
949
950                             if(im->gdes[gdi].end == 0 ||
951                                         im->gdes[gdi].end > im->gdes[ptr].end)
952                                 im->gdes[gdi].end = im->gdes[ptr].end;
953                 
954                             /* store pointer to the first element of
955                              * the rra providing data for variable,
956                              * further save step size and data source
957                              * count of this rra
958                              */ 
959                             im->gdes[gdi].rpnp[rpi].data = 
960                                         im->gdes[ptr].data + im->gdes[ptr].ds; 
961                             im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
962                             im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
963
964                             /* backoff the *.data ptr; this is done so
965                              * rpncalc() function doesn't have to treat
966                              * the first case differently
967                              */
968                             im->gdes[gdi].rpnp[rpi].data-=im->gdes[ptr].ds_cnt;
969                         } /* if ds_cnt != 0 */
970                     } /* if OP_VARIABLE */
971                 } /* loop through all rpi */
972
973                 if(steparray == NULL){
974                     rrd_set_error("rpn expressions without DEF"
975                                 " or CDEF variables are not supported");
976                     rpnstack_free(&rpnstack);
977                     return -1;    
978                 }
979                 steparray[stepcnt]=0;
980                 /* Now find the resulting step.  All steps in all
981                  * used RRAs have to be visited
982                  */
983                 im->gdes[gdi].step = lcd(steparray);
984                 free(steparray);
985                 if((im->gdes[gdi].data = malloc((
986                                 (im->gdes[gdi].end-im->gdes[gdi].start) 
987                                     / im->gdes[gdi].step +1)
988                                     * sizeof(double)))==NULL){
989                     rrd_set_error("malloc im->gdes[gdi].data");
990                     rpnstack_free(&rpnstack);
991                     return -1;
992                 }
993         
994                 /* Step through the new cdef results array and
995                  * calculate the values
996                  */
997                 for (now = im->gdes[gdi].start;
998                                 now<=im->gdes[gdi].end;
999                                 now += im->gdes[gdi].step)
1000                 {
1001                     rpnp_t  *rpnp = im -> gdes[gdi].rpnp;
1002
1003                     /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1004                      * in this case we are advancing by timesteps;
1005                      * we use the fact that time_t is a synonym for long
1006                      */
1007                     if (rpn_calc(rpnp,&rpnstack,(long) now, 
1008                                 im->gdes[gdi].data,++dataidx) == -1) {
1009                         /* rpn_calc sets the error string */
1010                         rpnstack_free(&rpnstack); 
1011                         return -1;
1012                     } 
1013                 } /* enumerate over time steps within a CDEF */
1014                 break;
1015             default:
1016                 continue;
1017         }
1018     } /* enumerate over CDEFs */
1019     rpnstack_free(&rpnstack);
1020     return 0;
1021 }
1022
1023 /* massage data so, that we get one value for each x coordinate in the graph */
1024 int
1025 data_proc( image_desc_t *im ){
1026     long i,ii;
1027     double pixstep = (double)(im->end-im->start)
1028         /(double)im->xsize; /* how much time 
1029                                passes in one pixel */
1030     double paintval;
1031     double minval=DNAN,maxval=DNAN;
1032     
1033     unsigned long gr_time;    
1034
1035     /* memory for the processed data */
1036     for(i=0;i<im->gdes_c;i++){
1037       if((im->gdes[i].gf==GF_LINE1) ||
1038          (im->gdes[i].gf==GF_LINE2) ||
1039          (im->gdes[i].gf==GF_LINE3) ||
1040          (im->gdes[i].gf==GF_AREA) ||
1041          (im->gdes[i].gf==GF_TICK) ||
1042          (im->gdes[i].gf==GF_STACK)){
1043         if((im->gdes[i].p_data = malloc((im->xsize +1)
1044                                         * sizeof(rrd_value_t)))==NULL){
1045           rrd_set_error("malloc data_proc");
1046           return -1;
1047         }
1048       }
1049     }
1050     
1051     for(i=0;i<im->xsize;i++){
1052         long vidx;
1053         gr_time = im->start+pixstep*i; /* time of the 
1054                                           current step */
1055         paintval=0.0;
1056         
1057         for(ii=0;ii<im->gdes_c;ii++){
1058           double value;
1059             switch(im->gdes[ii].gf){
1060             case GF_LINE1:
1061             case GF_LINE2:
1062             case GF_LINE3:
1063             case GF_AREA:
1064                 case GF_TICK:
1065                 paintval = 0.0;
1066             case GF_STACK:
1067                 vidx = im->gdes[ii].vidx;
1068
1069                 value =
1070                     im->gdes[vidx].data[
1071                                         ((unsigned long)floor((double)
1072                                                              (gr_time - im->gdes[vidx].start ) 
1073                                                              / im->gdes[vidx].step)+1)                  
1074
1075                                         /* added one because data was not being aligned properly
1076                                            this fixes it. We may also be having a problem in fetch ... */
1077
1078                                         *im->gdes[vidx].ds_cnt
1079                                         +im->gdes[vidx].ds];
1080
1081                 if (! isnan(value)) {
1082                   paintval += value;
1083                   im->gdes[ii].p_data[i] = paintval;
1084                   /* GF_TICK: the data values are not relevant for min and max */
1085                   if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1086                    if (isnan(minval) || paintval <  minval)
1087                      minval = paintval;
1088                    if (isnan(maxval) || paintval >  maxval)
1089                      maxval = paintval;
1090                   }
1091                 } else {
1092                   im->gdes[ii].p_data[i] = DNAN;
1093                 }
1094                 break;
1095             case GF_PRINT:
1096             case GF_GPRINT:
1097             case GF_COMMENT:
1098             case GF_HRULE:
1099             case GF_VRULE:
1100             case GF_DEF:               
1101             case GF_CDEF:
1102             case GF_VDEF:
1103                 break;
1104             }
1105         }
1106     }
1107
1108     /* if min or max have not been asigned a value this is because
1109        there was no data in the graph ... this is not good ...
1110        lets set these to dummy values then ... */
1111
1112     if (isnan(minval)) minval = 0.0;
1113     if (isnan(maxval)) maxval = 1.0;
1114     
1115     /* adjust min and max values */
1116     if (isnan(im->minval) 
1117         || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1118             && im->minval > minval))
1119         im->minval = minval;
1120     if (isnan(im->maxval) 
1121         || (!im->rigid 
1122             && im->maxval < maxval)){
1123         if (im->logarithmic)
1124             im->maxval = maxval * 1.1;
1125         else
1126             im->maxval = maxval;
1127     }
1128     /* make sure min and max are not equal */
1129     if (im->minval == im->maxval) {
1130       im->maxval *= 1.01; 
1131       if (! im->logarithmic) {
1132         im->minval *= 0.99;
1133       }
1134       
1135       /* make sure min and max are not both zero */
1136       if (im->maxval == 0.0) {
1137             im->maxval = 1.0;
1138       }
1139         
1140     }
1141     return 0;
1142 }
1143
1144
1145
1146 /* identify the point where the first gridline, label ... gets placed */
1147
1148 time_t
1149 find_first_time(
1150     time_t   start, /* what is the initial time */
1151     enum tmt_en baseint,  /* what is the basic interval */
1152     long     basestep /* how many if these do we jump a time */
1153     )
1154 {
1155     struct tm tm;
1156     tm = *localtime(&start);
1157     switch(baseint){
1158     case TMT_SECOND:
1159         tm.tm_sec -= tm.tm_sec % basestep; break;
1160     case TMT_MINUTE: 
1161         tm.tm_sec=0;
1162         tm.tm_min -= tm.tm_min % basestep; 
1163         break;
1164     case TMT_HOUR:
1165         tm.tm_sec=0;
1166         tm.tm_min = 0;
1167         tm.tm_hour -= tm.tm_hour % basestep; break;
1168     case TMT_DAY:
1169         /* we do NOT look at the basestep for this ... */
1170         tm.tm_sec=0;
1171         tm.tm_min = 0;
1172         tm.tm_hour = 0; break;
1173     case TMT_WEEK:
1174         /* we do NOT look at the basestep for this ... */
1175         tm.tm_sec=0;
1176         tm.tm_min = 0;
1177         tm.tm_hour = 0;
1178         tm.tm_mday -= tm.tm_wday -1;    /* -1 because we want the monday */
1179         if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1180         break;
1181     case TMT_MONTH:
1182         tm.tm_sec=0;
1183         tm.tm_min = 0;
1184         tm.tm_hour = 0;
1185         tm.tm_mday = 1;
1186         tm.tm_mon -= tm.tm_mon % basestep; break;
1187
1188     case TMT_YEAR:
1189         tm.tm_sec=0;
1190         tm.tm_min = 0;
1191         tm.tm_hour = 0;
1192         tm.tm_mday = 1;
1193         tm.tm_mon = 0;
1194         tm.tm_year -= (tm.tm_year+1900) % basestep;
1195         
1196     }
1197     return mktime(&tm);
1198 }
1199 /* identify the point where the next gridline, label ... gets placed */
1200 time_t 
1201 find_next_time(
1202     time_t   current, /* what is the initial time */
1203     enum tmt_en baseint,  /* what is the basic interval */
1204     long     basestep /* how many if these do we jump a time */
1205     )
1206 {
1207     struct tm tm;
1208     time_t madetime;
1209     tm = *localtime(&current);
1210     do {
1211         switch(baseint){
1212         case TMT_SECOND:
1213             tm.tm_sec += basestep; break;
1214         case TMT_MINUTE: 
1215             tm.tm_min += basestep; break;
1216         case TMT_HOUR:
1217             tm.tm_hour += basestep; break;
1218         case TMT_DAY:
1219             tm.tm_mday += basestep; break;
1220         case TMT_WEEK:
1221             tm.tm_mday += 7*basestep; break;
1222         case TMT_MONTH:
1223             tm.tm_mon += basestep; break;
1224         case TMT_YEAR:
1225             tm.tm_year += basestep;     
1226         }
1227         madetime = mktime(&tm);
1228     } while (madetime == -1); /* this is necessary to skip impssible times
1229                                  like the daylight saving time skips */
1230     return madetime;
1231           
1232 }
1233
1234 void gator( gdImagePtr gif, int x, int y){ 
1235
1236 /* this function puts the name of the author and the tool into the
1237    graph. Remove if you must, but please note, that it is here,
1238    because I would like people who look at rrdtool generated graphs to
1239    see what was used to do it. No obviously you can also add a credit
1240    line to your webpage or printed document, this is fine with me. But
1241    as I have no control over this, I added the little tag in here. 
1242 */
1243
1244 /* the fact that the text of what gets put into the graph is not
1245    visible in the function, has lead some to think this is for
1246    obfuscation reasons. While this is a nice side effect (I addmit),
1247    it is not the prime reason. The prime reason is, that the font
1248    used, is so small, that I had to hand edit the characters to ensure
1249    readability. I could thus not use the normal gd functions to write,
1250    but had to embed a slightly compressed bitmap version into the code. 
1251 */
1252
1253     int li[]={0,0,1, 0,4,5, 0,8,9, 0,12,14, 0,17,17, 0,21,21, 
1254               0,24,24, 0,34,34, 0,40,42, 0,45,45, 0,48,49, 0,52,54, 
1255               0,61,61, 0,64,66, 0,68,70, 0,72,74, 0,76,76, 0,78,78, 
1256               0,80,82, 0,84,85, 
1257               1,0,0, 1,2,2, 1,4,4, 1,6,6, 1,8,8, 1,10,10, 
1258               1,13,13, 1,16,16, 1,18,18, 1,20,20, 1,22,22, 1,24,24, 
1259               1,34,34, 1,41,41, 1,44,44, 1,46,46, 1,48,48, 1,50,50, 
1260               1,53,53, 1,60,60, 1,62,62, 1,64,64, 1,69,69, 1,73,73, 
1261               1,76,76, 1,78,78, 1,80,80, 1,84,84, 1,86,86, 
1262               2,0,1, 2,4,5, 2,8,8, 2,10,10, 2,13,13, 2,16,16, 
1263               2,18,18, 2,20,20, 2,22,22, 2,24,24, 2,33,33, 2,41,41, 
1264               2,44,44, 2,46,46, 2,48,49, 2,53,53, 2,60,60, 2,62,62, 
1265               2,64,65, 2,69,69, 2,73,73, 2,76,77, 2,80,81, 2,84,85,           
1266               3,0,0, 3,2,2, 3,4,4, 3,6,6, 3,8,8, 3,10,10, 
1267               3,13,13, 3,16,16, 3,18,18, 3,20,20, 3,22,22, 3,24,24, 
1268               3,32,32, 3,41,41, 3,44,44, 3,46,46, 3,48,48, 3,50,50, 
1269               3,53,53, 3,60,60, 3,62,62, 3,64,64, 3,69,69, 3,73,73, 
1270               3,76,76, 3,78,78, 3,80,80, 3,84,84, 3,86,86, 
1271               4,0,0, 4,2,2, 4,4,4, 4,6,6, 4,8,9, 4,13,13, 
1272               4,17,17, 4,21,21, 4,24,26, 4,32,32, 4,41,41, 4,45,45, 
1273               4,48,49, 4,52,54, 4,61,61, 4,64,66, 4,69,69, 4,72,74, 
1274               4,76,76, 4,78,78, 4,80,82, 4,84,84}; 
1275     int i,ii; 
1276     for(i=0; i<DIM(li); i=i+3)
1277         for(ii=y+li[i+1]; ii<=y+li[i+2];ii++)
1278           gdImageSetPixel(gif,x-li[i],ii,graph_col[GRC_GRID].i); 
1279 }
1280
1281
1282 /* calculate values required for PRINT and GPRINT functions */
1283
1284 int
1285 print_calc(image_desc_t *im, char ***prdata) 
1286 {
1287     long i,ii,validsteps;
1288     double printval;
1289     int graphelement = 0;
1290     long vidx;
1291     int max_ii; 
1292     double magfact = -1;
1293     char *si_symb = "";
1294     char *percent_s;
1295     int prlines = 1;
1296     if (im->imginfo) prlines++;
1297     for(i=0;i<im->gdes_c;i++){
1298         switch(im->gdes[i].gf){
1299         case GF_PRINT:
1300             prlines++;
1301             if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1302                 rrd_set_error("realloc prdata");
1303                 return 0;
1304             }
1305         case GF_GPRINT:
1306             vidx = im->gdes[i].vidx;
1307             max_ii =((im->gdes[vidx].end 
1308                       - im->gdes[vidx].start)
1309                      /im->gdes[vidx].step
1310                      *im->gdes[vidx].ds_cnt);
1311             printval = DNAN;
1312             validsteps = 0;
1313             for(ii=im->gdes[vidx].ds+im->gdes[vidx].ds_cnt;
1314                 ii < max_ii+im->gdes[vidx].ds_cnt;
1315                 ii+=im->gdes[vidx].ds_cnt){
1316                  if (! finite(im->gdes[vidx].data[ii]))
1317                      continue;
1318                  if (isnan(printval)){
1319                      printval = im->gdes[vidx].data[ii];
1320                      validsteps++;
1321                     continue;
1322                 }
1323
1324                 switch (im->gdes[i].cf){
1325                 case CF_HWPREDICT:
1326                 case CF_DEVPREDICT:
1327                 case CF_DEVSEASONAL:
1328                 case CF_SEASONAL:
1329                 case CF_AVERAGE:
1330                     validsteps++;
1331                     printval += im->gdes[vidx].data[ii];
1332                     break;
1333                 case CF_MINIMUM:
1334                     printval = min( printval, im->gdes[vidx].data[ii]);
1335                     break;
1336             case CF_FAILURES:
1337                 case CF_MAXIMUM:
1338                     printval = max( printval, im->gdes[vidx].data[ii]);
1339                     break;
1340                 case CF_LAST:
1341                     printval = im->gdes[vidx].data[ii];
1342                 }
1343             }
1344             if (im->gdes[i].cf ==  CF_AVERAGE || im -> gdes[i].cf > CF_LAST) {
1345                 if (validsteps > 1) {
1346                     printval = (printval / validsteps);
1347                 }
1348             }
1349             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1350                 /* Magfact is set to -1 upon entry to print_calc.  If it
1351                  * is still less than 0, then we need to run auto_scale.
1352                  * Otherwise, put the value into the correct units.  If
1353                  * the value is 0, then do not set the symbol or magnification
1354                  * so next the calculation will be performed again. */
1355                 if (magfact < 0.0) {
1356                     auto_scale(im,&printval,&si_symb,&magfact);
1357                     if (printval == 0.0)
1358                         magfact = -1.0;
1359                 } else {
1360                     printval /= magfact;
1361                 }
1362                 *(++percent_s) = 's';
1363             }
1364             else if (strstr(im->gdes[i].format,"%s") != NULL) {
1365                 auto_scale(im,&printval,&si_symb,&magfact);
1366             }
1367             if (im->gdes[i].gf == GF_PRINT){
1368                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1369                 if (bad_format(im->gdes[i].format)) {
1370                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1371                         return -1;
1372                 }
1373 #ifdef HAVE_SNPRINTF
1374                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1375 #else
1376                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1377 #endif
1378                 (*prdata)[prlines-1] = NULL;
1379             } else {
1380                 /* GF_GPRINT */
1381
1382                 if (bad_format(im->gdes[i].format)) {
1383                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1384                         return -1;
1385                 }
1386 #ifdef HAVE_SNPRINTF
1387                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1388 #else
1389                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1390 #endif
1391                 graphelement = 1;
1392             }
1393             break;
1394         case GF_COMMENT:
1395         case GF_LINE1:
1396         case GF_LINE2:
1397         case GF_LINE3:
1398         case GF_AREA:
1399         case GF_TICK:
1400         case GF_STACK:
1401         case GF_HRULE:
1402         case GF_VRULE:
1403             graphelement = 1;
1404             break;
1405         case GF_DEF:
1406         case GF_CDEF:       
1407         case GF_VDEF:       
1408             break;
1409         }
1410     }
1411     return graphelement;
1412 }
1413
1414
1415 /* place legends with color spots */
1416 int
1417 leg_place(image_desc_t *im)
1418 {
1419     /* graph labels */
1420     int   interleg = SmallFont->w*2;
1421     int   box = SmallFont->h*1.2;
1422     int   border = SmallFont->w*2;
1423     int   fill=0, fill_last;
1424     int   leg_c = 0;
1425     int   leg_x = border, leg_y = im->ygif;
1426     int   leg_cc;
1427     int   glue = 0;
1428     int   i,ii, mark = 0;
1429     char  prt_fctn; /*special printfunctions */
1430     int  *legspace;
1431
1432   if( !(im->extra_flags & NOLEGEND) ) {
1433     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1434        rrd_set_error("malloc for legspace");
1435        return -1;
1436     }
1437
1438     for(i=0;i<im->gdes_c;i++){
1439         fill_last = fill;
1440
1441         leg_cc = strlen(im->gdes[i].legend);
1442         
1443         /* is there a controle code ant the end of the legend string ? */ 
1444         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1445             prt_fctn = im->gdes[i].legend[leg_cc-1];
1446             leg_cc -= 2;
1447             im->gdes[i].legend[leg_cc] = '\0';
1448         } else {
1449             prt_fctn = '\0';
1450         }
1451         /* remove exess space */
1452         while (prt_fctn=='g' && 
1453                leg_cc > 0 && 
1454                im->gdes[i].legend[leg_cc-1]==' '){
1455            leg_cc--;
1456            im->gdes[i].legend[leg_cc]='\0';
1457         }
1458         if (leg_cc != 0 ){          
1459            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1460            
1461            if (fill > 0){ 
1462                /* no interleg space if string ends in \g */
1463                fill += legspace[i];
1464             }
1465             if (im->gdes[i].gf != GF_GPRINT && 
1466                 im->gdes[i].gf != GF_COMMENT) { 
1467                 fill += box;       
1468             }
1469             fill += leg_cc * SmallFont->w;
1470             leg_c++;
1471         } else {
1472            legspace[i]=0;
1473         }
1474         /* who said there was a special tag ... ?*/
1475         if (prt_fctn=='g') {    
1476            prt_fctn = '\0';
1477         }
1478         if (prt_fctn == '\0') {
1479             if (i == im->gdes_c -1 ) prt_fctn ='l';
1480             
1481             /* is it time to place the legends ? */
1482             if (fill > im->xgif - 2*border){
1483                 if (leg_c > 1) {
1484                     /* go back one */
1485                     i--; 
1486                     fill = fill_last;
1487                     leg_c--;
1488                     prt_fctn = 'j';
1489                 } else {
1490                     prt_fctn = 'l';
1491                 }
1492                 
1493             }
1494         }
1495
1496
1497         if (prt_fctn != '\0'){
1498             leg_x = border;
1499             if (leg_c >= 2 && prt_fctn == 'j') {
1500                 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1501                 /* if (glue > 2 * SmallFont->w) glue = 0; */
1502             } else {
1503                 glue = 0;
1504             }
1505             if (prt_fctn =='c') leg_x =  (im->xgif - fill) / 2.0;
1506             if (prt_fctn =='r') leg_x =  im->xgif - fill - border;
1507
1508             for(ii=mark;ii<=i;ii++){
1509                 if(im->gdes[ii].legend[0]=='\0')
1510                     continue;
1511                 im->gdes[ii].legloc.x = leg_x;
1512                 im->gdes[ii].legloc.y = leg_y;
1513                 leg_x =  leg_x 
1514                     + strlen(im->gdes[ii].legend)*SmallFont->w 
1515                     + legspace[ii]
1516                     + glue;
1517                 if (im->gdes[ii].gf != GF_GPRINT && 
1518                     im->gdes[ii].gf != GF_COMMENT) 
1519                     leg_x += box;          
1520             }       
1521             leg_y = leg_y + SmallFont->h*1.2;
1522             if (prt_fctn == 's') leg_y -= SmallFont->h *0.5;
1523             fill = 0;
1524             leg_c = 0;
1525             mark = ii;
1526         }          
1527     }
1528     im->ygif = leg_y+6;
1529     free(legspace);
1530   }
1531   return 0;
1532 }
1533
1534 /* create a grid on the graph. it determines what to do
1535    from the values of xsize, start and end */
1536
1537 /* the xaxis labels are determined from the number of seconds per pixel
1538    in the requested graph */
1539
1540
1541
1542 int
1543 horizontal_grid(gdImagePtr gif, image_desc_t   *im)
1544 {
1545     double   range;
1546     double   scaledrange;
1547     int      pixel,i;
1548     int      sgrid,egrid;
1549     double   gridstep;
1550     double   scaledstep;
1551     char     graph_label[100];
1552     gdPoint  polyPoints[4];
1553     int      labfact,gridind;
1554     int      styleMinor[2],styleMajor[2];
1555     int      decimals, fractionals;
1556     char     labfmt[64];
1557
1558     labfact=2;
1559     gridind=-1;
1560     range =  im->maxval - im->minval;
1561     scaledrange = range / im->magfact;
1562
1563         /* does the scale of this graph make it impossible to put lines
1564            on it? If so, give up. */
1565         if (isnan(scaledrange)) {
1566                 return 0;
1567         }
1568
1569     styleMinor[0] = graph_col[GRC_GRID].i;
1570     styleMinor[1] = gdTransparent;
1571
1572     styleMajor[0] = graph_col[GRC_MGRID].i;
1573     styleMajor[1] = gdTransparent;
1574
1575     /* find grid spaceing */
1576     pixel=1;
1577     if(isnan(im->ygridstep)){
1578         if(im->extra_flags & ALTYGRID) {
1579             /* find the value with max number of digits. Get number of digits */
1580             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1581             if(decimals <= 0) /* everything is small. make place for zero */
1582                 decimals = 1;
1583             
1584             fractionals = floor(log10(range));
1585             if(fractionals < 0) /* small amplitude. */
1586                 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1587             else
1588                 sprintf(labfmt, "%%%d.1f", decimals + 1);
1589             gridstep = pow((double)10, (double)fractionals);
1590             if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1591                 gridstep = 0.1;
1592             /* should have at least 5 lines but no more then 15 */
1593             if(range/gridstep < 5)
1594                 gridstep /= 10;
1595             if(range/gridstep > 15)
1596                 gridstep *= 10;
1597             if(range/gridstep > 5) {
1598                 labfact = 1;
1599                 if(range/gridstep > 8)
1600                     labfact = 2;
1601             }
1602             else {
1603                 gridstep /= 5;
1604                 labfact = 5;
1605             }
1606         }
1607         else {
1608             for(i=0;ylab[i].grid > 0;i++){
1609                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1610                 if (gridind == -1 && pixel > 5) {
1611                     gridind = i;
1612                     break;
1613                 }
1614             }
1615             
1616             for(i=0; i<4;i++) {
1617                 if (pixel * ylab[gridind].lfac[i] >=  2 * SmallFont->h) {
1618                     labfact =  ylab[gridind].lfac[i];
1619                     break;
1620                 }                         
1621             } 
1622             
1623             gridstep = ylab[gridind].grid * im->magfact;
1624         }
1625     } else {
1626         gridstep = im->ygridstep;
1627         labfact = im->ylabfact;
1628     }
1629     
1630     polyPoints[0].x=im->xorigin;
1631     polyPoints[1].x=im->xorigin+im->xsize;
1632     sgrid = (int)( im->minval / gridstep - 1);
1633     egrid = (int)( im->maxval / gridstep + 1);
1634     scaledstep = gridstep/im->magfact;
1635     for (i = sgrid; i <= egrid; i++){
1636         polyPoints[0].y=ytr(im,gridstep*i);
1637         if ( polyPoints[0].y >= im->yorigin-im->ysize
1638              && polyPoints[0].y <= im->yorigin) {
1639             if(i % labfact == 0){               
1640                 if (i==0 || im->symbol == ' ') {
1641                     if(scaledstep < 1){
1642                         if(im->extra_flags & ALTYGRID) {
1643                             sprintf(graph_label,labfmt,scaledstep*i);
1644                         }
1645                         else {
1646                             sprintf(graph_label,"%4.1f",scaledstep*i);
1647                         }
1648                     } else {
1649                         sprintf(graph_label,"%4.0f",scaledstep*i);
1650                     }
1651                 }else {
1652                     if(scaledstep < 1){
1653                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1654                     } else {
1655                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1656                     }
1657                 }
1658
1659                 gdImageString(gif, SmallFont,
1660                               (polyPoints[0].x - (strlen(graph_label) * 
1661                                                   SmallFont->w)-7), 
1662                               polyPoints[0].y - SmallFont->h/2+1,
1663                               (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1664                 
1665                 gdImageSetStyle(gif, styleMajor, 2);
1666
1667                 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1668                             polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1669                 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1670                             polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);              
1671             } else {            
1672                 gdImageSetStyle(gif, styleMinor, 2);
1673                 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1674                             polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1675                 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1676                             polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);               
1677             }       
1678             gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1679                         polyPoints[1].x,polyPoints[0].y,gdStyled);
1680         }       
1681     } 
1682 /*    if(im->minval * im->maxval < 0){
1683       polyPoints[0].y=ytr(0);
1684       gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1685       polyPoints[1].x,polyPoints[0].y,graph_col[GRC_MGRID].i);
1686       } */
1687
1688     return 1;
1689 }
1690
1691 /* logaritmic horizontal grid */
1692 int
1693 horizontal_log_grid(gdImagePtr gif, image_desc_t   *im)
1694 {
1695     double   pixpex;
1696     int      ii,i;
1697     int      minoridx=0, majoridx=0;
1698     char     graph_label[100];
1699     gdPoint  polyPoints[4];
1700     int      styleMinor[2],styleMajor[2];
1701     double   value, pixperstep, minstep;
1702
1703     /* find grid spaceing */
1704     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1705
1706         if (isnan(pixpex)) {
1707                 return 0;
1708         }
1709
1710     for(i=0;yloglab[i][0] > 0;i++){
1711         minstep = log10(yloglab[i][0]);
1712         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1713             if(yloglab[i][ii+2]==0){
1714                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1715                 break;
1716             }
1717         }
1718         pixperstep = pixpex * minstep;
1719         if(pixperstep > 5){minoridx = i;}
1720         if(pixperstep > 2 * SmallFont->h){majoridx = i;}
1721     }
1722    
1723     styleMinor[0] = graph_col[GRC_GRID].i;
1724     styleMinor[1] = gdTransparent;
1725
1726     styleMajor[0] = graph_col[GRC_MGRID].i;
1727     styleMajor[1] = gdTransparent;
1728
1729     polyPoints[0].x=im->xorigin;
1730     polyPoints[1].x=im->xorigin+im->xsize;
1731     /* paint minor grid */
1732     for (value = pow((double)10, log10(im->minval) 
1733                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1734          value  <= im->maxval;
1735          value *= yloglab[minoridx][0]){
1736         if (value < im->minval) continue;
1737         i=0;    
1738         while(yloglab[minoridx][++i] > 0){          
1739             polyPoints[0].y = ytr(im,value * yloglab[minoridx][i]);
1740             if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1741             gdImageSetStyle(gif, styleMinor, 2);
1742             gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1743                         polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1744             gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1745                         polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);           
1746
1747             gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1748                         polyPoints[1].x,polyPoints[0].y,gdStyled);
1749         }
1750     }
1751
1752     /* paint major grid and labels*/
1753     for (value = pow((double)10, log10(im->minval) 
1754                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1755          value <= im->maxval;
1756          value *= yloglab[majoridx][0]){
1757         if (value < im->minval) continue;
1758         i=0;    
1759         while(yloglab[majoridx][++i] > 0){          
1760             polyPoints[0].y = ytr(im,value * yloglab[majoridx][i]);         
1761             if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1762             gdImageSetStyle(gif, styleMajor, 2);
1763             gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1764                         polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1765             gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1766                         polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);                  
1767             
1768             gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1769                         polyPoints[1].x,polyPoints[0].y,gdStyled);
1770             sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1771             gdImageString(gif, SmallFont,
1772                           (polyPoints[0].x - (strlen(graph_label) * 
1773                                               SmallFont->w)-7), 
1774                           polyPoints[0].y - SmallFont->h/2+1,
1775                           (unsigned char *)graph_label, graph_col[GRC_FONT].i); 
1776         } 
1777     }
1778         return 1;
1779 }
1780
1781
1782 void
1783 vertical_grid(
1784     gdImagePtr     gif,
1785     image_desc_t   *im )
1786 {   
1787     int xlab_sel;               /* which sort of label and grid ? */
1788     time_t ti, tilab;
1789     long factor;
1790     char graph_label[100];
1791     gdPoint polyPoints[4];       /* points for filled graph and more*/
1792
1793     /* style for grid lines */
1794     int     styleDotted[4];
1795
1796     
1797     /* the type of time grid is determined by finding
1798        the number of seconds per pixel in the graph */
1799     
1800     
1801     if(im->xlab_user.minsec == -1){
1802         factor=(im->end - im->start)/im->xsize;
1803         xlab_sel=0;
1804         while ( xlab[xlab_sel+1].minsec != -1 
1805                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1806         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1807         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1808         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1809         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1810         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1811         im->xlab_user.labst = xlab[xlab_sel].labst;
1812         im->xlab_user.precis = xlab[xlab_sel].precis;
1813         im->xlab_user.stst = xlab[xlab_sel].stst;
1814     }
1815     
1816     /* y coords are the same for every line ... */
1817     polyPoints[0].y = im->yorigin;
1818     polyPoints[1].y = im->yorigin-im->ysize;
1819
1820     /* paint the minor grid */
1821     for(ti = find_first_time(im->start,
1822                             im->xlab_user.gridtm,
1823                             im->xlab_user.gridst);
1824         ti < im->end; 
1825         ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1826         ){
1827         /* are we inside the graph ? */
1828         if (ti < im->start || ti > im->end) continue;
1829         polyPoints[0].x = xtr(im,ti);
1830         styleDotted[0] = graph_col[GRC_GRID].i;
1831         styleDotted[1] = gdTransparent;
1832
1833         gdImageSetStyle(gif, styleDotted, 2);
1834
1835         gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1836                     polyPoints[0].x,polyPoints[1].y,gdStyled);
1837         gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-1,
1838                     polyPoints[0].x,polyPoints[0].y+1,graph_col[GRC_GRID].i);
1839         gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-1,
1840                     polyPoints[0].x,polyPoints[1].y+1,graph_col[GRC_GRID].i);
1841     }
1842
1843     /* paint the major grid */
1844     for(ti = find_first_time(im->start,
1845                             im->xlab_user.mgridtm,
1846                             im->xlab_user.mgridst);
1847         ti < im->end; 
1848         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1849         ){
1850         /* are we inside the graph ? */
1851         if (ti < im->start || ti > im->end) continue;
1852         polyPoints[0].x = xtr(im,ti);
1853         styleDotted[0] = graph_col[GRC_MGRID].i;
1854         styleDotted[1] = gdTransparent;
1855         gdImageSetStyle(gif, styleDotted, 2);
1856
1857         gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1858                     polyPoints[0].x,polyPoints[1].y,gdStyled);
1859         gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-2,
1860                     polyPoints[0].x,polyPoints[0].y+2,graph_col[GRC_MGRID].i);
1861         gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-2,
1862                     polyPoints[0].x,polyPoints[1].y+2,graph_col[GRC_MGRID].i);
1863     }
1864     /* paint the labels below the graph */
1865     for(ti = find_first_time(im->start,
1866                             im->xlab_user.labtm,
1867                             im->xlab_user.labst);
1868         ti <= im->end; 
1869         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1870         ){
1871         int gr_pos,width;
1872         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1873
1874 #if HAVE_STRFTIME
1875         strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1876 #else
1877 # error "your libc has no strftime I guess we'll abort the exercise here."
1878 #endif
1879         width=strlen(graph_label) *  SmallFont->w;
1880         gr_pos=xtr(im,tilab) - width/2;
1881         if (gr_pos  >= im->xorigin 
1882             && gr_pos + width <= im->xorigin+im->xsize) 
1883             gdImageString(gif, SmallFont,
1884                           gr_pos,  polyPoints[0].y+4,
1885                           (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1886     }
1887
1888 }
1889
1890
1891 void 
1892 axis_paint(
1893     image_desc_t   *im,
1894     gdImagePtr     gif
1895     )
1896 {   
1897     /* draw x and y axis */
1898     gdImageLine(gif, im->xorigin+im->xsize,im->yorigin,
1899                 im->xorigin+im->xsize,im->yorigin-im->ysize,
1900                 graph_col[GRC_GRID].i);
1901     
1902     gdImageLine(gif, im->xorigin,im->yorigin-im->ysize,
1903                 im->xorigin+im->xsize,im->yorigin-im->ysize,
1904                 graph_col[GRC_GRID].i);
1905
1906     gdImageLine(gif, im->xorigin-4,im->yorigin,
1907                 im->xorigin+im->xsize+4,im->yorigin,
1908                 graph_col[GRC_FONT].i);
1909
1910     gdImageLine(gif, im->xorigin,im->yorigin,
1911                 im->xorigin,im->yorigin-im->ysize,
1912                 graph_col[GRC_GRID].i);
1913     
1914     /* arrow for X axis direction */
1915     gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+4, im->yorigin+3,graph_col[GRC_ARROW].i);
1916     gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1917     gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin+3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1918
1919     /*    gdImageLine(gif, im->xorigin+im->xsize-1, im->yorigin-3, im->xorigin+im->xsize-1, im->yorigin+3,graph_col[GRC_MGRID].i);
1920     gdImageLine(gif, im->xorigin+im->xsize, im->yorigin-2, im->xorigin+im->xsize, im->yorigin+2,graph_col[GRC_MGRID].i);
1921     gdImageLine(gif, im->xorigin+im->xsize+1, im->yorigin-2, im->xorigin+im->xsize+1, im->yorigin+2,graph_col[GRC_MGRID].i);
1922     gdImageLine(gif, im->xorigin+im->xsize+2, im->yorigin-2, im->xorigin+im->xsize+2, im->yorigin+2,graph_col[GRC_MGRID].i);
1923     gdImageLine(gif, im->xorigin+im->xsize+3, im->yorigin-1, im->xorigin+im->xsize+3, im->yorigin+1,graph_col[GRC_MGRID].i);
1924     gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-1, im->xorigin+im->xsize+4, im->yorigin+1,graph_col[GRC_MGRID].i);
1925     gdImageLine(gif, im->xorigin+im->xsize+5, im->yorigin, im->xorigin+im->xsize+5, im->yorigin,graph_col[GRC_MGRID].i); */
1926
1927
1928
1929 }
1930
1931 void
1932 grid_paint(
1933     image_desc_t   *im,
1934     gdImagePtr     gif
1935     )
1936 {   
1937     long i;
1938     int boxH=8, boxV=8;
1939     int res=0;
1940     gdPoint polyPoints[4];       /* points for filled graph and more*/
1941
1942     /* draw 3d border */
1943     gdImageLine(gif,0,0,im->xgif-1,0,graph_col[GRC_SHADEA].i);
1944     gdImageLine(gif,1,1,im->xgif-2,1,graph_col[GRC_SHADEA].i);
1945     gdImageLine(gif,0,0,0,im->ygif-1,graph_col[GRC_SHADEA].i);
1946     gdImageLine(gif,1,1,1,im->ygif-2,graph_col[GRC_SHADEA].i);
1947     gdImageLine(gif,im->xgif-1,0,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1948     gdImageLine(gif,0,im->ygif-1,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1949     gdImageLine(gif,im->xgif-2,1,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1950     gdImageLine(gif,1,im->ygif-2,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1951
1952
1953     if (im->draw_x_grid == 1 )
1954       vertical_grid(gif, im);
1955     
1956     if (im->draw_y_grid == 1){
1957         if(im->logarithmic){
1958                 res = horizontal_log_grid(gif,im);
1959         } else {
1960                 res = horizontal_grid(gif,im);
1961         }
1962
1963         /* dont draw horizontal grid if there is no min and max val */
1964         if (! res ) {
1965           char *nodata = "No Data found";
1966           gdImageString(gif, LargeFont,
1967                         im->xgif/2 
1968                         - (strlen(nodata)*LargeFont->w)/2,
1969                         (2*im->yorigin-im->ysize) / 2,
1970                         (unsigned char *)nodata, graph_col[GRC_FONT].i);
1971         }
1972     }
1973
1974     /* yaxis description */
1975     gdImageStringUp(gif, SmallFont,
1976                     7,
1977                     (im->yorigin - im->ysize/2
1978                      +(strlen(im->ylegend)*SmallFont->w)/2 ),
1979                     (unsigned char *)im->ylegend, graph_col[GRC_FONT].i);
1980     
1981
1982     /* graph title */
1983     gdImageString(gif, LargeFont,
1984                     im->xgif/2 
1985                     - (strlen(im->title)*LargeFont->w)/2,
1986                   8,
1987                     (unsigned char *)im->title, graph_col[GRC_FONT].i);
1988     
1989     /* graph labels */
1990     if( !(im->extra_flags & NOLEGEND) ) {
1991       for(i=0;i<im->gdes_c;i++){
1992         if(im->gdes[i].legend[0] =='\0')
1993             continue;
1994         
1995         if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1996             
1997             polyPoints[0].x = im->gdes[i].legloc.x;
1998             polyPoints[0].y = im->gdes[i].legloc.y+1;
1999             polyPoints[1].x = polyPoints[0].x+boxH;
2000             polyPoints[2].x = polyPoints[0].x+boxH;
2001             polyPoints[3].x = polyPoints[0].x;
2002             polyPoints[1].y = polyPoints[0].y;
2003             polyPoints[2].y = polyPoints[0].y+boxV;
2004             polyPoints[3].y = polyPoints[0].y+boxV;
2005             gdImageFilledPolygon(gif,polyPoints,4,im->gdes[i].col.i);
2006             gdImagePolygon(gif,polyPoints,4,graph_col[GRC_FRAME].i);
2007         
2008             gdImageString(gif, SmallFont,
2009                           polyPoints[0].x+boxH+6, 
2010                           polyPoints[0].y-1,
2011                           (unsigned char *)im->gdes[i].legend,
2012                           graph_col[GRC_FONT].i);
2013         } else {
2014             polyPoints[0].x = im->gdes[i].legloc.x;
2015             polyPoints[0].y = im->gdes[i].legloc.y;
2016             
2017             gdImageString(gif, SmallFont,
2018                           polyPoints[0].x, 
2019                           polyPoints[0].y,
2020                           (unsigned char *)im->gdes[i].legend,
2021                           graph_col[GRC_FONT].i);
2022         }
2023       }
2024     }
2025     
2026     
2027     gator(gif, (int) im->xgif-5, 5);
2028
2029 }
2030
2031
2032 gdImagePtr
2033 MkLineBrush(image_desc_t *im,long cosel, enum gf_en typsel){
2034   gdImagePtr brush;
2035   int pen;
2036   switch (typsel){
2037   case GF_LINE1:
2038     brush=gdImageCreate(1,1);
2039     break;
2040   case GF_LINE2:
2041     brush=gdImageCreate(2,2);
2042     break;
2043   case GF_LINE3:
2044     brush=gdImageCreate(3,3);
2045     break;
2046   default:
2047     return NULL;
2048   }
2049
2050   gdImageColorTransparent(brush, 
2051                           gdImageColorAllocate(brush, 0, 0, 0));
2052
2053   pen = gdImageColorAllocate(brush, 
2054                              im->gdes[cosel].col.red,
2055                              im->gdes[cosel].col.green,
2056                              im->gdes[cosel].col.blue);
2057     
2058   switch (typsel){
2059   case GF_LINE1:
2060     gdImageSetPixel(brush,0,0,pen);
2061     break;
2062   case GF_LINE2:
2063     gdImageSetPixel(brush,0,0,pen);
2064     gdImageSetPixel(brush,0,1,pen);
2065     gdImageSetPixel(brush,1,0,pen);
2066     gdImageSetPixel(brush,1,1,pen);
2067     break;
2068   case GF_LINE3:
2069     gdImageSetPixel(brush,1,0,pen);
2070     gdImageSetPixel(brush,0,1,pen);
2071     gdImageSetPixel(brush,1,1,pen);
2072     gdImageSetPixel(brush,2,1,pen);
2073     gdImageSetPixel(brush,1,2,pen);
2074     break;
2075   default:
2076     return NULL;
2077   }
2078   return brush;
2079 }
2080 /*****************************************************
2081  * lazy check make sure we rely need to create this graph
2082  *****************************************************/
2083
2084 int lazy_check(image_desc_t *im){
2085     FILE *fd = NULL;
2086         int size = 1;
2087     struct stat  gifstat;
2088     
2089     if (im->lazy == 0) return 0; /* no lazy option */
2090     if (stat(im->graphfile,&gifstat) != 0) 
2091       return 0; /* can't stat */
2092     /* one pixel in the existing graph is more then what we would
2093        change here ... */
2094     if (time(NULL) - gifstat.st_mtime > 
2095         (im->end - im->start) / im->xsize) 
2096       return 0;
2097     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
2098       return 0; /* the file does not exist */
2099     switch (im->imgformat) {
2100     case IF_GIF:
2101            size = GifSize(fd,&(im->xgif),&(im->ygif));
2102            break;
2103     case IF_PNG:
2104            size = PngSize(fd,&(im->xgif),&(im->ygif));
2105            break;
2106     }
2107     fclose(fd);
2108     return size;
2109 }
2110
2111 /* draw that picture thing ... */
2112 int
2113 graph_paint(image_desc_t *im, char ***calcpr)
2114 {
2115     int i,ii;
2116     int lazy =     lazy_check(im);
2117     FILE  *fo;
2118     
2119     /* gif stuff */
2120     gdImagePtr  gif,brush;
2121
2122     double areazero = 0.0;
2123     enum gf_en stack_gf = GF_PRINT;
2124     graph_desc_t *lastgdes = NULL;    
2125     gdPoint canvas[4], back[4];  /* points for canvas*/
2126
2127     /* if we are lazy and there is nothing to PRINT ... quit now */
2128     if (lazy && im->prt_c==0) return 0;
2129     
2130     /* pull the data from the rrd files ... */
2131     
2132     if(data_fetch(im)==-1)
2133         return -1;
2134
2135     /* evaluate VDEF and CDEF operations ... */
2136     if(data_calc(im)==-1)
2137         return -1;
2138
2139     /* calculate and PRINT and GPRINT definitions. We have to do it at
2140      * this point because it will affect the length of the legends
2141      * if there are no graph elements we stop here ... 
2142      * if we are lazy, try to quit ... 
2143      */
2144     i=print_calc(im,calcpr);
2145     if(i<0) return -1;
2146     if(i==0 || lazy) return 0;
2147
2148     /* get actual drawing data and find min and max values*/
2149     if(data_proc(im)==-1)
2150         return -1;
2151
2152     if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2153
2154     if(!im->rigid && ! im->logarithmic)
2155         expand_range(im);   /* make sure the upper and lower limit are
2156                            sensible values */
2157
2158     /* init xtr and ytr */
2159     /* determine the actual size of the gif to draw. The size given
2160        on the cmdline is the graph area. But we need more as we have
2161        draw labels and other things outside the graph area */
2162
2163
2164     im->xorigin = 10 + 9 * SmallFont->w+SmallFont->h;
2165     xtr(im,0); 
2166
2167     im->yorigin = 14 + im->ysize;
2168     ytr(im,DNAN);
2169
2170     if(im->title[0] != '\0')
2171         im->yorigin += (LargeFont->h+4);
2172
2173     im->xgif=20+im->xsize + im->xorigin;
2174     im->ygif= im->yorigin+2*SmallFont->h;
2175     
2176     /* determine where to place the legends onto the graphics.
2177        and set im->ygif to match space requirements for text */
2178     if(leg_place(im)==-1)
2179      return -1;
2180
2181     gif=gdImageCreate(im->xgif,im->ygif);
2182
2183     gdImageInterlace(gif, im->interlaced);
2184     
2185     /* allocate colors for the screen elements */
2186     for(i=0;i<DIM(graph_col);i++)
2187         /* check for user override values */
2188         if(im->graph_col[i].red != -1)
2189             graph_col[i].i = 
2190                 gdImageColorAllocate( gif,
2191                                       im->graph_col[i].red, 
2192                                       im->graph_col[i].green, 
2193                                       im->graph_col[i].blue);
2194         else
2195             graph_col[i].i = 
2196                 gdImageColorAllocate( gif,
2197                                       graph_col[i].red, 
2198                                       graph_col[i].green, 
2199                                       graph_col[i].blue);
2200         
2201     
2202     /* allocate colors for the graph */
2203     for(i=0;i<im->gdes_c;i++)
2204         /* only for elements which have a color defined */
2205         if (im->gdes[i].col.red != -1)
2206             im->gdes[i].col.i = 
2207                 gdImageColorAllocate(gif,
2208                                      im->gdes[i].col.red,
2209                                      im->gdes[i].col.green,
2210                                      im->gdes[i].col.blue);
2211     
2212     
2213     /* the actual graph is created by going through the individual
2214        graph elements and then drawing them */
2215     
2216     back[0].x = 0;
2217     back[0].y = 0;
2218     back[1].x = back[0].x+im->xgif;
2219     back[1].y = back[0].y;
2220     back[2].x = back[1].x;
2221     back[2].y = back[0].y+im->ygif;
2222     back[3].x = back[0].x;
2223     back[3].y = back[2].y;
2224
2225     gdImageFilledPolygon(gif,back,4,graph_col[GRC_BACK].i);
2226
2227     canvas[0].x = im->xorigin;
2228     canvas[0].y = im->yorigin;
2229     canvas[1].x = canvas[0].x+im->xsize;
2230     canvas[1].y = canvas[0].y;
2231     canvas[2].x = canvas[1].x;
2232     canvas[2].y = canvas[0].y-im->ysize;
2233     canvas[3].x = canvas[0].x;
2234     canvas[3].y = canvas[2].y;
2235
2236     gdImageFilledPolygon(gif,canvas,4,graph_col[GRC_CANVAS].i);
2237
2238     if (im->minval > 0.0)
2239         areazero = im->minval;
2240     if (im->maxval < 0.0)
2241         areazero = im->maxval;
2242
2243     axis_paint(im,gif);
2244
2245     for(i=0;i<im->gdes_c;i++){  
2246         
2247         switch(im->gdes[i].gf){
2248         case GF_CDEF:
2249         case GF_VDEF:
2250         case GF_DEF:
2251         case GF_PRINT:
2252         case GF_GPRINT:
2253         case GF_COMMENT:
2254         case GF_HRULE:
2255         case GF_VRULE:
2256                 break;
2257         case GF_TICK:
2258                 for (ii = 0; ii < im->xsize; ii++)
2259                 {
2260                    if (!isnan(im->gdes[i].p_data[ii]) && 
2261                            im->gdes[i].p_data[ii] > 0.0)
2262                    { 
2263                           /* generate a tick */
2264                           gdImageLine(gif, im -> xorigin + ii, 
2265                                  im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2266                                  im -> xorigin + ii, 
2267                                  im -> yorigin,
2268                                  im -> gdes[i].col.i);
2269                    }
2270                 }
2271                 break;
2272         case GF_LINE1:
2273         case GF_LINE2:
2274         case GF_LINE3:
2275         case GF_AREA:
2276             stack_gf = im->gdes[i].gf;
2277         case GF_STACK:      
2278             /* fix data points at oo and -oo */
2279             for(ii=0;ii<im->xsize;ii++){
2280                 if (isinf(im->gdes[i].p_data[ii])){
2281                     if (im->gdes[i].p_data[ii] > 0) {
2282                         im->gdes[i].p_data[ii] = im->maxval ;
2283                     } else {
2284                         im->gdes[i].p_data[ii] = im->minval ;
2285                     }               
2286                 
2287                 }
2288             }
2289
2290             if (im->gdes[i].col.i != -1){               
2291                /* GF_LINE and frined */
2292                if(stack_gf == GF_LINE1 || stack_gf == GF_LINE2 || stack_gf == GF_LINE3 ){
2293                    brush = MkLineBrush(im,i,stack_gf);
2294                    gdImageSetBrush(gif, brush);
2295                    for(ii=1;ii<im->xsize;ii++){
2296                        if (isnan(im->gdes[i].p_data[ii-1]) ||
2297                            isnan(im->gdes[i].p_data[ii]))
2298                             continue;
2299                        gdImageLine(gif,
2300                                     ii+im->xorigin-1,ytr(im,im->gdes[i].p_data[ii-1]),
2301                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2302                                     gdBrushed);
2303                         
2304                     }
2305                     gdImageDestroy(brush);
2306                 }
2307                 else 
2308                     /* GF_AREA STACK type*/
2309                     if (im->gdes[i].gf == GF_STACK )
2310                         for(ii=0;ii<im->xsize;ii++){
2311                             if(isnan(im->gdes[i].p_data[ii])){
2312                                 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2313                                 continue;
2314                             }
2315                             
2316                             if (lastgdes->p_data[ii] == im->gdes[i].p_data[ii]){
2317                                 continue;
2318                             }
2319                             gdImageLine(gif,
2320                                         ii+im->xorigin,ytr(im,lastgdes->p_data[ii]),
2321                                         ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2322                                         im->gdes[i].col.i);
2323                         }
2324                
2325                     else /* simple GF_AREA */
2326                         for(ii=0;ii<im->xsize;ii++){
2327                             if (isnan(im->gdes[i].p_data[ii])) {
2328                                 im->gdes[i].p_data[ii] = 0;
2329                                 continue;
2330                             }
2331                             gdImageLine(gif,
2332                                         ii+im->xorigin,ytr(im,areazero),
2333                                         ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2334                                         im->gdes[i].col.i);
2335                         }
2336            }
2337            lastgdes = &(im->gdes[i]);              
2338            break;
2339         }
2340     }
2341     
2342     grid_paint(im,gif);
2343
2344     /* the RULES are the last thing to paint ... */
2345     for(i=0;i<im->gdes_c;i++){  
2346         
2347         switch(im->gdes[i].gf){
2348         case GF_HRULE:
2349             if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2350                 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2351             };
2352             if(im->gdes[i].yrule >= im->minval
2353                && im->gdes[i].yrule <= im->maxval)
2354               gdImageLine(gif,
2355                           im->xorigin,ytr(im,im->gdes[i].yrule),
2356                           im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2357                           im->gdes[i].col.i); 
2358             break;
2359         case GF_VRULE:
2360             if(im->gdes[i].xrule == 0) { /* fetch variable */
2361                 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2362             };
2363             if(im->gdes[i].xrule >= im->start
2364                         && im->gdes[i].xrule <= im->end)
2365                 gdImageLine(gif,
2366                         xtr(im,im->gdes[i].xrule),im->yorigin,
2367                         xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2368                         im->gdes[i].col.i); 
2369             break;
2370         default:
2371             break;
2372         }
2373     }
2374
2375     if (strcmp(im->graphfile,"-")==0) {
2376 #ifdef WIN32
2377         /* Change translation mode for stdout to BINARY */
2378         _setmode( _fileno( stdout ), O_BINARY );
2379 #endif
2380         fo = stdout;
2381     } else {
2382         if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2383             rrd_set_error("Opening '%s' for write: %s",im->graphfile, strerror(errno));
2384             return (-1);
2385         }
2386     }
2387     switch (im->imgformat) {
2388     case IF_GIF:
2389         gdImageGif(gif, fo);    
2390         break;
2391     case IF_PNG:
2392         gdImagePng(gif, fo);    
2393         break;
2394     }
2395     if (strcmp(im->graphfile,"-") != 0)
2396         fclose(fo);
2397     gdImageDestroy(gif);
2398
2399     return 0;
2400 }
2401
2402
2403 /*****************************************************
2404  * graph stuff 
2405  *****************************************************/
2406
2407 int
2408 gdes_alloc(image_desc_t *im){
2409
2410     long def_step = (im->end-im->start)/im->xsize;
2411     
2412     if (im->step > def_step) /* step can be increassed ... no decreassed */
2413       def_step = im->step;
2414
2415     im->gdes_c++;
2416     
2417     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2418                                            * sizeof(graph_desc_t)))==NULL){
2419         rrd_set_error("realloc graph_descs");
2420         return -1;
2421     }
2422
2423
2424     im->gdes[im->gdes_c-1].step=def_step; 
2425     im->gdes[im->gdes_c-1].start=im->start; 
2426     im->gdes[im->gdes_c-1].end=im->end; 
2427     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2428     im->gdes[im->gdes_c-1].data=NULL;
2429     im->gdes[im->gdes_c-1].ds_namv=NULL;
2430     im->gdes[im->gdes_c-1].data_first=0;
2431     im->gdes[im->gdes_c-1].p_data=NULL;
2432     im->gdes[im->gdes_c-1].rpnp=NULL;
2433     im->gdes[im->gdes_c-1].col.red = -1;
2434     im->gdes[im->gdes_c-1].col.i=-1;
2435     im->gdes[im->gdes_c-1].legend[0]='\0';
2436     im->gdes[im->gdes_c-1].rrd[0]='\0';
2437     im->gdes[im->gdes_c-1].ds=-1;    
2438     im->gdes[im->gdes_c-1].p_data=NULL;    
2439     return 0;
2440 }
2441
2442 /* copies input untill the first unescaped colon is found
2443    or until input ends. backslashes have to be escaped as well */
2444 int
2445 scan_for_col(char *input, int len, char *output)
2446 {
2447     int inp,outp=0;
2448     for (inp=0; 
2449          inp < len &&
2450            input[inp] != ':' &&
2451            input[inp] != '\0';
2452          inp++){
2453       if (input[inp] == '\\' &&
2454           input[inp+1] != '\0' && 
2455           (input[inp+1] == '\\' ||
2456            input[inp+1] == ':')){
2457         output[outp++] = input[++inp];
2458       }
2459       else {
2460         output[outp++] = input[inp];
2461       }
2462     }
2463     output[outp] = '\0';
2464     return inp;
2465 }
2466
2467 int 
2468 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2469 {
2470     
2471     image_desc_t   im;
2472     int            i;
2473     long           long_tmp;
2474     time_t         start_tmp=0,end_tmp=0;
2475     char           scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2476     char           symname[100];
2477     unsigned int            col_red,col_green,col_blue;
2478     long           scancount;
2479     int linepass = 0; /* stack can only follow directly after LINE* AREA or STACK */    
2480     struct time_value start_tv, end_tv;
2481     char *parsetime_error = NULL;
2482     int stroff;    
2483
2484     (*prdata)=NULL;
2485
2486     parsetime("end-24h", &start_tv);
2487     parsetime("now", &end_tv);
2488
2489     im.xlab_user.minsec = -1;
2490     im.xgif=0;
2491     im.ygif=0;
2492     im.xsize = 400;
2493     im.ysize = 100;
2494     im.step = 0;
2495     im.ylegend[0] = '\0';
2496     im.title[0] = '\0';
2497     im.minval = DNAN;
2498     im.maxval = DNAN;    
2499     im.interlaced = 0;
2500     im.unitsexponent= 9999;
2501     im.extra_flags= 0;
2502     im.rigid = 0;
2503     im.imginfo = NULL;
2504     im.lazy = 0;
2505     im.logarithmic = 0;
2506     im.ygridstep = DNAN;
2507     im.draw_x_grid = 1;
2508     im.draw_y_grid = 1;
2509     im.base = 1000;
2510     im.prt_c = 0;
2511     im.gdes_c = 0;
2512     im.gdes = NULL;
2513     im.imgformat = IF_GIF; /* we default to GIF output */
2514
2515     for(i=0;i<DIM(graph_col);i++)
2516         im.graph_col[i].red=-1;
2517     
2518     
2519     while (1){
2520         static struct option long_options[] =
2521         {
2522             {"start",      required_argument, 0,  's'},
2523             {"end",        required_argument, 0,  'e'},
2524             {"x-grid",     required_argument, 0,  'x'},
2525             {"y-grid",     required_argument, 0,  'y'},
2526             {"vertical-label",required_argument,0,'v'},
2527             {"width",      required_argument, 0,  'w'},
2528             {"height",     required_argument, 0,  'h'},
2529             {"interlaced", no_argument,       0,  'i'},
2530             {"upper-limit",required_argument, 0,  'u'},
2531             {"lower-limit",required_argument, 0,  'l'},
2532             {"rigid",      no_argument,       0,  'r'},
2533             {"base",       required_argument, 0,  'b'},
2534             {"logarithmic",no_argument,       0,  'o'},
2535             {"color",      required_argument, 0,  'c'},
2536             {"title",      required_argument, 0,  't'},
2537             {"imginfo",    required_argument, 0,  'f'},
2538             {"imgformat",  required_argument, 0,  'a'},
2539             {"lazy",       no_argument,       0,  'z'},
2540             {"no-legend",  no_argument,       0,  'g'},
2541             {"alt-y-grid", no_argument,       0,   257 },
2542             {"alt-autoscale", no_argument,    0,   258 },
2543             {"alt-autoscale-max", no_argument,    0,   259 },
2544             {"units-exponent",required_argument, 0,  260},
2545             {"step",       required_argument, 0,   261},
2546             {0,0,0,0}};
2547         int option_index = 0;
2548         int opt;
2549
2550         
2551         opt = getopt_long(argc, argv, 
2552                           "s:e:x:y:v:w:h:iu:l:rb:oc:t:f:a:z:g",
2553                           long_options, &option_index);
2554
2555         if (opt == EOF)
2556             break;
2557         
2558         switch(opt) {
2559         case 257:
2560             im.extra_flags |= ALTYGRID;
2561             break;
2562         case 258:
2563             im.extra_flags |= ALTAUTOSCALE;
2564             break;
2565         case 259:
2566             im.extra_flags |= ALTAUTOSCALE_MAX;
2567             break;
2568         case 'g':
2569             im.extra_flags |= NOLEGEND;
2570             break;
2571         case 260:
2572             im.unitsexponent = atoi(optarg);
2573             break;
2574         case 261:
2575             im.step =  atoi(optarg);
2576             break;
2577         case 's':
2578             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2579                 rrd_set_error( "start time: %s", parsetime_error );
2580                 return -1;
2581             }
2582             break;
2583         case 'e':
2584             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2585                 rrd_set_error( "end time: %s", parsetime_error );
2586                 return -1;
2587             }
2588             break;
2589         case 'x':
2590             if(strcmp(optarg,"none") == 0){
2591               im.draw_x_grid=0;
2592               break;
2593             };
2594                 
2595             if(sscanf(optarg,
2596                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2597                       scan_gtm,
2598                       &im.xlab_user.gridst,
2599                       scan_mtm,
2600                       &im.xlab_user.mgridst,
2601                       scan_ltm,
2602                       &im.xlab_user.labst,
2603                       &im.xlab_user.precis,
2604                       &stroff) == 7 && stroff != 0){
2605                 strncpy(im.xlab_form, optarg+stroff, sizeof(im.xlab_form) - 1);
2606                 if((im.xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2607                     rrd_set_error("unknown keyword %s",scan_gtm);
2608                     return -1;
2609                 } else if ((im.xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2610                     rrd_set_error("unknown keyword %s",scan_mtm);
2611                     return -1;
2612                 } else if ((im.xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2613                     rrd_set_error("unknown keyword %s",scan_ltm);
2614                     return -1;
2615                 } 
2616                 im.xlab_user.minsec = 1;
2617                 im.xlab_user.stst = im.xlab_form;
2618             } else {
2619                 rrd_set_error("invalid x-grid format");
2620                 return -1;
2621             }
2622             break;
2623         case 'y':
2624
2625             if(strcmp(optarg,"none") == 0){
2626               im.draw_y_grid=0;
2627               break;
2628             };
2629
2630             if(sscanf(optarg,
2631                       "%lf:%d",
2632                       &im.ygridstep,
2633                       &im.ylabfact) == 2) {
2634                 if(im.ygridstep<=0){
2635                     rrd_set_error("grid step must be > 0");
2636                     return -1;
2637                 } else if (im.ylabfact < 1){
2638                     rrd_set_error("label factor must be > 0");
2639                     return -1;
2640                 } 
2641             } else {
2642                 rrd_set_error("invalid y-grid format");
2643                 return -1;
2644             }
2645             break;
2646         case 'v':
2647             strncpy(im.ylegend,optarg,150);
2648             im.ylegend[150]='\0';
2649             break;
2650         case 'u':
2651             im.maxval = atof(optarg);
2652             break;
2653         case 'l':
2654             im.minval = atof(optarg);
2655             break;
2656         case 'b':
2657             im.base = atol(optarg);
2658             if(im.base != 1024 && im.base != 1000 ){
2659                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2660                 return -1;
2661             }
2662             break;
2663         case 'w':
2664             long_tmp = atol(optarg);
2665             if (long_tmp < 10) {
2666                 rrd_set_error("width below 10 pixels");
2667                 return -1;
2668             }
2669             im.xsize = long_tmp;
2670             break;
2671         case 'h':
2672             long_tmp = atol(optarg);
2673             if (long_tmp < 10) {
2674                 rrd_set_error("height below 10 pixels");
2675                 return -1;
2676             }
2677             im.ysize = long_tmp;
2678             break;
2679         case 'i':
2680             im.interlaced = 1;
2681             break;
2682         case 'r':
2683             im.rigid = 1;
2684             break;
2685         case 'f':
2686             im.imginfo = optarg;
2687             break;
2688         case 'a':
2689             if((im.imgformat = if_conv(optarg)) == -1) {
2690                 rrd_set_error("unsupported graphics format '%s'",optarg);
2691                 return -1;
2692             }
2693             break;
2694         case 'z':
2695             im.lazy = 1;
2696             break;
2697         case 'o':
2698             im.logarithmic = 1;
2699             if (isnan(im.minval))
2700                 im.minval=1;
2701             break;
2702         case 'c':
2703             if(sscanf(optarg,
2704                       "%10[A-Z]#%2x%2x%2x",
2705                       col_nam,&col_red,&col_green,&col_blue) == 4){
2706                 int ci;
2707                 if((ci=grc_conv(col_nam)) != -1){
2708                     im.graph_col[ci].red=col_red;
2709                     im.graph_col[ci].green=col_green;
2710                     im.graph_col[ci].blue=col_blue;
2711                 }  else {
2712                   rrd_set_error("invalid color name '%s'",col_nam);
2713                 }
2714             } else {
2715                 rrd_set_error("invalid color def format");
2716                 return -1;
2717             }
2718             break;        
2719         case 't':
2720             strncpy(im.title,optarg,150);
2721             im.title[150]='\0';
2722             break;
2723
2724         case '?':
2725             if (optopt != 0)
2726                 rrd_set_error("unknown option '%c'", optopt);
2727             else
2728                 rrd_set_error("unknown option '%s'",argv[optind-1]);
2729             return -1;
2730         }
2731     }
2732     
2733     if (optind >= argc) {
2734        rrd_set_error("missing filename");
2735        return -1;
2736     }
2737
2738     if (im.logarithmic == 1 && (im.minval <= 0 || isnan(im.minval))){
2739         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
2740         return -1;
2741     }
2742
2743     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2744     im.graphfile[MAXPATH-1]='\0';
2745
2746     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2747         return -1;
2748     }  
2749     
2750     if (start_tmp < 3600*24*365*10){
2751         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2752         return -1;
2753     }
2754     
2755     if (end_tmp < start_tmp) {
2756         rrd_set_error("start (%ld) should be less than end (%ld)", 
2757                start_tmp, end_tmp);
2758         return -1;
2759     }
2760     
2761     im.start = start_tmp;
2762     im.end = end_tmp;
2763
2764     
2765     for(i=optind+1;i<argc;i++){
2766         int   argstart=0;
2767         int   strstart=0;
2768         char  varname[30],*rpnex;
2769         gdes_alloc(&im);
2770         if(sscanf(argv[i],"%10[A-Z0-9]:%n",symname,&argstart)==1){
2771             if((im.gdes[im.gdes_c-1].gf=gf_conv(symname))==-1){
2772                 im_free(&im);
2773                 rrd_set_error("unknown function '%s'",symname);
2774                 return -1;
2775             }
2776         } else {
2777             rrd_set_error("can't parse '%s'",argv[i]);
2778             im_free(&im);
2779             return -1;
2780         }
2781
2782         /* reset linepass if a non LINE/STACK/AREA operator gets parsed 
2783         
2784            if (im.gdes[im.gdes_c-1].gf != GF_LINE1 &&
2785            im.gdes[im.gdes_c-1].gf != GF_LINE2 &&
2786            im.gdes[im.gdes_c-1].gf != GF_LINE3 &&
2787            im.gdes[im.gdes_c-1].gf != GF_AREA &&
2788            im.gdes[im.gdes_c-1].gf != GF_STACK) {
2789            linepass = 0;
2790            } 
2791         */
2792         
2793         switch(im.gdes[im.gdes_c-1].gf){
2794         case GF_PRINT:
2795             im.prt_c++;
2796         case GF_GPRINT:
2797             if(sscanf(
2798                 &argv[i][argstart],
2799                 "%29[^#:]:" CF_NAM_FMT ":%n",
2800                 varname,symname,&strstart) == 2){
2801                 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].format);
2802                 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2803                     im_free(&im);
2804                     rrd_set_error("unknown variable '%s'",varname);
2805                     return -1;
2806                 }       
2807                 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
2808                     im_free(&im);
2809                     return -1;
2810                 }
2811                 
2812             } else {
2813                 im_free(&im);
2814                 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2815                 return -1;
2816             }
2817             break;
2818         case GF_COMMENT:
2819             if(strlen(&argv[i][argstart])>FMT_LEG_LEN) argv[i][argstart+FMT_LEG_LEN-3]='\0' ;
2820             strcpy(im.gdes[im.gdes_c-1].legend, &argv[i][argstart]);
2821             break;
2822         case GF_HRULE:
2823             if(sscanf(
2824                 &argv[i][argstart],
2825                 "%lf#%2x%2x%2x:%n",
2826                 &im.gdes[im.gdes_c-1].yrule,
2827                 &col_red,&col_green,&col_blue,
2828                 &strstart) >=  4){
2829                 im.gdes[im.gdes_c-1].col.red = col_red;
2830                 im.gdes[im.gdes_c-1].col.green = col_green;
2831                 im.gdes[im.gdes_c-1].col.blue = col_blue;
2832                 if(strstart <= 0){
2833                     im.gdes[im.gdes_c-1].legend[0] = '\0';
2834                 } else { 
2835                     scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2836                 }
2837             } else {
2838                 im_free(&im);
2839                 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2840                 return -1;
2841             } 
2842             break;
2843         case GF_VRULE:
2844             /* scan for either "VRULE:vname#..." or "VRULE:num#..."
2845              *
2846              * If a vname is used, the value 0 is set; this is catched
2847              * when graphing.  Setting value 0 from the script is not
2848              * permitted
2849              */
2850             strstart=0;
2851             sscanf(&argv[i][argstart], DEF_NAM_FMT "#%n"
2852                 ,varname
2853                 ,&strstart
2854                 );
2855             if (strstart==0) {
2856                 sscanf(&argv[i][argstart], "%lu#%n"
2857                     ,(long unsigned int *)&im.gdes[im.gdes_c-1].xrule
2858                     ,&strstart
2859                 );
2860                 if (im.gdes[im.gdes_c-1].xrule==0)
2861                     strstart=0;
2862             } else {
2863                 im.gdes[im.gdes_c-1].xrule = 0; /* signal use of vname */
2864                 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2865                     im_free(&im);
2866                     rrd_set_error("unknown variable '%s' in VRULE",varname);
2867                     return -1;
2868                 }               
2869                 if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_VDEF) {
2870                     im_free(&im);
2871                     rrd_set_error("Only VDEF is allowed in VRULE",varname);
2872                     return -1;
2873                 }
2874             };
2875             if (strstart==0) {
2876                 im_free(&im);
2877                 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2878                 return -1;
2879             } else {
2880                 int n=0;
2881                 if(sscanf(
2882                         &argv[i][argstart+strstart],
2883                         "%2x%2x%2x:%n",
2884                         &col_red,
2885                         &col_green,
2886                         &col_blue,
2887                         &n)>=3) {
2888                     im.gdes[im.gdes_c-1].col.red = col_red;
2889                     im.gdes[im.gdes_c-1].col.green = col_green;
2890                     im.gdes[im.gdes_c-1].col.blue = col_blue;
2891                     if (n==0) {
2892                         im.gdes[im.gdes_c-1].legend[0] = '\0';
2893                     } else {
2894                         scan_for_col(&argv[i][argstart+strstart+n],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2895                     }
2896                 } else {
2897                     im_free(&im);
2898                     rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2899                     return -1;
2900                 }
2901             }
2902             break;
2903         case GF_TICK:
2904             if((scancount=sscanf(
2905                 &argv[i][argstart],
2906                 "%29[^:#]#%2x%2x%2x:%lf:%n",
2907                 varname,
2908                 &col_red,
2909                 &col_green,
2910                 &col_blue,
2911                 &(im.gdes[im.gdes_c-1].yrule),
2912                 &strstart))>=1)
2913                 {
2914                 im.gdes[im.gdes_c-1].col.red = col_red;
2915                 im.gdes[im.gdes_c-1].col.green = col_green;
2916                 im.gdes[im.gdes_c-1].col.blue = col_blue;
2917                 if(strstart <= 0){
2918                     im.gdes[im.gdes_c-1].legend[0] = '\0';
2919                 } else { 
2920                     scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2921                 }
2922                 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2923                     im_free(&im);
2924                     rrd_set_error("unknown variable '%s'",varname);
2925                     return -1;
2926                 }
2927                 if (im.gdes[im.gdes_c-1].yrule <= 0.0 || im.gdes[im.gdes_c-1].yrule > 1.0)
2928                 {
2929                     im_free(&im);
2930                     rrd_set_error("Tick mark scaling factor out of range");
2931                     return -1;
2932                 }
2933                 if (scancount < 4)
2934                    im.gdes[im.gdes_c-1].col.red = -1;           
2935             if (scancount < 5) 
2936                    /* default tick marks: 10% of the y-axis */
2937                    im.gdes[im.gdes_c-1].yrule = 0.1;
2938
2939                 } else {
2940                    im_free(&im);
2941                    rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2942                    return -1;
2943                 } /* endif sscanf */
2944                 break;
2945         case GF_STACK:
2946             if(linepass == 0){
2947                 im_free(&im);
2948                 rrd_set_error("STACK must follow AREA, LINE or STACK");
2949                 return -1; 
2950             }           
2951         case GF_LINE1:
2952         case GF_LINE2:
2953         case GF_LINE3:
2954         case GF_AREA:
2955             linepass = 1;
2956             if((scancount=sscanf(
2957                 &argv[i][argstart],
2958                 "%29[^:#]#%2x%2x%2x:%n",
2959                 varname,
2960                 &col_red,
2961                 &col_green,
2962                     &col_blue,
2963                 &strstart))>=1){
2964                 im.gdes[im.gdes_c-1].col.red = col_red;
2965                 im.gdes[im.gdes_c-1].col.green = col_green;
2966                 im.gdes[im.gdes_c-1].col.blue = col_blue;
2967                 if(strstart <= 0){
2968                     im.gdes[im.gdes_c-1].legend[0] = '\0';
2969                 } else { 
2970                     scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2971                 }
2972                 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2973                     im_free(&im);
2974                     rrd_set_error("unknown variable '%s'",varname);
2975                     return -1;
2976                 }               
2977                 if (scancount < 4)
2978                     im.gdes[im.gdes_c-1].col.red = -1;          
2979                 
2980             } else {
2981                 im_free(&im);
2982                 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2983                 return -1;
2984             }
2985             break;
2986         case GF_CDEF:
2987             if((rpnex = malloc(strlen(&argv[i][argstart])*sizeof(char)))==NULL){
2988                 rrd_set_error("malloc for CDEF");
2989                 return -1;
2990             }
2991             if(sscanf(
2992                     &argv[i][argstart],
2993                     DEF_NAM_FMT "=%[^: ]",
2994                     im.gdes[im.gdes_c-1].vname,
2995                     rpnex) != 2){
2996                 im_free(&im);
2997                 free(rpnex);
2998                 rrd_set_error("can't parse CDEF '%s'",&argv[i][argstart]);
2999                 return -1;
3000             }
3001             /* checking for duplicate variable names */
3002             if(find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3003                 im_free(&im);
3004                 rrd_set_error("duplicate variable '%s'",
3005                               im.gdes[im.gdes_c-1].vname);
3006                 return -1; 
3007             }      
3008             if((im.gdes[im.gdes_c-1].rpnp = 
3009                    rpn_parse((void*)&im,rpnex,&find_var_wrapper))== NULL){
3010                 rrd_set_error("invalid rpn expression '%s'", rpnex);
3011                 im_free(&im);           
3012                 return -1;
3013             }
3014             free(rpnex);
3015             break;
3016         case GF_VDEF:
3017             /*
3018              * strstart is set to zero and will NOT be changed
3019              * if the comma is not matched.  This means that it
3020              * remains zero. Although strstart is initialized to
3021              * zero at the beginning of this loop, we do it again
3022              * here just in case someone changes the code...
3023              *
3024              * According to the docs we cannot rely on the
3025              * returned value from sscanf; it can be 2 or 3,
3026              * depending on %n incrementing it or not.
3027              */
3028             strstart=0;
3029             sscanf(
3030                     &argv[i][argstart],
3031                     DEF_NAM_FMT "=" DEF_NAM_FMT ",%n",
3032                     im.gdes[im.gdes_c-1].vname,
3033                     varname,
3034                     &strstart);
3035             if (strstart){
3036                 /* checking both variable names */
3037                 if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3038                     im_free(&im);
3039                     rrd_set_error("duplicate variable '%s'",
3040                                 im.gdes[im.gdes_c-1].vname);
3041                     return -1; 
3042                 } else {
3043                     if ((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname)) == -1){
3044                         im_free(&im);
3045                         rrd_set_error("variable '%s' not known in VDEF '%s'",
3046                                 varname,
3047                                 im.gdes[im.gdes_c-1].vname);
3048                         return -1; 
3049                     } else {
3050                         if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_DEF
3051                         && im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_CDEF){
3052                             rrd_set_error("variable '%s' not DEF nor CDEF in VDEF '%s'",
3053                                 varname,
3054                                 im.gdes[im.gdes_c-1].vname);
3055                             im_free(&im);
3056                             return -1; 
3057                         }
3058                     }
3059                     /* parsed upto and including the first comma. Now
3060                      * see what function is requested.  This function
3061                      * sets the error string.
3062                      */
3063                     if (vdef_parse(&im.gdes[im.gdes_c-1],&argv[i][argstart+strstart])<0) {
3064                         im_free(&im);
3065                         return -1;
3066                     };
3067                 }
3068             } else {
3069                 im_free(&im);
3070                 rrd_set_error("can't parse VDEF '%s'",&argv[i][argstart]);
3071                 return -1;
3072             }
3073             break;
3074         case GF_DEF:
3075             if (sscanf(
3076                 &argv[i][argstart],
3077                 DEF_NAM_FMT "=%n",
3078                 im.gdes[im.gdes_c-1].vname,
3079                 &strstart)== 1 && strstart){ /* is the = did not match %n returns 0 */ 
3080                 if(sscanf(&argv[i][argstart
3081                                   +strstart
3082                                   +scan_for_col(&argv[i][argstart+strstart],
3083                                                 MAXPATH,im.gdes[im.gdes_c-1].rrd)],
3084                           ":" DS_NAM_FMT ":" CF_NAM_FMT,
3085                           im.gdes[im.gdes_c-1].ds_nam,
3086                           symname) != 2){
3087                     im_free(&im);
3088                     rrd_set_error("can't parse DEF '%s' -2",&argv[i][argstart]);
3089                     return -1;
3090                 }
3091             } else {
3092                 im_free(&im);
3093                 rrd_set_error("can't parse DEF '%s'",&argv[i][argstart]);
3094                 return -1;
3095             }
3096             
3097             /* checking for duplicate DEF CDEFS */
3098             if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3099                 im_free(&im);
3100                 rrd_set_error("duplicate variable '%s'",
3101                           im.gdes[im.gdes_c-1].vname);
3102                 return -1; 
3103             }      
3104             if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
3105                 im_free(&im);
3106                 rrd_set_error("unknown cf '%s'",symname);
3107                 return -1;
3108             }
3109             break;
3110         }
3111         
3112     }
3113
3114     if (im.gdes_c==0){
3115         rrd_set_error("can't make a graph without contents");
3116         im_free(&im);
3117         return(-1); 
3118     }
3119     
3120         /* parse rest of arguments containing information on what to draw*/
3121     if (graph_paint(&im,prdata)==-1){
3122         im_free(&im);
3123         return -1;
3124     }
3125     
3126     *xsize=im.xgif;
3127     *ysize=im.ygif;
3128     if (im.imginfo){
3129       char *filename;
3130       if (! (*prdata)) {        
3131         /* maybe prdata is not allocated yet ... lets do it now */
3132         if((*prdata = calloc(2,sizeof(char *)))==NULL){
3133           rrd_set_error("malloc imginfo");
3134           return -1; 
3135         };
3136       }
3137       if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3138          ==NULL){
3139         rrd_set_error("malloc imginfo");
3140         return -1;
3141       }
3142       filename=im.graphfile+strlen(im.graphfile);      
3143       while(filename > im.graphfile){
3144         if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3145         filename--;
3146       }
3147       
3148       sprintf((*prdata)[0],im.imginfo,filename,im.xgif,im.ygif);
3149     }
3150     im_free(&im);
3151     return 0;
3152 }
3153
3154 int bad_format(char *fmt) {
3155         char *ptr;
3156
3157         ptr = fmt;
3158         while (*ptr != '\0') {
3159                 if (*ptr == '%') {ptr++;
3160                         if (*ptr == '\0') return 1;
3161                         while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') { 
3162                                 ptr++;
3163                         }
3164                         if (*ptr == '\0') return 1;
3165                         if (*ptr == 'l') {
3166                                 ptr++;
3167                                 if (*ptr == '\0') return 1;
3168                                 if (*ptr == 'e' || *ptr == 'f') { 
3169                                         ptr++; 
3170                                         } else { return 1; }
3171                         }
3172                         else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3173                         else { return 1; }
3174                 } else {
3175                         ++ptr;
3176                 }
3177         }
3178         return 0;
3179 }
3180 int
3181 vdef_parse(gdes,str)
3182 struct graph_desc_t *gdes;
3183 char *str;
3184 {
3185     /* A VDEF currently is either "func" or "param,func"
3186      * so the parsing is rather simple.  Change if needed.
3187      */
3188     double      param;
3189     char        func[30];
3190     int         n;
3191     
3192     n=0;
3193     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3194     if (n==strlen(str)) { /* matched */
3195         ;
3196     } else {
3197         n=0;
3198         sscanf(str,"%29[A-Z]%n",func,&n);
3199         if (n==strlen(str)) { /* matched */
3200             param=DNAN;
3201         } else {
3202             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3203                 ,str
3204                 ,gdes->vname
3205                 );
3206             return -1;
3207         }
3208     }
3209     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3210     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3211     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3212     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3213     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3214     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3215     else {
3216         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3217             ,func
3218             ,gdes->vname
3219             );
3220         return -1;
3221     };
3222
3223     switch (gdes->vf.op) {
3224         case VDEF_PERCENT:
3225             if (isnan(param)) { /* no parameter given */
3226                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3227                     ,func
3228                     ,gdes->vname
3229                     );
3230                 return -1;
3231             };
3232             if (param>=0.0 && param<=100.0) {
3233                 gdes->vf.param = param;
3234                 gdes->vf.val   = DNAN;  /* undefined */
3235                 gdes->vf.when  = 0;     /* undefined */
3236             } else {
3237                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3238                     ,param
3239                     ,gdes->vname
3240                     );
3241                 return -1;
3242             };
3243             break;
3244         case VDEF_MAXIMUM:
3245         case VDEF_AVERAGE:
3246         case VDEF_MINIMUM:
3247         case VDEF_FIRST:
3248         case VDEF_LAST:
3249             if (isnan(param)) {
3250                 gdes->vf.param = DNAN;
3251                 gdes->vf.val   = DNAN;
3252                 gdes->vf.when  = 0;
3253             } else {
3254                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3255                     ,func
3256                     ,gdes->vname
3257                     );
3258                 return -1;
3259             };
3260             break;
3261     };
3262     return 0;
3263 }
3264 int
3265 vdef_calc(im,gdi)
3266 image_desc_t *im;
3267 int gdi;
3268 {
3269     graph_desc_t        *src,*dst;
3270     rrd_value_t         *data;
3271     long                step,steps;
3272
3273     dst = &im->gdes[gdi];
3274     src = &im->gdes[dst->vidx];
3275     data = src->data + src->ds + src->ds_cnt; /* skip first value! */
3276     steps = (src->end - src->start) / src->step;
3277
3278 #if 0
3279 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3280     ,src->start
3281     ,src->end
3282     ,steps
3283     );
3284 #endif
3285
3286     switch (im->gdes[gdi].vf.op) {
3287         case VDEF_PERCENT: {
3288                 rrd_value_t *   array;
3289                 int             field;
3290
3291
3292                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3293                     rrd_set_error("malloc VDEV_PERCENT");
3294                     return -1;
3295                 }
3296                 for (step=0;step < steps; step++) {
3297                     array[step]=data[step*src->ds_cnt];
3298                 }
3299                 qsort(array,step,sizeof(double),vdef_percent_compar);
3300
3301                 field = (steps-1)*dst->vf.param/100;
3302                 dst->vf.val  = array[field];
3303                 dst->vf.when = 0;       /* no time component */
3304 #if 0
3305 for(step=0;step<steps;step++)
3306 printf("DEBUG: %3i:%10.2f %c\n",step,array[step],step==field?'*':' ');
3307 #endif
3308             }
3309             break;
3310         case VDEF_MAXIMUM:
3311             step=0;
3312             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3313             if (step == steps) {
3314                 dst->vf.val  = DNAN;
3315                 dst->vf.when = 0;
3316             } else {
3317                 dst->vf.val  = data[steps*src->ds_cnt];
3318                 dst->vf.when = src->start + (step+1)*src->step;
3319             }
3320             while (step != steps) {
3321                 if (finite(data[step*src->ds_cnt])) {
3322                     if (data[step*src->ds_cnt] > dst->vf.val) {
3323                         dst->vf.val  = data[steps*src->ds_cnt];
3324                         dst->vf.when = src->start + (step+1)*src->step;
3325                     }
3326                 }
3327                 step++;
3328             }
3329             break;
3330         case VDEF_AVERAGE: {
3331             int cnt=0;
3332             double sum=0.0;
3333             for (step=0;step<steps;step++) {
3334                 if (finite(data[step*src->ds_cnt])) {
3335                     sum += data[step*src->ds_cnt];
3336                     cnt ++;
3337                 }
3338                 step++;
3339             }
3340             if (cnt) {
3341                 dst->vf.val  = sum/cnt;
3342                 dst->vf.when = 0;       /* no time component */
3343             } else {
3344                 dst->vf.val  = DNAN;
3345                 dst->vf.when = 0;
3346             }
3347             }
3348             break;
3349         case VDEF_MINIMUM:
3350             step=0;
3351             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3352             if (step == steps) {
3353                 dst->vf.val  = DNAN;
3354                 dst->vf.when = 0;
3355             } else {
3356                 dst->vf.val  = data[steps*src->ds_cnt];
3357                 dst->vf.when = src->start + (step+1)*src->step;
3358             }
3359             while (step != steps) {
3360                 if (finite(data[step*src->ds_cnt])) {
3361                     if (data[step*src->ds_cnt] < dst->vf.val) {
3362                         dst->vf.val  = data[steps*src->ds_cnt];
3363                         dst->vf.when = src->start + (step+1)*src->step;
3364                     }
3365                 }
3366                 step++;
3367             }
3368             break;
3369         case VDEF_FIRST:
3370             /* The time value returned here is one step before the
3371              * actual time value.  This is the start of the first
3372              * non-NaN interval.
3373              */
3374             step=0;
3375             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3376             if (step == steps) { /* all entries were NaN */
3377                 dst->vf.val  = DNAN;
3378                 dst->vf.when = 0;
3379             } else {
3380                 dst->vf.val  = data[step*src->ds_cnt];
3381                 dst->vf.when = src->start + step*src->step;
3382             }
3383             break;
3384         case VDEF_LAST:
3385             /* The time value returned here is the
3386              * actual time value.  This is the end of the last
3387              * non-NaN interval.
3388              */
3389             step=steps-1;
3390             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3391             if (step < 0) { /* all entries were NaN */
3392                 dst->vf.val  = DNAN;
3393                 dst->vf.when = 0;
3394             } else {
3395                 dst->vf.val  = data[step*src->ds_cnt];
3396                 dst->vf.when = src->start + (step+1)*src->step;
3397             }
3398             break;
3399     }
3400     return 0;
3401 }
3402
3403 /* NaN <= -INF <= finite_values <= INF */
3404 int
3405 vdef_percent_compar(a,b)
3406 const void *a,*b;
3407 {
3408     /* Equality is not returned; this doesn't hurt except
3409      * (maybe) for a little performance.
3410      */
3411
3412     /* First catch NaN values. They are smallest */
3413     if (isnan( *(double *)a )) return -1;
3414     if (isnan( *(double *)b )) return  1;
3415
3416     /* NaN doestn't reach this part so INF and -INF are extremes.
3417      * The sign from isinf() is compatible with the sign we return
3418      */
3419     if (isinf( *(double *)a )) return isinf( *(double *)a );
3420     if (isinf( *(double *)b )) return isinf( *(double *)b );
3421
3422     /* If we reach this, both values must be finite */
3423     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3424 }