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