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