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