1 /****************************************************************************
2 * RRDtool 1.1.x Copyright Tobias Oetiker, 1997 - 2002
3 ****************************************************************************
4 * rrd__graph.c make creates ne rrds
5 ****************************************************************************/
25 #include "rrd_graph.h"
26 #include "rrd_graph_helper.h"
28 /* some constant definitions */
31 #ifndef RRD_DEFAULT_FONT
33 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
35 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf"
36 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
41 text_prop_t text_prop[] = {
42 { 10.0, RRD_DEFAULT_FONT }, /* default */
43 { 12.0, RRD_DEFAULT_FONT }, /* title */
44 { 8.0, RRD_DEFAULT_FONT }, /* axis */
45 { 10.0, RRD_DEFAULT_FONT }, /* unit */
46 { 10.0, RRD_DEFAULT_FONT } /* legend */
50 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
51 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
52 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
53 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
54 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
55 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
56 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
57 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
58 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
59 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
60 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
61 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
62 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
63 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
64 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
65 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
68 /* sensible logarithmic y label intervals ...
69 the first element of each row defines the possible starting points on the
70 y axis ... the other specify the */
72 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
73 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
74 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
75 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
76 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
77 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
78 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
79 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
81 /* sensible y label intervals ...*/
99 gfx_color_t graph_col[] = /* default colors */
100 { 0xFFFFFFFF, /* canvas */
101 0xF0F0F0FF, /* background */
102 0xD0D0D0FF, /* shade A */
103 0xA0A0A0FF, /* shade B */
104 0x909090FF, /* grid */
105 0xE05050FF, /* major grid */
106 0x000000FF, /* font */
107 0x000000FF, /* frame */
108 0xFF0000FF /* arrow */
115 # define DPRINT(x) (void)(printf x, printf("\n"))
121 /* initialize with xtr(im,0); */
123 xtr(image_desc_t *im,time_t mytime){
126 pixie = (double) im->xsize / (double)(im->end - im->start);
129 return (int)((double)im->xorigin
130 + pixie * ( mytime - im->start ) );
133 /* translate data values into y coordinates */
135 ytr(image_desc_t *im, double value){
140 pixie = (double) im->ysize / (im->maxval - im->minval);
142 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
144 } else if(!im->logarithmic) {
145 yval = im->yorigin - pixie * (value - im->minval);
147 if (value < im->minval) {
150 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
153 /* make sure we don't return anything too unreasonable. GD lib can
154 get terribly slow when drawing lines outside its scope. This is
155 especially problematic in connection with the rigid option */
157 /* keep yval as-is */
158 } else if (yval > im->yorigin) {
159 yval = im->yorigin+2;
160 } else if (yval < im->yorigin - im->ysize){
161 yval = im->yorigin - im->ysize - 2;
168 /* conversion function for symbolic entry names */
171 #define conv_if(VV,VVV) \
172 if (strcmp(#VV, string) == 0) return VVV ;
174 enum gf_en gf_conv(char *string){
176 conv_if(PRINT,GF_PRINT)
177 conv_if(GPRINT,GF_GPRINT)
178 conv_if(COMMENT,GF_COMMENT)
179 conv_if(HRULE,GF_HRULE)
180 conv_if(VRULE,GF_VRULE)
181 conv_if(LINE,GF_LINE)
182 conv_if(AREA,GF_AREA)
183 conv_if(STACK,GF_STACK)
184 conv_if(TICK,GF_TICK)
186 conv_if(CDEF,GF_CDEF)
187 conv_if(VDEF,GF_VDEF)
188 conv_if(PART,GF_PART)
193 enum gfx_if_en if_conv(char *string){
203 enum tmt_en tmt_conv(char *string){
205 conv_if(SECOND,TMT_SECOND)
206 conv_if(MINUTE,TMT_MINUTE)
207 conv_if(HOUR,TMT_HOUR)
209 conv_if(WEEK,TMT_WEEK)
210 conv_if(MONTH,TMT_MONTH)
211 conv_if(YEAR,TMT_YEAR)
215 enum grc_en grc_conv(char *string){
217 conv_if(BACK,GRC_BACK)
218 conv_if(CANVAS,GRC_CANVAS)
219 conv_if(SHADEA,GRC_SHADEA)
220 conv_if(SHADEB,GRC_SHADEB)
221 conv_if(GRID,GRC_GRID)
222 conv_if(MGRID,GRC_MGRID)
223 conv_if(FONT,GRC_FONT)
224 conv_if(FRAME,GRC_FRAME)
225 conv_if(ARROW,GRC_ARROW)
230 enum text_prop_en text_prop_conv(char *string){
232 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
233 conv_if(TITLE,TEXT_PROP_TITLE)
234 conv_if(AXIS,TEXT_PROP_AXIS)
235 conv_if(UNIT,TEXT_PROP_UNIT)
236 conv_if(LEGEND,TEXT_PROP_LEGEND)
246 im_free(image_desc_t *im)
249 if (im == NULL) return 0;
250 for(i=0;i<im->gdes_c;i++){
251 if (im->gdes[i].data_first){
252 /* careful here, because a single pointer can occur several times */
253 free (im->gdes[i].data);
254 if (im->gdes[i].ds_namv){
255 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
256 free(im->gdes[i].ds_namv[ii]);
257 free(im->gdes[i].ds_namv);
260 free (im->gdes[i].p_data);
261 free (im->gdes[i].rpnp);
264 gfx_destroy(im->canvas);
268 /* find SI magnitude symbol for the given number*/
271 image_desc_t *im, /* image description */
278 char *symbol[] = {"a", /* 10e-18 Atto */
279 "f", /* 10e-15 Femto */
280 "p", /* 10e-12 Pico */
281 "n", /* 10e-9 Nano */
282 "u", /* 10e-6 Micro */
283 "m", /* 10e-3 Milli */
288 "T", /* 10e12 Tera */
289 "P", /* 10e15 Peta */
295 if (*value == 0.0 || isnan(*value) ) {
299 sindex = floor(log(fabs(*value))/log((double)im->base));
300 *magfact = pow((double)im->base, (double)sindex);
301 (*value) /= (*magfact);
303 if ( sindex <= symbcenter && sindex >= -symbcenter) {
304 (*symb_ptr) = symbol[sindex+symbcenter];
312 /* find SI magnitude symbol for the numbers on the y-axis*/
315 image_desc_t *im /* image description */
319 char symbol[] = {'a', /* 10e-18 Atto */
320 'f', /* 10e-15 Femto */
321 'p', /* 10e-12 Pico */
322 'n', /* 10e-9 Nano */
323 'u', /* 10e-6 Micro */
324 'm', /* 10e-3 Milli */
329 'T', /* 10e12 Tera */
330 'P', /* 10e15 Peta */
336 if (im->unitsexponent != 9999) {
337 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
338 digits = floor(im->unitsexponent / 3);
340 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
342 im->magfact = pow((double)im->base , digits);
345 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
348 if ( ((digits+symbcenter) < sizeof(symbol)) &&
349 ((digits+symbcenter) >= 0) )
350 im->symbol = symbol[(int)digits+symbcenter];
355 /* move min and max values around to become sensible */
358 expand_range(image_desc_t *im)
360 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
361 600.0,500.0,400.0,300.0,250.0,
362 200.0,125.0,100.0,90.0,80.0,
363 75.0,70.0,60.0,50.0,40.0,30.0,
364 25.0,20.0,10.0,9.0,8.0,
365 7.0,6.0,5.0,4.0,3.5,3.0,
366 2.5,2.0,1.8,1.5,1.2,1.0,
367 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
369 double scaled_min,scaled_max;
376 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
377 im->minval,im->maxval,im->magfact);
380 if (isnan(im->ygridstep)){
381 if(im->extra_flags & ALTAUTOSCALE) {
382 /* measure the amplitude of the function. Make sure that
383 graph boundaries are slightly higher then max/min vals
384 so we can see amplitude on the graph */
387 delt = im->maxval - im->minval;
389 fact = 2.0 * pow(10.0,
390 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
392 adj = (fact - delt) * 0.55;
394 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
400 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
401 /* measure the amplitude of the function. Make sure that
402 graph boundaries are slightly higher than max vals
403 so we can see amplitude on the graph */
404 adj = (im->maxval - im->minval) * 0.1;
408 scaled_min = im->minval / im->magfact;
409 scaled_max = im->maxval / im->magfact;
411 for (i=1; sensiblevalues[i] > 0; i++){
412 if (sensiblevalues[i-1]>=scaled_min &&
413 sensiblevalues[i]<=scaled_min)
414 im->minval = sensiblevalues[i]*(im->magfact);
416 if (-sensiblevalues[i-1]<=scaled_min &&
417 -sensiblevalues[i]>=scaled_min)
418 im->minval = -sensiblevalues[i-1]*(im->magfact);
420 if (sensiblevalues[i-1] >= scaled_max &&
421 sensiblevalues[i] <= scaled_max)
422 im->maxval = sensiblevalues[i-1]*(im->magfact);
424 if (-sensiblevalues[i-1]<=scaled_max &&
425 -sensiblevalues[i] >=scaled_max)
426 im->maxval = -sensiblevalues[i]*(im->magfact);
430 /* adjust min and max to the grid definition if there is one */
431 im->minval = (double)im->ylabfact * im->ygridstep *
432 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
433 im->maxval = (double)im->ylabfact * im->ygridstep *
434 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
438 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
439 im->minval,im->maxval,im->magfact);
444 apply_gridfit(image_desc_t *im)
446 if (isnan(im->minval) || isnan(im->maxval))
449 if (im->logarithmic) {
450 double ya, yb, ypix, ypixfrac;
451 double log10_range = log10(im->maxval) - log10(im->minval);
452 ya = pow((double)10, floor(log10(im->minval)));
453 while (ya < im->minval)
456 return; /* don't have y=10^x gridline */
458 if (yb <= im->maxval) {
459 /* we have at least 2 y=10^x gridlines.
460 Make sure distance between them in pixels
461 are an integer by expanding im->maxval */
462 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
463 double factor = y_pixel_delta / floor(y_pixel_delta);
464 double new_log10_range = factor * log10_range;
465 double new_ymax_log10 = log10(im->minval) + new_log10_range;
466 im->maxval = pow(10, new_ymax_log10);
467 ytr(im, DNAN); /* reset precalc */
468 log10_range = log10(im->maxval) - log10(im->minval);
470 /* make sure first y=10^x gridline is located on
471 integer pixel position by moving scale slightly
472 downwards (sub-pixel movement) */
473 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
474 ypixfrac = ypix - floor(ypix);
475 if (ypixfrac > 0 && ypixfrac < 1) {
476 double yfrac = ypixfrac / im->ysize;
477 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
478 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
479 ytr(im, DNAN); /* reset precalc */
482 /* Make sure we have an integer pixel distance between
483 each minor gridline */
484 double ypos1 = ytr(im, im->minval);
485 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
486 double y_pixel_delta = ypos1 - ypos2;
487 double factor = y_pixel_delta / floor(y_pixel_delta);
488 double new_range = factor * (im->maxval - im->minval);
489 double gridstep = im->ygrid_scale.gridstep;
490 double minor_y, minor_y_px, minor_y_px_frac;
491 im->maxval = im->minval + new_range;
492 ytr(im, DNAN); /* reset precalc */
493 /* make sure first minor gridline is on integer pixel y coord */
494 minor_y = gridstep * floor(im->minval / gridstep);
495 while (minor_y < im->minval)
497 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
498 minor_y_px_frac = minor_y_px - floor(minor_y_px);
499 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
500 double yfrac = minor_y_px_frac / im->ysize;
501 double range = im->maxval - im->minval;
502 im->minval = im->minval - yfrac * range;
503 im->maxval = im->maxval - yfrac * range;
504 ytr(im, DNAN); /* reset precalc */
506 calc_horizontal_grid(im); /* recalc with changed im->maxval */
510 /* reduce data reimplementation by Alex */
514 enum cf_en cf, /* which consolidation function ?*/
515 unsigned long cur_step, /* step the data currently is in */
516 time_t *start, /* start, end and step as requested ... */
517 time_t *end, /* ... by the application will be ... */
518 unsigned long *step, /* ... adjusted to represent reality */
519 unsigned long *ds_cnt, /* number of data sources in file */
520 rrd_value_t **data) /* two dimensional array containing the data */
522 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
523 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
524 rrd_value_t *srcptr,*dstptr;
526 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
529 row_cnt = ((*end)-(*start))/cur_step;
535 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
536 row_cnt,reduce_factor,*start,*end,cur_step);
537 for (col=0;col<row_cnt;col++) {
538 printf("time %10lu: ",*start+(col+1)*cur_step);
539 for (i=0;i<*ds_cnt;i++)
540 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
545 /* We have to combine [reduce_factor] rows of the source
546 ** into one row for the destination. Doing this we also
547 ** need to take care to combine the correct rows. First
548 ** alter the start and end time so that they are multiples
549 ** of the new step time. We cannot reduce the amount of
550 ** time so we have to move the end towards the future and
551 ** the start towards the past.
553 end_offset = (*end) % (*step);
554 start_offset = (*start) % (*step);
556 /* If there is a start offset (which cannot be more than
557 ** one destination row), skip the appropriate number of
558 ** source rows and one destination row. The appropriate
559 ** number is what we do know (start_offset/cur_step) of
560 ** the new interval (*step/cur_step aka reduce_factor).
563 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
564 printf("row_cnt before: %lu\n",row_cnt);
567 (*start) = (*start)-start_offset;
568 skiprows=reduce_factor-start_offset/cur_step;
569 srcptr+=skiprows* *ds_cnt;
570 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
574 printf("row_cnt between: %lu\n",row_cnt);
577 /* At the end we have some rows that are not going to be
578 ** used, the amount is end_offset/cur_step
581 (*end) = (*end)-end_offset+(*step);
582 skiprows = end_offset/cur_step;
586 printf("row_cnt after: %lu\n",row_cnt);
589 /* Sanity check: row_cnt should be multiple of reduce_factor */
590 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
592 if (row_cnt%reduce_factor) {
593 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
594 row_cnt,reduce_factor);
595 printf("BUG in reduce_data()\n");
599 /* Now combine reduce_factor intervals at a time
600 ** into one interval for the destination.
603 for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
604 for (col=0;col<(*ds_cnt);col++) {
605 rrd_value_t newval=DNAN;
606 unsigned long validval=0;
608 for (i=0;i<reduce_factor;i++) {
609 if (isnan(srcptr[i*(*ds_cnt)+col])) {
613 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
621 newval += srcptr[i*(*ds_cnt)+col];
624 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
627 /* an interval contains a failure if any subintervals contained a failure */
629 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
632 newval = srcptr[i*(*ds_cnt)+col];
637 if (validval == 0){newval = DNAN;} else{
655 srcptr+=(*ds_cnt)*reduce_factor;
656 row_cnt-=reduce_factor;
658 /* If we had to alter the endtime, we didn't have enough
659 ** source rows to fill the last row. Fill it with NaN.
661 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
663 row_cnt = ((*end)-(*start))/ *step;
665 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
666 row_cnt,*start,*end,*step);
667 for (col=0;col<row_cnt;col++) {
668 printf("time %10lu: ",*start+(col+1)*(*step));
669 for (i=0;i<*ds_cnt;i++)
670 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
677 /* get the data required for the graphs from the
681 data_fetch( image_desc_t *im )
685 /* pull the data from the log files ... */
686 for (i=0;i<im->gdes_c;i++){
687 /* only GF_DEF elements fetch data */
688 if (im->gdes[i].gf != GF_DEF)
692 /* do we have it already ?*/
693 for (ii=0;ii<i;ii++){
694 if (im->gdes[ii].gf != GF_DEF)
696 if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
697 && (im->gdes[i].cf == im->gdes[ii].cf)){
698 /* OK the data it is here already ...
699 * we just copy the header portion */
700 im->gdes[i].start = im->gdes[ii].start;
701 im->gdes[i].end = im->gdes[ii].end;
702 im->gdes[i].step = im->gdes[ii].step;
703 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
704 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
705 im->gdes[i].data = im->gdes[ii].data;
706 im->gdes[i].data_first = 0;
713 unsigned long ft_step = im->gdes[i].step ;
715 if((rrd_fetch_fn(im->gdes[i].rrd,
721 &im->gdes[i].ds_namv,
722 &im->gdes[i].data)) == -1){
725 im->gdes[i].data_first = 1;
727 if (ft_step < im->gdes[i].step) {
728 reduce_data(im->gdes[i].cf,
736 im->gdes[i].step = ft_step;
740 /* lets see if the required data source is realy there */
741 for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
742 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
745 if (im->gdes[i].ds== -1){
746 rrd_set_error("No DS called '%s' in '%s'",
747 im->gdes[i].ds_nam,im->gdes[i].rrd);
755 /* evaluate the expressions in the CDEF functions */
757 /*************************************************************
759 *************************************************************/
762 find_var_wrapper(void *arg1, char *key)
764 return find_var((image_desc_t *) arg1, key);
767 /* find gdes containing var*/
769 find_var(image_desc_t *im, char *key){
771 for(ii=0;ii<im->gdes_c-1;ii++){
772 if((im->gdes[ii].gf == GF_DEF
773 || im->gdes[ii].gf == GF_VDEF
774 || im->gdes[ii].gf == GF_CDEF)
775 && (strcmp(im->gdes[ii].vname,key) == 0)){
782 /* find the largest common denominator for all the numbers
783 in the 0 terminated num array */
788 for (i=0;num[i+1]!=0;i++){
790 rest=num[i] % num[i+1];
791 num[i]=num[i+1]; num[i+1]=rest;
795 /* return i==0?num[i]:num[i-1]; */
799 /* run the rpn calculator on all the VDEF and CDEF arguments */
801 data_calc( image_desc_t *im){
805 long *steparray, rpi;
810 rpnstack_init(&rpnstack);
812 for (gdi=0;gdi<im->gdes_c;gdi++){
813 /* Look for GF_VDEF and GF_CDEF in the same loop,
814 * so CDEFs can use VDEFs and vice versa
816 switch (im->gdes[gdi].gf) {
818 /* A VDEF has no DS. This also signals other parts
819 * of rrdtool that this is a VDEF value, not a CDEF.
821 im->gdes[gdi].ds_cnt = 0;
822 if (vdef_calc(im,gdi)) {
823 rrd_set_error("Error processing VDEF '%s'"
826 rpnstack_free(&rpnstack);
831 im->gdes[gdi].ds_cnt = 1;
832 im->gdes[gdi].ds = 0;
833 im->gdes[gdi].data_first = 1;
834 im->gdes[gdi].start = 0;
835 im->gdes[gdi].end = 0;
840 /* Find the variables in the expression.
841 * - VDEF variables are substituted by their values
842 * and the opcode is changed into OP_NUMBER.
843 * - CDEF variables are analized for their step size,
844 * the lowest common denominator of all the step
845 * sizes of the data sources involved is calculated
846 * and the resulting number is the step size for the
847 * resulting data source.
849 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
850 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
851 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
852 if (im->gdes[ptr].ds_cnt == 0) {
854 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
856 im->gdes[ptr].vname);
857 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
859 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
860 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
862 if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
863 rrd_set_error("realloc steparray");
864 rpnstack_free(&rpnstack);
868 steparray[stepcnt-1] = im->gdes[ptr].step;
870 /* adjust start and end of cdef (gdi) so
871 * that it runs from the latest start point
872 * to the earliest endpoint of any of the
873 * rras involved (ptr)
875 if(im->gdes[gdi].start < im->gdes[ptr].start)
876 im->gdes[gdi].start = im->gdes[ptr].start;
878 if(im->gdes[gdi].end == 0 ||
879 im->gdes[gdi].end > im->gdes[ptr].end)
880 im->gdes[gdi].end = im->gdes[ptr].end;
882 /* store pointer to the first element of
883 * the rra providing data for variable,
884 * further save step size and data source
887 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
888 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
889 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
891 /* backoff the *.data ptr; this is done so
892 * rpncalc() function doesn't have to treat
893 * the first case differently
895 } /* if ds_cnt != 0 */
896 } /* if OP_VARIABLE */
897 } /* loop through all rpi */
899 /* move the data pointers to the correct period */
900 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
901 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
902 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
903 if(im->gdes[gdi].start > im->gdes[ptr].start) {
904 im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
910 if(steparray == NULL){
911 rrd_set_error("rpn expressions without DEF"
912 " or CDEF variables are not supported");
913 rpnstack_free(&rpnstack);
916 steparray[stepcnt]=0;
917 /* Now find the resulting step. All steps in all
918 * used RRAs have to be visited
920 im->gdes[gdi].step = lcd(steparray);
922 if((im->gdes[gdi].data = malloc((
923 (im->gdes[gdi].end-im->gdes[gdi].start)
924 / im->gdes[gdi].step)
925 * sizeof(double)))==NULL){
926 rrd_set_error("malloc im->gdes[gdi].data");
927 rpnstack_free(&rpnstack);
931 /* Step through the new cdef results array and
932 * calculate the values
934 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
935 now<=im->gdes[gdi].end;
936 now += im->gdes[gdi].step)
938 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
940 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
941 * in this case we are advancing by timesteps;
942 * we use the fact that time_t is a synonym for long
944 if (rpn_calc(rpnp,&rpnstack,(long) now,
945 im->gdes[gdi].data,++dataidx) == -1) {
946 /* rpn_calc sets the error string */
947 rpnstack_free(&rpnstack);
950 } /* enumerate over time steps within a CDEF */
955 } /* enumerate over CDEFs */
956 rpnstack_free(&rpnstack);
960 /* massage data so, that we get one value for each x coordinate in the graph */
962 data_proc( image_desc_t *im ){
964 double pixstep = (double)(im->end-im->start)
965 /(double)im->xsize; /* how much time
966 passes in one pixel */
968 double minval=DNAN,maxval=DNAN;
970 unsigned long gr_time;
972 /* memory for the processed data */
973 for(i=0;i<im->gdes_c;i++){
974 if((im->gdes[i].gf==GF_LINE) ||
975 (im->gdes[i].gf==GF_AREA) ||
976 (im->gdes[i].gf==GF_TICK) ||
977 (im->gdes[i].gf==GF_STACK)){
978 if((im->gdes[i].p_data = malloc((im->xsize +1)
979 * sizeof(rrd_value_t)))==NULL){
980 rrd_set_error("malloc data_proc");
986 for(i=0;i<im->xsize;i++){
988 gr_time = im->start+pixstep*i; /* time of the
992 for(ii=0;ii<im->gdes_c;ii++){
994 switch(im->gdes[ii].gf){
1000 vidx = im->gdes[ii].vidx;
1003 im->gdes[vidx].data[
1004 ((unsigned long)floor(
1005 (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
1007 ) *im->gdes[vidx].ds_cnt
1008 +im->gdes[vidx].ds];
1010 if (! isnan(value)) {
1012 im->gdes[ii].p_data[i] = paintval;
1013 /* GF_TICK: the data values are not relevant for min and max */
1014 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1015 if (isnan(minval) || paintval < minval)
1017 if (isnan(maxval) || paintval > maxval)
1021 im->gdes[ii].p_data[i] = DNAN;
1038 /* if min or max have not been asigned a value this is because
1039 there was no data in the graph ... this is not good ...
1040 lets set these to dummy values then ... */
1042 if (isnan(minval)) minval = 0.0;
1043 if (isnan(maxval)) maxval = 1.0;
1045 /* adjust min and max values */
1046 if (isnan(im->minval)
1047 || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1048 && im->minval > minval))
1049 im->minval = minval;
1050 if (isnan(im->maxval)
1052 && im->maxval < maxval)){
1053 if (im->logarithmic)
1054 im->maxval = maxval * 1.1;
1056 im->maxval = maxval;
1058 /* make sure min and max are not equal */
1059 if (im->minval == im->maxval) {
1061 if (! im->logarithmic) {
1065 /* make sure min and max are not both zero */
1066 if (im->maxval == 0.0) {
1076 /* identify the point where the first gridline, label ... gets placed */
1080 time_t start, /* what is the initial time */
1081 enum tmt_en baseint, /* what is the basic interval */
1082 long basestep /* how many if these do we jump a time */
1086 tm = *localtime(&start);
1089 tm.tm_sec -= tm.tm_sec % basestep; break;
1092 tm.tm_min -= tm.tm_min % basestep;
1097 tm.tm_hour -= tm.tm_hour % basestep; break;
1099 /* we do NOT look at the basestep for this ... */
1102 tm.tm_hour = 0; break;
1104 /* we do NOT look at the basestep for this ... */
1108 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1109 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1116 tm.tm_mon -= tm.tm_mon % basestep; break;
1124 tm.tm_year -= (tm.tm_year+1900) % basestep;
1129 /* identify the point where the next gridline, label ... gets placed */
1132 time_t current, /* what is the initial time */
1133 enum tmt_en baseint, /* what is the basic interval */
1134 long basestep /* how many if these do we jump a time */
1139 tm = *localtime(¤t);
1143 tm.tm_sec += basestep; break;
1145 tm.tm_min += basestep; break;
1147 tm.tm_hour += basestep; break;
1149 tm.tm_mday += basestep; break;
1151 tm.tm_mday += 7*basestep; break;
1153 tm.tm_mon += basestep; break;
1155 tm.tm_year += basestep;
1157 madetime = mktime(&tm);
1158 } while (madetime == -1); /* this is necessary to skip impssible times
1159 like the daylight saving time skips */
1165 /* calculate values required for PRINT and GPRINT functions */
1168 print_calc(image_desc_t *im, char ***prdata)
1170 long i,ii,validsteps;
1173 int graphelement = 0;
1176 double magfact = -1;
1180 if (im->imginfo) prlines++;
1181 for(i=0;i<im->gdes_c;i++){
1182 switch(im->gdes[i].gf){
1185 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1186 rrd_set_error("realloc prdata");
1190 /* PRINT and GPRINT can now print VDEF generated values.
1191 * There's no need to do any calculations on them as these
1192 * calculations were already made.
1194 vidx = im->gdes[i].vidx;
1195 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1196 printval = im->gdes[vidx].vf.val;
1197 printtime = im->gdes[vidx].vf.when;
1198 } else { /* need to calculate max,min,avg etcetera */
1199 max_ii =((im->gdes[vidx].end
1200 - im->gdes[vidx].start)
1201 / im->gdes[vidx].step
1202 * im->gdes[vidx].ds_cnt);
1205 for( ii=im->gdes[vidx].ds;
1207 ii+=im->gdes[vidx].ds_cnt){
1208 if (! finite(im->gdes[vidx].data[ii]))
1210 if (isnan(printval)){
1211 printval = im->gdes[vidx].data[ii];
1216 switch (im->gdes[i].cf){
1219 case CF_DEVSEASONAL:
1223 printval += im->gdes[vidx].data[ii];
1226 printval = min( printval, im->gdes[vidx].data[ii]);
1230 printval = max( printval, im->gdes[vidx].data[ii]);
1233 printval = im->gdes[vidx].data[ii];
1236 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1237 if (validsteps > 1) {
1238 printval = (printval / validsteps);
1241 } /* prepare printval */
1243 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1244 if (im->gdes[i].gf == GF_PRINT){
1245 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1246 sprintf((*prdata)[prlines-2],"%s (%lu)",
1247 ctime(&printtime),printtime);
1248 (*prdata)[prlines-1] = NULL;
1250 sprintf(im->gdes[i].legend,"%s (%lu)",
1251 ctime(&printtime),printtime);
1255 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1256 /* Magfact is set to -1 upon entry to print_calc. If it
1257 * is still less than 0, then we need to run auto_scale.
1258 * Otherwise, put the value into the correct units. If
1259 * the value is 0, then do not set the symbol or magnification
1260 * so next the calculation will be performed again. */
1261 if (magfact < 0.0) {
1262 auto_scale(im,&printval,&si_symb,&magfact);
1263 if (printval == 0.0)
1266 printval /= magfact;
1268 *(++percent_s) = 's';
1269 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1270 auto_scale(im,&printval,&si_symb,&magfact);
1273 if (im->gdes[i].gf == GF_PRINT){
1274 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1275 if (bad_format(im->gdes[i].format)) {
1276 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1279 #ifdef HAVE_SNPRINTF
1280 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1282 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1284 (*prdata)[prlines-1] = NULL;
1288 if (bad_format(im->gdes[i].format)) {
1289 rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1292 #ifdef HAVE_SNPRINTF
1293 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1295 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1317 return graphelement;
1321 /* place legends with color spots */
1323 leg_place(image_desc_t *im)
1326 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1327 int box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1328 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1329 int fill=0, fill_last;
1331 int leg_x = border, leg_y = im->yimg;
1335 char prt_fctn; /*special printfunctions */
1338 if( !(im->extra_flags & NOLEGEND) ) {
1339 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1340 rrd_set_error("malloc for legspace");
1344 for(i=0;i<im->gdes_c;i++){
1347 leg_cc = strlen(im->gdes[i].legend);
1349 /* is there a controle code ant the end of the legend string ? */
1350 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1351 prt_fctn = im->gdes[i].legend[leg_cc-1];
1353 im->gdes[i].legend[leg_cc] = '\0';
1357 /* remove exess space */
1358 while (prt_fctn=='g' &&
1360 im->gdes[i].legend[leg_cc-1]==' '){
1362 im->gdes[i].legend[leg_cc]='\0';
1365 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1368 /* no interleg space if string ends in \g */
1369 fill += legspace[i];
1371 if (im->gdes[i].gf != GF_GPRINT &&
1372 im->gdes[i].gf != GF_COMMENT) {
1375 fill += gfx_get_text_width(im->canvas, fill+border,
1376 im->text_prop[TEXT_PROP_LEGEND].font,
1377 im->text_prop[TEXT_PROP_LEGEND].size,
1379 im->gdes[i].legend);
1384 /* who said there was a special tag ... ?*/
1385 if (prt_fctn=='g') {
1388 if (prt_fctn == '\0') {
1389 if (i == im->gdes_c -1 ) prt_fctn ='l';
1391 /* is it time to place the legends ? */
1392 if (fill > im->ximg - 2*border){
1407 if (prt_fctn != '\0'){
1409 if (leg_c >= 2 && prt_fctn == 'j') {
1410 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1414 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1415 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1417 for(ii=mark;ii<=i;ii++){
1418 if(im->gdes[ii].legend[0]=='\0')
1420 im->gdes[ii].leg_x = leg_x;
1421 im->gdes[ii].leg_y = leg_y;
1423 gfx_get_text_width(im->canvas, leg_x,
1424 im->text_prop[TEXT_PROP_LEGEND].font,
1425 im->text_prop[TEXT_PROP_LEGEND].size,
1427 im->gdes[ii].legend)
1430 if (im->gdes[ii].gf != GF_GPRINT &&
1431 im->gdes[ii].gf != GF_COMMENT)
1434 leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1435 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1447 /* create a grid on the graph. it determines what to do
1448 from the values of xsize, start and end */
1450 /* the xaxis labels are determined from the number of seconds per pixel
1451 in the requested graph */
1456 calc_horizontal_grid(image_desc_t *im)
1462 int decimals, fractionals;
1464 im->ygrid_scale.labfact=2;
1466 range = im->maxval - im->minval;
1467 scaledrange = range / im->magfact;
1469 /* does the scale of this graph make it impossible to put lines
1470 on it? If so, give up. */
1471 if (isnan(scaledrange)) {
1475 /* find grid spaceing */
1477 if(isnan(im->ygridstep)){
1478 if(im->extra_flags & ALTYGRID) {
1479 /* find the value with max number of digits. Get number of digits */
1480 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1481 if(decimals <= 0) /* everything is small. make place for zero */
1484 fractionals = floor(log10(range));
1485 if(fractionals < 0) /* small amplitude. */
1486 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1488 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1489 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1490 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1491 im->ygrid_scale.gridstep = 0.1;
1492 /* should have at least 5 lines but no more then 15 */
1493 if(range/im->ygrid_scale.gridstep < 5)
1494 im->ygrid_scale.gridstep /= 10;
1495 if(range/im->ygrid_scale.gridstep > 15)
1496 im->ygrid_scale.gridstep *= 10;
1497 if(range/im->ygrid_scale.gridstep > 5) {
1498 im->ygrid_scale.labfact = 1;
1499 if(range/im->ygrid_scale.gridstep > 8)
1500 im->ygrid_scale.labfact = 2;
1503 im->ygrid_scale.gridstep /= 5;
1504 im->ygrid_scale.labfact = 5;
1508 for(i=0;ylab[i].grid > 0;i++){
1509 pixel = im->ysize / (scaledrange / ylab[i].grid);
1510 if (gridind == -1 && pixel > 5) {
1517 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1518 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1523 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1526 im->ygrid_scale.gridstep = im->ygridstep;
1527 im->ygrid_scale.labfact = im->ylabfact;
1532 int draw_horizontal_grid(image_desc_t *im)
1536 char graph_label[100];
1537 double X0=im->xorigin;
1538 double X1=im->xorigin+im->xsize;
1540 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1541 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1542 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1543 for (i = sgrid; i <= egrid; i++){
1544 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1545 if ( Y0 >= im->yorigin-im->ysize
1546 && Y0 <= im->yorigin){
1547 if(i % im->ygrid_scale.labfact == 0){
1548 if (i==0 || im->symbol == ' ') {
1550 if(im->extra_flags & ALTYGRID) {
1551 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1554 sprintf(graph_label,"%4.1f",scaledstep*i);
1557 sprintf(graph_label,"%4.0f",scaledstep*i);
1561 sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1563 sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1567 gfx_new_text ( im->canvas,
1568 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1569 im->graph_col[GRC_FONT],
1570 im->text_prop[TEXT_PROP_AXIS].font,
1571 im->text_prop[TEXT_PROP_AXIS].size,
1572 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1574 gfx_new_dashed_line ( im->canvas,
1577 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1578 im->grid_dash_on, im->grid_dash_off);
1581 gfx_new_dashed_line ( im->canvas,
1584 GRIDWIDTH, im->graph_col[GRC_GRID],
1585 im->grid_dash_on, im->grid_dash_off);
1593 /* logaritmic horizontal grid */
1595 horizontal_log_grid(image_desc_t *im)
1599 int minoridx=0, majoridx=0;
1600 char graph_label[100];
1602 double value, pixperstep, minstep;
1604 /* find grid spaceing */
1605 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1607 if (isnan(pixpex)) {
1611 for(i=0;yloglab[i][0] > 0;i++){
1612 minstep = log10(yloglab[i][0]);
1613 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1614 if(yloglab[i][ii+2]==0){
1615 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1619 pixperstep = pixpex * minstep;
1620 if(pixperstep > 5){minoridx = i;}
1621 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1625 X1=im->xorigin+im->xsize;
1626 /* paint minor grid */
1627 for (value = pow((double)10, log10(im->minval)
1628 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1629 value <= im->maxval;
1630 value *= yloglab[minoridx][0]){
1631 if (value < im->minval) continue;
1633 while(yloglab[minoridx][++i] > 0){
1634 Y0 = ytr(im,value * yloglab[minoridx][i]);
1635 if (Y0 <= im->yorigin - im->ysize) break;
1636 gfx_new_dashed_line ( im->canvas,
1639 GRIDWIDTH, im->graph_col[GRC_GRID],
1640 im->grid_dash_on, im->grid_dash_off);
1644 /* paint major grid and labels*/
1645 for (value = pow((double)10, log10(im->minval)
1646 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1647 value <= im->maxval;
1648 value *= yloglab[majoridx][0]){
1649 if (value < im->minval) continue;
1651 while(yloglab[majoridx][++i] > 0){
1652 Y0 = ytr(im,value * yloglab[majoridx][i]);
1653 if (Y0 <= im->yorigin - im->ysize) break;
1654 gfx_new_dashed_line ( im->canvas,
1657 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1658 im->grid_dash_on, im->grid_dash_off);
1660 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1661 gfx_new_text ( im->canvas,
1662 X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1663 im->graph_col[GRC_FONT],
1664 im->text_prop[TEXT_PROP_AXIS].font,
1665 im->text_prop[TEXT_PROP_AXIS].size,
1666 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1678 int xlab_sel; /* which sort of label and grid ? */
1679 time_t ti, tilab, timajor;
1681 char graph_label[100];
1682 double X0,Y0,Y1; /* points for filled graph and more*/
1685 /* the type of time grid is determined by finding
1686 the number of seconds per pixel in the graph */
1689 if(im->xlab_user.minsec == -1){
1690 factor=(im->end - im->start)/im->xsize;
1692 while ( xlab[xlab_sel+1].minsec != -1
1693 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1694 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1695 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1696 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1697 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1698 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1699 im->xlab_user.labst = xlab[xlab_sel].labst;
1700 im->xlab_user.precis = xlab[xlab_sel].precis;
1701 im->xlab_user.stst = xlab[xlab_sel].stst;
1704 /* y coords are the same for every line ... */
1706 Y1 = im->yorigin-im->ysize;
1709 /* paint the minor grid */
1710 for(ti = find_first_time(im->start,
1711 im->xlab_user.gridtm,
1712 im->xlab_user.gridst),
1713 timajor = find_first_time(im->start,
1714 im->xlab_user.mgridtm,
1715 im->xlab_user.mgridst);
1717 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1719 /* are we inside the graph ? */
1720 if (ti < im->start || ti > im->end) continue;
1721 while (timajor < ti) {
1722 timajor = find_next_time(timajor,
1723 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1725 if (ti == timajor) continue; /* skip as falls on major grid line */
1727 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1728 im->graph_col[GRC_GRID],
1729 im->grid_dash_on, im->grid_dash_off);
1733 /* paint the major grid */
1734 for(ti = find_first_time(im->start,
1735 im->xlab_user.mgridtm,
1736 im->xlab_user.mgridst);
1738 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1740 /* are we inside the graph ? */
1741 if (ti < im->start || ti > im->end) continue;
1743 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1744 im->graph_col[GRC_MGRID],
1745 im->grid_dash_on, im->grid_dash_off);
1748 /* paint the labels below the graph */
1749 for(ti = find_first_time(im->start,
1750 im->xlab_user.labtm,
1751 im->xlab_user.labst);
1753 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1755 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1756 /* are we inside the graph ? */
1757 if (ti < im->start || ti > im->end) continue;
1760 strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1762 # error "your libc has no strftime I guess we'll abort the exercise here."
1764 gfx_new_text ( im->canvas,
1765 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1766 im->graph_col[GRC_FONT],
1767 im->text_prop[TEXT_PROP_AXIS].font,
1768 im->text_prop[TEXT_PROP_AXIS].size,
1769 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1782 /* draw x and y axis */
1783 gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1784 im->xorigin+im->xsize,im->yorigin-im->ysize,
1785 GRIDWIDTH, im->graph_col[GRC_GRID]);
1787 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1788 im->xorigin+im->xsize,im->yorigin-im->ysize,
1789 GRIDWIDTH, im->graph_col[GRC_GRID]);
1791 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1792 im->xorigin+im->xsize+4,im->yorigin,
1793 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1795 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1796 im->xorigin,im->yorigin-im->ysize-4,
1797 MGRIDWIDTH, im->graph_col[GRC_GRID]);
1800 /* arrow for X axis direction */
1801 gfx_new_area ( im->canvas,
1802 im->xorigin+im->xsize+3, im->yorigin-3,
1803 im->xorigin+im->xsize+3, im->yorigin+4,
1804 im->xorigin+im->xsize+8, im->yorigin+0.5, /* LINEOFFSET */
1805 im->graph_col[GRC_ARROW]);
1812 grid_paint(image_desc_t *im)
1816 double X0,Y0; /* points for filled graph and more*/
1819 /* draw 3d border */
1820 node = gfx_new_area (im->canvas, 0,im->yimg,
1822 2,2,im->graph_col[GRC_SHADEA]);
1823 gfx_add_point( node , im->ximg - 2, 2 );
1824 gfx_add_point( node , im->ximg, 0 );
1825 gfx_add_point( node , 0,0 );
1826 /* gfx_add_point( node , 0,im->yimg ); */
1828 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1829 im->ximg-2,im->yimg-2,
1831 im->graph_col[GRC_SHADEB]);
1832 gfx_add_point( node , im->ximg,0);
1833 gfx_add_point( node , im->ximg,im->yimg);
1834 gfx_add_point( node , 0,im->yimg);
1835 /* gfx_add_point( node , 0,im->yimg ); */
1838 if (im->draw_x_grid == 1 )
1841 if (im->draw_y_grid == 1){
1842 if(im->logarithmic){
1843 res = horizontal_log_grid(im);
1845 res = draw_horizontal_grid(im);
1848 /* dont draw horizontal grid if there is no min and max val */
1850 char *nodata = "No Data found";
1851 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1852 im->graph_col[GRC_FONT],
1853 im->text_prop[TEXT_PROP_AXIS].font,
1854 im->text_prop[TEXT_PROP_AXIS].size,
1855 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1860 /* yaxis description */
1861 if (im->canvas->imgformat != IF_PNG) {
1862 gfx_new_text( im->canvas,
1863 7, (im->yorigin - im->ysize/2),
1864 im->graph_col[GRC_FONT],
1865 im->text_prop[TEXT_PROP_AXIS].font,
1866 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1867 GFX_H_CENTER, GFX_V_CENTER,
1870 /* horrible hack until we can actually print vertically */
1873 int l=strlen(im->ylegend);
1875 for (n=0;n<strlen(im->ylegend);n++) {
1876 s[0]=im->ylegend[n];
1878 gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1879 im->graph_col[GRC_FONT],
1880 im->text_prop[TEXT_PROP_AXIS].font,
1881 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1882 GFX_H_CENTER, GFX_V_CENTER,
1889 gfx_new_text( im->canvas,
1890 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1891 im->graph_col[GRC_FONT],
1892 im->text_prop[TEXT_PROP_TITLE].font,
1893 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1894 GFX_H_CENTER, GFX_V_CENTER,
1898 if( !(im->extra_flags & NOLEGEND) ) {
1899 for(i=0;i<im->gdes_c;i++){
1900 if(im->gdes[i].legend[0] =='\0')
1903 /* im->gdes[i].leg_y is the bottom of the legend */
1904 X0 = im->gdes[i].leg_x;
1905 Y0 = im->gdes[i].leg_y;
1907 if ( im->gdes[i].gf != GF_GPRINT
1908 && im->gdes[i].gf != GF_COMMENT) {
1911 boxH = gfx_get_text_width(im->canvas, 0,
1912 im->text_prop[TEXT_PROP_AXIS].font,
1913 im->text_prop[TEXT_PROP_AXIS].size,
1914 im->tabwidth,"M") * 1.25;
1917 node = gfx_new_area(im->canvas,
1922 gfx_add_point ( node, X0+boxH, Y0-boxV );
1923 node = gfx_new_line(im->canvas,
1926 gfx_add_point(node,X0+boxH,Y0);
1927 gfx_add_point(node,X0+boxH,Y0-boxV);
1928 gfx_close_path(node);
1929 X0 += boxH / 1.25 * 2;
1931 gfx_new_text ( im->canvas, X0, Y0,
1932 im->graph_col[GRC_FONT],
1933 im->text_prop[TEXT_PROP_AXIS].font,
1934 im->text_prop[TEXT_PROP_AXIS].size,
1935 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1936 im->gdes[i].legend );
1942 /*****************************************************
1943 * lazy check make sure we rely need to create this graph
1944 *****************************************************/
1946 int lazy_check(image_desc_t *im){
1949 struct stat imgstat;
1951 if (im->lazy == 0) return 0; /* no lazy option */
1952 if (stat(im->graphfile,&imgstat) != 0)
1953 return 0; /* can't stat */
1954 /* one pixel in the existing graph is more then what we would
1956 if (time(NULL) - imgstat.st_mtime >
1957 (im->end - im->start) / im->xsize)
1959 if ((fd = fopen(im->graphfile,"rb")) == NULL)
1960 return 0; /* the file does not exist */
1961 switch (im->canvas->imgformat) {
1963 size = PngSize(fd,&(im->ximg),&(im->yimg));
1973 pie_part(image_desc_t *im, gfx_color_t color,
1974 double PieCenterX, double PieCenterY, double Radius,
1975 double startangle, double endangle)
1979 double step=M_PI/50; /* Number of iterations for the circle;
1980 ** 10 is definitely too low, more than
1981 ** 50 seems to be overkill
1984 /* Strange but true: we have to work clockwise or else
1985 ** anti aliasing nor transparency don't work.
1987 ** This test is here to make sure we do it right, also
1988 ** this makes the for...next loop more easy to implement.
1989 ** The return will occur if the user enters a negative number
1990 ** (which shouldn't be done according to the specs) or if the
1991 ** programmers do something wrong (which, as we all know, never
1992 ** happens anyway :)
1994 if (endangle<startangle) return;
1996 /* Hidden feature: Radius decreases each full circle */
1998 while (angle>=2*M_PI) {
2003 node=gfx_new_area(im->canvas,
2004 PieCenterX+sin(startangle)*Radius,
2005 PieCenterY-cos(startangle)*Radius,
2008 PieCenterX+sin(endangle)*Radius,
2009 PieCenterY-cos(endangle)*Radius,
2011 for (angle=endangle;angle-startangle>=step;angle-=step) {
2013 PieCenterX+sin(angle)*Radius,
2014 PieCenterY-cos(angle)*Radius );
2019 graph_size_location(image_desc_t *im, int elements, int piechart )
2021 /* The actual size of the image to draw is determined from
2022 ** several sources. The size given on the command line is
2023 ** the graph area but we need more as we have to draw labels
2024 ** and other things outside the graph area
2027 /* +-+-------------------------------------------+
2028 ** |l|.................title.....................|
2029 ** |e+--+-------------------------------+--------+
2032 ** |l| l| main graph area | chart |
2035 ** |r+--+-------------------------------+--------+
2036 ** |e| | x-axis labels | |
2037 ** |v+--+-------------------------------+--------+
2038 ** | |..............legends......................|
2039 ** +-+-------------------------------------------+
2041 int Xvertical=0, Yvertical=0,
2042 Xtitle =0, Ytitle =0,
2043 Xylabel =0, Yylabel =0,
2046 Xxlabel =0, Yxlabel =0,
2048 Xlegend =0, Ylegend =0,
2050 Xspacing =10, Yspacing =10;
2052 if (im->ylegend[0] != '\0') {
2053 Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2054 Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2057 if (im->title[0] != '\0') {
2058 /* The title is placed "inbetween" two text lines so it
2059 ** automatically has some vertical spacing. The horizontal
2060 ** spacing is added here, on each side.
2062 Xtitle = gfx_get_text_width(im->canvas, 0,
2063 im->text_prop[TEXT_PROP_TITLE].font,
2064 im->text_prop[TEXT_PROP_TITLE].size,
2066 im->title) + 2*Xspacing;
2067 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2073 if (im->draw_x_grid) {
2075 Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2077 if (im->draw_y_grid) {
2078 Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2084 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2089 /* Now calculate the total size. Insert some spacing where
2090 desired. im->xorigin and im->yorigin need to correspond
2091 with the lower left corner of the main graph area or, if
2092 this one is not set, the imaginary box surrounding the
2095 /* The legend width cannot yet be determined, as a result we
2096 ** have problems adjusting the image to it. For now, we just
2097 ** forget about it at all; the legend will have to fit in the
2098 ** size already allocated.
2100 im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2101 if (Xmain) im->ximg += Xspacing;
2102 if (Xpie) im->ximg += Xspacing;
2103 im->xorigin = Xspacing + Xylabel;
2104 if (Xtitle > im->ximg) im->ximg = Xtitle;
2106 im->ximg += Xvertical;
2107 im->xorigin += Xvertical;
2111 /* The vertical size is interesting... we need to compare
2112 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2113 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2114 ** start even thinking about Ylegend.
2116 ** Do it in three portions: First calculate the inner part,
2117 ** then do the legend, then adjust the total height of the img.
2120 /* reserve space for main and/or pie */
2121 im->yimg = Ymain + Yxlabel;
2122 if (im->yimg < Ypie) im->yimg = Ypie;
2123 im->yorigin = im->yimg - Yxlabel;
2124 /* reserve space for the title *or* some padding above the graph */
2127 im->yorigin += Ytitle;
2129 im->yimg += Yspacing;
2130 im->yorigin += Yspacing;
2132 /* reserve space for padding below the graph */
2133 im->yimg += Yspacing;
2136 /* Determine where to place the legends onto the image.
2137 ** Adjust im->yimg to match the space requirements.
2139 if(leg_place(im)==-1)
2142 /* last of three steps: check total height of image */
2143 if (im->yimg < Yvertical) im->yimg = Yvertical;
2146 if (Xlegend > im->ximg) {
2148 /* reposition Pie */
2151 /* The pie is placed in the upper right hand corner,
2152 ** just below the title (if any) and with sufficient
2156 im->pie_x = im->ximg - Xspacing - Xpie/2;
2157 im->pie_y = im->yorigin-Ymain+Ypie/2;
2159 im->pie_x = im->ximg/2;
2160 im->pie_y = im->yorigin-Ypie/2;
2166 /* draw that picture thing ... */
2168 graph_paint(image_desc_t *im, char ***calcpr)
2171 int lazy = lazy_check(im);
2173 double PieStart=0.0;
2177 double areazero = 0.0;
2178 enum gf_en stack_gf = GF_PRINT;
2179 graph_desc_t *lastgdes = NULL;
2181 /* if we are lazy and there is nothing to PRINT ... quit now */
2182 if (lazy && im->prt_c==0) return 0;
2184 /* pull the data from the rrd files ... */
2186 if(data_fetch(im)==-1)
2189 /* evaluate VDEF and CDEF operations ... */
2190 if(data_calc(im)==-1)
2193 /* check if we need to draw a piechart */
2194 for(i=0;i<im->gdes_c;i++){
2195 if (im->gdes[i].gf == GF_PART) {
2201 /* calculate and PRINT and GPRINT definitions. We have to do it at
2202 * this point because it will affect the length of the legends
2203 * if there are no graph elements we stop here ...
2204 * if we are lazy, try to quit ...
2206 i=print_calc(im,calcpr);
2208 if(((i==0)&&(piechart==0)) || lazy) return 0;
2210 /* If there's only the pie chart to draw, signal this */
2211 if (i==0) piechart=2;
2213 /* get actual drawing data and find min and max values*/
2214 if(data_proc(im)==-1)
2217 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2219 if(!im->rigid && ! im->logarithmic)
2220 expand_range(im); /* make sure the upper and lower limit are
2223 if (!calc_horizontal_grid(im))
2228 /**************************************************************
2229 *** Calculating sizes and locations became a bit confusing ***
2230 *** so I moved this into a separate function. ***
2231 **************************************************************/
2232 if(graph_size_location(im,i,piechart)==-1)
2235 /* the actual graph is created by going through the individual
2236 graph elements and then drawing them */
2238 node=gfx_new_area ( im->canvas,
2242 im->graph_col[GRC_BACK]);
2244 gfx_add_point(node,0, im->yimg);
2246 if (piechart != 2) {
2247 node=gfx_new_area ( im->canvas,
2248 im->xorigin, im->yorigin,
2249 im->xorigin + im->xsize, im->yorigin,
2250 im->xorigin + im->xsize, im->yorigin-im->ysize,
2251 im->graph_col[GRC_CANVAS]);
2253 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2255 if (im->minval > 0.0)
2256 areazero = im->minval;
2257 if (im->maxval < 0.0)
2258 areazero = im->maxval;
2264 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2267 for(i=0;i<im->gdes_c;i++){
2268 switch(im->gdes[i].gf){
2279 for (ii = 0; ii < im->xsize; ii++)
2281 if (!isnan(im->gdes[i].p_data[ii]) &&
2282 im->gdes[i].p_data[ii] > 0.0)
2284 /* generate a tick */
2285 gfx_new_line(im->canvas, im -> xorigin + ii,
2286 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2290 im -> gdes[i].col );
2296 stack_gf = im->gdes[i].gf;
2298 /* fix data points at oo and -oo */
2299 for(ii=0;ii<im->xsize;ii++){
2300 if (isinf(im->gdes[i].p_data[ii])){
2301 if (im->gdes[i].p_data[ii] > 0) {
2302 im->gdes[i].p_data[ii] = im->maxval ;
2304 im->gdes[i].p_data[ii] = im->minval ;
2310 if (im->gdes[i].col != 0x0){
2311 /* GF_LINE and friend */
2312 if(stack_gf == GF_LINE ){
2314 for(ii=1;ii<im->xsize;ii++){
2315 if ( ! isnan(im->gdes[i].p_data[ii-1])
2316 && ! isnan(im->gdes[i].p_data[ii])){
2318 node = gfx_new_line(im->canvas,
2319 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2320 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2321 im->gdes[i].linewidth,
2324 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2333 for(ii=1;ii<im->xsize;ii++){
2335 if ( ! isnan(im->gdes[i].p_data[ii-1])
2336 && ! isnan(im->gdes[i].p_data[ii])){
2339 if (im->gdes[i].gf == GF_STACK) {
2340 ybase = ytr(im,lastgdes->p_data[ii-1]);
2342 ybase = ytr(im,areazero);
2345 node = gfx_new_area(im->canvas,
2346 ii-1+im->xorigin,ybase,
2347 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2348 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2352 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2356 if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2357 /* GF_AREA STACK type*/
2358 if (im->gdes[i].gf == GF_STACK ) {
2360 for (iii=ii-1;iii>area_start;iii--){
2361 gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2364 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2369 } /* else GF_LINE */
2370 } /* if color != 0x0 */
2371 /* make sure we do not run into trouble when stacking on NaN */
2372 for(ii=0;ii<im->xsize;ii++){
2373 if (isnan(im->gdes[i].p_data[ii])) {
2376 ybase = ytr(im,lastgdes->p_data[ii-1]);
2378 if (isnan(ybase) || !lastgdes ){
2379 ybase = ytr(im,areazero);
2381 im->gdes[i].p_data[ii] = ybase;
2384 lastgdes = &(im->gdes[i]);
2387 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2388 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2390 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2391 pie_part(im,im->gdes[i].col,
2392 im->pie_x,im->pie_y,im->piesize*0.4,
2393 M_PI*2.0*PieStart/100.0,
2394 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2395 PieStart += im->gdes[i].yrule;
2404 /* grid_paint also does the text */
2407 /* the RULES are the last thing to paint ... */
2408 for(i=0;i<im->gdes_c;i++){
2410 switch(im->gdes[i].gf){
2412 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2413 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2415 if(im->gdes[i].yrule >= im->minval
2416 && im->gdes[i].yrule <= im->maxval)
2417 gfx_new_line(im->canvas,
2418 im->xorigin,ytr(im,im->gdes[i].yrule),
2419 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2420 1.0,im->gdes[i].col);
2423 if(im->gdes[i].xrule == 0) { /* fetch variable */
2424 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2426 if(im->gdes[i].xrule >= im->start
2427 && im->gdes[i].xrule <= im->end)
2428 gfx_new_line(im->canvas,
2429 xtr(im,im->gdes[i].xrule),im->yorigin,
2430 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2431 1.0,im->gdes[i].col);
2439 if (strcmp(im->graphfile,"-")==0) {
2441 /* Change translation mode for stdout to BINARY */
2442 _setmode( _fileno( stdout ), O_BINARY );
2446 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2447 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2452 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2453 if (strcmp(im->graphfile,"-") != 0)
2459 /*****************************************************
2461 *****************************************************/
2464 gdes_alloc(image_desc_t *im){
2466 long def_step = (im->end-im->start)/im->xsize;
2468 if (im->step > def_step) /* step can be increassed ... no decreassed */
2469 def_step = im->step;
2473 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2474 * sizeof(graph_desc_t)))==NULL){
2475 rrd_set_error("realloc graph_descs");
2480 im->gdes[im->gdes_c-1].step=def_step;
2481 im->gdes[im->gdes_c-1].start=im->start;
2482 im->gdes[im->gdes_c-1].end=im->end;
2483 im->gdes[im->gdes_c-1].vname[0]='\0';
2484 im->gdes[im->gdes_c-1].data=NULL;
2485 im->gdes[im->gdes_c-1].ds_namv=NULL;
2486 im->gdes[im->gdes_c-1].data_first=0;
2487 im->gdes[im->gdes_c-1].p_data=NULL;
2488 im->gdes[im->gdes_c-1].rpnp=NULL;
2489 im->gdes[im->gdes_c-1].col = 0x0;
2490 im->gdes[im->gdes_c-1].legend[0]='\0';
2491 im->gdes[im->gdes_c-1].rrd[0]='\0';
2492 im->gdes[im->gdes_c-1].ds=-1;
2493 im->gdes[im->gdes_c-1].p_data=NULL;
2497 /* copies input untill the first unescaped colon is found
2498 or until input ends. backslashes have to be escaped as well */
2500 scan_for_col(char *input, int len, char *output)
2505 input[inp] != ':' &&
2508 if (input[inp] == '\\' &&
2509 input[inp+1] != '\0' &&
2510 (input[inp+1] == '\\' ||
2511 input[inp+1] == ':')){
2512 output[outp++] = input[++inp];
2515 output[outp++] = input[inp];
2518 output[outp] = '\0';
2522 /* Some surgery done on this function, it became ridiculously big.
2524 ** - initializing now in rrd_graph_init()
2525 ** - options parsing now in rrd_graph_options()
2526 ** - script parsing now in rrd_graph_script()
2529 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2536 #ifdef HAVE_SETLOCALE
2537 setlocale(LC_TIME,"");
2541 rrd_graph_init(&im);
2543 rrd_graph_options(argc,argv,&im);
2544 if (rrd_test_error()) return -1;
2546 if (strlen(argv[optind])>=MAXPATH) {
2547 rrd_set_error("filename (including path) too long");
2550 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2551 im.graphfile[MAXPATH-1]='\0';
2553 rrd_graph_script(argc,argv,&im);
2554 if (rrd_test_error()) return -1;
2556 /* Everything is now read and the actual work can start */
2559 if (graph_paint(&im,prdata)==-1){
2564 /* The image is generated and needs to be output.
2565 ** Also, if needed, print a line with information about the image.
2573 /* maybe prdata is not allocated yet ... lets do it now */
2574 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2575 rrd_set_error("malloc imginfo");
2579 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2581 rrd_set_error("malloc imginfo");
2584 filename=im.graphfile+strlen(im.graphfile);
2585 while(filename > im.graphfile) {
2586 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2590 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2597 rrd_graph_init(image_desc_t *im)
2601 im->xlab_user.minsec = -1;
2607 im->ylegend[0] = '\0';
2608 im->title[0] = '\0';
2611 im->unitsexponent= 9999;
2617 im->logarithmic = 0;
2618 im->ygridstep = DNAN;
2619 im->draw_x_grid = 1;
2620 im->draw_y_grid = 1;
2625 im->canvas = gfx_new_canvas();
2626 im->grid_dash_on = 1;
2627 im->grid_dash_off = 1;
2629 for(i=0;i<DIM(graph_col);i++)
2630 im->graph_col[i]=graph_col[i];
2632 for(i=0;i<DIM(text_prop);i++){
2633 im->text_prop[i].size = text_prop[i].size;
2634 im->text_prop[i].font = text_prop[i].font;
2639 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2642 char *parsetime_error = NULL;
2643 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2644 time_t start_tmp=0,end_tmp=0;
2646 struct time_value start_tv, end_tv;
2649 parsetime("end-24h", &start_tv);
2650 parsetime("now", &end_tv);
2653 static struct option long_options[] =
2655 {"start", required_argument, 0, 's'},
2656 {"end", required_argument, 0, 'e'},
2657 {"x-grid", required_argument, 0, 'x'},
2658 {"y-grid", required_argument, 0, 'y'},
2659 {"vertical-label",required_argument,0,'v'},
2660 {"width", required_argument, 0, 'w'},
2661 {"height", required_argument, 0, 'h'},
2662 {"interlaced", no_argument, 0, 'i'},
2663 {"upper-limit",required_argument, 0, 'u'},
2664 {"lower-limit",required_argument, 0, 'l'},
2665 {"rigid", no_argument, 0, 'r'},
2666 {"base", required_argument, 0, 'b'},
2667 {"logarithmic",no_argument, 0, 'o'},
2668 {"color", required_argument, 0, 'c'},
2669 {"font", required_argument, 0, 'n'},
2670 {"title", required_argument, 0, 't'},
2671 {"imginfo", required_argument, 0, 'f'},
2672 {"imgformat", required_argument, 0, 'a'},
2673 {"lazy", no_argument, 0, 'z'},
2674 {"zoom", required_argument, 0, 'm'},
2675 {"no-legend", no_argument, 0, 'g'},
2676 {"alt-y-grid", no_argument, 0, 257 },
2677 {"alt-autoscale", no_argument, 0, 258 },
2678 {"alt-autoscale-max", no_argument, 0, 259 },
2679 {"units-exponent",required_argument, 0, 260},
2680 {"step", required_argument, 0, 261},
2681 {"no-gridfit", no_argument, 0, 262},
2683 int option_index = 0;
2687 opt = getopt_long(argc, argv,
2688 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2689 long_options, &option_index);
2696 im->extra_flags |= ALTYGRID;
2699 im->extra_flags |= ALTAUTOSCALE;
2702 im->extra_flags |= ALTAUTOSCALE_MAX;
2705 im->extra_flags |= NOLEGEND;
2708 im->unitsexponent = atoi(optarg);
2711 im->step = atoi(optarg);
2717 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2718 rrd_set_error( "start time: %s", parsetime_error );
2723 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2724 rrd_set_error( "end time: %s", parsetime_error );
2729 if(strcmp(optarg,"none") == 0){
2735 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2737 &im->xlab_user.gridst,
2739 &im->xlab_user.mgridst,
2741 &im->xlab_user.labst,
2742 &im->xlab_user.precis,
2743 &stroff) == 7 && stroff != 0){
2744 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2745 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2746 rrd_set_error("unknown keyword %s",scan_gtm);
2748 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2749 rrd_set_error("unknown keyword %s",scan_mtm);
2751 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2752 rrd_set_error("unknown keyword %s",scan_ltm);
2755 im->xlab_user.minsec = 1;
2756 im->xlab_user.stst = im->xlab_form;
2758 rrd_set_error("invalid x-grid format");
2764 if(strcmp(optarg,"none") == 0){
2772 &im->ylabfact) == 2) {
2773 if(im->ygridstep<=0){
2774 rrd_set_error("grid step must be > 0");
2776 } else if (im->ylabfact < 1){
2777 rrd_set_error("label factor must be > 0");
2781 rrd_set_error("invalid y-grid format");
2786 strncpy(im->ylegend,optarg,150);
2787 im->ylegend[150]='\0';
2790 im->maxval = atof(optarg);
2793 im->minval = atof(optarg);
2796 im->base = atol(optarg);
2797 if(im->base != 1024 && im->base != 1000 ){
2798 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2803 long_tmp = atol(optarg);
2804 if (long_tmp < 10) {
2805 rrd_set_error("width below 10 pixels");
2808 im->xsize = long_tmp;
2811 long_tmp = atol(optarg);
2812 if (long_tmp < 10) {
2813 rrd_set_error("height below 10 pixels");
2816 im->ysize = long_tmp;
2819 im->canvas->interlaced = 1;
2825 im->imginfo = optarg;
2828 if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2829 rrd_set_error("unsupported graphics format '%s'",optarg);
2837 im->logarithmic = 1;
2838 if (isnan(im->minval))
2844 col_nam,&color) == 2){
2846 if((ci=grc_conv(col_nam)) != -1){
2847 im->graph_col[ci]=color;
2849 rrd_set_error("invalid color name '%s'",col_nam);
2852 rrd_set_error("invalid color def format");
2857 /* originally this used char *prop = "" and
2858 ** char *font = "dummy" however this results
2859 ** in a SEG fault, at least on RH7.1
2861 ** The current implementation isn't proper
2862 ** either, font is never freed and prop uses
2863 ** a fixed width string
2872 prop,&size,font) == 3){
2874 if((sindex=text_prop_conv(prop)) != -1){
2875 im->text_prop[sindex].size=size;
2876 im->text_prop[sindex].font=font;
2877 if (sindex==0) { /* the default */
2878 im->text_prop[TEXT_PROP_TITLE].size=size;
2879 im->text_prop[TEXT_PROP_TITLE].font=font;
2880 im->text_prop[TEXT_PROP_AXIS].size=size;
2881 im->text_prop[TEXT_PROP_AXIS].font=font;
2882 im->text_prop[TEXT_PROP_UNIT].size=size;
2883 im->text_prop[TEXT_PROP_UNIT].font=font;
2884 im->text_prop[TEXT_PROP_LEGEND].size=size;
2885 im->text_prop[TEXT_PROP_LEGEND].font=font;
2888 rrd_set_error("invalid fonttag '%s'",prop);
2892 rrd_set_error("invalid text property format");
2898 im->canvas->zoom = atof(optarg);
2899 if (im->canvas->zoom <= 0.0) {
2900 rrd_set_error("zoom factor must be > 0");
2905 strncpy(im->title,optarg,150);
2906 im->title[150]='\0';
2911 rrd_set_error("unknown option '%c'", optopt);
2913 rrd_set_error("unknown option '%s'",argv[optind-1]);
2918 if (optind >= argc) {
2919 rrd_set_error("missing filename");
2923 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2924 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
2928 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2929 /* error string is set in parsetime.c */
2933 if (start_tmp < 3600*24*365*10){
2934 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2938 if (end_tmp < start_tmp) {
2939 rrd_set_error("start (%ld) should be less than end (%ld)",
2940 start_tmp, end_tmp);
2944 im->start = start_tmp;
2949 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2953 int linepass = 0; /* stack must follow LINE*, AREA or STACK */
2955 for (i=optind+1;i<argc;i++) {
2960 char funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2965 /* Each command is one element from *argv[], we call this "line".
2967 ** Each command defines the most current gdes inside struct im.
2968 ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2971 gdp=&im->gdes[im->gdes_c-1];
2974 /* function:newvname=string[:ds-name:CF] for xDEF
2975 ** function:vname[#color[:string]] for LINEx,AREA,STACK
2976 ** function:vname#color[:num[:string]] for TICK
2977 ** function:vname-or-num#color[:string] for xRULE,PART
2978 ** function:vname:CF:string for xPRINT
2979 ** function:string for COMMENT
2983 sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2985 rrd_set_error("Cannot parse function in line: %s",line);
2989 if(sscanf(funcname,"LINE%lf",&linewidth)){
2990 im->gdes[im->gdes_c-1].gf = GF_LINE;
2991 im->gdes[im->gdes_c-1].linewidth = linewidth;
2993 if ((gdp->gf=gf_conv(funcname))==-1) {
2994 rrd_set_error("'%s' is not a valid function name",funcname);
3000 /* If the error string is set, we exit at the end of the switch */
3003 if (rrd_graph_legend(gdp,&line[argstart])==0)
3004 rrd_set_error("Cannot parse comment in line: %s",line);
3010 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
3011 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
3013 rrd_set_error("Cannot parse name or num in line: %s",line);
3020 } else if (!rrd_graph_check_vname(im,vname,line)) {
3024 } else break; /* exit due to wrong vname */
3025 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
3027 if (strlen(&line[argstart])!=0) {
3028 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3029 rrd_set_error("Cannot parse comment in line: %s",line);
3034 rrd_set_error("STACK must follow another graphing element");
3042 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
3044 rrd_set_error("Cannot parse vname in line: %s",line);
3045 else if (rrd_graph_check_vname(im,vname,line))
3046 rrd_set_error("Undefined vname '%s' in line: %s",line);
3048 k=rrd_graph_color(im,&line[argstart],line,1);
3049 if (rrd_test_error()) break;
3050 argstart=argstart+j+k;
3051 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
3053 sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
3056 if (strlen(&line[argstart])!=0)
3057 if (rrd_graph_legend(gdp,&line[++argstart])==0)
3058 rrd_set_error("Cannot parse legend in line: %s",line);
3064 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3066 rrd_set_error("Cannot parse vname in line: '%s'",line);
3070 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3072 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3074 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3075 #define VIDX im->gdes[gdp->vidx]
3077 case -1: /* looks CF but is not really CF */
3078 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3080 case 0: /* CF present and correct */
3081 if (VIDX.gf == GF_VDEF)
3082 rrd_set_error("Don't use CF when printing VDEF");
3085 case 1: /* CF not present */
3086 if (VIDX.gf == GF_VDEF) rrd_clear_error();
3087 else rrd_set_error("Printing DEF or CDEF needs CF");
3090 rrd_set_error("Oops, bug in GPRINT scanning");
3093 if (rrd_test_error()) break;
3095 if (strlen(&line[argstart])!=0) {
3096 if (rrd_graph_legend(gdp,&line[argstart])==0)
3097 rrd_set_error("Cannot parse legend in line: %s",line);
3098 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3099 strcpy(gdp->format, gdp->legend);
3105 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
3107 rrd_set_error("Could not parse line: %s",line);
3110 if (find_var(im,gdp->vname)!=-1) {
3111 rrd_set_error("Variable '%s' in line '%s' already in use\n",
3118 argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3120 sscanf(&line[argstart],
3121 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3122 gdp->ds_nam, symname, &j, &k);
3123 if ((j==0)||(k!=0)) {
3124 rrd_set_error("Cannot parse DS or CF in '%s'",line);
3127 rrd_graph_check_CF(im,symname,line);
3131 sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3133 rrd_set_error("Cannot parse vname in line '%s'",line);
3137 if (rrd_graph_check_vname(im,vname,line)) return;
3138 if ( im->gdes[gdp->vidx].gf != GF_DEF
3139 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3140 rrd_set_error("variable '%s' not DEF nor "
3141 "CDEF in VDEF '%s'", vname,gdp->vname);
3144 vdef_parse(gdp,&line[argstart+strstart]);
3147 if (strstr(&line[argstart],":")!=NULL) {
3148 rrd_set_error("Error in RPN, line: %s",line);
3151 if ((gdp->rpnp = rpn_parse(
3156 rrd_set_error("invalid rpn expression in: %s",line);
3161 default: rrd_set_error("Big oops");
3163 if (rrd_test_error()) {
3170 rrd_set_error("can't make a graph without contents");
3171 im_free(im); /* ??? is this set ??? */
3176 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3178 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3179 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3185 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3188 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3190 color=strstr(var,"#");
3193 rrd_set_error("Found no color in %s",err);
3202 rest=strstr(color,":");
3210 sscanf(color,"#%6lx%n",&col,&n);
3211 col = (col << 8) + 0xff /* shift left by 8 */;
3212 if (n!=7) rrd_set_error("Color problem in %s",err);
3215 sscanf(color,"#%8lx%n",&col,&n);
3218 rrd_set_error("Color problem in %s",err);
3220 if (rrd_test_error()) return 0;
3226 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3228 if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3229 rrd_set_error("Unknown CF '%s' in %s",symname,err);
3235 rrd_graph_legend(graph_desc_t *gdp, char *line)
3239 i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3241 return (strlen(&line[i])==0);
3245 int bad_format(char *fmt) {
3250 while (*ptr != '\0') {
3251 if (*ptr == '%') {ptr++;
3252 if (*ptr == '\0') return 1;
3253 while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') {
3256 if (*ptr == '\0') return 1;
3260 if (*ptr == '\0') return 1;
3261 else if (*ptr == ' ') ptr++;
3262 else if (*ptr == '-') ptr++;
3263 else if (*ptr == '+') ptr++;
3264 if (*ptr == 'e' || *ptr == 'f') {
3266 } else { return 1; }
3268 else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3277 vdef_parse(gdes,str)
3278 struct graph_desc_t *gdes;
3281 /* A VDEF currently is either "func" or "param,func"
3282 * so the parsing is rather simple. Change if needed.
3289 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3290 if (n==strlen(str)) { /* matched */
3294 sscanf(str,"%29[A-Z]%n",func,&n);
3295 if (n==strlen(str)) { /* matched */
3298 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3305 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3306 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3307 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3308 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3309 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3310 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3311 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3313 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3320 switch (gdes->vf.op) {
3322 if (isnan(param)) { /* no parameter given */
3323 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3329 if (param>=0.0 && param<=100.0) {
3330 gdes->vf.param = param;
3331 gdes->vf.val = DNAN; /* undefined */
3332 gdes->vf.when = 0; /* undefined */
3334 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3348 gdes->vf.param = DNAN;
3349 gdes->vf.val = DNAN;
3352 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3367 graph_desc_t *src,*dst;
3371 dst = &im->gdes[gdi];
3372 src = &im->gdes[dst->vidx];
3373 data = src->data + src->ds;
3374 steps = (src->end - src->start) / src->step;
3377 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3384 switch (dst->vf.op) {
3385 case VDEF_PERCENT: {
3386 rrd_value_t * array;
3390 if ((array = malloc(steps*sizeof(double)))==NULL) {
3391 rrd_set_error("malloc VDEV_PERCENT");
3394 for (step=0;step < steps; step++) {
3395 array[step]=data[step*src->ds_cnt];
3397 qsort(array,step,sizeof(double),vdef_percent_compar);
3399 field = (steps-1)*dst->vf.param/100;
3400 dst->vf.val = array[field];
3401 dst->vf.when = 0; /* no time component */
3403 for(step=0;step<steps;step++)
3404 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3410 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3411 if (step == steps) {
3415 dst->vf.val = data[step*src->ds_cnt];
3416 dst->vf.when = src->start + (step+1)*src->step;
3418 while (step != steps) {
3419 if (finite(data[step*src->ds_cnt])) {
3420 if (data[step*src->ds_cnt] > dst->vf.val) {
3421 dst->vf.val = data[step*src->ds_cnt];
3422 dst->vf.when = src->start + (step+1)*src->step;
3429 case VDEF_AVERAGE: {
3432 for (step=0;step<steps;step++) {
3433 if (finite(data[step*src->ds_cnt])) {
3434 sum += data[step*src->ds_cnt];
3439 if (dst->vf.op == VDEF_TOTAL) {
3440 dst->vf.val = sum*src->step;
3441 dst->vf.when = cnt*src->step; /* not really "when" */
3443 dst->vf.val = sum/cnt;
3444 dst->vf.when = 0; /* no time component */
3454 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3455 if (step == steps) {
3459 dst->vf.val = data[step*src->ds_cnt];
3460 dst->vf.when = src->start + (step+1)*src->step;
3462 while (step != steps) {
3463 if (finite(data[step*src->ds_cnt])) {
3464 if (data[step*src->ds_cnt] < dst->vf.val) {
3465 dst->vf.val = data[step*src->ds_cnt];
3466 dst->vf.when = src->start + (step+1)*src->step;
3473 /* The time value returned here is one step before the
3474 * actual time value. This is the start of the first
3478 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3479 if (step == steps) { /* all entries were NaN */
3483 dst->vf.val = data[step*src->ds_cnt];
3484 dst->vf.when = src->start + step*src->step;
3488 /* The time value returned here is the
3489 * actual time value. This is the end of the last
3493 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3494 if (step < 0) { /* all entries were NaN */
3498 dst->vf.val = data[step*src->ds_cnt];
3499 dst->vf.when = src->start + (step+1)*src->step;
3506 /* NaN < -INF < finite_values < INF */
3508 vdef_percent_compar(a,b)
3511 /* Equality is not returned; this doesn't hurt except
3512 * (maybe) for a little performance.
3515 /* First catch NaN values. They are smallest */
3516 if (isnan( *(double *)a )) return -1;
3517 if (isnan( *(double *)b )) return 1;
3519 /* NaN doesn't reach this part so INF and -INF are extremes.
3520 * The sign from isinf() is compatible with the sign we return
3522 if (isinf( *(double *)a )) return isinf( *(double *)a );
3523 if (isinf( *(double *)b )) return isinf( *(double *)b );
3525 /* If we reach this, both values must be finite */
3526 if ( *(double *)a < *(double *)b ) return -1; else return 1;