1 /****************************************************************************
2 * RRDtool 1.2rc6 Copyright by Tobi Oetiker, 1997-2005
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
12 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
25 #include "rrd_graph.h"
27 /* some constant definitions */
31 #ifndef RRD_DEFAULT_FONT
32 /* there is special code later to pick Cour.ttf when running on windows */
33 #define RRD_DEFAULT_FONT "VeraMono.ttf"
36 text_prop_t text_prop[] = {
37 { 9.0, RRD_DEFAULT_FONT }, /* default */
38 { 11.0, RRD_DEFAULT_FONT }, /* title */
39 { 8.0, RRD_DEFAULT_FONT }, /* axis */
40 { 9.0, RRD_DEFAULT_FONT }, /* unit */
41 { 9.0, RRD_DEFAULT_FONT } /* legend */
45 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
46 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
47 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
48 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
49 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
50 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
51 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
52 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
53 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
54 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
55 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
56 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
57 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
58 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
59 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
60 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
63 /* sensible logarithmic y label intervals ...
64 the first element of each row defines the possible starting points on the
65 y axis ... the other specify the */
67 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
68 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
69 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
70 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
71 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
73 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
74 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
76 /* sensible y label intervals ...*/
94 gfx_color_t graph_col[] = /* default colors */
95 { 0xFFFFFFFF, /* canvas */
96 0xF0F0F0FF, /* background */
97 0xD0D0D0FF, /* shade A */
98 0xA0A0A0FF, /* shade B */
99 0x90909080, /* grid */
100 0xE0505080, /* major grid */
101 0x000000FF, /* font */
102 0x802020FF, /* arrow */
103 0x202020FF /* axis */
110 # define DPRINT(x) (void)(printf x, printf("\n"))
116 /* initialize with xtr(im,0); */
118 xtr(image_desc_t *im,time_t mytime){
121 pixie = (double) im->xsize / (double)(im->end - im->start);
124 return (int)((double)im->xorigin
125 + pixie * ( mytime - im->start ) );
128 /* translate data values into y coordinates */
130 ytr(image_desc_t *im, double value){
135 pixie = (double) im->ysize / (im->maxval - im->minval);
137 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
139 } else if(!im->logarithmic) {
140 yval = im->yorigin - pixie * (value - im->minval);
142 if (value < im->minval) {
145 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
148 /* make sure we don't return anything too unreasonable. GD lib can
149 get terribly slow when drawing lines outside its scope. This is
150 especially problematic in connection with the rigid option */
152 /* keep yval as-is */
153 } else if (yval > im->yorigin) {
154 yval = im->yorigin+2;
155 } else if (yval < im->yorigin - im->ysize){
156 yval = im->yorigin - im->ysize - 2;
163 /* conversion function for symbolic entry names */
166 #define conv_if(VV,VVV) \
167 if (strcmp(#VV, string) == 0) return VVV ;
169 enum gf_en gf_conv(char *string){
171 conv_if(PRINT,GF_PRINT)
172 conv_if(GPRINT,GF_GPRINT)
173 conv_if(COMMENT,GF_COMMENT)
174 conv_if(HRULE,GF_HRULE)
175 conv_if(VRULE,GF_VRULE)
176 conv_if(LINE,GF_LINE)
177 conv_if(AREA,GF_AREA)
178 conv_if(STACK,GF_STACK)
179 conv_if(TICK,GF_TICK)
181 conv_if(CDEF,GF_CDEF)
182 conv_if(VDEF,GF_VDEF)
184 conv_if(PART,GF_PART)
186 conv_if(XPORT,GF_XPORT)
187 conv_if(SHIFT,GF_SHIFT)
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(ARROW,GRC_ARROW)
224 conv_if(AXIS,GRC_AXIS)
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)
243 im_free(image_desc_t *im)
247 if (im == NULL) return 0;
248 for(i=0;i<(unsigned)im->gdes_c;i++){
249 if (im->gdes[i].data_first){
250 /* careful here, because a single pointer can occur several times */
251 free (im->gdes[i].data);
252 if (im->gdes[i].ds_namv){
253 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
254 free(im->gdes[i].ds_namv[ii]);
255 free(im->gdes[i].ds_namv);
258 free (im->gdes[i].p_data);
259 free (im->gdes[i].rpnp);
262 gfx_destroy(im->canvas);
266 /* find SI magnitude symbol for the given number*/
269 image_desc_t *im, /* image description */
276 char *symbol[] = {"a", /* 10e-18 Atto */
277 "f", /* 10e-15 Femto */
278 "p", /* 10e-12 Pico */
279 "n", /* 10e-9 Nano */
280 "u", /* 10e-6 Micro */
281 "m", /* 10e-3 Milli */
286 "T", /* 10e12 Tera */
287 "P", /* 10e15 Peta */
293 if (*value == 0.0 || isnan(*value) ) {
297 sindex = floor(log(fabs(*value))/log((double)im->base));
298 *magfact = pow((double)im->base, (double)sindex);
299 (*value) /= (*magfact);
301 if ( sindex <= symbcenter && sindex >= -symbcenter) {
302 (*symb_ptr) = symbol[sindex+symbcenter];
310 /* find SI magnitude symbol for the numbers on the y-axis*/
313 image_desc_t *im /* image description */
317 char symbol[] = {'a', /* 10e-18 Atto */
318 'f', /* 10e-15 Femto */
319 'p', /* 10e-12 Pico */
320 'n', /* 10e-9 Nano */
321 'u', /* 10e-6 Micro */
322 'm', /* 10e-3 Milli */
327 'T', /* 10e12 Tera */
328 'P', /* 10e15 Peta */
334 if (im->unitsexponent != 9999) {
335 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
336 digits = floor(im->unitsexponent / 3);
338 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
340 im->magfact = pow((double)im->base , digits);
343 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
346 if ( ((digits+symbcenter) < sizeof(symbol)) &&
347 ((digits+symbcenter) >= 0) )
348 im->symbol = symbol[(int)digits+symbcenter];
353 /* move min and max values around to become sensible */
356 expand_range(image_desc_t *im)
358 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
359 600.0,500.0,400.0,300.0,250.0,
360 200.0,125.0,100.0,90.0,80.0,
361 75.0,70.0,60.0,50.0,40.0,30.0,
362 25.0,20.0,10.0,9.0,8.0,
363 7.0,6.0,5.0,4.0,3.5,3.0,
364 2.5,2.0,1.8,1.5,1.2,1.0,
365 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
367 double scaled_min,scaled_max;
374 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
375 im->minval,im->maxval,im->magfact);
378 if (isnan(im->ygridstep)){
379 if(im->extra_flags & ALTAUTOSCALE) {
380 /* measure the amplitude of the function. Make sure that
381 graph boundaries are slightly higher then max/min vals
382 so we can see amplitude on the graph */
385 delt = im->maxval - im->minval;
387 fact = 2.0 * pow(10.0,
388 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
390 adj = (fact - delt) * 0.55;
392 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
398 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
399 /* measure the amplitude of the function. Make sure that
400 graph boundaries are slightly higher than max vals
401 so we can see amplitude on the graph */
402 adj = (im->maxval - im->minval) * 0.1;
406 scaled_min = im->minval / im->magfact;
407 scaled_max = im->maxval / im->magfact;
409 for (i=1; sensiblevalues[i] > 0; i++){
410 if (sensiblevalues[i-1]>=scaled_min &&
411 sensiblevalues[i]<=scaled_min)
412 im->minval = sensiblevalues[i]*(im->magfact);
414 if (-sensiblevalues[i-1]<=scaled_min &&
415 -sensiblevalues[i]>=scaled_min)
416 im->minval = -sensiblevalues[i-1]*(im->magfact);
418 if (sensiblevalues[i-1] >= scaled_max &&
419 sensiblevalues[i] <= scaled_max)
420 im->maxval = sensiblevalues[i-1]*(im->magfact);
422 if (-sensiblevalues[i-1]<=scaled_max &&
423 -sensiblevalues[i] >=scaled_max)
424 im->maxval = -sensiblevalues[i]*(im->magfact);
428 /* adjust min and max to the grid definition if there is one */
429 im->minval = (double)im->ylabfact * im->ygridstep *
430 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
431 im->maxval = (double)im->ylabfact * im->ygridstep *
432 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
436 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
437 im->minval,im->maxval,im->magfact);
442 apply_gridfit(image_desc_t *im)
444 if (isnan(im->minval) || isnan(im->maxval))
447 if (im->logarithmic) {
448 double ya, yb, ypix, ypixfrac;
449 double log10_range = log10(im->maxval) - log10(im->minval);
450 ya = pow((double)10, floor(log10(im->minval)));
451 while (ya < im->minval)
454 return; /* don't have y=10^x gridline */
456 if (yb <= im->maxval) {
457 /* we have at least 2 y=10^x gridlines.
458 Make sure distance between them in pixels
459 are an integer by expanding im->maxval */
460 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
461 double factor = y_pixel_delta / floor(y_pixel_delta);
462 double new_log10_range = factor * log10_range;
463 double new_ymax_log10 = log10(im->minval) + new_log10_range;
464 im->maxval = pow(10, new_ymax_log10);
465 ytr(im, DNAN); /* reset precalc */
466 log10_range = log10(im->maxval) - log10(im->minval);
468 /* make sure first y=10^x gridline is located on
469 integer pixel position by moving scale slightly
470 downwards (sub-pixel movement) */
471 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
472 ypixfrac = ypix - floor(ypix);
473 if (ypixfrac > 0 && ypixfrac < 1) {
474 double yfrac = ypixfrac / im->ysize;
475 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
476 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
477 ytr(im, DNAN); /* reset precalc */
480 /* Make sure we have an integer pixel distance between
481 each minor gridline */
482 double ypos1 = ytr(im, im->minval);
483 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
484 double y_pixel_delta = ypos1 - ypos2;
485 double factor = y_pixel_delta / floor(y_pixel_delta);
486 double new_range = factor * (im->maxval - im->minval);
487 double gridstep = im->ygrid_scale.gridstep;
488 double minor_y, minor_y_px, minor_y_px_frac;
489 im->maxval = im->minval + new_range;
490 ytr(im, DNAN); /* reset precalc */
491 /* make sure first minor gridline is on integer pixel y coord */
492 minor_y = gridstep * floor(im->minval / gridstep);
493 while (minor_y < im->minval)
495 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
496 minor_y_px_frac = minor_y_px - floor(minor_y_px);
497 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
498 double yfrac = minor_y_px_frac / im->ysize;
499 double range = im->maxval - im->minval;
500 im->minval = im->minval - yfrac * range;
501 im->maxval = im->maxval - yfrac * range;
502 ytr(im, DNAN); /* reset precalc */
504 calc_horizontal_grid(im); /* recalc with changed im->maxval */
508 /* reduce data reimplementation by Alex */
512 enum cf_en cf, /* which consolidation function ?*/
513 unsigned long cur_step, /* step the data currently is in */
514 time_t *start, /* start, end and step as requested ... */
515 time_t *end, /* ... by the application will be ... */
516 unsigned long *step, /* ... adjusted to represent reality */
517 unsigned long *ds_cnt, /* number of data sources in file */
518 rrd_value_t **data) /* two dimensional array containing the data */
520 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
521 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
522 rrd_value_t *srcptr,*dstptr;
524 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
527 row_cnt = ((*end)-(*start))/cur_step;
533 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
534 row_cnt,reduce_factor,*start,*end,cur_step);
535 for (col=0;col<row_cnt;col++) {
536 printf("time %10lu: ",*start+(col+1)*cur_step);
537 for (i=0;i<*ds_cnt;i++)
538 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
543 /* We have to combine [reduce_factor] rows of the source
544 ** into one row for the destination. Doing this we also
545 ** need to take care to combine the correct rows. First
546 ** alter the start and end time so that they are multiples
547 ** of the new step time. We cannot reduce the amount of
548 ** time so we have to move the end towards the future and
549 ** the start towards the past.
551 end_offset = (*end) % (*step);
552 start_offset = (*start) % (*step);
554 /* If there is a start offset (which cannot be more than
555 ** one destination row), skip the appropriate number of
556 ** source rows and one destination row. The appropriate
557 ** number is what we do know (start_offset/cur_step) of
558 ** the new interval (*step/cur_step aka reduce_factor).
561 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
562 printf("row_cnt before: %lu\n",row_cnt);
565 (*start) = (*start)-start_offset;
566 skiprows=reduce_factor-start_offset/cur_step;
567 srcptr+=skiprows* *ds_cnt;
568 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
572 printf("row_cnt between: %lu\n",row_cnt);
575 /* At the end we have some rows that are not going to be
576 ** used, the amount is end_offset/cur_step
579 (*end) = (*end)-end_offset+(*step);
580 skiprows = end_offset/cur_step;
584 printf("row_cnt after: %lu\n",row_cnt);
587 /* Sanity check: row_cnt should be multiple of reduce_factor */
588 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
590 if (row_cnt%reduce_factor) {
591 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
592 row_cnt,reduce_factor);
593 printf("BUG in reduce_data()\n");
597 /* Now combine reduce_factor intervals at a time
598 ** into one interval for the destination.
601 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
602 for (col=0;col<(*ds_cnt);col++) {
603 rrd_value_t newval=DNAN;
604 unsigned long validval=0;
606 for (i=0;i<reduce_factor;i++) {
607 if (isnan(srcptr[i*(*ds_cnt)+col])) {
611 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
619 newval += srcptr[i*(*ds_cnt)+col];
622 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
625 /* an interval contains a failure if any subintervals contained a failure */
627 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
630 newval = srcptr[i*(*ds_cnt)+col];
635 if (validval == 0){newval = DNAN;} else{
653 srcptr+=(*ds_cnt)*reduce_factor;
654 row_cnt-=reduce_factor;
656 /* If we had to alter the endtime, we didn't have enough
657 ** source rows to fill the last row. Fill it with NaN.
659 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
661 row_cnt = ((*end)-(*start))/ *step;
663 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
664 row_cnt,*start,*end,*step);
665 for (col=0;col<row_cnt;col++) {
666 printf("time %10lu: ",*start+(col+1)*(*step));
667 for (i=0;i<*ds_cnt;i++)
668 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
675 /* get the data required for the graphs from the
679 data_fetch(image_desc_t *im )
684 /* pull the data from the log files ... */
685 for (i=0;i< (int)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 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
698 && (im->gdes[i].start == im->gdes[ii].start)
699 && (im->gdes[i].end == im->gdes[ii].end)
700 && (im->gdes[i].step == im->gdes[ii].step)) {
701 /* OK, the data is already there.
702 ** Just copy the header portion
704 im->gdes[i].start = im->gdes[ii].start;
705 im->gdes[i].end = im->gdes[ii].end;
706 im->gdes[i].step = im->gdes[ii].step;
707 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
708 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
709 im->gdes[i].data = im->gdes[ii].data;
710 im->gdes[i].data_first = 0;
717 unsigned long ft_step = im->gdes[i].step ;
719 if((rrd_fetch_fn(im->gdes[i].rrd,
725 &im->gdes[i].ds_namv,
726 &im->gdes[i].data)) == -1){
729 im->gdes[i].data_first = 1;
730 im->gdes[i].step = im->step;
732 if (ft_step < im->gdes[i].step) {
733 reduce_data(im->gdes[i].cf_reduce,
741 im->gdes[i].step = ft_step;
745 /* lets see if the required data source is really there */
746 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
747 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
750 if (im->gdes[i].ds== -1){
751 rrd_set_error("No DS called '%s' in '%s'",
752 im->gdes[i].ds_nam,im->gdes[i].rrd);
760 /* evaluate the expressions in the CDEF functions */
762 /*************************************************************
764 *************************************************************/
767 find_var_wrapper(void *arg1, char *key)
769 return find_var((image_desc_t *) arg1, key);
772 /* find gdes containing var*/
774 find_var(image_desc_t *im, char *key){
776 for(ii=0;ii<im->gdes_c-1;ii++){
777 if((im->gdes[ii].gf == GF_DEF
778 || im->gdes[ii].gf == GF_VDEF
779 || im->gdes[ii].gf == GF_CDEF)
780 && (strcmp(im->gdes[ii].vname,key) == 0)){
787 /* find the largest common denominator for all the numbers
788 in the 0 terminated num array */
793 for (i=0;num[i+1]!=0;i++){
795 rest=num[i] % num[i+1];
796 num[i]=num[i+1]; num[i+1]=rest;
800 /* return i==0?num[i]:num[i-1]; */
804 /* run the rpn calculator on all the VDEF and CDEF arguments */
806 data_calc( image_desc_t *im){
810 long *steparray, rpi;
815 rpnstack_init(&rpnstack);
817 for (gdi=0;gdi<im->gdes_c;gdi++){
818 /* Look for GF_VDEF and GF_CDEF in the same loop,
819 * so CDEFs can use VDEFs and vice versa
821 switch (im->gdes[gdi].gf) {
825 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
827 /* remove current shift */
828 vdp->start -= vdp->shift;
829 vdp->end -= vdp->shift;
832 if (im->gdes[gdi].shidx >= 0)
833 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
836 vdp->shift = im->gdes[gdi].shval;
838 /* normalize shift to multiple of consolidated step */
839 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
842 vdp->start += vdp->shift;
843 vdp->end += vdp->shift;
847 /* A VDEF has no DS. This also signals other parts
848 * of rrdtool that this is a VDEF value, not a CDEF.
850 im->gdes[gdi].ds_cnt = 0;
851 if (vdef_calc(im,gdi)) {
852 rrd_set_error("Error processing VDEF '%s'"
855 rpnstack_free(&rpnstack);
860 im->gdes[gdi].ds_cnt = 1;
861 im->gdes[gdi].ds = 0;
862 im->gdes[gdi].data_first = 1;
863 im->gdes[gdi].start = 0;
864 im->gdes[gdi].end = 0;
869 /* Find the variables in the expression.
870 * - VDEF variables are substituted by their values
871 * and the opcode is changed into OP_NUMBER.
872 * - CDEF variables are analized for their step size,
873 * the lowest common denominator of all the step
874 * sizes of the data sources involved is calculated
875 * and the resulting number is the step size for the
876 * resulting data source.
878 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
879 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
880 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
881 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
882 if (im->gdes[ptr].ds_cnt == 0) {
884 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
886 im->gdes[ptr].vname);
887 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
889 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
890 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
893 rrd_realloc(steparray,
894 (++stepcnt+1)*sizeof(*steparray)))==NULL){
895 rrd_set_error("realloc steparray");
896 rpnstack_free(&rpnstack);
900 steparray[stepcnt-1] = im->gdes[ptr].step;
902 /* adjust start and end of cdef (gdi) so
903 * that it runs from the latest start point
904 * to the earliest endpoint of any of the
905 * rras involved (ptr)
907 if(im->gdes[gdi].start < im->gdes[ptr].start)
908 im->gdes[gdi].start = im->gdes[ptr].start;
910 if(im->gdes[gdi].end == 0 ||
911 im->gdes[gdi].end > im->gdes[ptr].end)
912 im->gdes[gdi].end = im->gdes[ptr].end;
914 /* store pointer to the first element of
915 * the rra providing data for variable,
916 * further save step size and data source
919 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
920 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
921 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
923 /* backoff the *.data ptr; this is done so
924 * rpncalc() function doesn't have to treat
925 * the first case differently
927 } /* if ds_cnt != 0 */
928 } /* if OP_VARIABLE */
929 } /* loop through all rpi */
931 /* move the data pointers to the correct period */
932 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
933 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
934 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
935 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
936 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
939 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
943 if(steparray == NULL){
944 rrd_set_error("rpn expressions without DEF"
945 " or CDEF variables are not supported");
946 rpnstack_free(&rpnstack);
949 steparray[stepcnt]=0;
950 /* Now find the resulting step. All steps in all
951 * used RRAs have to be visited
953 im->gdes[gdi].step = lcd(steparray);
955 if((im->gdes[gdi].data = malloc((
956 (im->gdes[gdi].end-im->gdes[gdi].start)
957 / im->gdes[gdi].step)
958 * sizeof(double)))==NULL){
959 rrd_set_error("malloc im->gdes[gdi].data");
960 rpnstack_free(&rpnstack);
964 /* Step through the new cdef results array and
965 * calculate the values
967 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
968 now<=im->gdes[gdi].end;
969 now += im->gdes[gdi].step)
971 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
973 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
974 * in this case we are advancing by timesteps;
975 * we use the fact that time_t is a synonym for long
977 if (rpn_calc(rpnp,&rpnstack,(long) now,
978 im->gdes[gdi].data,++dataidx) == -1) {
979 /* rpn_calc sets the error string */
980 rpnstack_free(&rpnstack);
983 } /* enumerate over time steps within a CDEF */
988 } /* enumerate over CDEFs */
989 rpnstack_free(&rpnstack);
993 /* massage data so, that we get one value for each x coordinate in the graph */
995 data_proc( image_desc_t *im ){
997 double pixstep = (double)(im->end-im->start)
998 /(double)im->xsize; /* how much time
999 passes in one pixel */
1001 double minval=DNAN,maxval=DNAN;
1003 unsigned long gr_time;
1005 /* memory for the processed data */
1006 for(i=0;i<im->gdes_c;i++) {
1007 if((im->gdes[i].gf==GF_LINE) ||
1008 (im->gdes[i].gf==GF_AREA) ||
1009 (im->gdes[i].gf==GF_TICK) ||
1010 (im->gdes[i].gf==GF_STACK)) {
1011 if((im->gdes[i].p_data = malloc((im->xsize +1)
1012 * sizeof(rrd_value_t)))==NULL){
1013 rrd_set_error("malloc data_proc");
1019 for (i=0;i<im->xsize;i++) { /* for each pixel */
1021 gr_time = im->start+pixstep*i; /* time of the current step */
1024 for (ii=0;ii<im->gdes_c;ii++) {
1026 switch (im->gdes[ii].gf) {
1030 if (!im->gdes[ii].stack)
1033 value = im->gdes[ii].yrule;
1034 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1035 /* The time of the data doesn't necessarily match
1036 ** the time of the graph. Beware.
1038 vidx = im->gdes[ii].vidx;
1039 if (im->gdes[vidx].gf == GF_VDEF) {
1040 value = im->gdes[vidx].vf.val;
1041 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1042 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1043 value = im->gdes[vidx].data[
1044 (unsigned long) floor(
1045 (double)(gr_time - im->gdes[vidx].start)
1046 / im->gdes[vidx].step)
1047 * im->gdes[vidx].ds_cnt
1055 if (! isnan(value)) {
1057 im->gdes[ii].p_data[i] = paintval;
1058 /* GF_TICK: the data values are not
1059 ** relevant for min and max
1061 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1062 if (isnan(minval) || paintval < minval)
1064 if (isnan(maxval) || paintval > maxval)
1068 im->gdes[ii].p_data[i] = DNAN;
1077 /* if min or max have not been asigned a value this is because
1078 there was no data in the graph ... this is not good ...
1079 lets set these to dummy values then ... */
1081 if (isnan(minval)) minval = 0.0;
1082 if (isnan(maxval)) maxval = 1.0;
1084 /* adjust min and max values */
1085 if (isnan(im->minval)
1086 /* don't adjust low-end with log scale */
1087 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1089 im->minval = minval;
1090 if (isnan(im->maxval)
1091 || (!im->rigid && im->maxval < maxval)
1093 if (im->logarithmic)
1094 im->maxval = maxval * 1.1;
1096 im->maxval = maxval;
1098 /* make sure min is smaller than max */
1099 if (im->minval > im->maxval) {
1100 im->minval = 0.99 * im->maxval;
1103 /* make sure min and max are not equal */
1104 if (im->minval == im->maxval) {
1106 if (! im->logarithmic) {
1109 /* make sure min and max are not both zero */
1110 if (im->maxval == 0.0) {
1119 /* identify the point where the first gridline, label ... gets placed */
1123 time_t start, /* what is the initial time */
1124 enum tmt_en baseint, /* what is the basic interval */
1125 long basestep /* how many if these do we jump a time */
1129 localtime_r(&start, &tm);
1132 tm.tm_sec -= tm.tm_sec % basestep; break;
1135 tm.tm_min -= tm.tm_min % basestep;
1140 tm.tm_hour -= tm.tm_hour % basestep; break;
1142 /* we do NOT look at the basestep for this ... */
1145 tm.tm_hour = 0; break;
1147 /* we do NOT look at the basestep for this ... */
1151 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1152 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1159 tm.tm_mon -= tm.tm_mon % basestep; break;
1167 tm.tm_year -= (tm.tm_year+1900) % basestep;
1172 /* identify the point where the next gridline, label ... gets placed */
1175 time_t current, /* what is the initial time */
1176 enum tmt_en baseint, /* what is the basic interval */
1177 long basestep /* how many if these do we jump a time */
1182 localtime_r(¤t, &tm);
1186 tm.tm_sec += basestep; break;
1188 tm.tm_min += basestep; break;
1190 tm.tm_hour += basestep; break;
1192 tm.tm_mday += basestep; break;
1194 tm.tm_mday += 7*basestep; break;
1196 tm.tm_mon += basestep; break;
1198 tm.tm_year += basestep;
1200 madetime = mktime(&tm);
1201 } while (madetime == -1); /* this is necessary to skip impssible times
1202 like the daylight saving time skips */
1208 /* calculate values required for PRINT and GPRINT functions */
1211 print_calc(image_desc_t *im, char ***prdata)
1213 long i,ii,validsteps;
1216 int graphelement = 0;
1219 double magfact = -1;
1223 if (im->imginfo) prlines++;
1224 for(i=0;i<im->gdes_c;i++){
1225 switch(im->gdes[i].gf){
1228 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1229 rrd_set_error("realloc prdata");
1233 /* PRINT and GPRINT can now print VDEF generated values.
1234 * There's no need to do any calculations on them as these
1235 * calculations were already made.
1237 vidx = im->gdes[i].vidx;
1238 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1239 printval = im->gdes[vidx].vf.val;
1240 printtime = im->gdes[vidx].vf.when;
1241 } else { /* need to calculate max,min,avg etcetera */
1242 max_ii =((im->gdes[vidx].end
1243 - im->gdes[vidx].start)
1244 / im->gdes[vidx].step
1245 * im->gdes[vidx].ds_cnt);
1248 for( ii=im->gdes[vidx].ds;
1250 ii+=im->gdes[vidx].ds_cnt){
1251 if (! finite(im->gdes[vidx].data[ii]))
1253 if (isnan(printval)){
1254 printval = im->gdes[vidx].data[ii];
1259 switch (im->gdes[i].cf){
1262 case CF_DEVSEASONAL:
1266 printval += im->gdes[vidx].data[ii];
1269 printval = min( printval, im->gdes[vidx].data[ii]);
1273 printval = max( printval, im->gdes[vidx].data[ii]);
1276 printval = im->gdes[vidx].data[ii];
1279 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1280 if (validsteps > 1) {
1281 printval = (printval / validsteps);
1284 } /* prepare printval */
1286 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1287 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1288 if (im->gdes[i].gf == GF_PRINT){
1289 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1290 sprintf((*prdata)[prlines-2],"%s (%lu)",
1291 ctime_r(&printtime,ctime_buf),printtime);
1292 (*prdata)[prlines-1] = NULL;
1294 sprintf(im->gdes[i].legend,"%s (%lu)",
1295 ctime_r(&printtime,ctime_buf),printtime);
1299 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1300 /* Magfact is set to -1 upon entry to print_calc. If it
1301 * is still less than 0, then we need to run auto_scale.
1302 * Otherwise, put the value into the correct units. If
1303 * the value is 0, then do not set the symbol or magnification
1304 * so next the calculation will be performed again. */
1305 if (magfact < 0.0) {
1306 auto_scale(im,&printval,&si_symb,&magfact);
1307 if (printval == 0.0)
1310 printval /= magfact;
1312 *(++percent_s) = 's';
1313 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1314 auto_scale(im,&printval,&si_symb,&magfact);
1317 if (im->gdes[i].gf == GF_PRINT){
1318 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1319 (*prdata)[prlines-1] = NULL;
1320 if (bad_format(im->gdes[i].format)) {
1321 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1324 #ifdef HAVE_SNPRINTF
1325 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1327 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1332 if (bad_format(im->gdes[i].format)) {
1333 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1336 #ifdef HAVE_SNPRINTF
1337 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1339 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1357 #ifdef WITH_PIECHART
1365 return graphelement;
1369 /* place legends with color spots */
1371 leg_place(image_desc_t *im)
1374 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1375 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1376 int fill=0, fill_last;
1378 int leg_x = border, leg_y = im->yimg;
1382 char prt_fctn; /*special printfunctions */
1385 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1386 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1387 rrd_set_error("malloc for legspace");
1391 for(i=0;i<im->gdes_c;i++){
1394 /* hid legends for rules which are not displayed */
1396 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1397 if (im->gdes[i].gf == GF_HRULE &&
1398 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1399 im->gdes[i].legend[0] = '\0';
1401 if (im->gdes[i].gf == GF_VRULE &&
1402 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1403 im->gdes[i].legend[0] = '\0';
1406 leg_cc = strlen(im->gdes[i].legend);
1408 /* is there a controle code ant the end of the legend string ? */
1409 /* and it is not a tab \\t */
1410 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1411 prt_fctn = im->gdes[i].legend[leg_cc-1];
1413 im->gdes[i].legend[leg_cc] = '\0';
1417 /* remove exess space */
1418 while (prt_fctn=='g' &&
1420 im->gdes[i].legend[leg_cc-1]==' '){
1422 im->gdes[i].legend[leg_cc]='\0';
1425 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1428 /* no interleg space if string ends in \g */
1429 fill += legspace[i];
1431 fill += gfx_get_text_width(im->canvas, fill+border,
1432 im->text_prop[TEXT_PROP_LEGEND].font,
1433 im->text_prop[TEXT_PROP_LEGEND].size,
1435 im->gdes[i].legend, 0);
1440 /* who said there was a special tag ... ?*/
1441 if (prt_fctn=='g') {
1444 if (prt_fctn == '\0') {
1445 if (i == im->gdes_c -1 ) prt_fctn ='l';
1447 /* is it time to place the legends ? */
1448 if (fill > im->ximg - 2*border){
1463 if (prt_fctn != '\0'){
1465 if (leg_c >= 2 && prt_fctn == 'j') {
1466 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1470 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1471 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1473 for(ii=mark;ii<=i;ii++){
1474 if(im->gdes[ii].legend[0]=='\0')
1475 continue; /* skip empty legends */
1476 im->gdes[ii].leg_x = leg_x;
1477 im->gdes[ii].leg_y = leg_y;
1479 gfx_get_text_width(im->canvas, leg_x,
1480 im->text_prop[TEXT_PROP_LEGEND].font,
1481 im->text_prop[TEXT_PROP_LEGEND].size,
1483 im->gdes[ii].legend, 0)
1487 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.7;
1488 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1500 /* create a grid on the graph. it determines what to do
1501 from the values of xsize, start and end */
1503 /* the xaxis labels are determined from the number of seconds per pixel
1504 in the requested graph */
1509 calc_horizontal_grid(image_desc_t *im)
1515 int decimals, fractionals;
1517 im->ygrid_scale.labfact=2;
1519 range = im->maxval - im->minval;
1520 scaledrange = range / im->magfact;
1522 /* does the scale of this graph make it impossible to put lines
1523 on it? If so, give up. */
1524 if (isnan(scaledrange)) {
1528 /* find grid spaceing */
1530 if(isnan(im->ygridstep)){
1531 if(im->extra_flags & ALTYGRID) {
1532 /* find the value with max number of digits. Get number of digits */
1533 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1534 if(decimals <= 0) /* everything is small. make place for zero */
1537 fractionals = floor(log10(range));
1538 if(fractionals < 0) /* small amplitude. */
1539 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1541 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1542 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1543 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1544 im->ygrid_scale.gridstep = 0.1;
1545 /* should have at least 5 lines but no more then 15 */
1546 if(range/im->ygrid_scale.gridstep < 5)
1547 im->ygrid_scale.gridstep /= 10;
1548 if(range/im->ygrid_scale.gridstep > 15)
1549 im->ygrid_scale.gridstep *= 10;
1550 if(range/im->ygrid_scale.gridstep > 5) {
1551 im->ygrid_scale.labfact = 1;
1552 if(range/im->ygrid_scale.gridstep > 8)
1553 im->ygrid_scale.labfact = 2;
1556 im->ygrid_scale.gridstep /= 5;
1557 im->ygrid_scale.labfact = 5;
1561 for(i=0;ylab[i].grid > 0;i++){
1562 pixel = im->ysize / (scaledrange / ylab[i].grid);
1563 if (gridind == -1 && pixel > 5) {
1570 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1571 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1576 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1579 im->ygrid_scale.gridstep = im->ygridstep;
1580 im->ygrid_scale.labfact = im->ylabfact;
1585 int draw_horizontal_grid(image_desc_t *im)
1589 char graph_label[100];
1590 double X0=im->xorigin;
1591 double X1=im->xorigin+im->xsize;
1593 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1594 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1595 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1596 for (i = sgrid; i <= egrid; i++){
1597 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1598 if ( Y0 >= im->yorigin-im->ysize
1599 && Y0 <= im->yorigin){
1600 if(i % im->ygrid_scale.labfact == 0){
1601 if (i==0 || im->symbol == ' ') {
1603 if(im->extra_flags & ALTYGRID) {
1604 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1607 sprintf(graph_label,"%4.1f",scaledstep*i);
1610 sprintf(graph_label,"%4.0f",scaledstep*i);
1614 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1616 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1620 gfx_new_text ( im->canvas,
1621 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1622 im->graph_col[GRC_FONT],
1623 im->text_prop[TEXT_PROP_AXIS].font,
1624 im->text_prop[TEXT_PROP_AXIS].size,
1625 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1627 gfx_new_dashed_line ( im->canvas,
1630 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1631 im->grid_dash_on, im->grid_dash_off);
1633 } else if (!(im->extra_flags & NOMINOR)) {
1634 gfx_new_dashed_line ( im->canvas,
1637 GRIDWIDTH, im->graph_col[GRC_GRID],
1638 im->grid_dash_on, im->grid_dash_off);
1646 /* logaritmic horizontal grid */
1648 horizontal_log_grid(image_desc_t *im)
1652 int minoridx=0, majoridx=0;
1653 char graph_label[100];
1655 double value, pixperstep, minstep;
1657 /* find grid spaceing */
1658 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1660 if (isnan(pixpex)) {
1664 for(i=0;yloglab[i][0] > 0;i++){
1665 minstep = log10(yloglab[i][0]);
1666 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1667 if(yloglab[i][ii+2]==0){
1668 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1672 pixperstep = pixpex * minstep;
1673 if(pixperstep > 5){minoridx = i;}
1674 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1678 X1=im->xorigin+im->xsize;
1679 /* paint minor grid */
1680 for (value = pow((double)10, log10(im->minval)
1681 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1682 value <= im->maxval;
1683 value *= yloglab[minoridx][0]){
1684 if (value < im->minval) continue;
1686 while(yloglab[minoridx][++i] > 0){
1687 Y0 = ytr(im,value * yloglab[minoridx][i]);
1688 if (Y0 <= im->yorigin - im->ysize) break;
1689 gfx_new_dashed_line ( im->canvas,
1692 GRIDWIDTH, im->graph_col[GRC_GRID],
1693 im->grid_dash_on, im->grid_dash_off);
1697 /* paint major grid and labels*/
1698 for (value = pow((double)10, log10(im->minval)
1699 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1700 value <= im->maxval;
1701 value *= yloglab[majoridx][0]){
1702 if (value < im->minval) continue;
1704 while(yloglab[majoridx][++i] > 0){
1705 Y0 = ytr(im,value * yloglab[majoridx][i]);
1706 if (Y0 <= im->yorigin - im->ysize) break;
1707 gfx_new_dashed_line ( im->canvas,
1710 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1711 im->grid_dash_on, im->grid_dash_off);
1713 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1714 gfx_new_text ( im->canvas,
1715 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1716 im->graph_col[GRC_FONT],
1717 im->text_prop[TEXT_PROP_AXIS].font,
1718 im->text_prop[TEXT_PROP_AXIS].size,
1719 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1731 int xlab_sel; /* which sort of label and grid ? */
1732 time_t ti, tilab, timajor;
1734 char graph_label[100];
1735 double X0,Y0,Y1; /* points for filled graph and more*/
1738 /* the type of time grid is determined by finding
1739 the number of seconds per pixel in the graph */
1742 if(im->xlab_user.minsec == -1){
1743 factor=(im->end - im->start)/im->xsize;
1745 while ( xlab[xlab_sel+1].minsec != -1
1746 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1747 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1748 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1749 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1750 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1751 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1752 im->xlab_user.labst = xlab[xlab_sel].labst;
1753 im->xlab_user.precis = xlab[xlab_sel].precis;
1754 im->xlab_user.stst = xlab[xlab_sel].stst;
1757 /* y coords are the same for every line ... */
1759 Y1 = im->yorigin-im->ysize;
1762 /* paint the minor grid */
1763 if (!(im->extra_flags & NOMINOR))
1765 for(ti = find_first_time(im->start,
1766 im->xlab_user.gridtm,
1767 im->xlab_user.gridst),
1768 timajor = find_first_time(im->start,
1769 im->xlab_user.mgridtm,
1770 im->xlab_user.mgridst);
1772 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1774 /* are we inside the graph ? */
1775 if (ti < im->start || ti > im->end) continue;
1776 while (timajor < ti) {
1777 timajor = find_next_time(timajor,
1778 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1780 if (ti == timajor) continue; /* skip as falls on major grid line */
1782 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1783 im->graph_col[GRC_GRID],
1784 im->grid_dash_on, im->grid_dash_off);
1789 /* paint the major grid */
1790 for(ti = find_first_time(im->start,
1791 im->xlab_user.mgridtm,
1792 im->xlab_user.mgridst);
1794 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1796 /* are we inside the graph ? */
1797 if (ti < im->start || ti > im->end) continue;
1799 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1800 im->graph_col[GRC_MGRID],
1801 im->grid_dash_on, im->grid_dash_off);
1804 /* paint the labels below the graph */
1805 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1806 im->xlab_user.labtm,
1807 im->xlab_user.labst);
1808 ti <= im->end - im->xlab_user.precis/2;
1809 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1811 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1812 /* are we inside the graph ? */
1813 if (tilab < im->start || tilab > im->end) continue;
1816 localtime_r(&tilab, &tm);
1817 strftime(graph_label,99,im->xlab_user.stst, &tm);
1819 # error "your libc has no strftime I guess we'll abort the exercise here."
1821 gfx_new_text ( im->canvas,
1822 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1823 im->graph_col[GRC_FONT],
1824 im->text_prop[TEXT_PROP_AXIS].font,
1825 im->text_prop[TEXT_PROP_AXIS].size,
1826 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1839 /* draw x and y axis */
1840 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1841 im->xorigin+im->xsize,im->yorigin-im->ysize,
1842 GRIDWIDTH, im->graph_col[GRC_AXIS]);
1844 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1845 im->xorigin+im->xsize,im->yorigin-im->ysize,
1846 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
1848 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1849 im->xorigin+im->xsize+4,im->yorigin,
1850 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1852 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1853 im->xorigin,im->yorigin-im->ysize-4,
1854 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1857 /* arrow for X and Y axis direction */
1858 gfx_new_area ( im->canvas,
1859 im->xorigin+im->xsize+2, im->yorigin-2,
1860 im->xorigin+im->xsize+2, im->yorigin+3,
1861 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
1862 im->graph_col[GRC_ARROW]);
1864 gfx_new_area ( im->canvas,
1865 im->xorigin-2, im->yorigin-im->ysize-2,
1866 im->xorigin+3, im->yorigin-im->ysize-2,
1867 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
1868 im->graph_col[GRC_ARROW]);
1873 grid_paint(image_desc_t *im)
1877 double X0,Y0; /* points for filled graph and more*/
1880 /* draw 3d border */
1881 node = gfx_new_area (im->canvas, 0,im->yimg,
1883 2,2,im->graph_col[GRC_SHADEA]);
1884 gfx_add_point( node , im->ximg - 2, 2 );
1885 gfx_add_point( node , im->ximg, 0 );
1886 gfx_add_point( node , 0,0 );
1887 /* gfx_add_point( node , 0,im->yimg ); */
1889 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1890 im->ximg-2,im->yimg-2,
1892 im->graph_col[GRC_SHADEB]);
1893 gfx_add_point( node , im->ximg,0);
1894 gfx_add_point( node , im->ximg,im->yimg);
1895 gfx_add_point( node , 0,im->yimg);
1896 /* gfx_add_point( node , 0,im->yimg ); */
1899 if (im->draw_x_grid == 1 )
1902 if (im->draw_y_grid == 1){
1903 if(im->logarithmic){
1904 res = horizontal_log_grid(im);
1906 res = draw_horizontal_grid(im);
1909 /* dont draw horizontal grid if there is no min and max val */
1911 char *nodata = "No Data found";
1912 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1913 im->graph_col[GRC_FONT],
1914 im->text_prop[TEXT_PROP_AXIS].font,
1915 im->text_prop[TEXT_PROP_AXIS].size,
1916 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1921 /* yaxis unit description */
1922 gfx_new_text( im->canvas,
1923 7, (im->yorigin - im->ysize/2),
1924 im->graph_col[GRC_FONT],
1925 im->text_prop[TEXT_PROP_UNIT].font,
1926 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
1927 RRDGRAPH_YLEGEND_ANGLE,
1928 GFX_H_LEFT, GFX_V_CENTER,
1932 gfx_new_text( im->canvas,
1933 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.2,
1934 im->graph_col[GRC_FONT],
1935 im->text_prop[TEXT_PROP_TITLE].font,
1936 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1937 GFX_H_CENTER, GFX_V_CENTER,
1941 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1942 for(i=0;i<im->gdes_c;i++){
1943 if(im->gdes[i].legend[0] =='\0')
1946 /* im->gdes[i].leg_y is the bottom of the legend */
1947 X0 = im->gdes[i].leg_x;
1948 Y0 = im->gdes[i].leg_y;
1949 gfx_new_text ( im->canvas, X0, Y0,
1950 im->graph_col[GRC_FONT],
1951 im->text_prop[TEXT_PROP_LEGEND].font,
1952 im->text_prop[TEXT_PROP_LEGEND].size,
1953 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1954 im->gdes[i].legend );
1955 /* The legend for GRAPH items starts with "M " to have
1956 enough space for the box */
1957 if ( im->gdes[i].gf != GF_PRINT &&
1958 im->gdes[i].gf != GF_GPRINT &&
1959 im->gdes[i].gf != GF_COMMENT) {
1962 boxH = gfx_get_text_width(im->canvas, 0,
1963 im->text_prop[TEXT_PROP_LEGEND].font,
1964 im->text_prop[TEXT_PROP_LEGEND].size,
1965 im->tabwidth,"M", 0)*1.2;
1968 /* make sure transparent colors show up all the same */
1969 node = gfx_new_area(im->canvas,
1973 im->graph_col[GRC_CANVAS]);
1974 gfx_add_point ( node, X0+boxH, Y0-boxV );
1976 node = gfx_new_area(im->canvas,
1981 gfx_add_point ( node, X0+boxH, Y0-boxV );
1982 node = gfx_new_line(im->canvas,
1984 1,im->graph_col[GRC_FONT]);
1985 gfx_add_point(node,X0+boxH,Y0);
1986 gfx_add_point(node,X0+boxH,Y0-boxV);
1987 gfx_close_path(node);
1994 /*****************************************************
1995 * lazy check make sure we rely need to create this graph
1996 *****************************************************/
1998 int lazy_check(image_desc_t *im){
2001 struct stat imgstat;
2003 if (im->lazy == 0) return 0; /* no lazy option */
2004 if (stat(im->graphfile,&imgstat) != 0)
2005 return 0; /* can't stat */
2006 /* one pixel in the existing graph is more then what we would
2008 if (time(NULL) - imgstat.st_mtime >
2009 (im->end - im->start) / im->xsize)
2011 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2012 return 0; /* the file does not exist */
2013 switch (im->canvas->imgformat) {
2015 size = PngSize(fd,&(im->ximg),&(im->yimg));
2024 #ifdef WITH_PIECHART
2026 pie_part(image_desc_t *im, gfx_color_t color,
2027 double PieCenterX, double PieCenterY, double Radius,
2028 double startangle, double endangle)
2032 double step=M_PI/50; /* Number of iterations for the circle;
2033 ** 10 is definitely too low, more than
2034 ** 50 seems to be overkill
2037 /* Strange but true: we have to work clockwise or else
2038 ** anti aliasing nor transparency don't work.
2040 ** This test is here to make sure we do it right, also
2041 ** this makes the for...next loop more easy to implement.
2042 ** The return will occur if the user enters a negative number
2043 ** (which shouldn't be done according to the specs) or if the
2044 ** programmers do something wrong (which, as we all know, never
2045 ** happens anyway :)
2047 if (endangle<startangle) return;
2049 /* Hidden feature: Radius decreases each full circle */
2051 while (angle>=2*M_PI) {
2056 node=gfx_new_area(im->canvas,
2057 PieCenterX+sin(startangle)*Radius,
2058 PieCenterY-cos(startangle)*Radius,
2061 PieCenterX+sin(endangle)*Radius,
2062 PieCenterY-cos(endangle)*Radius,
2064 for (angle=endangle;angle-startangle>=step;angle-=step) {
2066 PieCenterX+sin(angle)*Radius,
2067 PieCenterY-cos(angle)*Radius );
2074 graph_size_location(image_desc_t *im, int elements
2076 #ifdef WITH_PIECHART
2082 /* The actual size of the image to draw is determined from
2083 ** several sources. The size given on the command line is
2084 ** the graph area but we need more as we have to draw labels
2085 ** and other things outside the graph area
2088 /* +-+-------------------------------------------+
2089 ** |l|.................title.....................|
2090 ** |e+--+-------------------------------+--------+
2093 ** |l| l| main graph area | chart |
2096 ** |r+--+-------------------------------+--------+
2097 ** |e| | x-axis labels | |
2098 ** |v+--+-------------------------------+--------+
2099 ** | |..............legends......................|
2100 ** +-+-------------------------------------------+
2102 int Xvertical=0, Yvertical=0,
2103 Xtitle =0, Ytitle =0,
2104 Xylabel =0, Yylabel =0,
2106 #ifdef WITH_PIECHART
2109 Xxlabel =0, Yxlabel =0,
2111 Xlegend =0, Ylegend =0,
2113 Xspacing =10, Yspacing =10;
2115 if (im->extra_flags & ONLY_GRAPH) {
2117 im->ximg = im->xsize;
2118 im->yimg = im->ysize;
2119 im->yorigin = im->ysize;
2123 if (im->ylegend[0] != '\0' ) {
2124 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2125 Yvertical = gfx_get_text_width(im->canvas, 0,
2126 im->text_prop[TEXT_PROP_UNIT].font,
2127 im->text_prop[TEXT_PROP_UNIT].size,
2128 im->tabwidth,im->ylegend, 0);
2132 if (im->title[0] != '\0') {
2133 /* The title is placed "inbetween" two text lines so it
2134 ** automatically has some vertical spacing. The horizontal
2135 ** spacing is added here, on each side.
2137 Xtitle = gfx_get_text_width(im->canvas, 0,
2138 im->text_prop[TEXT_PROP_TITLE].font,
2139 im->text_prop[TEXT_PROP_TITLE].size,
2141 im->title, 0) + 2*Xspacing;
2142 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.5;
2148 if (im->draw_x_grid) {
2150 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2152 if (im->draw_y_grid) {
2153 Xylabel=im->text_prop[TEXT_PROP_AXIS].size *6;
2158 #ifdef WITH_PIECHART
2160 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2166 /* Now calculate the total size. Insert some spacing where
2167 desired. im->xorigin and im->yorigin need to correspond
2168 with the lower left corner of the main graph area or, if
2169 this one is not set, the imaginary box surrounding the
2172 /* The legend width cannot yet be determined, as a result we
2173 ** have problems adjusting the image to it. For now, we just
2174 ** forget about it at all; the legend will have to fit in the
2175 ** size already allocated.
2177 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2179 #ifdef WITH_PIECHART
2183 if (Xmain) im->ximg += Xspacing;
2184 #ifdef WITH_PIECHART
2185 if (Xpie) im->ximg += Xspacing;
2188 im->xorigin = Xspacing + Xylabel;
2190 if (Xtitle > im->ximg) im->ximg = Xtitle;
2192 if (Xvertical) { /* unit description */
2193 im->ximg += Xvertical;
2194 im->xorigin += Xvertical;
2198 /* The vertical size is interesting... we need to compare
2199 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2200 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2201 ** start even thinking about Ylegend.
2203 ** Do it in three portions: First calculate the inner part,
2204 ** then do the legend, then adjust the total height of the img.
2207 /* reserve space for main and/or pie */
2209 im->yimg = Ymain + Yxlabel;
2211 #ifdef WITH_PIECHART
2212 if (im->yimg < Ypie) im->yimg = Ypie;
2215 im->yorigin = im->yimg - Yxlabel;
2217 /* reserve space for the title *or* some padding above the graph */
2220 im->yorigin += Ytitle;
2222 im->yimg += Yspacing;
2223 im->yorigin += Yspacing;
2225 /* reserve space for padding below the graph */
2226 im->yimg += Yspacing;
2229 /* Determine where to place the legends onto the image.
2230 ** Adjust im->yimg to match the space requirements.
2232 if(leg_place(im)==-1)
2235 /* last of three steps: check total height of image */
2236 if (im->yimg < Yvertical) im->yimg = Yvertical;
2239 if (Xlegend > im->ximg) {
2241 /* reposition Pie */
2245 #ifdef WITH_PIECHART
2246 /* The pie is placed in the upper right hand corner,
2247 ** just below the title (if any) and with sufficient
2251 im->pie_x = im->ximg - Xspacing - Xpie/2;
2252 im->pie_y = im->yorigin-Ymain+Ypie/2;
2254 im->pie_x = im->ximg/2;
2255 im->pie_y = im->yorigin-Ypie/2;
2262 /* draw that picture thing ... */
2264 graph_paint(image_desc_t *im, char ***calcpr)
2267 int lazy = lazy_check(im);
2268 #ifdef WITH_PIECHART
2270 double PieStart=0.0;
2275 double areazero = 0.0;
2276 enum gf_en stack_gf = GF_PRINT;
2277 graph_desc_t *lastgdes = NULL;
2279 /* if we are lazy and there is nothing to PRINT ... quit now */
2280 if (lazy && im->prt_c==0) return 0;
2282 /* pull the data from the rrd files ... */
2284 if(data_fetch(im)==-1)
2287 /* evaluate VDEF and CDEF operations ... */
2288 if(data_calc(im)==-1)
2291 #ifdef WITH_PIECHART
2292 /* check if we need to draw a piechart */
2293 for(i=0;i<im->gdes_c;i++){
2294 if (im->gdes[i].gf == GF_PART) {
2301 /* calculate and PRINT and GPRINT definitions. We have to do it at
2302 * this point because it will affect the length of the legends
2303 * if there are no graph elements we stop here ...
2304 * if we are lazy, try to quit ...
2306 i=print_calc(im,calcpr);
2309 #ifdef WITH_PIECHART
2312 ) || lazy) return 0;
2314 #ifdef WITH_PIECHART
2315 /* If there's only the pie chart to draw, signal this */
2316 if (i==0) piechart=2;
2319 /* get actual drawing data and find min and max values*/
2320 if(data_proc(im)==-1)
2323 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2325 if(!im->rigid && ! im->logarithmic)
2326 expand_range(im); /* make sure the upper and lower limit are
2329 if (!calc_horizontal_grid(im))
2336 /**************************************************************
2337 *** Calculating sizes and locations became a bit confusing ***
2338 *** so I moved this into a separate function. ***
2339 **************************************************************/
2340 if(graph_size_location(im,i
2341 #ifdef WITH_PIECHART
2347 /* the actual graph is created by going through the individual
2348 graph elements and then drawing them */
2350 node=gfx_new_area ( im->canvas,
2354 im->graph_col[GRC_BACK]);
2356 gfx_add_point(node,0, im->yimg);
2358 #ifdef WITH_PIECHART
2359 if (piechart != 2) {
2361 node=gfx_new_area ( im->canvas,
2362 im->xorigin, im->yorigin,
2363 im->xorigin + im->xsize, im->yorigin,
2364 im->xorigin + im->xsize, im->yorigin-im->ysize,
2365 im->graph_col[GRC_CANVAS]);
2367 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2369 if (im->minval > 0.0)
2370 areazero = im->minval;
2371 if (im->maxval < 0.0)
2372 areazero = im->maxval;
2373 #ifdef WITH_PIECHART
2377 #ifdef WITH_PIECHART
2379 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2383 for(i=0;i<im->gdes_c;i++){
2384 switch(im->gdes[i].gf){
2397 for (ii = 0; ii < im->xsize; ii++)
2399 if (!isnan(im->gdes[i].p_data[ii]) &&
2400 im->gdes[i].p_data[ii] > 0.0)
2402 /* generate a tick */
2403 gfx_new_line(im->canvas, im -> xorigin + ii,
2404 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2408 im -> gdes[i].col );
2414 stack_gf = im->gdes[i].gf;
2416 /* fix data points at oo and -oo */
2417 for(ii=0;ii<im->xsize;ii++){
2418 if (isinf(im->gdes[i].p_data[ii])){
2419 if (im->gdes[i].p_data[ii] > 0) {
2420 im->gdes[i].p_data[ii] = im->maxval ;
2422 im->gdes[i].p_data[ii] = im->minval ;
2428 /* *******************************************************
2433 -------|---------------------------------------
2435 if we know the value of y at time t was a then
2436 we draw a square from t-1 to t with the value a.
2438 ********************************************************* */
2439 if (im->gdes[i].col != 0x0){
2440 /* GF_LINE and friend */
2441 if(stack_gf == GF_LINE ){
2443 for(ii=1;ii<im->xsize;ii++){
2444 if (isnan(im->gdes[i].p_data[ii])){
2448 if ( node == NULL ) {
2449 node = gfx_new_line(im->canvas,
2450 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2451 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2452 im->gdes[i].linewidth,
2455 gfx_add_point(node,ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2456 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2461 for(ii=1;ii<im->xsize;ii++){
2462 /* keep things simple for now, just draw these bars
2463 do not try to build a big and complex area */
2465 if ( isnan(im->gdes[i].p_data[ii]) ) {
2468 ytop = ytr(im,im->gdes[i].p_data[ii]);
2469 if ( im->gdes[i].stack ) {
2470 ybase = ytr(im,lastgdes->p_data[ii]);
2472 ybase = ytr(im,areazero);
2474 if ( ybase == ytop ){
2477 node = gfx_new_area(im->canvas,
2478 ii-1+im->xorigin,ybase,
2479 ii-1+im->xorigin,ytop,
2480 ii+im->xorigin,ytop,
2483 gfx_add_point(node,ii+im->xorigin,ybase);
2485 } /* else GF_LINE */
2486 } /* if color != 0x0 */
2487 /* make sure we do not run into trouble when stacking on NaN */
2488 for(ii=0;ii<im->xsize;ii++){
2489 if (isnan(im->gdes[i].p_data[ii])) {
2490 if (lastgdes && (im->gdes[i].stack)) {
2491 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2493 im->gdes[i].p_data[ii] = ytr(im,areazero);
2497 lastgdes = &(im->gdes[i]);
2499 #ifdef WITH_PIECHART
2501 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2502 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2504 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2505 pie_part(im,im->gdes[i].col,
2506 im->pie_x,im->pie_y,im->piesize*0.4,
2507 M_PI*2.0*PieStart/100.0,
2508 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2509 PieStart += im->gdes[i].yrule;
2516 #ifdef WITH_PIECHART
2523 if( !(im->extra_flags & ONLY_GRAPH) )
2526 /* grid_paint also does the text */
2527 if( !(im->extra_flags & ONLY_GRAPH) )
2530 /* the RULES are the last thing to paint ... */
2531 for(i=0;i<im->gdes_c;i++){
2533 switch(im->gdes[i].gf){
2535 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2536 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2538 if(im->gdes[i].yrule >= im->minval
2539 && im->gdes[i].yrule <= im->maxval)
2540 gfx_new_line(im->canvas,
2541 im->xorigin,ytr(im,im->gdes[i].yrule),
2542 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2543 1.0,im->gdes[i].col);
2546 if(im->gdes[i].xrule == 0) { /* fetch variable */
2547 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2549 if(im->gdes[i].xrule >= im->start
2550 && im->gdes[i].xrule <= im->end)
2551 gfx_new_line(im->canvas,
2552 xtr(im,im->gdes[i].xrule),im->yorigin,
2553 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2554 1.0,im->gdes[i].col);
2562 if (strcmp(im->graphfile,"-")==0) {
2563 fo = im->graphhandle ? im->graphhandle : stdout;
2564 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2565 /* Change translation mode for stdout to BINARY */
2566 _setmode( _fileno( fo ), O_BINARY );
2569 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2570 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2571 rrd_strerror(errno));
2575 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2576 if (strcmp(im->graphfile,"-") != 0)
2582 /*****************************************************
2584 *****************************************************/
2587 gdes_alloc(image_desc_t *im){
2590 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2591 * sizeof(graph_desc_t)))==NULL){
2592 rrd_set_error("realloc graph_descs");
2597 im->gdes[im->gdes_c-1].step=im->step;
2598 im->gdes[im->gdes_c-1].stack=0;
2599 im->gdes[im->gdes_c-1].debug=0;
2600 im->gdes[im->gdes_c-1].start=im->start;
2601 im->gdes[im->gdes_c-1].end=im->end;
2602 im->gdes[im->gdes_c-1].vname[0]='\0';
2603 im->gdes[im->gdes_c-1].data=NULL;
2604 im->gdes[im->gdes_c-1].ds_namv=NULL;
2605 im->gdes[im->gdes_c-1].data_first=0;
2606 im->gdes[im->gdes_c-1].p_data=NULL;
2607 im->gdes[im->gdes_c-1].rpnp=NULL;
2608 im->gdes[im->gdes_c-1].shift=0;
2609 im->gdes[im->gdes_c-1].col = 0x0;
2610 im->gdes[im->gdes_c-1].legend[0]='\0';
2611 im->gdes[im->gdes_c-1].format[0]='\0';
2612 im->gdes[im->gdes_c-1].rrd[0]='\0';
2613 im->gdes[im->gdes_c-1].ds=-1;
2614 im->gdes[im->gdes_c-1].p_data=NULL;
2615 im->gdes[im->gdes_c-1].yrule=DNAN;
2616 im->gdes[im->gdes_c-1].xrule=0;
2620 /* copies input untill the first unescaped colon is found
2621 or until input ends. backslashes have to be escaped as well */
2623 scan_for_col(char *input, int len, char *output)
2628 input[inp] != ':' &&
2631 if (input[inp] == '\\' &&
2632 input[inp+1] != '\0' &&
2633 (input[inp+1] == '\\' ||
2634 input[inp+1] == ':')){
2635 output[outp++] = input[++inp];
2638 output[outp++] = input[inp];
2641 output[outp] = '\0';
2644 /* Some surgery done on this function, it became ridiculously big.
2646 ** - initializing now in rrd_graph_init()
2647 ** - options parsing now in rrd_graph_options()
2648 ** - script parsing now in rrd_graph_script()
2651 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2655 rrd_graph_init(&im);
2656 im.graphhandle = stream;
2658 rrd_graph_options(argc,argv,&im);
2659 if (rrd_test_error()) {
2664 if (strlen(argv[optind])>=MAXPATH) {
2665 rrd_set_error("filename (including path) too long");
2669 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2670 im.graphfile[MAXPATH-1]='\0';
2672 rrd_graph_script(argc,argv,&im,1);
2673 if (rrd_test_error()) {
2678 /* Everything is now read and the actual work can start */
2681 if (graph_paint(&im,prdata)==-1){
2686 /* The image is generated and needs to be output.
2687 ** Also, if needed, print a line with information about the image.
2697 /* maybe prdata is not allocated yet ... lets do it now */
2698 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2699 rrd_set_error("malloc imginfo");
2703 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2705 rrd_set_error("malloc imginfo");
2708 filename=im.graphfile+strlen(im.graphfile);
2709 while(filename > im.graphfile) {
2710 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2714 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2721 rrd_graph_init(image_desc_t *im)
2728 #ifdef HAVE_SETLOCALE
2729 setlocale(LC_TIME,"");
2732 im->xlab_user.minsec = -1;
2738 im->ylegend[0] = '\0';
2739 im->title[0] = '\0';
2742 im->unitsexponent= 9999;
2748 im->logarithmic = 0;
2749 im->ygridstep = DNAN;
2750 im->draw_x_grid = 1;
2751 im->draw_y_grid = 1;
2756 im->canvas = gfx_new_canvas();
2757 im->grid_dash_on = 1;
2758 im->grid_dash_off = 1;
2759 im->tabwidth = 40.0;
2761 for(i=0;i<DIM(graph_col);i++)
2762 im->graph_col[i]=graph_col[i];
2764 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2767 char rrd_win_default_font[1000];
2768 windir = getenv("windir");
2769 /* %windir% is something like D:\windows or C:\winnt */
2770 if (windir != NULL) {
2771 strncpy(rrd_win_default_font,windir,999);
2772 rrd_win_default_font[999] = '\0';
2773 strcat(rrd_win_default_font,"\\fonts\\cour.ttf");
2774 for(i=0;i<DIM(text_prop);i++){
2775 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
2776 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2782 deffont = getenv("RRD_DEFAULT_FONT");
2783 if (deffont != NULL) {
2784 for(i=0;i<DIM(text_prop);i++){
2785 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
2786 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2790 for(i=0;i<DIM(text_prop);i++){
2791 im->text_prop[i].size = text_prop[i].size;
2792 strcpy(im->text_prop[i].font,text_prop[i].font);
2797 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2800 char *parsetime_error = NULL;
2801 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2802 time_t start_tmp=0,end_tmp=0;
2804 struct rrd_time_value start_tv, end_tv;
2807 parsetime("end-24h", &start_tv);
2808 parsetime("now", &end_tv);
2811 static struct option long_options[] =
2813 {"start", required_argument, 0, 's'},
2814 {"end", required_argument, 0, 'e'},
2815 {"x-grid", required_argument, 0, 'x'},
2816 {"y-grid", required_argument, 0, 'y'},
2817 {"vertical-label",required_argument,0,'v'},
2818 {"width", required_argument, 0, 'w'},
2819 {"height", required_argument, 0, 'h'},
2820 {"interlaced", no_argument, 0, 'i'},
2821 {"upper-limit",required_argument, 0, 'u'},
2822 {"lower-limit",required_argument, 0, 'l'},
2823 {"rigid", no_argument, 0, 'r'},
2824 {"base", required_argument, 0, 'b'},
2825 {"logarithmic",no_argument, 0, 'o'},
2826 {"color", required_argument, 0, 'c'},
2827 {"font", required_argument, 0, 'n'},
2828 {"title", required_argument, 0, 't'},
2829 {"imginfo", required_argument, 0, 'f'},
2830 {"imgformat", required_argument, 0, 'a'},
2831 {"lazy", no_argument, 0, 'z'},
2832 {"zoom", required_argument, 0, 'm'},
2833 {"no-legend", no_argument, 0, 'g'},
2834 {"force-rules-legend",no_argument,0, 'F'},
2835 {"only-graph", no_argument, 0, 'j'},
2836 {"alt-y-grid", no_argument, 0, 'Y'},
2837 {"no-minor", no_argument, 0, 'I'},
2838 {"alt-autoscale", no_argument, 0, 'A'},
2839 {"alt-autoscale-max", no_argument, 0, 'M'},
2840 {"units-exponent",required_argument, 0, 'X'},
2841 {"step", required_argument, 0, 'S'},
2842 {"tabwidth", required_argument, 0, 'T'},
2843 {"no-gridfit", no_argument, 0, 'N'},
2845 int option_index = 0;
2847 int col_start,col_end;
2849 opt = getopt_long(argc, argv,
2850 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMX:S:NT:",
2851 long_options, &option_index);
2858 im->extra_flags |= NOMINOR;
2861 im->extra_flags |= ALTYGRID;
2864 im->extra_flags |= ALTAUTOSCALE;
2867 im->extra_flags |= ALTAUTOSCALE_MAX;
2870 im->extra_flags |= ONLY_GRAPH;
2873 im->extra_flags |= NOLEGEND;
2876 im->extra_flags |= FORCE_RULES_LEGEND;
2879 im->unitsexponent = atoi(optarg);
2882 im->tabwidth = atof(optarg);
2885 im->step = atoi(optarg);
2891 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2892 rrd_set_error( "start time: %s", parsetime_error );
2897 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2898 rrd_set_error( "end time: %s", parsetime_error );
2903 if(strcmp(optarg,"none") == 0){
2909 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2911 &im->xlab_user.gridst,
2913 &im->xlab_user.mgridst,
2915 &im->xlab_user.labst,
2916 &im->xlab_user.precis,
2917 &stroff) == 7 && stroff != 0){
2918 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2919 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
2920 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2921 rrd_set_error("unknown keyword %s",scan_gtm);
2923 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2924 rrd_set_error("unknown keyword %s",scan_mtm);
2926 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2927 rrd_set_error("unknown keyword %s",scan_ltm);
2930 im->xlab_user.minsec = 1;
2931 im->xlab_user.stst = im->xlab_form;
2933 rrd_set_error("invalid x-grid format");
2939 if(strcmp(optarg,"none") == 0){
2947 &im->ylabfact) == 2) {
2948 if(im->ygridstep<=0){
2949 rrd_set_error("grid step must be > 0");
2951 } else if (im->ylabfact < 1){
2952 rrd_set_error("label factor must be > 0");
2956 rrd_set_error("invalid y-grid format");
2961 strncpy(im->ylegend,optarg,150);
2962 im->ylegend[150]='\0';
2965 im->maxval = atof(optarg);
2968 im->minval = atof(optarg);
2971 im->base = atol(optarg);
2972 if(im->base != 1024 && im->base != 1000 ){
2973 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2978 long_tmp = atol(optarg);
2979 if (long_tmp < 10) {
2980 rrd_set_error("width below 10 pixels");
2983 im->xsize = long_tmp;
2986 long_tmp = atol(optarg);
2987 if (long_tmp < 10) {
2988 rrd_set_error("height below 10 pixels");
2991 im->ysize = long_tmp;
2994 im->canvas->interlaced = 1;
3000 im->imginfo = optarg;
3003 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3004 rrd_set_error("unsupported graphics format '%s'",optarg);
3012 im->logarithmic = 1;
3013 if (isnan(im->minval))
3018 "%10[A-Z]#%n%8lx%n",
3019 col_nam,&col_start,&color,&col_end) == 2){
3021 int col_len = col_end - col_start;
3024 color = (color << 8) + 0xff /* shift left by 8 */;
3029 rrd_set_error("the color format is #RRGGBB[AA]");
3032 if((ci=grc_conv(col_nam)) != -1){
3033 im->graph_col[ci]=color;
3035 rrd_set_error("invalid color name '%s'",col_nam);
3039 rrd_set_error("invalid color def format");
3049 "%10[A-Z]:%lf:%1000s",
3050 prop,&size,font) == 3){
3052 if((sindex=text_prop_conv(prop)) != -1){
3053 im->text_prop[sindex].size=size;
3054 strcpy(im->text_prop[sindex].font,font);
3055 if (sindex==0) { /* the default */
3056 im->text_prop[TEXT_PROP_TITLE].size=size;
3057 strcpy(im->text_prop[TEXT_PROP_TITLE].font,font);
3058 im->text_prop[TEXT_PROP_AXIS].size=size;
3059 strcpy(im->text_prop[TEXT_PROP_AXIS].font,font);
3060 im->text_prop[TEXT_PROP_UNIT].size=size;
3061 strcpy(im->text_prop[TEXT_PROP_UNIT].font,font);
3062 im->text_prop[TEXT_PROP_LEGEND].size=size;
3063 strcpy(im->text_prop[TEXT_PROP_LEGEND].font,font);
3066 rrd_set_error("invalid fonttag '%s'",prop);
3070 rrd_set_error("invalid text property format");
3076 im->canvas->zoom = atof(optarg);
3077 if (im->canvas->zoom <= 0.0) {
3078 rrd_set_error("zoom factor must be > 0");
3083 strncpy(im->title,optarg,150);
3084 im->title[150]='\0';
3089 rrd_set_error("unknown option '%c'", optopt);
3091 rrd_set_error("unknown option '%s'",argv[optind-1]);
3096 if (optind >= argc) {
3097 rrd_set_error("missing filename");
3101 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3102 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3106 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3107 /* error string is set in parsetime.c */
3111 if (start_tmp < 3600*24*365*10){
3112 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3116 if (end_tmp < start_tmp) {
3117 rrd_set_error("start (%ld) should be less than end (%ld)",
3118 start_tmp, end_tmp);
3122 im->start = start_tmp;
3124 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3128 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3130 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3131 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3137 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3140 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3142 color=strstr(var,"#");
3145 rrd_set_error("Found no color in %s",err);
3154 rest=strstr(color,":");
3162 sscanf(color,"#%6lx%n",&col,&n);
3163 col = (col << 8) + 0xff /* shift left by 8 */;
3164 if (n!=7) rrd_set_error("Color problem in %s",err);
3167 sscanf(color,"#%8lx%n",&col,&n);
3170 rrd_set_error("Color problem in %s",err);
3172 if (rrd_test_error()) return 0;
3179 int bad_format(char *fmt) {
3183 while (*ptr != '\0')
3184 if (*ptr++ == '%') {
3186 /* line cannot end with percent char */
3187 if (*ptr == '\0') return 1;
3189 /* '%s', '%S' and '%%' are allowed */
3190 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3192 /* or else '% 6.2lf' and such are allowed */
3195 /* optional padding character */
3196 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3198 /* This should take care of 'm.n' with all three optional */
3199 while (*ptr >= '0' && *ptr <= '9') ptr++;
3200 if (*ptr == '.') ptr++;
3201 while (*ptr >= '0' && *ptr <= '9') ptr++;
3203 /* Either 'le', 'lf' or 'lg' must follow here */
3204 if (*ptr++ != 'l') return 1;
3205 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3216 vdef_parse(gdes,str)
3217 struct graph_desc_t *gdes;
3220 /* A VDEF currently is either "func" or "param,func"
3221 * so the parsing is rather simple. Change if needed.
3228 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3229 if (n== (int)strlen(str)) { /* matched */
3233 sscanf(str,"%29[A-Z]%n",func,&n);
3234 if (n== (int)strlen(str)) { /* matched */
3237 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3244 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3245 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3246 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3247 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3248 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3249 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3250 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3252 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3259 switch (gdes->vf.op) {
3261 if (isnan(param)) { /* no parameter given */
3262 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3268 if (param>=0.0 && param<=100.0) {
3269 gdes->vf.param = param;
3270 gdes->vf.val = DNAN; /* undefined */
3271 gdes->vf.when = 0; /* undefined */
3273 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3287 gdes->vf.param = DNAN;
3288 gdes->vf.val = DNAN;
3291 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3308 graph_desc_t *src,*dst;
3312 dst = &im->gdes[gdi];
3313 src = &im->gdes[dst->vidx];
3314 data = src->data + src->ds;
3315 steps = (src->end - src->start) / src->step;
3318 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3325 switch (dst->vf.op) {
3326 case VDEF_PERCENT: {
3327 rrd_value_t * array;
3331 if ((array = malloc(steps*sizeof(double)))==NULL) {
3332 rrd_set_error("malloc VDEV_PERCENT");
3335 for (step=0;step < steps; step++) {
3336 array[step]=data[step*src->ds_cnt];
3338 qsort(array,step,sizeof(double),vdef_percent_compar);
3340 field = (steps-1)*dst->vf.param/100;
3341 dst->vf.val = array[field];
3342 dst->vf.when = 0; /* no time component */
3345 for(step=0;step<steps;step++)
3346 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3352 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3353 if (step == steps) {
3357 dst->vf.val = data[step*src->ds_cnt];
3358 dst->vf.when = src->start + (step+1)*src->step;
3360 while (step != steps) {
3361 if (finite(data[step*src->ds_cnt])) {
3362 if (data[step*src->ds_cnt] > dst->vf.val) {
3363 dst->vf.val = data[step*src->ds_cnt];
3364 dst->vf.when = src->start + (step+1)*src->step;
3371 case VDEF_AVERAGE: {
3374 for (step=0;step<steps;step++) {
3375 if (finite(data[step*src->ds_cnt])) {
3376 sum += data[step*src->ds_cnt];
3381 if (dst->vf.op == VDEF_TOTAL) {
3382 dst->vf.val = sum*src->step;
3383 dst->vf.when = cnt*src->step; /* not really "when" */
3385 dst->vf.val = sum/cnt;
3386 dst->vf.when = 0; /* no time component */
3396 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3397 if (step == steps) {
3401 dst->vf.val = data[step*src->ds_cnt];
3402 dst->vf.when = src->start + (step+1)*src->step;
3404 while (step != steps) {
3405 if (finite(data[step*src->ds_cnt])) {
3406 if (data[step*src->ds_cnt] < dst->vf.val) {
3407 dst->vf.val = data[step*src->ds_cnt];
3408 dst->vf.when = src->start + (step+1)*src->step;
3415 /* The time value returned here is one step before the
3416 * actual time value. This is the start of the first
3420 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3421 if (step == steps) { /* all entries were NaN */
3425 dst->vf.val = data[step*src->ds_cnt];
3426 dst->vf.when = src->start + step*src->step;
3430 /* The time value returned here is the
3431 * actual time value. This is the end of the last
3435 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3436 if (step < 0) { /* all entries were NaN */
3440 dst->vf.val = data[step*src->ds_cnt];
3441 dst->vf.when = src->start + (step+1)*src->step;
3448 /* NaN < -INF < finite_values < INF */
3450 vdef_percent_compar(a,b)
3453 /* Equality is not returned; this doesn't hurt except
3454 * (maybe) for a little performance.
3457 /* First catch NaN values. They are smallest */
3458 if (isnan( *(double *)a )) return -1;
3459 if (isnan( *(double *)b )) return 1;
3461 /* NaN doesn't reach this part so INF and -INF are extremes.
3462 * The sign from isinf() is compatible with the sign we return
3464 if (isinf( *(double *)a )) return isinf( *(double *)a );
3465 if (isinf( *(double *)b )) return isinf( *(double *)b );
3467 /* If we reach this, both values must be finite */
3468 if ( *(double *)a < *(double *)b ) return -1; else return 1;