1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
25 #include "rrd_graph.h"
26 #include "rrd_graph_helper.h"
28 /* some constant definitions */
31 #ifndef RRD_DEFAULT_FONT
33 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
35 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
36 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
41 text_prop_t text_prop[] = {
42 { 10.0, RRD_DEFAULT_FONT }, /* default */
43 { 12.0, RRD_DEFAULT_FONT }, /* title */
44 { 8.0, RRD_DEFAULT_FONT }, /* axis */
45 { 10.0, RRD_DEFAULT_FONT }, /* unit */
46 { 10.0, RRD_DEFAULT_FONT } /* legend */
50 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
51 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
52 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
53 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
54 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
55 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
56 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
57 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
58 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
59 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
60 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
61 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
62 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
63 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
64 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
65 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
68 /* sensible logarithmic y label intervals ...
69 the first element of each row defines the possible starting points on the
70 y axis ... the other specify the */
72 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
75 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
76 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
77 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
78 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
79 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
81 /* sensible y label intervals ...*/
99 gfx_color_t graph_col[] = /* default colors */
100 { 0xFFFFFFFF, /* canvas */
101 0xF0F0F0FF, /* background */
102 0xD0D0D0FF, /* shade A */
103 0xA0A0A0FF, /* shade B */
104 0x909090FF, /* grid */
105 0xE05050FF, /* major grid */
106 0x000000FF, /* font */
107 0x000000FF, /* frame */
108 0xFF0000FF /* arrow */
115 # define DPRINT(x) (void)(printf x, printf("\n"))
121 /* initialize with xtr(im,0); */
123 xtr(image_desc_t *im,time_t mytime){
126 pixie = (double) im->xsize / (double)(im->end - im->start);
129 return (int)((double)im->xorigin
130 + pixie * ( mytime - im->start ) );
133 /* translate data values into y coordinates */
135 ytr(image_desc_t *im, double value){
140 pixie = (double) im->ysize / (im->maxval - im->minval);
142 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
144 } else if(!im->logarithmic) {
145 yval = im->yorigin - pixie * (value - im->minval);
147 if (value < im->minval) {
150 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
153 /* make sure we don't return anything too unreasonable. GD lib can
154 get terribly slow when drawing lines outside its scope. This is
155 especially problematic in connection with the rigid option */
157 /* keep yval as-is */
158 } else if (yval > im->yorigin) {
159 yval = im->yorigin+2;
160 } else if (yval < im->yorigin - im->ysize){
161 yval = im->yorigin - im->ysize - 2;
168 /* conversion function for symbolic entry names */
171 #define conv_if(VV,VVV) \
172 if (strcmp(#VV, string) == 0) return VVV ;
174 enum gf_en gf_conv(char *string){
176 conv_if(PRINT,GF_PRINT)
177 conv_if(GPRINT,GF_GPRINT)
178 conv_if(COMMENT,GF_COMMENT)
179 conv_if(HRULE,GF_HRULE)
180 conv_if(VRULE,GF_VRULE)
181 conv_if(LINE,GF_LINE)
182 conv_if(AREA,GF_AREA)
183 conv_if(STACK,GF_STACK)
184 conv_if(TICK,GF_TICK)
186 conv_if(CDEF,GF_CDEF)
187 conv_if(VDEF,GF_VDEF)
188 conv_if(PART,GF_PART)
189 conv_if(XPORT,GF_XPORT)
194 enum gfx_if_en if_conv(char *string){
204 enum tmt_en tmt_conv(char *string){
206 conv_if(SECOND,TMT_SECOND)
207 conv_if(MINUTE,TMT_MINUTE)
208 conv_if(HOUR,TMT_HOUR)
210 conv_if(WEEK,TMT_WEEK)
211 conv_if(MONTH,TMT_MONTH)
212 conv_if(YEAR,TMT_YEAR)
216 enum grc_en grc_conv(char *string){
218 conv_if(BACK,GRC_BACK)
219 conv_if(CANVAS,GRC_CANVAS)
220 conv_if(SHADEA,GRC_SHADEA)
221 conv_if(SHADEB,GRC_SHADEB)
222 conv_if(GRID,GRC_GRID)
223 conv_if(MGRID,GRC_MGRID)
224 conv_if(FONT,GRC_FONT)
225 conv_if(FRAME,GRC_FRAME)
226 conv_if(ARROW,GRC_ARROW)
231 enum text_prop_en text_prop_conv(char *string){
233 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
234 conv_if(TITLE,TEXT_PROP_TITLE)
235 conv_if(AXIS,TEXT_PROP_AXIS)
236 conv_if(UNIT,TEXT_PROP_UNIT)
237 conv_if(LEGEND,TEXT_PROP_LEGEND)
247 im_free(image_desc_t *im)
250 if (im == NULL) return 0;
251 for(i=0;i<im->gdes_c;i++){
252 if (im->gdes[i].data_first){
253 /* careful here, because a single pointer can occur several times */
254 free (im->gdes[i].data);
255 if (im->gdes[i].ds_namv){
256 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
257 free(im->gdes[i].ds_namv[ii]);
258 free(im->gdes[i].ds_namv);
261 free (im->gdes[i].p_data);
262 free (im->gdes[i].rpnp);
265 gfx_destroy(im->canvas);
269 /* find SI magnitude symbol for the given number*/
272 image_desc_t *im, /* image description */
279 char *symbol[] = {"a", /* 10e-18 Atto */
280 "f", /* 10e-15 Femto */
281 "p", /* 10e-12 Pico */
282 "n", /* 10e-9 Nano */
283 "u", /* 10e-6 Micro */
284 "m", /* 10e-3 Milli */
289 "T", /* 10e12 Tera */
290 "P", /* 10e15 Peta */
296 if (*value == 0.0 || isnan(*value) ) {
300 sindex = floor(log(fabs(*value))/log((double)im->base));
301 *magfact = pow((double)im->base, (double)sindex);
302 (*value) /= (*magfact);
304 if ( sindex <= symbcenter && sindex >= -symbcenter) {
305 (*symb_ptr) = symbol[sindex+symbcenter];
313 /* find SI magnitude symbol for the numbers on the y-axis*/
316 image_desc_t *im /* image description */
320 char symbol[] = {'a', /* 10e-18 Atto */
321 'f', /* 10e-15 Femto */
322 'p', /* 10e-12 Pico */
323 'n', /* 10e-9 Nano */
324 'u', /* 10e-6 Micro */
325 'm', /* 10e-3 Milli */
330 'T', /* 10e12 Tera */
331 'P', /* 10e15 Peta */
337 if (im->unitsexponent != 9999) {
338 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
339 digits = floor(im->unitsexponent / 3);
341 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
343 im->magfact = pow((double)im->base , digits);
346 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
349 if ( ((digits+symbcenter) < sizeof(symbol)) &&
350 ((digits+symbcenter) >= 0) )
351 im->symbol = symbol[(int)digits+symbcenter];
356 /* move min and max values around to become sensible */
359 expand_range(image_desc_t *im)
361 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
362 600.0,500.0,400.0,300.0,250.0,
363 200.0,125.0,100.0,90.0,80.0,
364 75.0,70.0,60.0,50.0,40.0,30.0,
365 25.0,20.0,10.0,9.0,8.0,
366 7.0,6.0,5.0,4.0,3.5,3.0,
367 2.5,2.0,1.8,1.5,1.2,1.0,
368 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
370 double scaled_min,scaled_max;
377 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
378 im->minval,im->maxval,im->magfact);
381 if (isnan(im->ygridstep)){
382 if(im->extra_flags & ALTAUTOSCALE) {
383 /* measure the amplitude of the function. Make sure that
384 graph boundaries are slightly higher then max/min vals
385 so we can see amplitude on the graph */
388 delt = im->maxval - im->minval;
390 fact = 2.0 * pow(10.0,
391 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
393 adj = (fact - delt) * 0.55;
395 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
401 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
402 /* measure the amplitude of the function. Make sure that
403 graph boundaries are slightly higher than max vals
404 so we can see amplitude on the graph */
405 adj = (im->maxval - im->minval) * 0.1;
409 scaled_min = im->minval / im->magfact;
410 scaled_max = im->maxval / im->magfact;
412 for (i=1; sensiblevalues[i] > 0; i++){
413 if (sensiblevalues[i-1]>=scaled_min &&
414 sensiblevalues[i]<=scaled_min)
415 im->minval = sensiblevalues[i]*(im->magfact);
417 if (-sensiblevalues[i-1]<=scaled_min &&
418 -sensiblevalues[i]>=scaled_min)
419 im->minval = -sensiblevalues[i-1]*(im->magfact);
421 if (sensiblevalues[i-1] >= scaled_max &&
422 sensiblevalues[i] <= scaled_max)
423 im->maxval = sensiblevalues[i-1]*(im->magfact);
425 if (-sensiblevalues[i-1]<=scaled_max &&
426 -sensiblevalues[i] >=scaled_max)
427 im->maxval = -sensiblevalues[i]*(im->magfact);
431 /* adjust min and max to the grid definition if there is one */
432 im->minval = (double)im->ylabfact * im->ygridstep *
433 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
434 im->maxval = (double)im->ylabfact * im->ygridstep *
435 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
439 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
440 im->minval,im->maxval,im->magfact);
445 apply_gridfit(image_desc_t *im)
447 if (isnan(im->minval) || isnan(im->maxval))
450 if (im->logarithmic) {
451 double ya, yb, ypix, ypixfrac;
452 double log10_range = log10(im->maxval) - log10(im->minval);
453 ya = pow((double)10, floor(log10(im->minval)));
454 while (ya < im->minval)
457 return; /* don't have y=10^x gridline */
459 if (yb <= im->maxval) {
460 /* we have at least 2 y=10^x gridlines.
461 Make sure distance between them in pixels
462 are an integer by expanding im->maxval */
463 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
464 double factor = y_pixel_delta / floor(y_pixel_delta);
465 double new_log10_range = factor * log10_range;
466 double new_ymax_log10 = log10(im->minval) + new_log10_range;
467 im->maxval = pow(10, new_ymax_log10);
468 ytr(im, DNAN); /* reset precalc */
469 log10_range = log10(im->maxval) - log10(im->minval);
471 /* make sure first y=10^x gridline is located on
472 integer pixel position by moving scale slightly
473 downwards (sub-pixel movement) */
474 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
475 ypixfrac = ypix - floor(ypix);
476 if (ypixfrac > 0 && ypixfrac < 1) {
477 double yfrac = ypixfrac / im->ysize;
478 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
479 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
480 ytr(im, DNAN); /* reset precalc */
483 /* Make sure we have an integer pixel distance between
484 each minor gridline */
485 double ypos1 = ytr(im, im->minval);
486 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
487 double y_pixel_delta = ypos1 - ypos2;
488 double factor = y_pixel_delta / floor(y_pixel_delta);
489 double new_range = factor * (im->maxval - im->minval);
490 double gridstep = im->ygrid_scale.gridstep;
491 double minor_y, minor_y_px, minor_y_px_frac;
492 im->maxval = im->minval + new_range;
493 ytr(im, DNAN); /* reset precalc */
494 /* make sure first minor gridline is on integer pixel y coord */
495 minor_y = gridstep * floor(im->minval / gridstep);
496 while (minor_y < im->minval)
498 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
499 minor_y_px_frac = minor_y_px - floor(minor_y_px);
500 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
501 double yfrac = minor_y_px_frac / im->ysize;
502 double range = im->maxval - im->minval;
503 im->minval = im->minval - yfrac * range;
504 im->maxval = im->maxval - yfrac * range;
505 ytr(im, DNAN); /* reset precalc */
507 calc_horizontal_grid(im); /* recalc with changed im->maxval */
511 /* reduce data reimplementation by Alex */
515 enum cf_en cf, /* which consolidation function ?*/
516 unsigned long cur_step, /* step the data currently is in */
517 time_t *start, /* start, end and step as requested ... */
518 time_t *end, /* ... by the application will be ... */
519 unsigned long *step, /* ... adjusted to represent reality */
520 unsigned long *ds_cnt, /* number of data sources in file */
521 rrd_value_t **data) /* two dimensional array containing the data */
523 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
524 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
525 rrd_value_t *srcptr,*dstptr;
527 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
530 row_cnt = ((*end)-(*start))/cur_step;
536 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
537 row_cnt,reduce_factor,*start,*end,cur_step);
538 for (col=0;col<row_cnt;col++) {
539 printf("time %10lu: ",*start+(col+1)*cur_step);
540 for (i=0;i<*ds_cnt;i++)
541 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
546 /* We have to combine [reduce_factor] rows of the source
547 ** into one row for the destination. Doing this we also
548 ** need to take care to combine the correct rows. First
549 ** alter the start and end time so that they are multiples
550 ** of the new step time. We cannot reduce the amount of
551 ** time so we have to move the end towards the future and
552 ** the start towards the past.
554 end_offset = (*end) % (*step);
555 start_offset = (*start) % (*step);
557 /* If there is a start offset (which cannot be more than
558 ** one destination row), skip the appropriate number of
559 ** source rows and one destination row. The appropriate
560 ** number is what we do know (start_offset/cur_step) of
561 ** the new interval (*step/cur_step aka reduce_factor).
564 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
565 printf("row_cnt before: %lu\n",row_cnt);
568 (*start) = (*start)-start_offset;
569 skiprows=reduce_factor-start_offset/cur_step;
570 srcptr+=skiprows* *ds_cnt;
571 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
575 printf("row_cnt between: %lu\n",row_cnt);
578 /* At the end we have some rows that are not going to be
579 ** used, the amount is end_offset/cur_step
582 (*end) = (*end)-end_offset+(*step);
583 skiprows = end_offset/cur_step;
587 printf("row_cnt after: %lu\n",row_cnt);
590 /* Sanity check: row_cnt should be multiple of reduce_factor */
591 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
593 if (row_cnt%reduce_factor) {
594 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
595 row_cnt,reduce_factor);
596 printf("BUG in reduce_data()\n");
600 /* Now combine reduce_factor intervals at a time
601 ** into one interval for the destination.
604 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
605 for (col=0;col<(*ds_cnt);col++) {
606 rrd_value_t newval=DNAN;
607 unsigned long validval=0;
609 for (i=0;i<reduce_factor;i++) {
610 if (isnan(srcptr[i*(*ds_cnt)+col])) {
614 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
622 newval += srcptr[i*(*ds_cnt)+col];
625 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
628 /* an interval contains a failure if any subintervals contained a failure */
630 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
633 newval = srcptr[i*(*ds_cnt)+col];
638 if (validval == 0){newval = DNAN;} else{
656 srcptr+=(*ds_cnt)*reduce_factor;
657 row_cnt-=reduce_factor;
659 /* If we had to alter the endtime, we didn't have enough
660 ** source rows to fill the last row. Fill it with NaN.
662 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
664 row_cnt = ((*end)-(*start))/ *step;
666 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
667 row_cnt,*start,*end,*step);
668 for (col=0;col<row_cnt;col++) {
669 printf("time %10lu: ",*start+(col+1)*(*step));
670 for (i=0;i<*ds_cnt;i++)
671 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
678 /* get the data required for the graphs from the
682 data_fetch( image_desc_t *im )
686 /* pull the data from the log files ... */
687 for (i=0;i<im->gdes_c;i++){
688 /* only GF_DEF elements fetch data */
689 if (im->gdes[i].gf != GF_DEF)
693 /* do we have it already ?*/
694 for (ii=0;ii<i;ii++){
695 if (im->gdes[ii].gf != GF_DEF)
697 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
698 && (im->gdes[i].cf == im->gdes[ii].cf)){
699 /* OK the data it is here already ...
700 * we just copy the header portion */
701 im->gdes[i].start = im->gdes[ii].start;
702 im->gdes[i].end = im->gdes[ii].end;
703 im->gdes[i].step = im->gdes[ii].step;
704 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
705 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
706 im->gdes[i].data = im->gdes[ii].data;
707 im->gdes[i].data_first = 0;
714 unsigned long ft_step = im->gdes[i].step ;
716 if((rrd_fetch_fn(im->gdes[i].rrd,
722 &im->gdes[i].ds_namv,
723 &im->gdes[i].data)) == -1){
726 im->gdes[i].data_first = 1;
728 if (ft_step < im->gdes[i].step) {
729 reduce_data(im->gdes[i].cf,
737 im->gdes[i].step = ft_step;
741 /* lets see if the required data source is realy there */
742 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
743 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
746 if (im->gdes[i].ds== -1){
747 rrd_set_error("No DS called '%s' in '%s'",
748 im->gdes[i].ds_nam,im->gdes[i].rrd);
756 /* evaluate the expressions in the CDEF functions */
758 /*************************************************************
760 *************************************************************/
763 find_var_wrapper(void *arg1, char *key)
765 return find_var((image_desc_t *) arg1, key);
768 /* find gdes containing var*/
770 find_var(image_desc_t *im, char *key){
772 for(ii=0;ii<im->gdes_c-1;ii++){
773 if((im->gdes[ii].gf == GF_DEF
774 || im->gdes[ii].gf == GF_VDEF
775 || im->gdes[ii].gf == GF_CDEF)
776 && (strcmp(im->gdes[ii].vname,key) == 0)){
783 /* find the largest common denominator for all the numbers
784 in the 0 terminated num array */
789 for (i=0;num[i+1]!=0;i++){
791 rest=num[i] % num[i+1];
792 num[i]=num[i+1]; num[i+1]=rest;
796 /* return i==0?num[i]:num[i-1]; */
800 /* run the rpn calculator on all the VDEF and CDEF arguments */
802 data_calc( image_desc_t *im){
806 long *steparray, rpi;
811 rpnstack_init(&rpnstack);
813 for (gdi=0;gdi<im->gdes_c;gdi++){
814 /* Look for GF_VDEF and GF_CDEF in the same loop,
815 * so CDEFs can use VDEFs and vice versa
817 switch (im->gdes[gdi].gf) {
821 /* A VDEF has no DS. This also signals other parts
822 * of rrdtool that this is a VDEF value, not a CDEF.
824 im->gdes[gdi].ds_cnt = 0;
825 if (vdef_calc(im,gdi)) {
826 rrd_set_error("Error processing VDEF '%s'"
829 rpnstack_free(&rpnstack);
834 im->gdes[gdi].ds_cnt = 1;
835 im->gdes[gdi].ds = 0;
836 im->gdes[gdi].data_first = 1;
837 im->gdes[gdi].start = 0;
838 im->gdes[gdi].end = 0;
843 /* Find the variables in the expression.
844 * - VDEF variables are substituted by their values
845 * and the opcode is changed into OP_NUMBER.
846 * - CDEF variables are analized for their step size,
847 * the lowest common denominator of all the step
848 * sizes of the data sources involved is calculated
849 * and the resulting number is the step size for the
850 * resulting data source.
852 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
853 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
854 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
855 if (im->gdes[ptr].ds_cnt == 0) {
857 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
859 im->gdes[ptr].vname);
860 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
862 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
863 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
865 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
866 rrd_set_error("realloc steparray");
867 rpnstack_free(&rpnstack);
871 steparray[stepcnt-1] = im->gdes[ptr].step;
873 /* adjust start and end of cdef (gdi) so
874 * that it runs from the latest start point
875 * to the earliest endpoint of any of the
876 * rras involved (ptr)
878 if(im->gdes[gdi].start < im->gdes[ptr].start)
879 im->gdes[gdi].start = im->gdes[ptr].start;
881 if(im->gdes[gdi].end == 0 ||
882 im->gdes[gdi].end > im->gdes[ptr].end)
883 im->gdes[gdi].end = im->gdes[ptr].end;
885 /* store pointer to the first element of
886 * the rra providing data for variable,
887 * further save step size and data source
890 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
891 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
892 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
894 /* backoff the *.data ptr; this is done so
895 * rpncalc() function doesn't have to treat
896 * the first case differently
898 } /* if ds_cnt != 0 */
899 } /* if OP_VARIABLE */
900 } /* loop through all rpi */
902 /* move the data pointers to the correct period */
903 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
904 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
905 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
906 if(im->gdes[gdi].start > im->gdes[ptr].start) {
907 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
913 if(steparray == NULL){
914 rrd_set_error("rpn expressions without DEF"
915 " or CDEF variables are not supported");
916 rpnstack_free(&rpnstack);
919 steparray[stepcnt]=0;
920 /* Now find the resulting step. All steps in all
921 * used RRAs have to be visited
923 im->gdes[gdi].step = lcd(steparray);
925 if((im->gdes[gdi].data = malloc((
926 (im->gdes[gdi].end-im->gdes[gdi].start)
927 / im->gdes[gdi].step)
928 * sizeof(double)))==NULL){
929 rrd_set_error("malloc im->gdes[gdi].data");
930 rpnstack_free(&rpnstack);
934 /* Step through the new cdef results array and
935 * calculate the values
937 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
938 now<=im->gdes[gdi].end;
939 now += im->gdes[gdi].step)
941 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
943 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
944 * in this case we are advancing by timesteps;
945 * we use the fact that time_t is a synonym for long
947 if (rpn_calc(rpnp,&rpnstack,(long) now,
948 im->gdes[gdi].data,++dataidx) == -1) {
949 /* rpn_calc sets the error string */
950 rpnstack_free(&rpnstack);
953 } /* enumerate over time steps within a CDEF */
958 } /* enumerate over CDEFs */
959 rpnstack_free(&rpnstack);
963 /* massage data so, that we get one value for each x coordinate in the graph */
965 data_proc( image_desc_t *im ){
967 double pixstep = (double)(im->end-im->start)
968 /(double)im->xsize; /* how much time
969 passes in one pixel */
971 double minval=DNAN,maxval=DNAN;
973 unsigned long gr_time;
975 /* memory for the processed data */
976 for(i=0;i<im->gdes_c;i++){
977 if((im->gdes[i].gf==GF_LINE) ||
978 (im->gdes[i].gf==GF_AREA) ||
979 (im->gdes[i].gf==GF_TICK) ||
980 (im->gdes[i].gf==GF_STACK)){
981 if((im->gdes[i].p_data = malloc((im->xsize +1)
982 * sizeof(rrd_value_t)))==NULL){
983 rrd_set_error("malloc data_proc");
989 for(i=0;i<im->xsize;i++){
991 gr_time = im->start+pixstep*i; /* time of the
995 for(ii=0;ii<im->gdes_c;ii++){
997 switch(im->gdes[ii].gf){
1003 vidx = im->gdes[ii].vidx;
1006 im->gdes[vidx].data[
1007 ((unsigned long)floor(
1008 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
1010 ) *im->gdes[vidx].ds_cnt
1011 +im->gdes[vidx].ds];
1013 if (! isnan(value)) {
1015 im->gdes[ii].p_data[i] = paintval;
1016 /* GF_TICK: the data values are not relevant for min and max */
1017 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1018 if (isnan(minval) || paintval < minval)
1020 if (isnan(maxval) || paintval > maxval)
1024 im->gdes[ii].p_data[i] = DNAN;
1042 /* if min or max have not been asigned a value this is because
1043 there was no data in the graph ... this is not good ...
1044 lets set these to dummy values then ... */
1046 if (isnan(minval)) minval = 0.0;
1047 if (isnan(maxval)) maxval = 1.0;
1049 /* adjust min and max values */
1050 if (isnan(im->minval)
1051 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1052 && im->minval > minval))
1053 im->minval = minval;
1054 if (isnan(im->maxval)
1056 && im->maxval < maxval)){
1057 if (im->logarithmic)
1058 im->maxval = maxval * 1.1;
1060 im->maxval = maxval;
1062 /* make sure min and max are not equal */
1063 if (im->minval == im->maxval) {
1065 if (! im->logarithmic) {
1069 /* make sure min and max are not both zero */
1070 if (im->maxval == 0.0) {
1080 /* identify the point where the first gridline, label ... gets placed */
1084 time_t start, /* what is the initial time */
1085 enum tmt_en baseint, /* what is the basic interval */
1086 long basestep /* how many if these do we jump a time */
1090 tm = *localtime(&start);
1093 tm.tm_sec -= tm.tm_sec % basestep; break;
1096 tm.tm_min -= tm.tm_min % basestep;
1101 tm.tm_hour -= tm.tm_hour % basestep; break;
1103 /* we do NOT look at the basestep for this ... */
1106 tm.tm_hour = 0; break;
1108 /* we do NOT look at the basestep for this ... */
1112 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1113 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1120 tm.tm_mon -= tm.tm_mon % basestep; break;
1128 tm.tm_year -= (tm.tm_year+1900) % basestep;
1133 /* identify the point where the next gridline, label ... gets placed */
1136 time_t current, /* what is the initial time */
1137 enum tmt_en baseint, /* what is the basic interval */
1138 long basestep /* how many if these do we jump a time */
1143 tm = *localtime(¤t);
1147 tm.tm_sec += basestep; break;
1149 tm.tm_min += basestep; break;
1151 tm.tm_hour += basestep; break;
1153 tm.tm_mday += basestep; break;
1155 tm.tm_mday += 7*basestep; break;
1157 tm.tm_mon += basestep; break;
1159 tm.tm_year += basestep;
1161 madetime = mktime(&tm);
1162 } while (madetime == -1); /* this is necessary to skip impssible times
1163 like the daylight saving time skips */
1169 /* calculate values required for PRINT and GPRINT functions */
1172 print_calc(image_desc_t *im, char ***prdata)
1174 long i,ii,validsteps;
1177 int graphelement = 0;
1180 double magfact = -1;
1184 if (im->imginfo) prlines++;
1185 for(i=0;i<im->gdes_c;i++){
1186 switch(im->gdes[i].gf){
1189 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1190 rrd_set_error("realloc prdata");
1194 /* PRINT and GPRINT can now print VDEF generated values.
1195 * There's no need to do any calculations on them as these
1196 * calculations were already made.
1198 vidx = im->gdes[i].vidx;
1199 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1200 printval = im->gdes[vidx].vf.val;
1201 printtime = im->gdes[vidx].vf.when;
1202 } else { /* need to calculate max,min,avg etcetera */
1203 max_ii =((im->gdes[vidx].end
1204 - im->gdes[vidx].start)
1205 / im->gdes[vidx].step
1206 * im->gdes[vidx].ds_cnt);
1209 for( ii=im->gdes[vidx].ds;
1211 ii+=im->gdes[vidx].ds_cnt){
1212 if (! finite(im->gdes[vidx].data[ii]))
1214 if (isnan(printval)){
1215 printval = im->gdes[vidx].data[ii];
1220 switch (im->gdes[i].cf){
1223 case CF_DEVSEASONAL:
1227 printval += im->gdes[vidx].data[ii];
1230 printval = min( printval, im->gdes[vidx].data[ii]);
1234 printval = max( printval, im->gdes[vidx].data[ii]);
1237 printval = im->gdes[vidx].data[ii];
1240 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1241 if (validsteps > 1) {
1242 printval = (printval / validsteps);
1245 } /* prepare printval */
1247 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1248 if (im->gdes[i].gf == GF_PRINT){
1249 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1250 sprintf((*prdata)[prlines-2],"%s (%lu)",
1251 ctime(&printtime),printtime);
1252 (*prdata)[prlines-1] = NULL;
1254 sprintf(im->gdes[i].legend,"%s (%lu)",
1255 ctime(&printtime),printtime);
1259 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1260 /* Magfact is set to -1 upon entry to print_calc. If it
1261 * is still less than 0, then we need to run auto_scale.
1262 * Otherwise, put the value into the correct units. If
1263 * the value is 0, then do not set the symbol or magnification
1264 * so next the calculation will be performed again. */
1265 if (magfact < 0.0) {
1266 auto_scale(im,&printval,&si_symb,&magfact);
1267 if (printval == 0.0)
1270 printval /= magfact;
1272 *(++percent_s) = 's';
1273 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1274 auto_scale(im,&printval,&si_symb,&magfact);
1277 if (im->gdes[i].gf == GF_PRINT){
1278 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1279 if (bad_format(im->gdes[i].format)) {
1280 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1283 #ifdef HAVE_SNPRINTF
1284 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1286 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1288 (*prdata)[prlines-1] = NULL;
1292 if (bad_format(im->gdes[i].format)) {
1293 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1296 #ifdef HAVE_SNPRINTF
1297 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1299 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1322 return graphelement;
1326 /* place legends with color spots */
1328 leg_place(image_desc_t *im)
1331 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1332 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1333 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1334 int fill=0, fill_last;
1336 int leg_x = border, leg_y = im->yimg;
1340 char prt_fctn; /*special printfunctions */
1343 if( !(im->extra_flags & NOLEGEND) ) {
1344 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1345 rrd_set_error("malloc for legspace");
1349 for(i=0;i<im->gdes_c;i++){
1352 leg_cc = strlen(im->gdes[i].legend);
1354 /* is there a controle code ant the end of the legend string ? */
1355 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1356 prt_fctn = im->gdes[i].legend[leg_cc-1];
1358 im->gdes[i].legend[leg_cc] = '\0';
1362 /* remove exess space */
1363 while (prt_fctn=='g' &&
1365 im->gdes[i].legend[leg_cc-1]==' '){
1367 im->gdes[i].legend[leg_cc]='\0';
1370 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1373 /* no interleg space if string ends in \g */
1374 fill += legspace[i];
1376 if (im->gdes[i].gf != GF_GPRINT &&
1377 im->gdes[i].gf != GF_COMMENT) {
1380 fill += gfx_get_text_width(im->canvas, fill+border,
1381 im->text_prop[TEXT_PROP_LEGEND].font,
1382 im->text_prop[TEXT_PROP_LEGEND].size,
1384 im->gdes[i].legend);
1389 /* who said there was a special tag ... ?*/
1390 if (prt_fctn=='g') {
1393 if (prt_fctn == '\0') {
1394 if (i == im->gdes_c -1 ) prt_fctn ='l';
1396 /* is it time to place the legends ? */
1397 if (fill > im->ximg - 2*border){
1412 if (prt_fctn != '\0'){
1414 if (leg_c >= 2 && prt_fctn == 'j') {
1415 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1419 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1420 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1422 for(ii=mark;ii<=i;ii++){
1423 if(im->gdes[ii].legend[0]=='\0')
1425 im->gdes[ii].leg_x = leg_x;
1426 im->gdes[ii].leg_y = leg_y;
1428 gfx_get_text_width(im->canvas, leg_x,
1429 im->text_prop[TEXT_PROP_LEGEND].font,
1430 im->text_prop[TEXT_PROP_LEGEND].size,
1432 im->gdes[ii].legend)
1435 if (im->gdes[ii].gf != GF_GPRINT &&
1436 im->gdes[ii].gf != GF_COMMENT)
1439 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1440 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1452 /* create a grid on the graph. it determines what to do
1453 from the values of xsize, start and end */
1455 /* the xaxis labels are determined from the number of seconds per pixel
1456 in the requested graph */
1461 calc_horizontal_grid(image_desc_t *im)
1467 int decimals, fractionals;
1469 im->ygrid_scale.labfact=2;
1471 range = im->maxval - im->minval;
1472 scaledrange = range / im->magfact;
1474 /* does the scale of this graph make it impossible to put lines
1475 on it? If so, give up. */
1476 if (isnan(scaledrange)) {
1480 /* find grid spaceing */
1482 if(isnan(im->ygridstep)){
1483 if(im->extra_flags & ALTYGRID) {
1484 /* find the value with max number of digits. Get number of digits */
1485 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1486 if(decimals <= 0) /* everything is small. make place for zero */
1489 fractionals = floor(log10(range));
1490 if(fractionals < 0) /* small amplitude. */
1491 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1493 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1494 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1495 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1496 im->ygrid_scale.gridstep = 0.1;
1497 /* should have at least 5 lines but no more then 15 */
1498 if(range/im->ygrid_scale.gridstep < 5)
1499 im->ygrid_scale.gridstep /= 10;
1500 if(range/im->ygrid_scale.gridstep > 15)
1501 im->ygrid_scale.gridstep *= 10;
1502 if(range/im->ygrid_scale.gridstep > 5) {
1503 im->ygrid_scale.labfact = 1;
1504 if(range/im->ygrid_scale.gridstep > 8)
1505 im->ygrid_scale.labfact = 2;
1508 im->ygrid_scale.gridstep /= 5;
1509 im->ygrid_scale.labfact = 5;
1513 for(i=0;ylab[i].grid > 0;i++){
1514 pixel = im->ysize / (scaledrange / ylab[i].grid);
1515 if (gridind == -1 && pixel > 5) {
1522 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1523 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1528 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1531 im->ygrid_scale.gridstep = im->ygridstep;
1532 im->ygrid_scale.labfact = im->ylabfact;
1537 int draw_horizontal_grid(image_desc_t *im)
1541 char graph_label[100];
1542 double X0=im->xorigin;
1543 double X1=im->xorigin+im->xsize;
1545 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1546 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1547 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1548 for (i = sgrid; i <= egrid; i++){
1549 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1550 if ( Y0 >= im->yorigin-im->ysize
1551 && Y0 <= im->yorigin){
1552 if(i % im->ygrid_scale.labfact == 0){
1553 if (i==0 || im->symbol == ' ') {
1555 if(im->extra_flags & ALTYGRID) {
1556 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1559 sprintf(graph_label,"%4.1f",scaledstep*i);
1562 sprintf(graph_label,"%4.0f",scaledstep*i);
1566 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1568 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1572 gfx_new_text ( im->canvas,
1573 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1574 im->graph_col[GRC_FONT],
1575 im->text_prop[TEXT_PROP_AXIS].font,
1576 im->text_prop[TEXT_PROP_AXIS].size,
1577 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1579 gfx_new_dashed_line ( im->canvas,
1582 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1583 im->grid_dash_on, im->grid_dash_off);
1586 gfx_new_dashed_line ( im->canvas,
1589 GRIDWIDTH, im->graph_col[GRC_GRID],
1590 im->grid_dash_on, im->grid_dash_off);
1598 /* logaritmic horizontal grid */
1600 horizontal_log_grid(image_desc_t *im)
1604 int minoridx=0, majoridx=0;
1605 char graph_label[100];
1607 double value, pixperstep, minstep;
1609 /* find grid spaceing */
1610 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1612 if (isnan(pixpex)) {
1616 for(i=0;yloglab[i][0] > 0;i++){
1617 minstep = log10(yloglab[i][0]);
1618 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1619 if(yloglab[i][ii+2]==0){
1620 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1624 pixperstep = pixpex * minstep;
1625 if(pixperstep > 5){minoridx = i;}
1626 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1630 X1=im->xorigin+im->xsize;
1631 /* paint minor grid */
1632 for (value = pow((double)10, log10(im->minval)
1633 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1634 value <= im->maxval;
1635 value *= yloglab[minoridx][0]){
1636 if (value < im->minval) continue;
1638 while(yloglab[minoridx][++i] > 0){
1639 Y0 = ytr(im,value * yloglab[minoridx][i]);
1640 if (Y0 <= im->yorigin - im->ysize) break;
1641 gfx_new_dashed_line ( im->canvas,
1644 GRIDWIDTH, im->graph_col[GRC_GRID],
1645 im->grid_dash_on, im->grid_dash_off);
1649 /* paint major grid and labels*/
1650 for (value = pow((double)10, log10(im->minval)
1651 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1652 value <= im->maxval;
1653 value *= yloglab[majoridx][0]){
1654 if (value < im->minval) continue;
1656 while(yloglab[majoridx][++i] > 0){
1657 Y0 = ytr(im,value * yloglab[majoridx][i]);
1658 if (Y0 <= im->yorigin - im->ysize) break;
1659 gfx_new_dashed_line ( im->canvas,
1662 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1663 im->grid_dash_on, im->grid_dash_off);
1665 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1666 gfx_new_text ( im->canvas,
1667 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1668 im->graph_col[GRC_FONT],
1669 im->text_prop[TEXT_PROP_AXIS].font,
1670 im->text_prop[TEXT_PROP_AXIS].size,
1671 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1683 int xlab_sel; /* which sort of label and grid ? */
1684 time_t ti, tilab, timajor;
1686 char graph_label[100];
1687 double X0,Y0,Y1; /* points for filled graph and more*/
1690 /* the type of time grid is determined by finding
1691 the number of seconds per pixel in the graph */
1694 if(im->xlab_user.minsec == -1){
1695 factor=(im->end - im->start)/im->xsize;
1697 while ( xlab[xlab_sel+1].minsec != -1
1698 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1699 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1700 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1701 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1702 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1703 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1704 im->xlab_user.labst = xlab[xlab_sel].labst;
1705 im->xlab_user.precis = xlab[xlab_sel].precis;
1706 im->xlab_user.stst = xlab[xlab_sel].stst;
1709 /* y coords are the same for every line ... */
1711 Y1 = im->yorigin-im->ysize;
1714 /* paint the minor grid */
1715 for(ti = find_first_time(im->start,
1716 im->xlab_user.gridtm,
1717 im->xlab_user.gridst),
1718 timajor = find_first_time(im->start,
1719 im->xlab_user.mgridtm,
1720 im->xlab_user.mgridst);
1722 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1724 /* are we inside the graph ? */
1725 if (ti < im->start || ti > im->end) continue;
1726 while (timajor < ti) {
1727 timajor = find_next_time(timajor,
1728 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1730 if (ti == timajor) continue; /* skip as falls on major grid line */
1732 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1733 im->graph_col[GRC_GRID],
1734 im->grid_dash_on, im->grid_dash_off);
1738 /* paint the major grid */
1739 for(ti = find_first_time(im->start,
1740 im->xlab_user.mgridtm,
1741 im->xlab_user.mgridst);
1743 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1745 /* are we inside the graph ? */
1746 if (ti < im->start || ti > im->end) continue;
1748 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1749 im->graph_col[GRC_MGRID],
1750 im->grid_dash_on, im->grid_dash_off);
1753 /* paint the labels below the graph */
1754 for(ti = find_first_time(im->start,
1755 im->xlab_user.labtm,
1756 im->xlab_user.labst);
1758 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1760 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1761 /* are we inside the graph ? */
1762 if (ti < im->start || ti > im->end) continue;
1765 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1767 # error "your libc has no strftime I guess we'll abort the exercise here."
1769 gfx_new_text ( im->canvas,
1770 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1771 im->graph_col[GRC_FONT],
1772 im->text_prop[TEXT_PROP_AXIS].font,
1773 im->text_prop[TEXT_PROP_AXIS].size,
1774 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1787 /* draw x and y axis */
1788 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1789 im->xorigin+im->xsize,im->yorigin-im->ysize,
1790 GRIDWIDTH, im->graph_col[GRC_GRID]);
1792 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1793 im->xorigin+im->xsize,im->yorigin-im->ysize,
1794 GRIDWIDTH, im->graph_col[GRC_GRID]);
1796 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1797 im->xorigin+im->xsize+4,im->yorigin,
1798 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1800 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1801 im->xorigin,im->yorigin-im->ysize-4,
1802 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1805 /* arrow for X axis direction */
1806 gfx_new_area ( im->canvas,
1807 im->xorigin+im->xsize+3, im->yorigin-3,
1808 im->xorigin+im->xsize+3, im->yorigin+4,
1809 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1810 im->graph_col[GRC_ARROW]);
1817 grid_paint(image_desc_t *im)
1821 double X0,Y0; /* points for filled graph and more*/
1824 /* draw 3d border */
1825 node = gfx_new_area (im->canvas, 0,im->yimg,
1827 2,2,im->graph_col[GRC_SHADEA]);
1828 gfx_add_point( node , im->ximg - 2, 2 );
1829 gfx_add_point( node , im->ximg, 0 );
1830 gfx_add_point( node , 0,0 );
1831 /* gfx_add_point( node , 0,im->yimg ); */
1833 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1834 im->ximg-2,im->yimg-2,
1836 im->graph_col[GRC_SHADEB]);
1837 gfx_add_point( node , im->ximg,0);
1838 gfx_add_point( node , im->ximg,im->yimg);
1839 gfx_add_point( node , 0,im->yimg);
1840 /* gfx_add_point( node , 0,im->yimg ); */
1843 if (im->draw_x_grid == 1 )
1846 if (im->draw_y_grid == 1){
1847 if(im->logarithmic){
1848 res = horizontal_log_grid(im);
1850 res = draw_horizontal_grid(im);
1853 /* dont draw horizontal grid if there is no min and max val */
1855 char *nodata = "No Data found";
1856 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1857 im->graph_col[GRC_FONT],
1858 im->text_prop[TEXT_PROP_AXIS].font,
1859 im->text_prop[TEXT_PROP_AXIS].size,
1860 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1865 /* yaxis description */
1866 if (im->canvas->imgformat != IF_PNG) {
1867 gfx_new_text( im->canvas,
1868 7, (im->yorigin - im->ysize/2),
1869 im->graph_col[GRC_FONT],
1870 im->text_prop[TEXT_PROP_AXIS].font,
1871 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1872 GFX_H_CENTER, GFX_V_CENTER,
1875 /* horrible hack until we can actually print vertically */
1878 int l=strlen(im->ylegend);
1880 for (n=0;n<strlen(im->ylegend);n++) {
1881 s[0]=im->ylegend[n];
1883 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1884 im->graph_col[GRC_FONT],
1885 im->text_prop[TEXT_PROP_AXIS].font,
1886 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1887 GFX_H_CENTER, GFX_V_CENTER,
1894 gfx_new_text( im->canvas,
1895 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1896 im->graph_col[GRC_FONT],
1897 im->text_prop[TEXT_PROP_TITLE].font,
1898 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1899 GFX_H_CENTER, GFX_V_CENTER,
1903 if( !(im->extra_flags & NOLEGEND) ) {
1904 for(i=0;i<im->gdes_c;i++){
1905 if(im->gdes[i].legend[0] =='\0')
1908 /* im->gdes[i].leg_y is the bottom of the legend */
1909 X0 = im->gdes[i].leg_x;
1910 Y0 = im->gdes[i].leg_y;
1912 if ( im->gdes[i].gf != GF_GPRINT
1913 && im->gdes[i].gf != GF_COMMENT) {
1916 boxH = gfx_get_text_width(im->canvas, 0,
1917 im->text_prop[TEXT_PROP_AXIS].font,
1918 im->text_prop[TEXT_PROP_AXIS].size,
1919 im->tabwidth,"M") * 1.25;
1922 node = gfx_new_area(im->canvas,
1927 gfx_add_point ( node, X0+boxH, Y0-boxV );
1928 node = gfx_new_line(im->canvas,
1931 gfx_add_point(node,X0+boxH,Y0);
1932 gfx_add_point(node,X0+boxH,Y0-boxV);
1933 gfx_close_path(node);
1934 X0 += boxH / 1.25 * 2;
1936 gfx_new_text ( im->canvas, X0, Y0,
1937 im->graph_col[GRC_FONT],
1938 im->text_prop[TEXT_PROP_AXIS].font,
1939 im->text_prop[TEXT_PROP_AXIS].size,
1940 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1941 im->gdes[i].legend );
1947 /*****************************************************
1948 * lazy check make sure we rely need to create this graph
1949 *****************************************************/
1951 int lazy_check(image_desc_t *im){
1954 struct stat imgstat;
1956 if (im->lazy == 0) return 0; /* no lazy option */
1957 if (stat(im->graphfile,&imgstat) != 0)
1958 return 0; /* can't stat */
1959 /* one pixel in the existing graph is more then what we would
1961 if (time(NULL) - imgstat.st_mtime >
1962 (im->end - im->start) / im->xsize)
1964 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1965 return 0; /* the file does not exist */
1966 switch (im->canvas->imgformat) {
1968 size = PngSize(fd,&(im->ximg),&(im->yimg));
1978 pie_part(image_desc_t *im, gfx_color_t color,
1979 double PieCenterX, double PieCenterY, double Radius,
1980 double startangle, double endangle)
1984 double step=M_PI/50; /* Number of iterations for the circle;
1985 ** 10 is definitely too low, more than
1986 ** 50 seems to be overkill
1989 /* Strange but true: we have to work clockwise or else
1990 ** anti aliasing nor transparency don't work.
1992 ** This test is here to make sure we do it right, also
1993 ** this makes the for...next loop more easy to implement.
1994 ** The return will occur if the user enters a negative number
1995 ** (which shouldn't be done according to the specs) or if the
1996 ** programmers do something wrong (which, as we all know, never
1997 ** happens anyway :)
1999 if (endangle<startangle) return;
2001 /* Hidden feature: Radius decreases each full circle */
2003 while (angle>=2*M_PI) {
2008 node=gfx_new_area(im->canvas,
2009 PieCenterX+sin(startangle)*Radius,
2010 PieCenterY-cos(startangle)*Radius,
2013 PieCenterX+sin(endangle)*Radius,
2014 PieCenterY-cos(endangle)*Radius,
2016 for (angle=endangle;angle-startangle>=step;angle-=step) {
2018 PieCenterX+sin(angle)*Radius,
2019 PieCenterY-cos(angle)*Radius );
2024 graph_size_location(image_desc_t *im, int elements, int piechart )
2026 /* The actual size of the image to draw is determined from
2027 ** several sources. The size given on the command line is
2028 ** the graph area but we need more as we have to draw labels
2029 ** and other things outside the graph area
2032 /* +-+-------------------------------------------+
2033 ** |l|.................title.....................|
2034 ** |e+--+-------------------------------+--------+
2037 ** |l| l| main graph area | chart |
2040 ** |r+--+-------------------------------+--------+
2041 ** |e| | x-axis labels | |
2042 ** |v+--+-------------------------------+--------+
2043 ** | |..............legends......................|
2044 ** +-+-------------------------------------------+
2046 int Xvertical=0, Yvertical=0,
2047 Xtitle =0, Ytitle =0,
2048 Xylabel =0, Yylabel =0,
2051 Xxlabel =0, Yxlabel =0,
2053 Xlegend =0, Ylegend =0,
2055 Xspacing =10, Yspacing =10;
2057 if (im->ylegend[0] != '\0') {
2058 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2059 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2062 if (im->title[0] != '\0') {
2063 /* The title is placed "inbetween" two text lines so it
2064 ** automatically has some vertical spacing. The horizontal
2065 ** spacing is added here, on each side.
2067 Xtitle = gfx_get_text_width(im->canvas, 0,
2068 im->text_prop[TEXT_PROP_TITLE].font,
2069 im->text_prop[TEXT_PROP_TITLE].size,
2071 im->title) + 2*Xspacing;
2072 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2078 if (im->draw_x_grid) {
2080 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2082 if (im->draw_y_grid) {
2083 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2089 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2094 /* Now calculate the total size. Insert some spacing where
2095 desired. im->xorigin and im->yorigin need to correspond
2096 with the lower left corner of the main graph area or, if
2097 this one is not set, the imaginary box surrounding the
2100 /* The legend width cannot yet be determined, as a result we
2101 ** have problems adjusting the image to it. For now, we just
2102 ** forget about it at all; the legend will have to fit in the
2103 ** size already allocated.
2105 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2106 if (Xmain) im->ximg += Xspacing;
2107 if (Xpie) im->ximg += Xspacing;
2108 im->xorigin = Xspacing + Xylabel;
2109 if (Xtitle > im->ximg) im->ximg = Xtitle;
2111 im->ximg += Xvertical;
2112 im->xorigin += Xvertical;
2116 /* The vertical size is interesting... we need to compare
2117 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2118 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2119 ** start even thinking about Ylegend.
2121 ** Do it in three portions: First calculate the inner part,
2122 ** then do the legend, then adjust the total height of the img.
2125 /* reserve space for main and/or pie */
2126 im->yimg = Ymain + Yxlabel;
2127 if (im->yimg < Ypie) im->yimg = Ypie;
2128 im->yorigin = im->yimg - Yxlabel;
2129 /* reserve space for the title *or* some padding above the graph */
2132 im->yorigin += Ytitle;
2134 im->yimg += Yspacing;
2135 im->yorigin += Yspacing;
2137 /* reserve space for padding below the graph */
2138 im->yimg += Yspacing;
2141 /* Determine where to place the legends onto the image.
2142 ** Adjust im->yimg to match the space requirements.
2144 if(leg_place(im)==-1)
2147 /* last of three steps: check total height of image */
2148 if (im->yimg < Yvertical) im->yimg = Yvertical;
2151 if (Xlegend > im->ximg) {
2153 /* reposition Pie */
2156 /* The pie is placed in the upper right hand corner,
2157 ** just below the title (if any) and with sufficient
2161 im->pie_x = im->ximg - Xspacing - Xpie/2;
2162 im->pie_y = im->yorigin-Ymain+Ypie/2;
2164 im->pie_x = im->ximg/2;
2165 im->pie_y = im->yorigin-Ypie/2;
2171 /* draw that picture thing ... */
2173 graph_paint(image_desc_t *im, char ***calcpr)
2176 int lazy = lazy_check(im);
2178 double PieStart=0.0;
2182 double areazero = 0.0;
2183 enum gf_en stack_gf = GF_PRINT;
2184 graph_desc_t *lastgdes = NULL;
2186 /* if we are lazy and there is nothing to PRINT ... quit now */
2187 if (lazy && im->prt_c==0) return 0;
2189 /* pull the data from the rrd files ... */
2191 if(data_fetch(im)==-1)
2194 /* evaluate VDEF and CDEF operations ... */
2195 if(data_calc(im)==-1)
2198 /* check if we need to draw a piechart */
2199 for(i=0;i<im->gdes_c;i++){
2200 if (im->gdes[i].gf == GF_PART) {
2206 /* calculate and PRINT and GPRINT definitions. We have to do it at
2207 * this point because it will affect the length of the legends
2208 * if there are no graph elements we stop here ...
2209 * if we are lazy, try to quit ...
2211 i=print_calc(im,calcpr);
2213 if(((i==0)&&(piechart==0)) || lazy) return 0;
2215 /* If there's only the pie chart to draw, signal this */
2216 if (i==0) piechart=2;
2218 /* get actual drawing data and find min and max values*/
2219 if(data_proc(im)==-1)
2222 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2224 if(!im->rigid && ! im->logarithmic)
2225 expand_range(im); /* make sure the upper and lower limit are
2228 if (!calc_horizontal_grid(im))
2233 /**************************************************************
2234 *** Calculating sizes and locations became a bit confusing ***
2235 *** so I moved this into a separate function. ***
2236 **************************************************************/
2237 if(graph_size_location(im,i,piechart)==-1)
2240 /* the actual graph is created by going through the individual
2241 graph elements and then drawing them */
2243 node=gfx_new_area ( im->canvas,
2247 im->graph_col[GRC_BACK]);
2249 gfx_add_point(node,0, im->yimg);
2251 if (piechart != 2) {
2252 node=gfx_new_area ( im->canvas,
2253 im->xorigin, im->yorigin,
2254 im->xorigin + im->xsize, im->yorigin,
2255 im->xorigin + im->xsize, im->yorigin-im->ysize,
2256 im->graph_col[GRC_CANVAS]);
2258 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2260 if (im->minval > 0.0)
2261 areazero = im->minval;
2262 if (im->maxval < 0.0)
2263 areazero = im->maxval;
2269 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2272 for(i=0;i<im->gdes_c;i++){
2273 switch(im->gdes[i].gf){
2285 for (ii = 0; ii < im->xsize; ii++)
2287 if (!isnan(im->gdes[i].p_data[ii]) &&
2288 im->gdes[i].p_data[ii] > 0.0)
2290 /* generate a tick */
2291 gfx_new_line(im->canvas, im -> xorigin + ii,
2292 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2296 im -> gdes[i].col );
2302 stack_gf = im->gdes[i].gf;
2304 /* fix data points at oo and -oo */
2305 for(ii=0;ii<im->xsize;ii++){
2306 if (isinf(im->gdes[i].p_data[ii])){
2307 if (im->gdes[i].p_data[ii] > 0) {
2308 im->gdes[i].p_data[ii] = im->maxval ;
2310 im->gdes[i].p_data[ii] = im->minval ;
2316 if (im->gdes[i].col != 0x0){
2317 /* GF_LINE and friend */
2318 if(stack_gf == GF_LINE ){
2320 for(ii=1;ii<im->xsize;ii++){
2321 if ( ! isnan(im->gdes[i].p_data[ii-1])
2322 && ! isnan(im->gdes[i].p_data[ii])){
2324 node = gfx_new_line(im->canvas,
2325 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2326 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2327 im->gdes[i].linewidth,
2330 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2339 for(ii=1;ii<im->xsize;ii++){
2341 if ( ! isnan(im->gdes[i].p_data[ii-1])
2342 && ! isnan(im->gdes[i].p_data[ii])){
2345 if (im->gdes[i].gf == GF_STACK) {
2346 ybase = ytr(im,lastgdes->p_data[ii-1]);
2348 ybase = ytr(im,areazero);
2351 node = gfx_new_area(im->canvas,
2352 ii-1+im->xorigin,ybase,
2353 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2354 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2358 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2362 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2363 /* GF_AREA STACK type*/
2364 if (im->gdes[i].gf == GF_STACK ) {
2366 for (iii=ii-1;iii>area_start;iii--){
2367 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2370 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2375 } /* else GF_LINE */
2376 } /* if color != 0x0 */
2377 /* make sure we do not run into trouble when stacking on NaN */
2378 for(ii=0;ii<im->xsize;ii++){
2379 if (isnan(im->gdes[i].p_data[ii])) {
2382 ybase = ytr(im,lastgdes->p_data[ii-1]);
2384 if (isnan(ybase) || !lastgdes ){
2385 ybase = ytr(im,areazero);
2387 im->gdes[i].p_data[ii] = ybase;
2390 lastgdes = &(im->gdes[i]);
2393 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2394 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2396 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2397 pie_part(im,im->gdes[i].col,
2398 im->pie_x,im->pie_y,im->piesize*0.4,
2399 M_PI*2.0*PieStart/100.0,
2400 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2401 PieStart += im->gdes[i].yrule;
2410 /* grid_paint also does the text */
2413 /* the RULES are the last thing to paint ... */
2414 for(i=0;i<im->gdes_c;i++){
2416 switch(im->gdes[i].gf){
2418 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2419 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2421 if(im->gdes[i].yrule >= im->minval
2422 && im->gdes[i].yrule <= im->maxval)
2423 gfx_new_line(im->canvas,
2424 im->xorigin,ytr(im,im->gdes[i].yrule),
2425 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2426 1.0,im->gdes[i].col);
2429 if(im->gdes[i].xrule == 0) { /* fetch variable */
2430 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2432 if(im->gdes[i].xrule >= im->start
2433 && im->gdes[i].xrule <= im->end)
2434 gfx_new_line(im->canvas,
2435 xtr(im,im->gdes[i].xrule),im->yorigin,
2436 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2437 1.0,im->gdes[i].col);
2445 if (strcmp(im->graphfile,"-")==0) {
2447 /* Change translation mode for stdout to BINARY */
2448 _setmode( _fileno( stdout ), O_BINARY );
2452 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2453 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2458 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2459 if (strcmp(im->graphfile,"-") != 0)
2465 /*****************************************************
2467 *****************************************************/
2470 gdes_alloc(image_desc_t *im){
2472 long def_step = (im->end-im->start)/im->xsize;
2474 if (im->step > def_step) /* step can be increassed ... no decreassed */
2475 def_step = im->step;
2479 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2480 * sizeof(graph_desc_t)))==NULL){
2481 rrd_set_error("realloc graph_descs");
2486 im->gdes[im->gdes_c-1].step=def_step;
2487 im->gdes[im->gdes_c-1].start=im->start;
2488 im->gdes[im->gdes_c-1].end=im->end;
2489 im->gdes[im->gdes_c-1].vname[0]='\0';
2490 im->gdes[im->gdes_c-1].data=NULL;
2491 im->gdes[im->gdes_c-1].ds_namv=NULL;
2492 im->gdes[im->gdes_c-1].data_first=0;
2493 im->gdes[im->gdes_c-1].p_data=NULL;
2494 im->gdes[im->gdes_c-1].rpnp=NULL;
2495 im->gdes[im->gdes_c-1].col = 0x0;
2496 im->gdes[im->gdes_c-1].legend[0]='\0';
2497 im->gdes[im->gdes_c-1].rrd[0]='\0';
2498 im->gdes[im->gdes_c-1].ds=-1;
2499 im->gdes[im->gdes_c-1].p_data=NULL;
2503 /* copies input untill the first unescaped colon is found
2504 or until input ends. backslashes have to be escaped as well */
2506 scan_for_col(char *input, int len, char *output)
2511 input[inp] != ':' &&
2514 if (input[inp] == '\\' &&
2515 input[inp+1] != '\0' &&
2516 (input[inp+1] == '\\' ||
2517 input[inp+1] == ':')){
2518 output[outp++] = input[++inp];
2521 output[outp++] = input[inp];
2524 output[outp] = '\0';
2528 /* Some surgery done on this function, it became ridiculously big.
2530 ** - initializing now in rrd_graph_init()
2531 ** - options parsing now in rrd_graph_options()
2532 ** - script parsing now in rrd_graph_script()
2535 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2542 #ifdef HAVE_SETLOCALE
2543 setlocale(LC_TIME,"");
2547 rrd_graph_init(&im);
2549 rrd_graph_options(argc,argv,&im);
2550 if (rrd_test_error()) return -1;
2552 if (strlen(argv[optind])>=MAXPATH) {
2553 rrd_set_error("filename (including path) too long");
2556 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2557 im.graphfile[MAXPATH-1]='\0';
2559 rrd_graph_script(argc,argv,&im);
2560 if (rrd_test_error()) return -1;
2562 /* Everything is now read and the actual work can start */
2565 if (graph_paint(&im,prdata)==-1){
2570 /* The image is generated and needs to be output.
2571 ** Also, if needed, print a line with information about the image.
2579 /* maybe prdata is not allocated yet ... lets do it now */
2580 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2581 rrd_set_error("malloc imginfo");
2585 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2587 rrd_set_error("malloc imginfo");
2590 filename=im.graphfile+strlen(im.graphfile);
2591 while(filename > im.graphfile) {
2592 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2596 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2603 rrd_graph_init(image_desc_t *im)
2607 im->xlab_user.minsec = -1;
2613 im->ylegend[0] = '\0';
2614 im->title[0] = '\0';
2617 im->unitsexponent= 9999;
2623 im->logarithmic = 0;
2624 im->ygridstep = DNAN;
2625 im->draw_x_grid = 1;
2626 im->draw_y_grid = 1;
2631 im->canvas = gfx_new_canvas();
2632 im->grid_dash_on = 1;
2633 im->grid_dash_off = 1;
2635 for(i=0;i<DIM(graph_col);i++)
2636 im->graph_col[i]=graph_col[i];
2638 for(i=0;i<DIM(text_prop);i++){
2639 im->text_prop[i].size = text_prop[i].size;
2640 im->text_prop[i].font = text_prop[i].font;
2645 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2648 char *parsetime_error = NULL;
2649 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2650 time_t start_tmp=0,end_tmp=0;
2652 struct time_value start_tv, end_tv;
2655 parsetime("end-24h", &start_tv);
2656 parsetime("now", &end_tv);
2659 static struct option long_options[] =
2661 {"start", required_argument, 0, 's'},
2662 {"end", required_argument, 0, 'e'},
2663 {"x-grid", required_argument, 0, 'x'},
2664 {"y-grid", required_argument, 0, 'y'},
2665 {"vertical-label",required_argument,0,'v'},
2666 {"width", required_argument, 0, 'w'},
2667 {"height", required_argument, 0, 'h'},
2668 {"interlaced", no_argument, 0, 'i'},
2669 {"upper-limit",required_argument, 0, 'u'},
2670 {"lower-limit",required_argument, 0, 'l'},
2671 {"rigid", no_argument, 0, 'r'},
2672 {"base", required_argument, 0, 'b'},
2673 {"logarithmic",no_argument, 0, 'o'},
2674 {"color", required_argument, 0, 'c'},
2675 {"font", required_argument, 0, 'n'},
2676 {"title", required_argument, 0, 't'},
2677 {"imginfo", required_argument, 0, 'f'},
2678 {"imgformat", required_argument, 0, 'a'},
2679 {"lazy", no_argument, 0, 'z'},
2680 {"zoom", required_argument, 0, 'm'},
2681 {"no-legend", no_argument, 0, 'g'},
2682 {"alt-y-grid", no_argument, 0, 257 },
2683 {"alt-autoscale", no_argument, 0, 258 },
2684 {"alt-autoscale-max", no_argument, 0, 259 },
2685 {"units-exponent",required_argument, 0, 260},
2686 {"step", required_argument, 0, 261},
2687 {"no-gridfit", no_argument, 0, 262},
2689 int option_index = 0;
2693 opt = getopt_long(argc, argv,
2694 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2695 long_options, &option_index);
2702 im->extra_flags |= ALTYGRID;
2705 im->extra_flags |= ALTAUTOSCALE;
2708 im->extra_flags |= ALTAUTOSCALE_MAX;
2711 im->extra_flags |= NOLEGEND;
2714 im->unitsexponent = atoi(optarg);
2717 im->step = atoi(optarg);
2723 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2724 rrd_set_error( "start time: %s", parsetime_error );
2729 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2730 rrd_set_error( "end time: %s", parsetime_error );
2735 if(strcmp(optarg,"none") == 0){
2741 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2743 &im->xlab_user.gridst,
2745 &im->xlab_user.mgridst,
2747 &im->xlab_user.labst,
2748 &im->xlab_user.precis,
2749 &stroff) == 7 && stroff != 0){
2750 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2751 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2752 rrd_set_error("unknown keyword %s",scan_gtm);
2754 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2755 rrd_set_error("unknown keyword %s",scan_mtm);
2757 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2758 rrd_set_error("unknown keyword %s",scan_ltm);
2761 im->xlab_user.minsec = 1;
2762 im->xlab_user.stst = im->xlab_form;
2764 rrd_set_error("invalid x-grid format");
2770 if(strcmp(optarg,"none") == 0){
2778 &im->ylabfact) == 2) {
2779 if(im->ygridstep<=0){
2780 rrd_set_error("grid step must be > 0");
2782 } else if (im->ylabfact < 1){
2783 rrd_set_error("label factor must be > 0");
2787 rrd_set_error("invalid y-grid format");
2792 strncpy(im->ylegend,optarg,150);
2793 im->ylegend[150]='\0';
2796 im->maxval = atof(optarg);
2799 im->minval = atof(optarg);
2802 im->base = atol(optarg);
2803 if(im->base != 1024 && im->base != 1000 ){
2804 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2809 long_tmp = atol(optarg);
2810 if (long_tmp < 10) {
2811 rrd_set_error("width below 10 pixels");
2814 im->xsize = long_tmp;
2817 long_tmp = atol(optarg);
2818 if (long_tmp < 10) {
2819 rrd_set_error("height below 10 pixels");
2822 im->ysize = long_tmp;
2825 im->canvas->interlaced = 1;
2831 im->imginfo = optarg;
2834 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2835 rrd_set_error("unsupported graphics format '%s'",optarg);
2843 im->logarithmic = 1;
2844 if (isnan(im->minval))
2850 col_nam,&color) == 2){
2852 if((ci=grc_conv(col_nam)) != -1){
2853 im->graph_col[ci]=color;
2855 rrd_set_error("invalid color name '%s'",col_nam);
2858 rrd_set_error("invalid color def format");
2863 /* originally this used char *prop = "" and
2864 ** char *font = "dummy" however this results
2865 ** in a SEG fault, at least on RH7.1
2867 ** The current implementation isn't proper
2868 ** either, font is never freed and prop uses
2869 ** a fixed width string
2878 prop,&size,font) == 3){
2880 if((sindex=text_prop_conv(prop)) != -1){
2881 im->text_prop[sindex].size=size;
2882 im->text_prop[sindex].font=font;
2883 if (sindex==0) { /* the default */
2884 im->text_prop[TEXT_PROP_TITLE].size=size;
2885 im->text_prop[TEXT_PROP_TITLE].font=font;
2886 im->text_prop[TEXT_PROP_AXIS].size=size;
2887 im->text_prop[TEXT_PROP_AXIS].font=font;
2888 im->text_prop[TEXT_PROP_UNIT].size=size;
2889 im->text_prop[TEXT_PROP_UNIT].font=font;
2890 im->text_prop[TEXT_PROP_LEGEND].size=size;
2891 im->text_prop[TEXT_PROP_LEGEND].font=font;
2894 rrd_set_error("invalid fonttag '%s'",prop);
2898 rrd_set_error("invalid text property format");
2904 im->canvas->zoom = atof(optarg);
2905 if (im->canvas->zoom <= 0.0) {
2906 rrd_set_error("zoom factor must be > 0");
2911 strncpy(im->title,optarg,150);
2912 im->title[150]='\0';
2917 rrd_set_error("unknown option '%c'", optopt);
2919 rrd_set_error("unknown option '%s'",argv[optind-1]);
2924 if (optind >= argc) {
2925 rrd_set_error("missing filename");
2929 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2930 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2934 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2935 /* error string is set in parsetime.c */
2939 if (start_tmp < 3600*24*365*10){
2940 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2944 if (end_tmp < start_tmp) {
2945 rrd_set_error("start (%ld) should be less than end (%ld)",
2946 start_tmp, end_tmp);
2950 im->start = start_tmp;
2955 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2959 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2961 for (i=optind+1;i<argc;i++) {
2966 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2971 /* Each command is one element from *argv[], we call this "line".
2973 ** Each command defines the most current gdes inside struct im.
2974 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2977 gdp=&im->gdes[im->gdes_c-1];
2980 /* function:newvname=string[:ds-name:CF] for xDEF
2981 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2982 ** function:vname#color[:num[:string]] for TICK
2983 ** function:vname-or-num#color[:string] for xRULE,PART
2984 ** function:vname:CF:string for xPRINT
2985 ** function:string for COMMENT
2989 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2991 rrd_set_error("Cannot parse function in line: %s",line);
2995 if(sscanf(funcname,"LINE%lf",&linewidth)){
2996 im->gdes[im->gdes_c-1].gf = GF_LINE;
2997 im->gdes[im->gdes_c-1].linewidth = linewidth;
2999 if ((gdp->gf=gf_conv(funcname))==-1) {
3000 rrd_set_error("'%s' is not a valid function name",funcname);
3006 /* If the error string is set, we exit at the end of the switch */
3011 if (rrd_graph_legend(gdp,&line[argstart])==0)
3012 rrd_set_error("Cannot parse comment in line: %s",line);
3018 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
3019 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
3021 rrd_set_error("Cannot parse name or num in line: %s",line);
3028 } else if (!rrd_graph_check_vname(im,vname,line)) {
3032 } else break; /* exit due to wrong vname */
3033 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
3035 if (strlen(&line[argstart])!=0) {
3036 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3037 rrd_set_error("Cannot parse comment in line: %s",line);
3042 rrd_set_error("STACK must follow another graphing element");
3050 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
3052 rrd_set_error("Cannot parse vname in line: %s",line);
3053 else if (rrd_graph_check_vname(im,vname,line))
3054 rrd_set_error("Undefined vname '%s' in line: %s",line);
3056 k=rrd_graph_color(im,&line[argstart],line,1);
3057 if (rrd_test_error()) break;
3058 argstart=argstart+j+k;
3059 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
3061 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
3064 if (strlen(&line[argstart])!=0)
3065 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3066 rrd_set_error("Cannot parse legend in line: %s",line);
3072 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3074 rrd_set_error("Cannot parse vname in line: '%s'",line);
3078 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3080 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3082 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3083 #define VIDX im->gdes[gdp->vidx]
3085 case -1: /* looks CF but is not really CF */
3086 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3088 case 0: /* CF present and correct */
3089 if (VIDX.gf == GF_VDEF)
3090 rrd_set_error("Don't use CF when printing VDEF");
3093 case 1: /* CF not present */
3094 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3095 else rrd_set_error("Printing DEF or CDEF needs CF");
3098 rrd_set_error("Oops, bug in GPRINT scanning");
3101 if (rrd_test_error()) break;
3103 if (strlen(&line[argstart])!=0) {
3104 if (rrd_graph_legend(gdp,&line[argstart])==0)
3105 rrd_set_error("Cannot parse legend in line: %s",line);
3106 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3107 strcpy(gdp->format, gdp->legend);
3113 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
3115 rrd_set_error("Could not parse line: %s",line);
3118 if (find_var(im,gdp->vname)!=-1) {
3119 rrd_set_error("Variable '%s' in line '%s' already in use\n",
3126 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3128 sscanf(&line[argstart],
3129 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3130 gdp->ds_nam, symname, &j, &k);
3131 if ((j==0)||(k!=0)) {
3132 rrd_set_error("Cannot parse DS or CF in '%s'",line);
3135 rrd_graph_check_CF(im,symname,line);
3139 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3141 rrd_set_error("Cannot parse vname in line '%s'",line);
3145 if (rrd_graph_check_vname(im,vname,line)) return;
3146 if ( im->gdes[gdp->vidx].gf != GF_DEF
3147 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3148 rrd_set_error("variable '%s' not DEF nor "
3149 "CDEF in VDEF '%s'", vname,gdp->vname);
3152 vdef_parse(gdp,&line[argstart+strstart]);
3155 if (strstr(&line[argstart],":")!=NULL) {
3156 rrd_set_error("Error in RPN, line: %s",line);
3159 if ((gdp->rpnp = rpn_parse(
3164 rrd_set_error("invalid rpn expression in: %s",line);
3169 default: rrd_set_error("Big oops");
3171 if (rrd_test_error()) {
3178 rrd_set_error("can't make a graph without contents");
3179 im_free(im); /* ??? is this set ??? */
3184 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3186 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3187 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3193 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3196 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3198 color=strstr(var,"#");
3201 rrd_set_error("Found no color in %s",err);
3210 rest=strstr(color,":");
3218 sscanf(color,"#%6lx%n",&col,&n);
3219 col = (col << 8) + 0xff /* shift left by 8 */;
3220 if (n!=7) rrd_set_error("Color problem in %s",err);
3223 sscanf(color,"#%8lx%n",&col,&n);
3226 rrd_set_error("Color problem in %s",err);
3228 if (rrd_test_error()) return 0;
3234 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3236 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3237 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3243 rrd_graph_legend(graph_desc_t *gdp, char *line)
3247 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3249 return (strlen(&line[i])==0);
3253 int bad_format(char *fmt) {
3257 while (*ptr != '\0')
3258 if (*ptr++ == '%') {
3260 /* line cannot end with percent char */
3261 if (*ptr == '\0') return 1;
3263 /* '%s', '%S' and '%%' are allowed */
3264 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3266 /* or else '% 6.2lf' and such are allowed */
3269 /* optional padding character */
3270 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3272 /* This should take care of 'm.n' with all three optional */
3273 while (*ptr >= '0' && *ptr <= '9') ptr++;
3274 if (*ptr == '.') ptr++;
3275 while (*ptr >= '0' && *ptr <= '9') ptr++;
3277 /* Either 'le' or 'lf' must follow here */
3278 if (*ptr++ != 'l') return 1;
3279 if (*ptr == 'e' || *ptr == 'f') ptr++;
3290 vdef_parse(gdes,str)
3291 struct graph_desc_t *gdes;
3294 /* A VDEF currently is either "func" or "param,func"
3295 * so the parsing is rather simple. Change if needed.
3302 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3303 if (n==strlen(str)) { /* matched */
3307 sscanf(str,"%29[A-Z]%n",func,&n);
3308 if (n==strlen(str)) { /* matched */
3311 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3318 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3319 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3320 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3321 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3322 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3323 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3324 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3326 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3333 switch (gdes->vf.op) {
3335 if (isnan(param)) { /* no parameter given */
3336 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3342 if (param>=0.0 && param<=100.0) {
3343 gdes->vf.param = param;
3344 gdes->vf.val = DNAN; /* undefined */
3345 gdes->vf.when = 0; /* undefined */
3347 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3361 gdes->vf.param = DNAN;
3362 gdes->vf.val = DNAN;
3365 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3382 graph_desc_t *src,*dst;
3386 dst = &im->gdes[gdi];
3387 src = &im->gdes[dst->vidx];
3388 data = src->data + src->ds;
3389 steps = (src->end - src->start) / src->step;
3392 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3399 switch (dst->vf.op) {
3400 case VDEF_PERCENT: {
3401 rrd_value_t * array;
3405 if ((array = malloc(steps*sizeof(double)))==NULL) {
3406 rrd_set_error("malloc VDEV_PERCENT");
3409 for (step=0;step < steps; step++) {
3410 array[step]=data[step*src->ds_cnt];
3412 qsort(array,step,sizeof(double),vdef_percent_compar);
3414 field = (steps-1)*dst->vf.param/100;
3415 dst->vf.val = array[field];
3416 dst->vf.when = 0; /* no time component */
3418 for(step=0;step<steps;step++)
3419 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3425 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3426 if (step == steps) {
3430 dst->vf.val = data[step*src->ds_cnt];
3431 dst->vf.when = src->start + (step+1)*src->step;
3433 while (step != steps) {
3434 if (finite(data[step*src->ds_cnt])) {
3435 if (data[step*src->ds_cnt] > dst->vf.val) {
3436 dst->vf.val = data[step*src->ds_cnt];
3437 dst->vf.when = src->start + (step+1)*src->step;
3444 case VDEF_AVERAGE: {
3447 for (step=0;step<steps;step++) {
3448 if (finite(data[step*src->ds_cnt])) {
3449 sum += data[step*src->ds_cnt];
3454 if (dst->vf.op == VDEF_TOTAL) {
3455 dst->vf.val = sum*src->step;
3456 dst->vf.when = cnt*src->step; /* not really "when" */
3458 dst->vf.val = sum/cnt;
3459 dst->vf.when = 0; /* no time component */
3469 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3470 if (step == steps) {
3474 dst->vf.val = data[step*src->ds_cnt];
3475 dst->vf.when = src->start + (step+1)*src->step;
3477 while (step != steps) {
3478 if (finite(data[step*src->ds_cnt])) {
3479 if (data[step*src->ds_cnt] < dst->vf.val) {
3480 dst->vf.val = data[step*src->ds_cnt];
3481 dst->vf.when = src->start + (step+1)*src->step;
3488 /* The time value returned here is one step before the
3489 * actual time value. This is the start of the first
3493 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3494 if (step == steps) { /* all entries were NaN */
3498 dst->vf.val = data[step*src->ds_cnt];
3499 dst->vf.when = src->start + step*src->step;
3503 /* The time value returned here is the
3504 * actual time value. This is the end of the last
3508 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3509 if (step < 0) { /* all entries were NaN */
3513 dst->vf.val = data[step*src->ds_cnt];
3514 dst->vf.when = src->start + (step+1)*src->step;
3521 /* NaN < -INF < finite_values < INF */
3523 vdef_percent_compar(a,b)
3526 /* Equality is not returned; this doesn't hurt except
3527 * (maybe) for a little performance.
3530 /* First catch NaN values. They are smallest */
3531 if (isnan( *(double *)a )) return -1;
3532 if (isnan( *(double *)b )) return 1;
3534 /* NaN doesn't reach this part so INF and -INF are extremes.
3535 * The sign from isinf() is compatible with the sign we return
3537 if (isinf( *(double *)a )) return isinf( *(double *)a );
3538 if (isinf( *(double *)b )) return isinf( *(double *)b );
3540 /* If we reach this, both values must be finite */
3541 if ( *(double *)a < *(double *)b ) return -1; else return 1;