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