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