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