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