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