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