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