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