1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
24 #include "rrd_graph.h"
25 #include "rrd_graph_helper.h"
27 /* some constant definitions */
30 #ifndef RRD_DEFAULT_FONT
32 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
34 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
35 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
40 text_prop_t text_prop[] = {
41 { 10.0, RRD_DEFAULT_FONT }, /* default */
42 { 12.0, RRD_DEFAULT_FONT }, /* title */
43 { 8.0, RRD_DEFAULT_FONT }, /* axis */
44 { 10.0, RRD_DEFAULT_FONT }, /* unit */
45 { 10.0, RRD_DEFAULT_FONT } /* legend */
49 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
51 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
52 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
53 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
54 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
55 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
56 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
57 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
58 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
59 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
60 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
61 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
62 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
63 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
64 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
67 /* sensible logarithmic y label intervals ...
68 the first element of each row defines the possible starting points on the
69 y axis ... the other specify the */
71 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
75 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
76 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
77 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
78 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
80 /* sensible y label intervals ...*/
98 gfx_color_t graph_col[] = /* default colors */
99 { 0xFFFFFFFF, /* canvas */
100 0xF0F0F0FF, /* background */
101 0xD0D0D0FF, /* shade A */
102 0xA0A0A0FF, /* shade B */
103 0x909090FF, /* grid */
104 0xE05050FF, /* major grid */
105 0x000000FF, /* font */
106 0x000000FF, /* frame */
107 0xFF0000FF /* arrow */
114 # define DPRINT(x) (void)(printf x, printf("\n"))
120 /* initialize with xtr(im,0); */
122 xtr(image_desc_t *im,time_t mytime){
125 pixie = (double) im->xsize / (double)(im->end - im->start);
128 return (int)((double)im->xorigin
129 + pixie * ( mytime - im->start ) );
132 /* translate data values into y coordinates */
134 ytr(image_desc_t *im, double value){
139 pixie = (double) im->ysize / (im->maxval - im->minval);
141 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
143 } else if(!im->logarithmic) {
144 yval = im->yorigin - pixie * (value - im->minval);
146 if (value < im->minval) {
149 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
152 /* make sure we don't return anything too unreasonable. GD lib can
153 get terribly slow when drawing lines outside its scope. This is
154 especially problematic in connection with the rigid option */
156 /* keep yval as-is */
157 } else if (yval > im->yorigin) {
158 yval = im->yorigin+2;
159 } else if (yval < im->yorigin - im->ysize){
160 yval = im->yorigin - im->ysize - 2;
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)
185 conv_if(CDEF,GF_CDEF)
186 conv_if(VDEF,GF_VDEF)
187 conv_if(PART,GF_PART)
192 enum gfx_if_en if_conv(char *string){
202 enum tmt_en tmt_conv(char *string){
204 conv_if(SECOND,TMT_SECOND)
205 conv_if(MINUTE,TMT_MINUTE)
206 conv_if(HOUR,TMT_HOUR)
208 conv_if(WEEK,TMT_WEEK)
209 conv_if(MONTH,TMT_MONTH)
210 conv_if(YEAR,TMT_YEAR)
214 enum grc_en grc_conv(char *string){
216 conv_if(BACK,GRC_BACK)
217 conv_if(CANVAS,GRC_CANVAS)
218 conv_if(SHADEA,GRC_SHADEA)
219 conv_if(SHADEB,GRC_SHADEB)
220 conv_if(GRID,GRC_GRID)
221 conv_if(MGRID,GRC_MGRID)
222 conv_if(FONT,GRC_FONT)
223 conv_if(FRAME,GRC_FRAME)
224 conv_if(ARROW,GRC_ARROW)
229 enum text_prop_en text_prop_conv(char *string){
231 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
232 conv_if(TITLE,TEXT_PROP_TITLE)
233 conv_if(AXIS,TEXT_PROP_AXIS)
234 conv_if(UNIT,TEXT_PROP_UNIT)
235 conv_if(LEGEND,TEXT_PROP_LEGEND)
245 im_free(image_desc_t *im)
248 if (im == NULL) return 0;
249 for(i=0;i<im->gdes_c;i++){
250 if (im->gdes[i].data_first){
251 /* careful here, because a single pointer can occur several times */
252 free (im->gdes[i].data);
253 if (im->gdes[i].ds_namv){
254 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
255 free(im->gdes[i].ds_namv[ii]);
256 free(im->gdes[i].ds_namv);
259 free (im->gdes[i].p_data);
260 free (im->gdes[i].rpnp);
263 gfx_destroy(im->canvas);
267 /* find SI magnitude symbol for the given number*/
270 image_desc_t *im, /* image description */
277 char *symbol[] = {"a", /* 10e-18 Atto */
278 "f", /* 10e-15 Femto */
279 "p", /* 10e-12 Pico */
280 "n", /* 10e-9 Nano */
281 "u", /* 10e-6 Micro */
282 "m", /* 10e-3 Milli */
287 "T", /* 10e12 Tera */
288 "P", /* 10e15 Peta */
294 if (*value == 0.0 || isnan(*value) ) {
298 sindex = floor(log(fabs(*value))/log((double)im->base));
299 *magfact = pow((double)im->base, (double)sindex);
300 (*value) /= (*magfact);
302 if ( sindex <= symbcenter && sindex >= -symbcenter) {
303 (*symb_ptr) = symbol[sindex+symbcenter];
311 /* find SI magnitude symbol for the numbers on the y-axis*/
314 image_desc_t *im /* image description */
318 char symbol[] = {'a', /* 10e-18 Atto */
319 'f', /* 10e-15 Femto */
320 'p', /* 10e-12 Pico */
321 'n', /* 10e-9 Nano */
322 'u', /* 10e-6 Micro */
323 'm', /* 10e-3 Milli */
328 'T', /* 10e12 Tera */
329 'P', /* 10e15 Peta */
335 if (im->unitsexponent != 9999) {
336 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
337 digits = floor(im->unitsexponent / 3);
339 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
341 im->magfact = pow((double)im->base , digits);
344 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
347 if ( ((digits+symbcenter) < sizeof(symbol)) &&
348 ((digits+symbcenter) >= 0) )
349 im->symbol = symbol[(int)digits+symbcenter];
354 /* move min and max values around to become sensible */
357 expand_range(image_desc_t *im)
359 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
360 600.0,500.0,400.0,300.0,250.0,
361 200.0,125.0,100.0,90.0,80.0,
362 75.0,70.0,60.0,50.0,40.0,30.0,
363 25.0,20.0,10.0,9.0,8.0,
364 7.0,6.0,5.0,4.0,3.5,3.0,
365 2.5,2.0,1.8,1.5,1.2,1.0,
366 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
368 double scaled_min,scaled_max;
375 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
376 im->minval,im->maxval,im->magfact);
379 if (isnan(im->ygridstep)){
380 if(im->extra_flags & ALTAUTOSCALE) {
381 /* measure the amplitude of the function. Make sure that
382 graph boundaries are slightly higher then max/min vals
383 so we can see amplitude on the graph */
386 delt = im->maxval - im->minval;
388 fact = 2.0 * pow(10.0,
389 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
391 adj = (fact - delt) * 0.55;
393 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
399 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
400 /* measure the amplitude of the function. Make sure that
401 graph boundaries are slightly higher than max vals
402 so we can see amplitude on the graph */
403 adj = (im->maxval - im->minval) * 0.1;
407 scaled_min = im->minval / im->magfact;
408 scaled_max = im->maxval / im->magfact;
410 for (i=1; sensiblevalues[i] > 0; i++){
411 if (sensiblevalues[i-1]>=scaled_min &&
412 sensiblevalues[i]<=scaled_min)
413 im->minval = sensiblevalues[i]*(im->magfact);
415 if (-sensiblevalues[i-1]<=scaled_min &&
416 -sensiblevalues[i]>=scaled_min)
417 im->minval = -sensiblevalues[i-1]*(im->magfact);
419 if (sensiblevalues[i-1] >= scaled_max &&
420 sensiblevalues[i] <= scaled_max)
421 im->maxval = sensiblevalues[i-1]*(im->magfact);
423 if (-sensiblevalues[i-1]<=scaled_max &&
424 -sensiblevalues[i] >=scaled_max)
425 im->maxval = -sensiblevalues[i]*(im->magfact);
429 /* adjust min and max to the grid definition if there is one */
430 im->minval = (double)im->ylabfact * im->ygridstep *
431 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
432 im->maxval = (double)im->ylabfact * im->ygridstep *
433 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
437 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
438 im->minval,im->maxval,im->magfact);
443 apply_gridfit(image_desc_t *im)
445 if (isnan(im->minval) || isnan(im->maxval))
448 if (im->logarithmic) {
449 double ya, yb, ypix, ypixfrac;
450 double log10_range = log10(im->maxval) - log10(im->minval);
451 ya = pow((double)10, floor(log10(im->minval)));
452 while (ya < im->minval)
455 return; /* don't have y=10^x gridline */
457 if (yb <= im->maxval) {
458 /* we have at least 2 y=10^x gridlines.
459 Make sure distance between them in pixels
460 are an integer by expanding im->maxval */
461 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
462 double factor = y_pixel_delta / floor(y_pixel_delta);
463 double new_log10_range = factor * log10_range;
464 double new_ymax_log10 = log10(im->minval) + new_log10_range;
465 im->maxval = pow(10, new_ymax_log10);
466 ytr(im, DNAN); /* reset precalc */
467 log10_range = log10(im->maxval) - log10(im->minval);
469 /* make sure first y=10^x gridline is located on
470 integer pixel position by moving scale slightly
471 downwards (sub-pixel movement) */
472 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
473 ypixfrac = ypix - floor(ypix);
474 if (ypixfrac > 0 && ypixfrac < 1) {
475 double yfrac = ypixfrac / im->ysize;
476 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
477 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
478 ytr(im, DNAN); /* reset precalc */
481 /* Make sure we have an integer pixel distance between
482 each minor gridline */
483 double ypos1 = ytr(im, im->minval);
484 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
485 double y_pixel_delta = ypos1 - ypos2;
486 double factor = y_pixel_delta / floor(y_pixel_delta);
487 double new_range = factor * (im->maxval - im->minval);
488 double gridstep = im->ygrid_scale.gridstep;
489 double minor_y, minor_y_px, minor_y_px_frac;
490 im->maxval = im->minval + new_range;
491 ytr(im, DNAN); /* reset precalc */
492 /* make sure first minor gridline is on integer pixel y coord */
493 minor_y = gridstep * floor(im->minval / gridstep);
494 while (minor_y < im->minval)
496 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
497 minor_y_px_frac = minor_y_px - floor(minor_y_px);
498 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
499 double yfrac = minor_y_px_frac / im->ysize;
500 double range = im->maxval - im->minval;
501 im->minval = im->minval - yfrac * range;
502 im->maxval = im->maxval - yfrac * range;
503 ytr(im, DNAN); /* reset precalc */
505 calc_horizontal_grid(im); /* recalc with changed im->maxval */
509 /* reduce data reimplementation by Alex */
513 enum cf_en cf, /* which consolidation function ?*/
514 unsigned long cur_step, /* step the data currently is in */
515 time_t *start, /* start, end and step as requested ... */
516 time_t *end, /* ... by the application will be ... */
517 unsigned long *step, /* ... adjusted to represent reality */
518 unsigned long *ds_cnt, /* number of data sources in file */
519 rrd_value_t **data) /* two dimensional array containing the data */
521 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
522 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
523 rrd_value_t *srcptr,*dstptr;
525 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
528 row_cnt = ((*end)-(*start))/cur_step;
534 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
535 row_cnt,reduce_factor,*start,*end,cur_step);
536 for (col=0;col<row_cnt;col++) {
537 printf("time %10lu: ",*start+(col+1)*cur_step);
538 for (i=0;i<*ds_cnt;i++)
539 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
544 /* We have to combine [reduce_factor] rows of the source
545 ** into one row for the destination. Doing this we also
546 ** need to take care to combine the correct rows. First
547 ** alter the start and end time so that they are multiples
548 ** of the new step time. We cannot reduce the amount of
549 ** time so we have to move the end towards the future and
550 ** the start towards the past.
552 end_offset = (*end) % (*step);
553 start_offset = (*start) % (*step);
555 /* If there is a start offset (which cannot be more than
556 ** one destination row), skip the appropriate number of
557 ** source rows and one destination row. The appropriate
558 ** number is what we do know (start_offset/cur_step) of
559 ** the new interval (*step/cur_step aka reduce_factor).
562 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
563 printf("row_cnt before: %lu\n",row_cnt);
566 (*start) = (*start)-start_offset;
567 skiprows=reduce_factor-start_offset/cur_step;
568 srcptr+=skiprows* *ds_cnt;
569 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
573 printf("row_cnt between: %lu\n",row_cnt);
576 /* At the end we have some rows that are not going to be
577 ** used, the amount is end_offset/cur_step
580 (*end) = (*end)-end_offset+(*step);
581 skiprows = end_offset/cur_step;
585 printf("row_cnt after: %lu\n",row_cnt);
588 /* Sanity check: row_cnt should be multiple of reduce_factor */
589 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
591 if (row_cnt%reduce_factor) {
592 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
593 row_cnt,reduce_factor);
594 printf("BUG in reduce_data()\n");
598 /* Now combine reduce_factor intervals at a time
599 ** into one interval for the destination.
602 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
603 for (col=0;col<(*ds_cnt);col++) {
604 rrd_value_t newval=DNAN;
605 unsigned long validval=0;
607 for (i=0;i<reduce_factor;i++) {
608 if (isnan(srcptr[i*(*ds_cnt)+col])) {
612 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
620 newval += srcptr[i*(*ds_cnt)+col];
623 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
626 /* an interval contains a failure if any subintervals contained a failure */
628 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
631 newval = srcptr[i*(*ds_cnt)+col];
636 if (validval == 0){newval = DNAN;} else{
654 srcptr+=(*ds_cnt)*reduce_factor;
655 row_cnt-=reduce_factor;
657 /* If we had to alter the endtime, we didn't have enough
658 ** source rows to fill the last row. Fill it with NaN.
660 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
662 row_cnt = ((*end)-(*start))/ *step;
664 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
665 row_cnt,*start,*end,*step);
666 for (col=0;col<row_cnt;col++) {
667 printf("time %10lu: ",*start+(col+1)*(*step));
668 for (i=0;i<*ds_cnt;i++)
669 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
676 /* get the data required for the graphs from the
680 data_fetch( image_desc_t *im )
684 /* pull the data from the log files ... */
685 for (i=0;i<im->gdes_c;i++){
686 /* only GF_DEF elements fetch data */
687 if (im->gdes[i].gf != GF_DEF)
691 /* do we have it already ?*/
692 for (ii=0;ii<i;ii++){
693 if (im->gdes[ii].gf != GF_DEF)
695 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
696 && (im->gdes[i].cf == im->gdes[ii].cf)){
697 /* OK the data it is here already ...
698 * we just copy the header portion */
699 im->gdes[i].start = im->gdes[ii].start;
700 im->gdes[i].end = im->gdes[ii].end;
701 im->gdes[i].step = im->gdes[ii].step;
702 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
703 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
704 im->gdes[i].data = im->gdes[ii].data;
705 im->gdes[i].data_first = 0;
712 unsigned long ft_step = im->gdes[i].step ;
714 if((rrd_fetch_fn(im->gdes[i].rrd,
720 &im->gdes[i].ds_namv,
721 &im->gdes[i].data)) == -1){
724 im->gdes[i].data_first = 1;
726 if (ft_step < im->gdes[i].step) {
727 reduce_data(im->gdes[i].cf,
735 im->gdes[i].step = ft_step;
739 /* lets see if the required data source is realy there */
740 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
741 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
744 if (im->gdes[i].ds== -1){
745 rrd_set_error("No DS called '%s' in '%s'",
746 im->gdes[i].ds_nam,im->gdes[i].rrd);
754 /* evaluate the expressions in the CDEF functions */
756 /*************************************************************
758 *************************************************************/
761 find_var_wrapper(void *arg1, char *key)
763 return find_var((image_desc_t *) arg1, key);
766 /* find gdes containing var*/
768 find_var(image_desc_t *im, char *key){
770 for(ii=0;ii<im->gdes_c-1;ii++){
771 if((im->gdes[ii].gf == GF_DEF
772 || im->gdes[ii].gf == GF_VDEF
773 || im->gdes[ii].gf == GF_CDEF)
774 && (strcmp(im->gdes[ii].vname,key) == 0)){
781 /* find the largest common denominator for all the numbers
782 in the 0 terminated num array */
787 for (i=0;num[i+1]!=0;i++){
789 rest=num[i] % num[i+1];
790 num[i]=num[i+1]; num[i+1]=rest;
794 /* return i==0?num[i]:num[i-1]; */
798 /* run the rpn calculator on all the VDEF and CDEF arguments */
800 data_calc( image_desc_t *im){
804 long *steparray, rpi;
809 rpnstack_init(&rpnstack);
811 for (gdi=0;gdi<im->gdes_c;gdi++){
812 /* Look for GF_VDEF and GF_CDEF in the same loop,
813 * so CDEFs can use VDEFs and vice versa
815 switch (im->gdes[gdi].gf) {
817 /* A VDEF has no DS. This also signals other parts
818 * of rrdtool that this is a VDEF value, not a CDEF.
820 im->gdes[gdi].ds_cnt = 0;
821 if (vdef_calc(im,gdi)) {
822 rrd_set_error("Error processing VDEF '%s'"
825 rpnstack_free(&rpnstack);
830 im->gdes[gdi].ds_cnt = 1;
831 im->gdes[gdi].ds = 0;
832 im->gdes[gdi].data_first = 1;
833 im->gdes[gdi].start = 0;
834 im->gdes[gdi].end = 0;
839 /* Find the variables in the expression.
840 * - VDEF variables are substituted by their values
841 * and the opcode is changed into OP_NUMBER.
842 * - CDEF variables are analized for their step size,
843 * the lowest common denominator of all the step
844 * sizes of the data sources involved is calculated
845 * and the resulting number is the step size for the
846 * resulting data source.
848 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
849 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
850 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
851 if (im->gdes[ptr].ds_cnt == 0) {
853 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
855 im->gdes[ptr].vname);
856 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
858 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
859 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
861 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
862 rrd_set_error("realloc steparray");
863 rpnstack_free(&rpnstack);
867 steparray[stepcnt-1] = im->gdes[ptr].step;
869 /* adjust start and end of cdef (gdi) so
870 * that it runs from the latest start point
871 * to the earliest endpoint of any of the
872 * rras involved (ptr)
874 if(im->gdes[gdi].start < im->gdes[ptr].start)
875 im->gdes[gdi].start = im->gdes[ptr].start;
877 if(im->gdes[gdi].end == 0 ||
878 im->gdes[gdi].end > im->gdes[ptr].end)
879 im->gdes[gdi].end = im->gdes[ptr].end;
881 /* store pointer to the first element of
882 * the rra providing data for variable,
883 * further save step size and data source
886 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
887 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
888 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
890 /* backoff the *.data ptr; this is done so
891 * rpncalc() function doesn't have to treat
892 * the first case differently
894 } /* if ds_cnt != 0 */
895 } /* if OP_VARIABLE */
896 } /* loop through all rpi */
898 /* move the data pointers to the correct period */
899 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
900 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
901 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
902 if(im->gdes[gdi].start > im->gdes[ptr].start) {
903 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
909 if(steparray == NULL){
910 rrd_set_error("rpn expressions without DEF"
911 " or CDEF variables are not supported");
912 rpnstack_free(&rpnstack);
915 steparray[stepcnt]=0;
916 /* Now find the resulting step. All steps in all
917 * used RRAs have to be visited
919 im->gdes[gdi].step = lcd(steparray);
921 if((im->gdes[gdi].data = malloc((
922 (im->gdes[gdi].end-im->gdes[gdi].start)
923 / im->gdes[gdi].step)
924 * sizeof(double)))==NULL){
925 rrd_set_error("malloc im->gdes[gdi].data");
926 rpnstack_free(&rpnstack);
930 /* Step through the new cdef results array and
931 * calculate the values
933 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
934 now<=im->gdes[gdi].end;
935 now += im->gdes[gdi].step)
937 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
939 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
940 * in this case we are advancing by timesteps;
941 * we use the fact that time_t is a synonym for long
943 if (rpn_calc(rpnp,&rpnstack,(long) now,
944 im->gdes[gdi].data,++dataidx) == -1) {
945 /* rpn_calc sets the error string */
946 rpnstack_free(&rpnstack);
949 } /* enumerate over time steps within a CDEF */
954 } /* enumerate over CDEFs */
955 rpnstack_free(&rpnstack);
959 /* massage data so, that we get one value for each x coordinate in the graph */
961 data_proc( image_desc_t *im ){
963 double pixstep = (double)(im->end-im->start)
964 /(double)im->xsize; /* how much time
965 passes in one pixel */
967 double minval=DNAN,maxval=DNAN;
969 unsigned long gr_time;
971 /* memory for the processed data */
972 for(i=0;i<im->gdes_c;i++){
973 if((im->gdes[i].gf==GF_LINE) ||
974 (im->gdes[i].gf==GF_AREA) ||
975 (im->gdes[i].gf==GF_TICK) ||
976 (im->gdes[i].gf==GF_STACK)){
977 if((im->gdes[i].p_data = malloc((im->xsize +1)
978 * sizeof(rrd_value_t)))==NULL){
979 rrd_set_error("malloc data_proc");
985 for(i=0;i<im->xsize;i++){
987 gr_time = im->start+pixstep*i; /* time of the
991 for(ii=0;ii<im->gdes_c;ii++){
993 switch(im->gdes[ii].gf){
999 vidx = im->gdes[ii].vidx;
1002 im->gdes[vidx].data[
1003 ((unsigned long)floor(
1004 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
1006 ) *im->gdes[vidx].ds_cnt
1007 +im->gdes[vidx].ds];
1009 if (! isnan(value)) {
1011 im->gdes[ii].p_data[i] = paintval;
1012 /* GF_TICK: the data values are not relevant for min and max */
1013 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1014 if (isnan(minval) || paintval < minval)
1016 if (isnan(maxval) || paintval > maxval)
1020 im->gdes[ii].p_data[i] = DNAN;
1037 /* if min or max have not been asigned a value this is because
1038 there was no data in the graph ... this is not good ...
1039 lets set these to dummy values then ... */
1041 if (isnan(minval)) minval = 0.0;
1042 if (isnan(maxval)) maxval = 1.0;
1044 /* adjust min and max values */
1045 if (isnan(im->minval)
1046 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1047 && im->minval > minval))
1048 im->minval = minval;
1049 if (isnan(im->maxval)
1051 && im->maxval < maxval)){
1052 if (im->logarithmic)
1053 im->maxval = maxval * 1.1;
1055 im->maxval = maxval;
1057 /* make sure min and max are not equal */
1058 if (im->minval == im->maxval) {
1060 if (! im->logarithmic) {
1064 /* make sure min and max are not both zero */
1065 if (im->maxval == 0.0) {
1075 /* identify the point where the first gridline, label ... gets placed */
1079 time_t start, /* what is the initial time */
1080 enum tmt_en baseint, /* what is the basic interval */
1081 long basestep /* how many if these do we jump a time */
1085 tm = *localtime(&start);
1088 tm.tm_sec -= tm.tm_sec % basestep; break;
1091 tm.tm_min -= tm.tm_min % basestep;
1096 tm.tm_hour -= tm.tm_hour % basestep; break;
1098 /* we do NOT look at the basestep for this ... */
1101 tm.tm_hour = 0; break;
1103 /* we do NOT look at the basestep for this ... */
1107 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1108 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1115 tm.tm_mon -= tm.tm_mon % basestep; break;
1123 tm.tm_year -= (tm.tm_year+1900) % basestep;
1128 /* identify the point where the next gridline, label ... gets placed */
1131 time_t current, /* what is the initial time */
1132 enum tmt_en baseint, /* what is the basic interval */
1133 long basestep /* how many if these do we jump a time */
1138 tm = *localtime(¤t);
1142 tm.tm_sec += basestep; break;
1144 tm.tm_min += basestep; break;
1146 tm.tm_hour += basestep; break;
1148 tm.tm_mday += basestep; break;
1150 tm.tm_mday += 7*basestep; break;
1152 tm.tm_mon += basestep; break;
1154 tm.tm_year += basestep;
1156 madetime = mktime(&tm);
1157 } while (madetime == -1); /* this is necessary to skip impssible times
1158 like the daylight saving time skips */
1164 /* calculate values required for PRINT and GPRINT functions */
1167 print_calc(image_desc_t *im, char ***prdata)
1169 long i,ii,validsteps;
1172 int graphelement = 0;
1175 double magfact = -1;
1179 if (im->imginfo) prlines++;
1180 for(i=0;i<im->gdes_c;i++){
1181 switch(im->gdes[i].gf){
1184 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1185 rrd_set_error("realloc prdata");
1189 /* PRINT and GPRINT can now print VDEF generated values.
1190 * There's no need to do any calculations on them as these
1191 * calculations were already made.
1193 vidx = im->gdes[i].vidx;
1194 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1195 printval = im->gdes[vidx].vf.val;
1196 printtime = im->gdes[vidx].vf.when;
1197 } else { /* need to calculate max,min,avg etcetera */
1198 max_ii =((im->gdes[vidx].end
1199 - im->gdes[vidx].start)
1200 / im->gdes[vidx].step
1201 * im->gdes[vidx].ds_cnt);
1204 for( ii=im->gdes[vidx].ds;
1206 ii+=im->gdes[vidx].ds_cnt){
1207 if (! finite(im->gdes[vidx].data[ii]))
1209 if (isnan(printval)){
1210 printval = im->gdes[vidx].data[ii];
1215 switch (im->gdes[i].cf){
1218 case CF_DEVSEASONAL:
1222 printval += im->gdes[vidx].data[ii];
1225 printval = min( printval, im->gdes[vidx].data[ii]);
1229 printval = max( printval, im->gdes[vidx].data[ii]);
1232 printval = im->gdes[vidx].data[ii];
1235 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1236 if (validsteps > 1) {
1237 printval = (printval / validsteps);
1240 } /* prepare printval */
1242 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1243 if (im->gdes[i].gf == GF_PRINT){
1244 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1245 sprintf((*prdata)[prlines-2],"%s (%lu)",
1246 ctime(&printtime),printtime);
1247 (*prdata)[prlines-1] = NULL;
1249 sprintf(im->gdes[i].legend,"%s (%lu)",
1250 ctime(&printtime),printtime);
1254 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1255 /* Magfact is set to -1 upon entry to print_calc. If it
1256 * is still less than 0, then we need to run auto_scale.
1257 * Otherwise, put the value into the correct units. If
1258 * the value is 0, then do not set the symbol or magnification
1259 * so next the calculation will be performed again. */
1260 if (magfact < 0.0) {
1261 auto_scale(im,&printval,&si_symb,&magfact);
1262 if (printval == 0.0)
1265 printval /= magfact;
1267 *(++percent_s) = 's';
1268 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1269 auto_scale(im,&printval,&si_symb,&magfact);
1272 if (im->gdes[i].gf == GF_PRINT){
1273 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1274 if (bad_format(im->gdes[i].format)) {
1275 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1278 #ifdef HAVE_SNPRINTF
1279 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1281 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1283 (*prdata)[prlines-1] = NULL;
1287 if (bad_format(im->gdes[i].format)) {
1288 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1291 #ifdef HAVE_SNPRINTF
1292 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1294 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1316 return graphelement;
1320 /* place legends with color spots */
1322 leg_place(image_desc_t *im)
1325 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1326 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1327 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1328 int fill=0, fill_last;
1330 int leg_x = border, leg_y = im->yimg;
1334 char prt_fctn; /*special printfunctions */
1337 if( !(im->extra_flags & NOLEGEND) ) {
1338 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1339 rrd_set_error("malloc for legspace");
1343 for(i=0;i<im->gdes_c;i++){
1346 leg_cc = strlen(im->gdes[i].legend);
1348 /* is there a controle code ant the end of the legend string ? */
1349 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1350 prt_fctn = im->gdes[i].legend[leg_cc-1];
1352 im->gdes[i].legend[leg_cc] = '\0';
1356 /* remove exess space */
1357 while (prt_fctn=='g' &&
1359 im->gdes[i].legend[leg_cc-1]==' '){
1361 im->gdes[i].legend[leg_cc]='\0';
1364 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1367 /* no interleg space if string ends in \g */
1368 fill += legspace[i];
1370 if (im->gdes[i].gf != GF_GPRINT &&
1371 im->gdes[i].gf != GF_COMMENT) {
1374 fill += gfx_get_text_width(im->canvas, fill+border,
1375 im->text_prop[TEXT_PROP_LEGEND].font,
1376 im->text_prop[TEXT_PROP_LEGEND].size,
1378 im->gdes[i].legend);
1383 /* who said there was a special tag ... ?*/
1384 if (prt_fctn=='g') {
1387 if (prt_fctn == '\0') {
1388 if (i == im->gdes_c -1 ) prt_fctn ='l';
1390 /* is it time to place the legends ? */
1391 if (fill > im->ximg - 2*border){
1406 if (prt_fctn != '\0'){
1408 if (leg_c >= 2 && prt_fctn == 'j') {
1409 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1413 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1414 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1416 for(ii=mark;ii<=i;ii++){
1417 if(im->gdes[ii].legend[0]=='\0')
1419 im->gdes[ii].leg_x = leg_x;
1420 im->gdes[ii].leg_y = leg_y;
1422 gfx_get_text_width(im->canvas, leg_x,
1423 im->text_prop[TEXT_PROP_LEGEND].font,
1424 im->text_prop[TEXT_PROP_LEGEND].size,
1426 im->gdes[ii].legend)
1429 if (im->gdes[ii].gf != GF_GPRINT &&
1430 im->gdes[ii].gf != GF_COMMENT)
1433 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1434 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1446 /* create a grid on the graph. it determines what to do
1447 from the values of xsize, start and end */
1449 /* the xaxis labels are determined from the number of seconds per pixel
1450 in the requested graph */
1455 calc_horizontal_grid(image_desc_t *im)
1461 int decimals, fractionals;
1463 im->ygrid_scale.labfact=2;
1465 range = im->maxval - im->minval;
1466 scaledrange = range / im->magfact;
1468 /* does the scale of this graph make it impossible to put lines
1469 on it? If so, give up. */
1470 if (isnan(scaledrange)) {
1474 /* find grid spaceing */
1476 if(isnan(im->ygridstep)){
1477 if(im->extra_flags & ALTYGRID) {
1478 /* find the value with max number of digits. Get number of digits */
1479 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1480 if(decimals <= 0) /* everything is small. make place for zero */
1483 fractionals = floor(log10(range));
1484 if(fractionals < 0) /* small amplitude. */
1485 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1487 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1488 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1489 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1490 im->ygrid_scale.gridstep = 0.1;
1491 /* should have at least 5 lines but no more then 15 */
1492 if(range/im->ygrid_scale.gridstep < 5)
1493 im->ygrid_scale.gridstep /= 10;
1494 if(range/im->ygrid_scale.gridstep > 15)
1495 im->ygrid_scale.gridstep *= 10;
1496 if(range/im->ygrid_scale.gridstep > 5) {
1497 im->ygrid_scale.labfact = 1;
1498 if(range/im->ygrid_scale.gridstep > 8)
1499 im->ygrid_scale.labfact = 2;
1502 im->ygrid_scale.gridstep /= 5;
1503 im->ygrid_scale.labfact = 5;
1507 for(i=0;ylab[i].grid > 0;i++){
1508 pixel = im->ysize / (scaledrange / ylab[i].grid);
1509 if (gridind == -1 && pixel > 5) {
1516 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1517 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1522 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1525 im->ygrid_scale.gridstep = im->ygridstep;
1526 im->ygrid_scale.labfact = im->ylabfact;
1531 int draw_horizontal_grid(image_desc_t *im)
1535 char graph_label[100];
1536 double X0=im->xorigin;
1537 double X1=im->xorigin+im->xsize;
1539 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1540 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1541 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1542 for (i = sgrid; i <= egrid; i++){
1543 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1544 if ( Y0 >= im->yorigin-im->ysize
1545 && Y0 <= im->yorigin){
1546 if(i % im->ygrid_scale.labfact == 0){
1547 if (i==0 || im->symbol == ' ') {
1549 if(im->extra_flags & ALTYGRID) {
1550 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1553 sprintf(graph_label,"%4.1f",scaledstep*i);
1556 sprintf(graph_label,"%4.0f",scaledstep*i);
1560 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1562 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1566 gfx_new_text ( im->canvas,
1567 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1568 im->graph_col[GRC_FONT],
1569 im->text_prop[TEXT_PROP_AXIS].font,
1570 im->text_prop[TEXT_PROP_AXIS].size,
1571 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1573 gfx_new_dashed_line ( im->canvas,
1576 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1577 im->grid_dash_on, im->grid_dash_off);
1580 gfx_new_dashed_line ( im->canvas,
1583 GRIDWIDTH, im->graph_col[GRC_GRID],
1584 im->grid_dash_on, im->grid_dash_off);
1592 /* logaritmic horizontal grid */
1594 horizontal_log_grid(image_desc_t *im)
1598 int minoridx=0, majoridx=0;
1599 char graph_label[100];
1601 double value, pixperstep, minstep;
1603 /* find grid spaceing */
1604 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1606 if (isnan(pixpex)) {
1610 for(i=0;yloglab[i][0] > 0;i++){
1611 minstep = log10(yloglab[i][0]);
1612 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1613 if(yloglab[i][ii+2]==0){
1614 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1618 pixperstep = pixpex * minstep;
1619 if(pixperstep > 5){minoridx = i;}
1620 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1624 X1=im->xorigin+im->xsize;
1625 /* paint minor grid */
1626 for (value = pow((double)10, log10(im->minval)
1627 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1628 value <= im->maxval;
1629 value *= yloglab[minoridx][0]){
1630 if (value < im->minval) continue;
1632 while(yloglab[minoridx][++i] > 0){
1633 Y0 = ytr(im,value * yloglab[minoridx][i]);
1634 if (Y0 <= im->yorigin - im->ysize) break;
1635 gfx_new_dashed_line ( im->canvas,
1638 GRIDWIDTH, im->graph_col[GRC_GRID],
1639 im->grid_dash_on, im->grid_dash_off);
1643 /* paint major grid and labels*/
1644 for (value = pow((double)10, log10(im->minval)
1645 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1646 value <= im->maxval;
1647 value *= yloglab[majoridx][0]){
1648 if (value < im->minval) continue;
1650 while(yloglab[majoridx][++i] > 0){
1651 Y0 = ytr(im,value * yloglab[majoridx][i]);
1652 if (Y0 <= im->yorigin - im->ysize) break;
1653 gfx_new_dashed_line ( im->canvas,
1656 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1657 im->grid_dash_on, im->grid_dash_off);
1659 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1660 gfx_new_text ( im->canvas,
1661 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1662 im->graph_col[GRC_FONT],
1663 im->text_prop[TEXT_PROP_AXIS].font,
1664 im->text_prop[TEXT_PROP_AXIS].size,
1665 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1677 int xlab_sel; /* which sort of label and grid ? */
1678 time_t ti, tilab, timajor;
1680 char graph_label[100];
1681 double X0,Y0,Y1; /* points for filled graph and more*/
1684 /* the type of time grid is determined by finding
1685 the number of seconds per pixel in the graph */
1688 if(im->xlab_user.minsec == -1){
1689 factor=(im->end - im->start)/im->xsize;
1691 while ( xlab[xlab_sel+1].minsec != -1
1692 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1693 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1694 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1695 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1696 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1697 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1698 im->xlab_user.labst = xlab[xlab_sel].labst;
1699 im->xlab_user.precis = xlab[xlab_sel].precis;
1700 im->xlab_user.stst = xlab[xlab_sel].stst;
1703 /* y coords are the same for every line ... */
1705 Y1 = im->yorigin-im->ysize;
1708 /* paint the minor grid */
1709 for(ti = find_first_time(im->start,
1710 im->xlab_user.gridtm,
1711 im->xlab_user.gridst),
1712 timajor = find_first_time(im->start,
1713 im->xlab_user.mgridtm,
1714 im->xlab_user.mgridst);
1716 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1718 /* are we inside the graph ? */
1719 if (ti < im->start || ti > im->end) continue;
1720 while (timajor < ti) {
1721 timajor = find_next_time(timajor,
1722 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1724 if (ti == timajor) continue; /* skip as falls on major grid line */
1726 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1727 im->graph_col[GRC_GRID],
1728 im->grid_dash_on, im->grid_dash_off);
1732 /* paint the major grid */
1733 for(ti = find_first_time(im->start,
1734 im->xlab_user.mgridtm,
1735 im->xlab_user.mgridst);
1737 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1739 /* are we inside the graph ? */
1740 if (ti < im->start || ti > im->end) continue;
1742 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1743 im->graph_col[GRC_MGRID],
1744 im->grid_dash_on, im->grid_dash_off);
1747 /* paint the labels below the graph */
1748 for(ti = find_first_time(im->start,
1749 im->xlab_user.labtm,
1750 im->xlab_user.labst);
1752 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1754 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1755 /* are we inside the graph ? */
1756 if (ti < im->start || ti > im->end) continue;
1759 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1761 # error "your libc has no strftime I guess we'll abort the exercise here."
1763 gfx_new_text ( im->canvas,
1764 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1765 im->graph_col[GRC_FONT],
1766 im->text_prop[TEXT_PROP_AXIS].font,
1767 im->text_prop[TEXT_PROP_AXIS].size,
1768 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1781 /* draw x and y axis */
1782 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1783 im->xorigin+im->xsize,im->yorigin-im->ysize,
1784 GRIDWIDTH, im->graph_col[GRC_GRID]);
1786 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1787 im->xorigin+im->xsize,im->yorigin-im->ysize,
1788 GRIDWIDTH, im->graph_col[GRC_GRID]);
1790 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1791 im->xorigin+im->xsize+4,im->yorigin,
1792 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1794 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1795 im->xorigin,im->yorigin-im->ysize-4,
1796 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1799 /* arrow for X axis direction */
1800 gfx_new_area ( im->canvas,
1801 im->xorigin+im->xsize+3, im->yorigin-3,
1802 im->xorigin+im->xsize+3, im->yorigin+4,
1803 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1804 im->graph_col[GRC_ARROW]);
1811 grid_paint(image_desc_t *im)
1815 double X0,Y0; /* points for filled graph and more*/
1818 /* draw 3d border */
1819 node = gfx_new_area (im->canvas, 0,im->yimg,
1821 2,2,im->graph_col[GRC_SHADEA]);
1822 gfx_add_point( node , im->ximg - 2, 2 );
1823 gfx_add_point( node , im->ximg, 0 );
1824 gfx_add_point( node , 0,0 );
1825 /* gfx_add_point( node , 0,im->yimg ); */
1827 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1828 im->ximg-2,im->yimg-2,
1830 im->graph_col[GRC_SHADEB]);
1831 gfx_add_point( node , im->ximg,0);
1832 gfx_add_point( node , im->ximg,im->yimg);
1833 gfx_add_point( node , 0,im->yimg);
1834 /* gfx_add_point( node , 0,im->yimg ); */
1837 if (im->draw_x_grid == 1 )
1840 if (im->draw_y_grid == 1){
1841 if(im->logarithmic){
1842 res = horizontal_log_grid(im);
1844 res = draw_horizontal_grid(im);
1847 /* dont draw horizontal grid if there is no min and max val */
1849 char *nodata = "No Data found";
1850 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1851 im->graph_col[GRC_FONT],
1852 im->text_prop[TEXT_PROP_AXIS].font,
1853 im->text_prop[TEXT_PROP_AXIS].size,
1854 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1859 /* yaxis description */
1860 if (im->canvas->imgformat != IF_PNG) {
1861 gfx_new_text( im->canvas,
1862 7, (im->yorigin - im->ysize/2),
1863 im->graph_col[GRC_FONT],
1864 im->text_prop[TEXT_PROP_AXIS].font,
1865 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1866 GFX_H_CENTER, GFX_V_CENTER,
1869 /* horrible hack until we can actually print vertically */
1872 int l=strlen(im->ylegend);
1874 for (n=0;n<strlen(im->ylegend);n++) {
1875 s[0]=im->ylegend[n];
1877 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1878 im->graph_col[GRC_FONT],
1879 im->text_prop[TEXT_PROP_AXIS].font,
1880 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1881 GFX_H_CENTER, GFX_V_CENTER,
1888 gfx_new_text( im->canvas,
1889 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1890 im->graph_col[GRC_FONT],
1891 im->text_prop[TEXT_PROP_TITLE].font,
1892 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1893 GFX_H_CENTER, GFX_V_CENTER,
1897 if( !(im->extra_flags & NOLEGEND) ) {
1898 for(i=0;i<im->gdes_c;i++){
1899 if(im->gdes[i].legend[0] =='\0')
1902 /* im->gdes[i].leg_y is the bottom of the legend */
1903 X0 = im->gdes[i].leg_x;
1904 Y0 = im->gdes[i].leg_y;
1906 if ( im->gdes[i].gf != GF_GPRINT
1907 && im->gdes[i].gf != GF_COMMENT) {
1910 boxH = gfx_get_text_width(im->canvas, 0,
1911 im->text_prop[TEXT_PROP_AXIS].font,
1912 im->text_prop[TEXT_PROP_AXIS].size,
1913 im->tabwidth,"M") * 1.25;
1916 node = gfx_new_area(im->canvas,
1921 gfx_add_point ( node, X0+boxH, Y0-boxV );
1922 node = gfx_new_line(im->canvas,
1925 gfx_add_point(node,X0+boxH,Y0);
1926 gfx_add_point(node,X0+boxH,Y0-boxV);
1927 gfx_close_path(node);
1928 X0 += boxH / 1.25 * 2;
1930 gfx_new_text ( im->canvas, X0, Y0,
1931 im->graph_col[GRC_FONT],
1932 im->text_prop[TEXT_PROP_AXIS].font,
1933 im->text_prop[TEXT_PROP_AXIS].size,
1934 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1935 im->gdes[i].legend );
1941 /*****************************************************
1942 * lazy check make sure we rely need to create this graph
1943 *****************************************************/
1945 int lazy_check(image_desc_t *im){
1948 struct stat imgstat;
1950 if (im->lazy == 0) return 0; /* no lazy option */
1951 if (stat(im->graphfile,&imgstat) != 0)
1952 return 0; /* can't stat */
1953 /* one pixel in the existing graph is more then what we would
1955 if (time(NULL) - imgstat.st_mtime >
1956 (im->end - im->start) / im->xsize)
1958 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1959 return 0; /* the file does not exist */
1960 switch (im->canvas->imgformat) {
1962 size = PngSize(fd,&(im->ximg),&(im->yimg));
1972 pie_part(image_desc_t *im, gfx_color_t color,
1973 double PieCenterX, double PieCenterY, double Radius,
1974 double startangle, double endangle)
1978 double step=M_PI/50; /* Number of iterations for the circle;
1979 ** 10 is definitely too low, more than
1980 ** 50 seems to be overkill
1983 /* Strange but true: we have to work clockwise or else
1984 ** anti aliasing nor transparency don't work.
1986 ** This test is here to make sure we do it right, also
1987 ** this makes the for...next loop more easy to implement.
1988 ** The return will occur if the user enters a negative number
1989 ** (which shouldn't be done according to the specs) or if the
1990 ** programmers do something wrong (which, as we all know, never
1991 ** happens anyway :)
1993 if (endangle<startangle) return;
1995 /* Hidden feature: Radius decreases each full circle */
1997 while (angle>=2*M_PI) {
2002 node=gfx_new_area(im->canvas,
2003 PieCenterX+sin(startangle)*Radius,
2004 PieCenterY-cos(startangle)*Radius,
2007 PieCenterX+sin(endangle)*Radius,
2008 PieCenterY-cos(endangle)*Radius,
2010 for (angle=endangle;angle-startangle>=step;angle-=step) {
2012 PieCenterX+sin(angle)*Radius,
2013 PieCenterY-cos(angle)*Radius );
2018 graph_size_location(image_desc_t *im, int elements, int piechart )
2020 /* The actual size of the image to draw is determined from
2021 ** several sources. The size given on the command line is
2022 ** the graph area but we need more as we have to draw labels
2023 ** and other things outside the graph area
2026 /* +-+-------------------------------------------+
2027 ** |l|.................title.....................|
2028 ** |e+--+-------------------------------+--------+
2031 ** |l| l| main graph area | chart |
2034 ** |r+--+-------------------------------+--------+
2035 ** |e| | x-axis labels | |
2036 ** |v+--+-------------------------------+--------+
2037 ** | |..............legends......................|
2038 ** +-+-------------------------------------------+
2040 int Xvertical=0, Yvertical=0,
2041 Xtitle =0, Ytitle =0,
2042 Xylabel =0, Yylabel =0,
2045 Xxlabel =0, Yxlabel =0,
2047 Xlegend =0, Ylegend =0,
2049 Xspacing =10, Yspacing =10;
2051 if (im->ylegend[0] != '\0') {
2052 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2053 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2056 if (im->title[0] != '\0') {
2057 /* The title is placed "inbetween" two text lines so it
2058 ** automatically has some vertical spacing. The horizontal
2059 ** spacing is added here, on each side.
2061 Xtitle = gfx_get_text_width(im->canvas, 0,
2062 im->text_prop[TEXT_PROP_TITLE].font,
2063 im->text_prop[TEXT_PROP_TITLE].size,
2065 im->title) + 2*Xspacing;
2066 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2072 if (im->draw_x_grid) {
2074 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2076 if (im->draw_y_grid) {
2077 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2083 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2088 /* Now calculate the total size. Insert some spacing where
2089 desired. im->xorigin and im->yorigin need to correspond
2090 with the lower left corner of the main graph area or, if
2091 this one is not set, the imaginary box surrounding the
2094 /* The legend width cannot yet be determined, as a result we
2095 ** have problems adjusting the image to it. For now, we just
2096 ** forget about it at all; the legend will have to fit in the
2097 ** size already allocated.
2099 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2100 if (Xmain) im->ximg += Xspacing;
2101 if (Xpie) im->ximg += Xspacing;
2102 im->xorigin = Xspacing + Xylabel;
2103 if (Xtitle > im->ximg) im->ximg = Xtitle;
2105 im->ximg += Xvertical;
2106 im->xorigin += Xvertical;
2110 /* The vertical size is interesting... we need to compare
2111 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2112 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2113 ** start even thinking about Ylegend.
2115 ** Do it in three portions: First calculate the inner part,
2116 ** then do the legend, then adjust the total height of the img.
2119 /* reserve space for main and/or pie */
2120 im->yimg = Ymain + Yxlabel;
2121 if (im->yimg < Ypie) im->yimg = Ypie;
2122 im->yorigin = im->yimg - Yxlabel;
2123 /* reserve space for the title *or* some padding above the graph */
2126 im->yorigin += Ytitle;
2128 im->yimg += Yspacing;
2129 im->yorigin += Yspacing;
2131 /* reserve space for padding below the graph */
2132 im->yimg += Yspacing;
2135 /* Determine where to place the legends onto the image.
2136 ** Adjust im->yimg to match the space requirements.
2138 if(leg_place(im)==-1)
2141 /* last of three steps: check total height of image */
2142 if (im->yimg < Yvertical) im->yimg = Yvertical;
2145 if (Xlegend > im->ximg) {
2147 /* reposition Pie */
2150 /* The pie is placed in the upper right hand corner,
2151 ** just below the title (if any) and with sufficient
2155 im->pie_x = im->ximg - Xspacing - Xpie/2;
2156 im->pie_y = im->yorigin-Ymain+Ypie/2;
2158 im->pie_x = im->ximg/2;
2159 im->pie_y = im->yorigin-Ypie/2;
2165 /* draw that picture thing ... */
2167 graph_paint(image_desc_t *im, char ***calcpr)
2170 int lazy = lazy_check(im);
2172 double PieStart=0.0;
2176 double areazero = 0.0;
2177 enum gf_en stack_gf = GF_PRINT;
2178 graph_desc_t *lastgdes = NULL;
2180 /* if we are lazy and there is nothing to PRINT ... quit now */
2181 if (lazy && im->prt_c==0) return 0;
2183 /* pull the data from the rrd files ... */
2185 if(data_fetch(im)==-1)
2188 /* evaluate VDEF and CDEF operations ... */
2189 if(data_calc(im)==-1)
2192 /* check if we need to draw a piechart */
2193 for(i=0;i<im->gdes_c;i++){
2194 if (im->gdes[i].gf == GF_PART) {
2200 /* calculate and PRINT and GPRINT definitions. We have to do it at
2201 * this point because it will affect the length of the legends
2202 * if there are no graph elements we stop here ...
2203 * if we are lazy, try to quit ...
2205 i=print_calc(im,calcpr);
2207 if(((i==0)&&(piechart==0)) || lazy) return 0;
2209 /* If there's only the pie chart to draw, signal this */
2210 if (i==0) piechart=2;
2212 /* get actual drawing data and find min and max values*/
2213 if(data_proc(im)==-1)
2216 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2218 if(!im->rigid && ! im->logarithmic)
2219 expand_range(im); /* make sure the upper and lower limit are
2222 if (!calc_horizontal_grid(im))
2227 /**************************************************************
2228 *** Calculating sizes and locations became a bit confusing ***
2229 *** so I moved this into a separate function. ***
2230 **************************************************************/
2231 if(graph_size_location(im,i,piechart)==-1)
2234 /* the actual graph is created by going through the individual
2235 graph elements and then drawing them */
2237 node=gfx_new_area ( im->canvas,
2241 im->graph_col[GRC_BACK]);
2243 gfx_add_point(node,0, im->yimg);
2245 if (piechart != 2) {
2246 node=gfx_new_area ( im->canvas,
2247 im->xorigin, im->yorigin,
2248 im->xorigin + im->xsize, im->yorigin,
2249 im->xorigin + im->xsize, im->yorigin-im->ysize,
2250 im->graph_col[GRC_CANVAS]);
2252 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2254 if (im->minval > 0.0)
2255 areazero = im->minval;
2256 if (im->maxval < 0.0)
2257 areazero = im->maxval;
2263 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2266 for(i=0;i<im->gdes_c;i++){
2267 switch(im->gdes[i].gf){
2278 for (ii = 0; ii < im->xsize; ii++)
2280 if (!isnan(im->gdes[i].p_data[ii]) &&
2281 im->gdes[i].p_data[ii] > 0.0)
2283 /* generate a tick */
2284 gfx_new_line(im->canvas, im -> xorigin + ii,
2285 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2289 im -> gdes[i].col );
2295 stack_gf = im->gdes[i].gf;
2297 /* fix data points at oo and -oo */
2298 for(ii=0;ii<im->xsize;ii++){
2299 if (isinf(im->gdes[i].p_data[ii])){
2300 if (im->gdes[i].p_data[ii] > 0) {
2301 im->gdes[i].p_data[ii] = im->maxval ;
2303 im->gdes[i].p_data[ii] = im->minval ;
2309 if (im->gdes[i].col != 0x0){
2310 /* GF_LINE and friend */
2311 if(stack_gf == GF_LINE ){
2313 for(ii=1;ii<im->xsize;ii++){
2314 if ( ! isnan(im->gdes[i].p_data[ii-1])
2315 && ! isnan(im->gdes[i].p_data[ii])){
2317 node = gfx_new_line(im->canvas,
2318 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2319 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2320 im->gdes[i].linewidth,
2323 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2332 for(ii=1;ii<im->xsize;ii++){
2334 if ( ! isnan(im->gdes[i].p_data[ii-1])
2335 && ! isnan(im->gdes[i].p_data[ii])){
2338 if (im->gdes[i].gf == GF_STACK) {
2339 ybase = ytr(im,lastgdes->p_data[ii-1]);
2341 ybase = ytr(im,areazero);
2344 node = gfx_new_area(im->canvas,
2345 ii-1+im->xorigin,ybase,
2346 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2347 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2351 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2355 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2356 /* GF_AREA STACK type*/
2357 if (im->gdes[i].gf == GF_STACK ) {
2359 for (iii=ii-1;iii>area_start;iii--){
2360 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2363 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2368 } /* else GF_LINE */
2369 } /* if color != 0x0 */
2370 /* make sure we do not run into trouble when stacking on NaN */
2371 for(ii=0;ii<im->xsize;ii++){
2372 if (isnan(im->gdes[i].p_data[ii])) {
2375 ybase = ytr(im,lastgdes->p_data[ii-1]);
2377 if (isnan(ybase) || !lastgdes ){
2378 ybase = ytr(im,areazero);
2380 im->gdes[i].p_data[ii] = ybase;
2383 lastgdes = &(im->gdes[i]);
2386 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2387 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2389 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2390 pie_part(im,im->gdes[i].col,
2391 im->pie_x,im->pie_y,im->piesize*0.4,
2392 M_PI*2.0*PieStart/100.0,
2393 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2394 PieStart += im->gdes[i].yrule;
2403 /* grid_paint also does the text */
2406 /* the RULES are the last thing to paint ... */
2407 for(i=0;i<im->gdes_c;i++){
2409 switch(im->gdes[i].gf){
2411 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2412 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2414 if(im->gdes[i].yrule >= im->minval
2415 && im->gdes[i].yrule <= im->maxval)
2416 gfx_new_line(im->canvas,
2417 im->xorigin,ytr(im,im->gdes[i].yrule),
2418 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2419 1.0,im->gdes[i].col);
2422 if(im->gdes[i].xrule == 0) { /* fetch variable */
2423 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2425 if(im->gdes[i].xrule >= im->start
2426 && im->gdes[i].xrule <= im->end)
2427 gfx_new_line(im->canvas,
2428 xtr(im,im->gdes[i].xrule),im->yorigin,
2429 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2430 1.0,im->gdes[i].col);
2438 if (strcmp(im->graphfile,"-")==0) {
2440 /* Change translation mode for stdout to BINARY */
2441 _setmode( _fileno( stdout ), O_BINARY );
2445 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2446 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2451 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2452 if (strcmp(im->graphfile,"-") != 0)
2458 /*****************************************************
2460 *****************************************************/
2463 gdes_alloc(image_desc_t *im){
2465 long def_step = (im->end-im->start)/im->xsize;
2467 if (im->step > def_step) /* step can be increassed ... no decreassed */
2468 def_step = im->step;
2472 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2473 * sizeof(graph_desc_t)))==NULL){
2474 rrd_set_error("realloc graph_descs");
2479 im->gdes[im->gdes_c-1].step=def_step;
2480 im->gdes[im->gdes_c-1].start=im->start;
2481 im->gdes[im->gdes_c-1].end=im->end;
2482 im->gdes[im->gdes_c-1].vname[0]='\0';
2483 im->gdes[im->gdes_c-1].data=NULL;
2484 im->gdes[im->gdes_c-1].ds_namv=NULL;
2485 im->gdes[im->gdes_c-1].data_first=0;
2486 im->gdes[im->gdes_c-1].p_data=NULL;
2487 im->gdes[im->gdes_c-1].rpnp=NULL;
2488 im->gdes[im->gdes_c-1].col = 0x0;
2489 im->gdes[im->gdes_c-1].legend[0]='\0';
2490 im->gdes[im->gdes_c-1].rrd[0]='\0';
2491 im->gdes[im->gdes_c-1].ds=-1;
2492 im->gdes[im->gdes_c-1].p_data=NULL;
2496 /* copies input untill the first unescaped colon is found
2497 or until input ends. backslashes have to be escaped as well */
2499 scan_for_col(char *input, int len, char *output)
2504 input[inp] != ':' &&
2507 if (input[inp] == '\\' &&
2508 input[inp+1] != '\0' &&
2509 (input[inp+1] == '\\' ||
2510 input[inp+1] == ':')){
2511 output[outp++] = input[++inp];
2514 output[outp++] = input[inp];
2517 output[outp] = '\0';
2521 /* Some surgery done on this function, it became ridiculously big.
2523 ** - initializing now in rrd_graph_init()
2524 ** - options parsing now in rrd_graph_options()
2525 ** - script parsing now in rrd_graph_script()
2528 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2535 #ifdef HAVE_SETLOCALE
2536 setlocale(LC_TIME,"");
2540 rrd_graph_init(&im);
2542 rrd_graph_options(argc,argv,&im);
2543 if (rrd_test_error()) return -1;
2545 if (strlen(argv[optind])>=MAXPATH) {
2546 rrd_set_error("filename (including path) too long");
2549 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2550 im.graphfile[MAXPATH-1]='\0';
2552 rrd_graph_script(argc,argv,&im);
2553 if (rrd_test_error()) return -1;
2555 /* Everything is now read and the actual work can start */
2558 if (graph_paint(&im,prdata)==-1){
2563 /* The image is generated and needs to be output.
2564 ** Also, if needed, print a line with information about the image.
2572 /* maybe prdata is not allocated yet ... lets do it now */
2573 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2574 rrd_set_error("malloc imginfo");
2578 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2580 rrd_set_error("malloc imginfo");
2583 filename=im.graphfile+strlen(im.graphfile);
2584 while(filename > im.graphfile) {
2585 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2589 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2596 rrd_graph_init(image_desc_t *im)
2600 im->xlab_user.minsec = -1;
2606 im->ylegend[0] = '\0';
2607 im->title[0] = '\0';
2610 im->unitsexponent= 9999;
2616 im->logarithmic = 0;
2617 im->ygridstep = DNAN;
2618 im->draw_x_grid = 1;
2619 im->draw_y_grid = 1;
2624 im->canvas = gfx_new_canvas();
2625 im->grid_dash_on = 1;
2626 im->grid_dash_off = 1;
2628 for(i=0;i<DIM(graph_col);i++)
2629 im->graph_col[i]=graph_col[i];
2631 for(i=0;i<DIM(text_prop);i++){
2632 im->text_prop[i].size = text_prop[i].size;
2633 im->text_prop[i].font = text_prop[i].font;
2638 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2641 char *parsetime_error = NULL;
2642 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2643 time_t start_tmp=0,end_tmp=0;
2645 struct time_value start_tv, end_tv;
2648 parsetime("end-24h", &start_tv);
2649 parsetime("now", &end_tv);
2652 static struct option long_options[] =
2654 {"start", required_argument, 0, 's'},
2655 {"end", required_argument, 0, 'e'},
2656 {"x-grid", required_argument, 0, 'x'},
2657 {"y-grid", required_argument, 0, 'y'},
2658 {"vertical-label",required_argument,0,'v'},
2659 {"width", required_argument, 0, 'w'},
2660 {"height", required_argument, 0, 'h'},
2661 {"interlaced", no_argument, 0, 'i'},
2662 {"upper-limit",required_argument, 0, 'u'},
2663 {"lower-limit",required_argument, 0, 'l'},
2664 {"rigid", no_argument, 0, 'r'},
2665 {"base", required_argument, 0, 'b'},
2666 {"logarithmic",no_argument, 0, 'o'},
2667 {"color", required_argument, 0, 'c'},
2668 {"font", required_argument, 0, 'n'},
2669 {"title", required_argument, 0, 't'},
2670 {"imginfo", required_argument, 0, 'f'},
2671 {"imgformat", required_argument, 0, 'a'},
2672 {"lazy", no_argument, 0, 'z'},
2673 {"zoom", required_argument, 0, 'm'},
2674 {"no-legend", no_argument, 0, 'g'},
2675 {"alt-y-grid", no_argument, 0, 257 },
2676 {"alt-autoscale", no_argument, 0, 258 },
2677 {"alt-autoscale-max", no_argument, 0, 259 },
2678 {"units-exponent",required_argument, 0, 260},
2679 {"step", required_argument, 0, 261},
2680 {"no-gridfit", no_argument, 0, 262},
2682 int option_index = 0;
2686 opt = getopt_long(argc, argv,
2687 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2688 long_options, &option_index);
2695 im->extra_flags |= ALTYGRID;
2698 im->extra_flags |= ALTAUTOSCALE;
2701 im->extra_flags |= ALTAUTOSCALE_MAX;
2704 im->extra_flags |= NOLEGEND;
2707 im->unitsexponent = atoi(optarg);
2710 im->step = atoi(optarg);
2716 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2717 rrd_set_error( "start time: %s", parsetime_error );
2722 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2723 rrd_set_error( "end time: %s", parsetime_error );
2728 if(strcmp(optarg,"none") == 0){
2734 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2736 &im->xlab_user.gridst,
2738 &im->xlab_user.mgridst,
2740 &im->xlab_user.labst,
2741 &im->xlab_user.precis,
2742 &stroff) == 7 && stroff != 0){
2743 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2744 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2745 rrd_set_error("unknown keyword %s",scan_gtm);
2747 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2748 rrd_set_error("unknown keyword %s",scan_mtm);
2750 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2751 rrd_set_error("unknown keyword %s",scan_ltm);
2754 im->xlab_user.minsec = 1;
2755 im->xlab_user.stst = im->xlab_form;
2757 rrd_set_error("invalid x-grid format");
2763 if(strcmp(optarg,"none") == 0){
2771 &im->ylabfact) == 2) {
2772 if(im->ygridstep<=0){
2773 rrd_set_error("grid step must be > 0");
2775 } else if (im->ylabfact < 1){
2776 rrd_set_error("label factor must be > 0");
2780 rrd_set_error("invalid y-grid format");
2785 strncpy(im->ylegend,optarg,150);
2786 im->ylegend[150]='\0';
2789 im->maxval = atof(optarg);
2792 im->minval = atof(optarg);
2795 im->base = atol(optarg);
2796 if(im->base != 1024 && im->base != 1000 ){
2797 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2802 long_tmp = atol(optarg);
2803 if (long_tmp < 10) {
2804 rrd_set_error("width below 10 pixels");
2807 im->xsize = long_tmp;
2810 long_tmp = atol(optarg);
2811 if (long_tmp < 10) {
2812 rrd_set_error("height below 10 pixels");
2815 im->ysize = long_tmp;
2818 im->canvas->interlaced = 1;
2824 im->imginfo = optarg;
2827 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2828 rrd_set_error("unsupported graphics format '%s'",optarg);
2836 im->logarithmic = 1;
2837 if (isnan(im->minval))
2843 col_nam,&color) == 2){
2845 if((ci=grc_conv(col_nam)) != -1){
2846 im->graph_col[ci]=color;
2848 rrd_set_error("invalid color name '%s'",col_nam);
2851 rrd_set_error("invalid color def format");
2856 /* originally this used char *prop = "" and
2857 ** char *font = "dummy" however this results
2858 ** in a SEG fault, at least on RH7.1
2860 ** The current implementation isn't proper
2861 ** either, font is never freed and prop uses
2862 ** a fixed width string
2871 prop,&size,font) == 3){
2873 if((sindex=text_prop_conv(prop)) != -1){
2874 im->text_prop[sindex].size=size;
2875 im->text_prop[sindex].font=font;
2876 if (sindex==0) { /* the default */
2877 im->text_prop[TEXT_PROP_TITLE].size=size;
2878 im->text_prop[TEXT_PROP_TITLE].font=font;
2879 im->text_prop[TEXT_PROP_AXIS].size=size;
2880 im->text_prop[TEXT_PROP_AXIS].font=font;
2881 im->text_prop[TEXT_PROP_UNIT].size=size;
2882 im->text_prop[TEXT_PROP_UNIT].font=font;
2883 im->text_prop[TEXT_PROP_LEGEND].size=size;
2884 im->text_prop[TEXT_PROP_LEGEND].font=font;
2887 rrd_set_error("invalid fonttag '%s'",prop);
2891 rrd_set_error("invalid text property format");
2897 im->canvas->zoom = atof(optarg);
2898 if (im->canvas->zoom <= 0.0) {
2899 rrd_set_error("zoom factor must be > 0");
2904 strncpy(im->title,optarg,150);
2905 im->title[150]='\0';
2910 rrd_set_error("unknown option '%c'", optopt);
2912 rrd_set_error("unknown option '%s'",argv[optind-1]);
2917 if (optind >= argc) {
2918 rrd_set_error("missing filename");
2922 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2923 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2927 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2928 /* error string is set in parsetime.c */
2932 if (start_tmp < 3600*24*365*10){
2933 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2937 if (end_tmp < start_tmp) {
2938 rrd_set_error("start (%ld) should be less than end (%ld)",
2939 start_tmp, end_tmp);
2943 im->start = start_tmp;
2948 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2952 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2954 for (i=optind+1;i<argc;i++) {
2959 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2964 /* Each command is one element from *argv[], we call this "line".
2966 ** Each command defines the most current gdes inside struct im.
2967 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2970 gdp=&im->gdes[im->gdes_c-1];
2973 /* function:newvname=string[:ds-name:CF] for xDEF
2974 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2975 ** function:vname#color[:num[:string]] for TICK
2976 ** function:vname-or-num#color[:string] for xRULE,PART
2977 ** function:vname:CF:string for xPRINT
2978 ** function:string for COMMENT
2982 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2984 rrd_set_error("Cannot parse function in line: %s",line);
2988 if(sscanf(funcname,"LINE%lf",&linewidth)){
2989 im->gdes[im->gdes_c-1].gf = GF_LINE;
2990 im->gdes[im->gdes_c-1].linewidth = linewidth;
2992 if ((gdp->gf=gf_conv(funcname))==-1) {
2993 rrd_set_error("'%s' is not a valid function name",funcname);
2999 /* If the error string is set, we exit at the end of the switch */
3002 if (rrd_graph_legend(gdp,&line[argstart])==0)
3003 rrd_set_error("Cannot parse comment in line: %s",line);
3009 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
3010 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
3012 rrd_set_error("Cannot parse name or num in line: %s",line);
3019 } else if (!rrd_graph_check_vname(im,vname,line)) {
3023 } else break; /* exit due to wrong vname */
3024 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
3026 if (strlen(&line[argstart])!=0) {
3027 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3028 rrd_set_error("Cannot parse comment in line: %s",line);
3033 rrd_set_error("STACK must follow another graphing element");
3041 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
3043 rrd_set_error("Cannot parse vname in line: %s",line);
3044 else if (rrd_graph_check_vname(im,vname,line))
3045 rrd_set_error("Undefined vname '%s' in line: %s",line);
3047 k=rrd_graph_color(im,&line[argstart],line,1);
3048 if (rrd_test_error()) break;
3049 argstart=argstart+j+k;
3050 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
3052 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
3055 if (strlen(&line[argstart])!=0)
3056 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3057 rrd_set_error("Cannot parse legend in line: %s",line);
3063 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3065 rrd_set_error("Cannot parse vname in line: '%s'",line);
3069 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3071 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3073 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3074 #define VIDX im->gdes[gdp->vidx]
3076 case -1: /* looks CF but is not really CF */
3077 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3079 case 0: /* CF present and correct */
3080 if (VIDX.gf == GF_VDEF)
3081 rrd_set_error("Don't use CF when printing VDEF");
3084 case 1: /* CF not present */
3085 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3086 else rrd_set_error("Printing DEF or CDEF needs CF");
3089 rrd_set_error("Oops, bug in GPRINT scanning");
3092 if (rrd_test_error()) break;
3094 if (strlen(&line[argstart])!=0) {
3095 if (rrd_graph_legend(gdp,&line[argstart])==0)
3096 rrd_set_error("Cannot parse legend in line: %s",line);
3097 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3098 strcpy(gdp->format, gdp->legend);
3104 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
3106 rrd_set_error("Could not parse line: %s",line);
3109 if (find_var(im,gdp->vname)!=-1) {
3110 rrd_set_error("Variable '%s' in line '%s' already in use\n",
3117 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3119 sscanf(&line[argstart],
3120 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3121 gdp->ds_nam, symname, &j, &k);
3122 if ((j==0)||(k!=0)) {
3123 rrd_set_error("Cannot parse DS or CF in '%s'",line);
3126 rrd_graph_check_CF(im,symname,line);
3130 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3132 rrd_set_error("Cannot parse vname in line '%s'",line);
3136 if (rrd_graph_check_vname(im,vname,line)) return;
3137 if ( im->gdes[gdp->vidx].gf != GF_DEF
3138 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3139 rrd_set_error("variable '%s' not DEF nor "
3140 "CDEF in VDEF '%s'", vname,gdp->vname);
3143 vdef_parse(gdp,&line[argstart+strstart]);
3146 if (strstr(&line[argstart],":")!=NULL) {
3147 rrd_set_error("Error in RPN, line: %s",line);
3150 if ((gdp->rpnp = rpn_parse(
3155 rrd_set_error("invalid rpn expression in: %s",line);
3160 default: rrd_set_error("Big oops");
3162 if (rrd_test_error()) {
3169 rrd_set_error("can't make a graph without contents");
3170 im_free(im); /* ??? is this set ??? */
3175 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3177 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3178 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3184 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3187 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3189 color=strstr(var,"#");
3192 rrd_set_error("Found no color in %s",err);
3201 rest=strstr(color,":");
3209 sscanf(color,"#%6lx%n",&col,&n);
3210 col = (col << 8) + 0xff /* shift left by 8 */;
3211 if (n!=7) rrd_set_error("Color problem in %s",err);
3214 sscanf(color,"#%8lx%n",&col,&n);
3217 rrd_set_error("Color problem in %s",err);
3219 if (rrd_test_error()) return 0;
3225 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3227 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3228 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3234 rrd_graph_legend(graph_desc_t *gdp, char *line)
3238 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3240 return (strlen(&line[i])==0);
3244 int bad_format(char *fmt) {
3249 while (*ptr != '\0') {
3250 if (*ptr == '%') {ptr++;
3251 if (*ptr == '\0') return 1;
3252 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3255 if (*ptr == '\0') return 1;
3259 if (*ptr == '\0') return 1;
3260 if (*ptr == 'e' || *ptr == 'f') {
3262 } else { return 1; }
3264 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3273 vdef_parse(gdes,str)
3274 struct graph_desc_t *gdes;
3277 /* A VDEF currently is either "func" or "param,func"
3278 * so the parsing is rather simple. Change if needed.
3285 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3286 if (n==strlen(str)) { /* matched */
3290 sscanf(str,"%29[A-Z]%n",func,&n);
3291 if (n==strlen(str)) { /* matched */
3294 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3301 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3302 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3303 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3304 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3305 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3306 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3307 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3309 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3316 switch (gdes->vf.op) {
3318 if (isnan(param)) { /* no parameter given */
3319 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3325 if (param>=0.0 && param<=100.0) {
3326 gdes->vf.param = param;
3327 gdes->vf.val = DNAN; /* undefined */
3328 gdes->vf.when = 0; /* undefined */
3330 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3344 gdes->vf.param = DNAN;
3345 gdes->vf.val = DNAN;
3348 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3363 graph_desc_t *src,*dst;
3367 dst = &im->gdes[gdi];
3368 src = &im->gdes[dst->vidx];
3369 data = src->data + src->ds;
3370 steps = (src->end - src->start) / src->step;
3373 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3380 switch (dst->vf.op) {
3381 case VDEF_PERCENT: {
3382 rrd_value_t * array;
3386 if ((array = malloc(steps*sizeof(double)))==NULL) {
3387 rrd_set_error("malloc VDEV_PERCENT");
3390 for (step=0;step < steps; step++) {
3391 array[step]=data[step*src->ds_cnt];
3393 qsort(array,step,sizeof(double),vdef_percent_compar);
3395 field = (steps-1)*dst->vf.param/100;
3396 dst->vf.val = array[field];
3397 dst->vf.when = 0; /* no time component */
3399 for(step=0;step<steps;step++)
3400 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3406 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3407 if (step == steps) {
3411 dst->vf.val = data[step*src->ds_cnt];
3412 dst->vf.when = src->start + (step+1)*src->step;
3414 while (step != steps) {
3415 if (finite(data[step*src->ds_cnt])) {
3416 if (data[step*src->ds_cnt] > dst->vf.val) {
3417 dst->vf.val = data[step*src->ds_cnt];
3418 dst->vf.when = src->start + (step+1)*src->step;
3425 case VDEF_AVERAGE: {
3428 for (step=0;step<steps;step++) {
3429 if (finite(data[step*src->ds_cnt])) {
3430 sum += data[step*src->ds_cnt];
3435 if (dst->vf.op == VDEF_TOTAL) {
3436 dst->vf.val = sum*src->step;
3437 dst->vf.when = cnt*src->step; /* not really "when" */
3439 dst->vf.val = sum/cnt;
3440 dst->vf.when = 0; /* no time component */
3450 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3451 if (step == steps) {
3455 dst->vf.val = data[step*src->ds_cnt];
3456 dst->vf.when = src->start + (step+1)*src->step;
3458 while (step != steps) {
3459 if (finite(data[step*src->ds_cnt])) {
3460 if (data[step*src->ds_cnt] < dst->vf.val) {
3461 dst->vf.val = data[step*src->ds_cnt];
3462 dst->vf.when = src->start + (step+1)*src->step;
3469 /* The time value returned here is one step before the
3470 * actual time value. This is the start of the first
3474 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3475 if (step == steps) { /* all entries were NaN */
3479 dst->vf.val = data[step*src->ds_cnt];
3480 dst->vf.when = src->start + step*src->step;
3484 /* The time value returned here is the
3485 * actual time value. This is the end of the last
3489 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3490 if (step < 0) { /* all entries were NaN */
3494 dst->vf.val = data[step*src->ds_cnt];
3495 dst->vf.when = src->start + (step+1)*src->step;
3502 /* NaN < -INF < finite_values < INF */
3504 vdef_percent_compar(a,b)
3507 /* Equality is not returned; this doesn't hurt except
3508 * (maybe) for a little performance.
3511 /* First catch NaN values. They are smallest */
3512 if (isnan( *(double *)a )) return -1;
3513 if (isnan( *(double *)b )) return 1;
3515 /* NaN doesn't reach this part so INF and -INF are extremes.
3516 * The sign from isinf() is compatible with the sign we return
3518 if (isinf( *(double *)a )) return isinf( *(double *)a );
3519 if (isinf( *(double *)b )) return isinf( *(double *)b );
3521 /* If we reach this, both values must be finite */
3522 if ( *(double *)a < *(double *)b ) return -1; else return 1;