/**************************************************************************** * RRDtool 1.2.23 Copyright by Tobi Oetiker, 1997-2007 **************************************************************************** * rrd__graph.c produce graphs from data in rrdfiles ****************************************************************************/ #include #ifdef WIN32 #include "strftime.h" #endif #include "rrd_tool.h" #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__) #include #include #endif #ifdef HAVE_TIME_H #include #endif #ifdef HAVE_LOCALE_H #include #endif #include "rrd_graph.h" /* some constant definitions */ #ifndef RRD_DEFAULT_FONT /* there is special code later to pick Cour.ttf when running on windows */ #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf" #endif text_prop_t text_prop[] = { { 8.0, RRD_DEFAULT_FONT }, /* default */ { 9.0, RRD_DEFAULT_FONT }, /* title */ { 7.0, RRD_DEFAULT_FONT }, /* axis */ { 8.0, RRD_DEFAULT_FONT }, /* unit */ { 8.0, RRD_DEFAULT_FONT } /* legend */ }; xlab_t xlab[] = { {0, 0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"}, {2, 0, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"}, {5, 0, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"}, {10, 0, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"}, {30, 0, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"}, {60, 0, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"}, {60, 24*3600, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,4, 0,"%a %H:%M"}, {180, 0, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"}, {180, 24*3600, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,12, 0,"%a %H:%M"}, /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/ {600, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"}, {1200, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%d"}, {1800, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a %d"}, {2400, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"}, {3600, 0, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"}, {3*3600, 0, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"}, {6*3600, 0, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"}, {48*3600, 0, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"}, {315360, 0, TMT_MONTH,3, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%Y"}, {10*24*3600, 0, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"}, {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""} }; /* sensible y label intervals ...*/ ylab_t ylab[]= { {0.1, {1,2, 5,10}}, {0.2, {1,5,10,20}}, {0.5, {1,2, 4,10}}, {1.0, {1,2, 5,10}}, {2.0, {1,5,10,20}}, {5.0, {1,2, 4,10}}, {10.0, {1,2, 5,10}}, {20.0, {1,5,10,20}}, {50.0, {1,2, 4,10}}, {100.0, {1,2, 5,10}}, {200.0, {1,5,10,20}}, {500.0, {1,2, 4,10}}, {0.0, {0,0,0,0}}}; gfx_color_t graph_col[] = /* default colors */ { 0xFFFFFFFF, /* canvas */ 0xF0F0F0FF, /* background */ 0xD0D0D0FF, /* shade A */ 0xA0A0A0FF, /* shade B */ 0x90909080, /* grid */ 0xE0505080, /* major grid */ 0x000000FF, /* font */ 0x802020FF, /* arrow */ 0x202020FF, /* axis */ 0x000000FF /* frame */ }; /* #define DEBUG */ #ifdef DEBUG # define DPRINT(x) (void)(printf x, printf("\n")) #else # define DPRINT(x) #endif /* initialize with xtr(im,0); */ int xtr(image_desc_t *im,time_t mytime){ static double pixie; if (mytime==0){ pixie = (double) im->xsize / (double)(im->end - im->start); return im->xorigin; } return (int)((double)im->xorigin + pixie * ( mytime - im->start ) ); } /* translate data values into y coordinates */ double ytr(image_desc_t *im, double value){ static double pixie; double yval; if (isnan(value)){ if(!im->logarithmic) pixie = (double) im->ysize / (im->maxval - im->minval); else pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval)); yval = im->yorigin; } else if(!im->logarithmic) { yval = im->yorigin - pixie * (value - im->minval); } else { if (value < im->minval) { yval = im->yorigin; } else { yval = im->yorigin - pixie * (log10(value) - log10(im->minval)); } } /* make sure we don't return anything too unreasonable. GD lib can get terribly slow when drawing lines outside its scope. This is especially problematic in connection with the rigid option */ if (! im->rigid) { /* keep yval as-is */ } else if (yval > im->yorigin) { yval = im->yorigin +0.00001; } else if (yval < im->yorigin - im->ysize){ yval = im->yorigin - im->ysize - 0.00001; } return yval; } /* conversion function for symbolic entry names */ #define conv_if(VV,VVV) \ if (strcmp(#VV, string) == 0) return VVV ; enum gf_en gf_conv(char *string){ conv_if(PRINT,GF_PRINT) conv_if(GPRINT,GF_GPRINT) conv_if(COMMENT,GF_COMMENT) conv_if(HRULE,GF_HRULE) conv_if(VRULE,GF_VRULE) conv_if(LINE,GF_LINE) conv_if(AREA,GF_AREA) conv_if(STACK,GF_STACK) conv_if(TICK,GF_TICK) conv_if(DEF,GF_DEF) conv_if(CDEF,GF_CDEF) conv_if(VDEF,GF_VDEF) #ifdef WITH_PIECHART conv_if(PART,GF_PART) #endif conv_if(XPORT,GF_XPORT) conv_if(SHIFT,GF_SHIFT) return (-1); } enum gfx_if_en if_conv(char *string){ conv_if(PNG,IF_PNG) conv_if(SVG,IF_SVG) conv_if(EPS,IF_EPS) conv_if(PDF,IF_PDF) return (-1); } enum tmt_en tmt_conv(char *string){ conv_if(SECOND,TMT_SECOND) conv_if(MINUTE,TMT_MINUTE) conv_if(HOUR,TMT_HOUR) conv_if(DAY,TMT_DAY) conv_if(WEEK,TMT_WEEK) conv_if(MONTH,TMT_MONTH) conv_if(YEAR,TMT_YEAR) return (-1); } enum grc_en grc_conv(char *string){ conv_if(BACK,GRC_BACK) conv_if(CANVAS,GRC_CANVAS) conv_if(SHADEA,GRC_SHADEA) conv_if(SHADEB,GRC_SHADEB) conv_if(GRID,GRC_GRID) conv_if(MGRID,GRC_MGRID) conv_if(FONT,GRC_FONT) conv_if(ARROW,GRC_ARROW) conv_if(AXIS,GRC_AXIS) conv_if(FRAME,GRC_FRAME) return -1; } enum text_prop_en text_prop_conv(char *string){ conv_if(DEFAULT,TEXT_PROP_DEFAULT) conv_if(TITLE,TEXT_PROP_TITLE) conv_if(AXIS,TEXT_PROP_AXIS) conv_if(UNIT,TEXT_PROP_UNIT) conv_if(LEGEND,TEXT_PROP_LEGEND) return -1; } #undef conv_if int im_free(image_desc_t *im) { unsigned long i,ii; if (im == NULL) return 0; for(i=0;i<(unsigned)im->gdes_c;i++){ if (im->gdes[i].data_first){ /* careful here, because a single pointer can occur several times */ free (im->gdes[i].data); if (im->gdes[i].ds_namv){ for (ii=0;iigdes[i].ds_cnt;ii++) free(im->gdes[i].ds_namv[ii]); free(im->gdes[i].ds_namv); } } free (im->gdes[i].p_data); free (im->gdes[i].rpnp); } free(im->gdes); gfx_destroy(im->canvas); return 0; } /* find SI magnitude symbol for the given number*/ void auto_scale( image_desc_t *im, /* image description */ double *value, char **symb_ptr, double *magfact ) { char *symbol[] = {"a", /* 10e-18 Atto */ "f", /* 10e-15 Femto */ "p", /* 10e-12 Pico */ "n", /* 10e-9 Nano */ "u", /* 10e-6 Micro */ "m", /* 10e-3 Milli */ " ", /* Base */ "k", /* 10e3 Kilo */ "M", /* 10e6 Mega */ "G", /* 10e9 Giga */ "T", /* 10e12 Tera */ "P", /* 10e15 Peta */ "E"};/* 10e18 Exa */ int symbcenter = 6; int sindex; if (*value == 0.0 || isnan(*value) ) { sindex = 0; *magfact = 1.0; } else { sindex = floor(log(fabs(*value))/log((double)im->base)); *magfact = pow((double)im->base, (double)sindex); (*value) /= (*magfact); } if ( sindex <= symbcenter && sindex >= -symbcenter) { (*symb_ptr) = symbol[sindex+symbcenter]; } else { (*symb_ptr) = "?"; } } static char si_symbol[] = { 'a', /* 10e-18 Atto */ 'f', /* 10e-15 Femto */ 'p', /* 10e-12 Pico */ 'n', /* 10e-9 Nano */ 'u', /* 10e-6 Micro */ 'm', /* 10e-3 Milli */ ' ', /* Base */ 'k', /* 10e3 Kilo */ 'M', /* 10e6 Mega */ 'G', /* 10e9 Giga */ 'T', /* 10e12 Tera */ 'P', /* 10e15 Peta */ 'E', /* 10e18 Exa */ }; static const int si_symbcenter = 6; /* find SI magnitude symbol for the numbers on the y-axis*/ void si_unit( image_desc_t *im /* image description */ ) { double digits,viewdigits=0; digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base)); if (im->unitsexponent != 9999) { /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */ viewdigits = floor(im->unitsexponent / 3); } else { viewdigits = digits; } im->magfact = pow((double)im->base , digits); #ifdef DEBUG printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact); #endif im->viewfactor = im->magfact / pow((double)im->base , viewdigits); if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) && ((viewdigits+si_symbcenter) >= 0) ) im->symbol = si_symbol[(int)viewdigits+si_symbcenter]; else im->symbol = '?'; } /* move min and max values around to become sensible */ void expand_range(image_desc_t *im) { double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0, 600.0,500.0,400.0,300.0,250.0, 200.0,125.0,100.0,90.0,80.0, 75.0,70.0,60.0,50.0,40.0,30.0, 25.0,20.0,10.0,9.0,8.0, 7.0,6.0,5.0,4.0,3.5,3.0, 2.5,2.0,1.8,1.5,1.2,1.0, 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1}; double scaled_min,scaled_max; double adj; int i; #ifdef DEBUG printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n", im->minval,im->maxval,im->magfact); #endif if (isnan(im->ygridstep)){ if(im->extra_flags & ALTAUTOSCALE) { /* measure the amplitude of the function. Make sure that graph boundaries are slightly higher then max/min vals so we can see amplitude on the graph */ double delt, fact; delt = im->maxval - im->minval; adj = delt * 0.1; fact = 2.0 * pow(10.0, floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2); if (delt < fact) { adj = (fact - delt) * 0.55; #ifdef DEBUG printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj); #endif } im->minval -= adj; im->maxval += adj; } else if(im->extra_flags & ALTAUTOSCALE_MIN) { /* measure the amplitude of the function. Make sure that graph boundaries are slightly lower than min vals so we can see amplitude on the graph */ adj = (im->maxval - im->minval) * 0.1; im->minval -= adj; } else if(im->extra_flags & ALTAUTOSCALE_MAX) { /* measure the amplitude of the function. Make sure that graph boundaries are slightly higher than max vals so we can see amplitude on the graph */ adj = (im->maxval - im->minval) * 0.1; im->maxval += adj; } else { scaled_min = im->minval / im->magfact; scaled_max = im->maxval / im->magfact; for (i=1; sensiblevalues[i] > 0; i++){ if (sensiblevalues[i-1]>=scaled_min && sensiblevalues[i]<=scaled_min) im->minval = sensiblevalues[i]*(im->magfact); if (-sensiblevalues[i-1]<=scaled_min && -sensiblevalues[i]>=scaled_min) im->minval = -sensiblevalues[i-1]*(im->magfact); if (sensiblevalues[i-1] >= scaled_max && sensiblevalues[i] <= scaled_max) im->maxval = sensiblevalues[i-1]*(im->magfact); if (-sensiblevalues[i-1]<=scaled_max && -sensiblevalues[i] >=scaled_max) im->maxval = -sensiblevalues[i]*(im->magfact); } } } else { /* adjust min and max to the grid definition if there is one */ im->minval = (double)im->ylabfact * im->ygridstep * floor(im->minval / ((double)im->ylabfact * im->ygridstep)); im->maxval = (double)im->ylabfact * im->ygridstep * ceil(im->maxval /( (double)im->ylabfact * im->ygridstep)); } #ifdef DEBUG fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n", im->minval,im->maxval,im->magfact); #endif } void apply_gridfit(image_desc_t *im) { if (isnan(im->minval) || isnan(im->maxval)) return; ytr(im,DNAN); if (im->logarithmic) { double ya, yb, ypix, ypixfrac; double log10_range = log10(im->maxval) - log10(im->minval); ya = pow((double)10, floor(log10(im->minval))); while (ya < im->minval) ya *= 10; if (ya > im->maxval) return; /* don't have y=10^x gridline */ yb = ya * 10; if (yb <= im->maxval) { /* we have at least 2 y=10^x gridlines. Make sure distance between them in pixels are an integer by expanding im->maxval */ double y_pixel_delta = ytr(im, ya) - ytr(im, yb); double factor = y_pixel_delta / floor(y_pixel_delta); double new_log10_range = factor * log10_range; double new_ymax_log10 = log10(im->minval) + new_log10_range; im->maxval = pow(10, new_ymax_log10); ytr(im,DNAN); /* reset precalc */ log10_range = log10(im->maxval) - log10(im->minval); } /* make sure first y=10^x gridline is located on integer pixel position by moving scale slightly downwards (sub-pixel movement) */ ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */ ypixfrac = ypix - floor(ypix); if (ypixfrac > 0 && ypixfrac < 1) { double yfrac = ypixfrac / im->ysize; im->minval = pow(10, log10(im->minval) - yfrac * log10_range); im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range); ytr(im,DNAN); /* reset precalc */ } } else { /* Make sure we have an integer pixel distance between each minor gridline */ double ypos1 = ytr(im, im->minval); double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep); double y_pixel_delta = ypos1 - ypos2; double factor = y_pixel_delta / floor(y_pixel_delta); double new_range = factor * (im->maxval - im->minval); double gridstep = im->ygrid_scale.gridstep; double minor_y, minor_y_px, minor_y_px_frac; if (im->maxval > 0.0) im->maxval = im->minval + new_range; else im->minval = im->maxval - new_range; ytr(im,DNAN); /* reset precalc */ /* make sure first minor gridline is on integer pixel y coord */ minor_y = gridstep * floor(im->minval / gridstep); while (minor_y < im->minval) minor_y += gridstep; minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */ minor_y_px_frac = minor_y_px - floor(minor_y_px); if (minor_y_px_frac > 0 && minor_y_px_frac < 1) { double yfrac = minor_y_px_frac / im->ysize; double range = im->maxval - im->minval; im->minval = im->minval - yfrac * range; im->maxval = im->maxval - yfrac * range; ytr(im,DNAN); /* reset precalc */ } calc_horizontal_grid(im); /* recalc with changed im->maxval */ } } /* reduce data reimplementation by Alex */ void reduce_data( enum cf_en cf, /* which consolidation function ?*/ unsigned long cur_step, /* step the data currently is in */ time_t *start, /* start, end and step as requested ... */ time_t *end, /* ... by the application will be ... */ unsigned long *step, /* ... adjusted to represent reality */ unsigned long *ds_cnt, /* number of data sources in file */ rrd_value_t **data) /* two dimensional array containing the data */ { int i,reduce_factor = ceil((double)(*step) / (double)cur_step); unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0; rrd_value_t *srcptr,*dstptr; (*step) = cur_step*reduce_factor; /* set new step size for reduced data */ dstptr = *data; srcptr = *data; row_cnt = ((*end)-(*start))/cur_step; #ifdef DEBUG #define DEBUG_REDUCE #endif #ifdef DEBUG_REDUCE printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n", row_cnt,reduce_factor,*start,*end,cur_step); for (col=0;col=reduce_factor;dst_row++) { for (col=0;col<(*ds_cnt);col++) { rrd_value_t newval=DNAN; unsigned long validval=0; for (i=0;igdes_c;i++){ /* only GF_DEF elements fetch data */ if (im->gdes[i].gf != GF_DEF) continue; skip=0; /* do we have it already ?*/ for (ii=0;iigdes[ii].gf != GF_DEF) continue; if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0) && (im->gdes[i].cf == im->gdes[ii].cf) && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce) && (im->gdes[i].start_orig == im->gdes[ii].start_orig) && (im->gdes[i].end_orig == im->gdes[ii].end_orig) && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) { /* OK, the data is already there. ** Just copy the header portion */ im->gdes[i].start = im->gdes[ii].start; im->gdes[i].end = im->gdes[ii].end; im->gdes[i].step = im->gdes[ii].step; im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt; im->gdes[i].ds_namv = im->gdes[ii].ds_namv; im->gdes[i].data = im->gdes[ii].data; im->gdes[i].data_first = 0; skip=1; } if (skip) break; } if (! skip) { unsigned long ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */ if((rrd_fetch_fn(im->gdes[i].rrd, im->gdes[i].cf, &im->gdes[i].start, &im->gdes[i].end, &ft_step, &im->gdes[i].ds_cnt, &im->gdes[i].ds_namv, &im->gdes[i].data)) == -1){ return -1; } im->gdes[i].data_first = 1; if (ft_step < im->gdes[i].step) { reduce_data(im->gdes[i].cf_reduce, ft_step, &im->gdes[i].start, &im->gdes[i].end, &im->gdes[i].step, &im->gdes[i].ds_cnt, &im->gdes[i].data); } else { im->gdes[i].step = ft_step; } } /* lets see if the required data source is really there */ for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){ if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){ im->gdes[i].ds=ii; } } if (im->gdes[i].ds== -1){ rrd_set_error("No DS called '%s' in '%s'", im->gdes[i].ds_nam,im->gdes[i].rrd); return -1; } } return 0; } /* evaluate the expressions in the CDEF functions */ /************************************************************* * CDEF stuff *************************************************************/ long find_var_wrapper(void *arg1, char *key) { return find_var((image_desc_t *) arg1, key); } /* find gdes containing var*/ long find_var(image_desc_t *im, char *key){ long ii; for(ii=0;iigdes_c-1;ii++){ if((im->gdes[ii].gf == GF_DEF || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF) && (strcmp(im->gdes[ii].vname,key) == 0)){ return ii; } } return -1; } /* find the largest common denominator for all the numbers in the 0 terminated num array */ long lcd(long *num){ long rest; int i; for (i=0;num[i+1]!=0;i++){ do { rest=num[i] % num[i+1]; num[i]=num[i+1]; num[i+1]=rest; } while (rest!=0); num[i+1] = num[i]; } /* return i==0?num[i]:num[i-1]; */ return num[i]; } /* run the rpn calculator on all the VDEF and CDEF arguments */ int data_calc( image_desc_t *im){ int gdi; int dataidx; long *steparray, rpi; int stepcnt; time_t now; rpnstack_t rpnstack; rpnstack_init(&rpnstack); for (gdi=0;gdigdes_c;gdi++){ /* Look for GF_VDEF and GF_CDEF in the same loop, * so CDEFs can use VDEFs and vice versa */ switch (im->gdes[gdi].gf) { case GF_XPORT: break; case GF_SHIFT: { graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx]; /* remove current shift */ vdp->start -= vdp->shift; vdp->end -= vdp->shift; /* vdef */ if (im->gdes[gdi].shidx >= 0) vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val; /* constant */ else vdp->shift = im->gdes[gdi].shval; /* normalize shift to multiple of consolidated step */ vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step; /* apply shift */ vdp->start += vdp->shift; vdp->end += vdp->shift; break; } case GF_VDEF: /* A VDEF has no DS. This also signals other parts * of rrdtool that this is a VDEF value, not a CDEF. */ im->gdes[gdi].ds_cnt = 0; if (vdef_calc(im,gdi)) { rrd_set_error("Error processing VDEF '%s'" ,im->gdes[gdi].vname ); rpnstack_free(&rpnstack); return -1; } break; case GF_CDEF: im->gdes[gdi].ds_cnt = 1; im->gdes[gdi].ds = 0; im->gdes[gdi].data_first = 1; im->gdes[gdi].start = 0; im->gdes[gdi].end = 0; steparray=NULL; stepcnt = 0; dataidx=-1; /* Find the variables in the expression. * - VDEF variables are substituted by their values * and the opcode is changed into OP_NUMBER. * - CDEF variables are analized for their step size, * the lowest common denominator of all the step * sizes of the data sources involved is calculated * and the resulting number is the step size for the * resulting data source. */ for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){ if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE || im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){ long ptr = im->gdes[gdi].rpnp[rpi].ptr; if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */ #if 0 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n", im->gdes[gdi].vname, im->gdes[ptr].vname); printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val); #endif im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val; im->gdes[gdi].rpnp[rpi].op = OP_NUMBER; } else { /* normal variables and PREF(variables) */ /* add one entry to the array that keeps track of the step sizes of the * data sources going into the CDEF. */ if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){ rrd_set_error("realloc steparray"); rpnstack_free(&rpnstack); return -1; }; steparray[stepcnt-1] = im->gdes[ptr].step; /* adjust start and end of cdef (gdi) so * that it runs from the latest start point * to the earliest endpoint of any of the * rras involved (ptr) */ if(im->gdes[gdi].start < im->gdes[ptr].start) im->gdes[gdi].start = im->gdes[ptr].start; if(im->gdes[gdi].end == 0 || im->gdes[gdi].end > im->gdes[ptr].end) im->gdes[gdi].end = im->gdes[ptr].end; /* store pointer to the first element of * the rra providing data for variable, * further save step size and data source * count of this rra */ im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds; im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step; im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt; /* backoff the *.data ptr; this is done so * rpncalc() function doesn't have to treat * the first case differently */ } /* if ds_cnt != 0 */ } /* if OP_VARIABLE */ } /* loop through all rpi */ /* move the data pointers to the correct period */ for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){ if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE || im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){ long ptr = im->gdes[gdi].rpnp[rpi].ptr; long diff = im->gdes[gdi].start - im->gdes[ptr].start; if(diff > 0) im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt; } } if(steparray == NULL){ rrd_set_error("rpn expressions without DEF" " or CDEF variables are not supported"); rpnstack_free(&rpnstack); return -1; } steparray[stepcnt]=0; /* Now find the resulting step. All steps in all * used RRAs have to be visited */ im->gdes[gdi].step = lcd(steparray); free(steparray); if((im->gdes[gdi].data = malloc(( (im->gdes[gdi].end-im->gdes[gdi].start) / im->gdes[gdi].step) * sizeof(double)))==NULL){ rrd_set_error("malloc im->gdes[gdi].data"); rpnstack_free(&rpnstack); return -1; } /* Step through the new cdef results array and * calculate the values */ for (now = im->gdes[gdi].start + im->gdes[gdi].step; now<=im->gdes[gdi].end; now += im->gdes[gdi].step) { rpnp_t *rpnp = im -> gdes[gdi].rpnp; /* 3rd arg of rpn_calc is for OP_VARIABLE lookups; * in this case we are advancing by timesteps; * we use the fact that time_t is a synonym for long */ if (rpn_calc(rpnp,&rpnstack,(long) now, im->gdes[gdi].data,++dataidx) == -1) { /* rpn_calc sets the error string */ rpnstack_free(&rpnstack); return -1; } } /* enumerate over time steps within a CDEF */ break; default: continue; } } /* enumerate over CDEFs */ rpnstack_free(&rpnstack); return 0; } /* massage data so, that we get one value for each x coordinate in the graph */ int data_proc( image_desc_t *im ){ long i,ii; double pixstep = (double)(im->end-im->start) /(double)im->xsize; /* how much time passes in one pixel */ double paintval; double minval=DNAN,maxval=DNAN; unsigned long gr_time; /* memory for the processed data */ for(i=0;igdes_c;i++) { if((im->gdes[i].gf==GF_LINE) || (im->gdes[i].gf==GF_AREA) || (im->gdes[i].gf==GF_TICK)) { if((im->gdes[i].p_data = malloc((im->xsize +1) * sizeof(rrd_value_t)))==NULL){ rrd_set_error("malloc data_proc"); return -1; } } } for (i=0;ixsize;i++) { /* for each pixel */ long vidx; gr_time = im->start+pixstep*i; /* time of the current step */ paintval=0.0; for (ii=0;iigdes_c;ii++) { double value; switch (im->gdes[ii].gf) { case GF_LINE: case GF_AREA: case GF_TICK: if (!im->gdes[ii].stack) paintval = 0.0; value = im->gdes[ii].yrule; if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) { /* The time of the data doesn't necessarily match ** the time of the graph. Beware. */ vidx = im->gdes[ii].vidx; if (im->gdes[vidx].gf == GF_VDEF) { value = im->gdes[vidx].vf.val; } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) && ((long int)gr_time <= (long int)im->gdes[vidx].end) ) { value = im->gdes[vidx].data[ (unsigned long) floor( (double)(gr_time - im->gdes[vidx].start) / im->gdes[vidx].step) * im->gdes[vidx].ds_cnt + im->gdes[vidx].ds ]; } else { value = DNAN; } }; if (! isnan(value)) { paintval += value; im->gdes[ii].p_data[i] = paintval; /* GF_TICK: the data values are not ** relevant for min and max */ if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) { if ((isnan(minval) || paintval < minval ) && ! (im->logarithmic && paintval <= 0.0)) minval = paintval; if (isnan(maxval) || paintval > maxval) maxval = paintval; } } else { im->gdes[ii].p_data[i] = DNAN; } break; case GF_STACK: rrd_set_error("STACK should already be turned into LINE or AREA here"); return -1; break; default: break; } } } /* if min or max have not been asigned a value this is because there was no data in the graph ... this is not good ... lets set these to dummy values then ... */ if (im->logarithmic) { if (isnan(minval)) minval = 0.2; if (isnan(maxval)) maxval = 5.1; } else { if (isnan(minval)) minval = 0.0; if (isnan(maxval)) maxval = 1.0; } /* adjust min and max values */ if (isnan(im->minval) /* don't adjust low-end with log scale */ /* why not? */ || ((!im->rigid) && im->minval > minval) ) { if (im->logarithmic) im->minval = minval * 0.5; else im->minval = minval; } if (isnan(im->maxval) || (!im->rigid && im->maxval < maxval) ) { if (im->logarithmic) im->maxval = maxval * 2.0; else im->maxval = maxval; } /* make sure min is smaller than max */ if (im->minval > im->maxval) { im->minval = 0.99 * im->maxval; } /* make sure min and max are not equal */ if (im->minval == im->maxval) { im->maxval *= 1.01; if (! im->logarithmic) { im->minval *= 0.99; } /* make sure min and max are not both zero */ if (im->maxval == 0.0) { im->maxval = 1.0; } } return 0; } /* identify the point where the first gridline, label ... gets placed */ time_t find_first_time( time_t start, /* what is the initial time */ enum tmt_en baseint, /* what is the basic interval */ long basestep /* how many if these do we jump a time */ ) { struct tm tm; localtime_r(&start, &tm); switch(baseint){ case TMT_SECOND: tm.tm_sec -= tm.tm_sec % basestep; break; case TMT_MINUTE: tm.tm_sec=0; tm.tm_min -= tm.tm_min % basestep; break; case TMT_HOUR: tm.tm_sec=0; tm.tm_min = 0; tm.tm_hour -= tm.tm_hour % basestep; break; case TMT_DAY: /* we do NOT look at the basestep for this ... */ tm.tm_sec=0; tm.tm_min = 0; tm.tm_hour = 0; break; case TMT_WEEK: /* we do NOT look at the basestep for this ... */ tm.tm_sec=0; tm.tm_min = 0; tm.tm_hour = 0; tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */ if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */ break; case TMT_MONTH: tm.tm_sec=0; tm.tm_min = 0; tm.tm_hour = 0; tm.tm_mday = 1; tm.tm_mon -= tm.tm_mon % basestep; break; case TMT_YEAR: tm.tm_sec=0; tm.tm_min = 0; tm.tm_hour = 0; tm.tm_mday = 1; tm.tm_mon = 0; tm.tm_year -= (tm.tm_year+1900) % basestep; } return mktime(&tm); } /* identify the point where the next gridline, label ... gets placed */ time_t find_next_time( time_t current, /* what is the initial time */ enum tmt_en baseint, /* what is the basic interval */ long basestep /* how many if these do we jump a time */ ) { struct tm tm; time_t madetime; localtime_r(¤t, &tm); do { switch(baseint){ case TMT_SECOND: tm.tm_sec += basestep; break; case TMT_MINUTE: tm.tm_min += basestep; break; case TMT_HOUR: tm.tm_hour += basestep; break; case TMT_DAY: tm.tm_mday += basestep; break; case TMT_WEEK: tm.tm_mday += 7*basestep; break; case TMT_MONTH: tm.tm_mon += basestep; break; case TMT_YEAR: tm.tm_year += basestep; } madetime = mktime(&tm); } while (madetime == -1); /* this is necessary to skip impssible times like the daylight saving time skips */ return madetime; } /* calculate values required for PRINT and GPRINT functions */ int print_calc(image_desc_t *im, char ***prdata) { long i,ii,validsteps; double printval; struct tm tmvdef; int graphelement = 0; long vidx; int max_ii; double magfact = -1; char *si_symb = ""; char *percent_s; int prlines = 1; /* wow initializing tmvdef is quite a task :-) */ time_t now = time(NULL); localtime_r(&now,&tmvdef); if (im->imginfo) prlines++; for(i=0;igdes_c;i++){ vidx = im->gdes[i].vidx; switch(im->gdes[i].gf){ case GF_PRINT: prlines++; if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){ rrd_set_error("realloc prdata"); return 0; } case GF_GPRINT: /* PRINT and GPRINT can now print VDEF generated values. * There's no need to do any calculations on them as these * calculations were already made. */ if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */ printval = im->gdes[vidx].vf.val; localtime_r(&im->gdes[vidx].vf.when,&tmvdef); } else { /* need to calculate max,min,avg etcetera */ max_ii =((im->gdes[vidx].end - im->gdes[vidx].start) / im->gdes[vidx].step * im->gdes[vidx].ds_cnt); printval = DNAN; validsteps = 0; for( ii=im->gdes[vidx].ds; ii < max_ii; ii+=im->gdes[vidx].ds_cnt){ if (! finite(im->gdes[vidx].data[ii])) continue; if (isnan(printval)){ printval = im->gdes[vidx].data[ii]; validsteps++; continue; } switch (im->gdes[i].cf){ case CF_HWPREDICT: case CF_DEVPREDICT: case CF_DEVSEASONAL: case CF_SEASONAL: case CF_AVERAGE: validsteps++; printval += im->gdes[vidx].data[ii]; break; case CF_MINIMUM: printval = min( printval, im->gdes[vidx].data[ii]); break; case CF_FAILURES: case CF_MAXIMUM: printval = max( printval, im->gdes[vidx].data[ii]); break; case CF_LAST: printval = im->gdes[vidx].data[ii]; } } if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) { if (validsteps > 1) { printval = (printval / validsteps); } } } /* prepare printval */ if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) { /* Magfact is set to -1 upon entry to print_calc. If it * is still less than 0, then we need to run auto_scale. * Otherwise, put the value into the correct units. If * the value is 0, then do not set the symbol or magnification * so next the calculation will be performed again. */ if (magfact < 0.0) { auto_scale(im,&printval,&si_symb,&magfact); if (printval == 0.0) magfact = -1.0; } else { printval /= magfact; } *(++percent_s) = 's'; } else if (strstr(im->gdes[i].format,"%s") != NULL) { auto_scale(im,&printval,&si_symb,&magfact); } if (im->gdes[i].gf == GF_PRINT){ (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char)); (*prdata)[prlines-1] = NULL; if (im->gdes[i].strftm){ strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef); } else { if (bad_format(im->gdes[i].format)) { rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format); return -1; } #ifdef HAVE_SNPRINTF snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb); #else sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb); #endif } } else { /* GF_GPRINT */ if (im->gdes[i].strftm){ strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef); } else { if (bad_format(im->gdes[i].format)) { rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format); return -1; } #ifdef HAVE_SNPRINTF snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb); #else sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb); #endif } graphelement = 1; } break; case GF_LINE: case GF_AREA: case GF_TICK: graphelement = 1; break; case GF_HRULE: if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */ im->gdes[i].yrule=im->gdes[vidx].vf.val; }; graphelement = 1; break; case GF_VRULE: if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/ im->gdes[i].xrule = im->gdes[vidx].vf.when; }; graphelement = 1; break; case GF_COMMENT: case GF_DEF: case GF_CDEF: case GF_VDEF: #ifdef WITH_PIECHART case GF_PART: #endif case GF_SHIFT: case GF_XPORT: break; case GF_STACK: rrd_set_error("STACK should already be turned into LINE or AREA here"); return -1; break; } } return graphelement; } /* place legends with color spots */ int leg_place(image_desc_t *im) { /* graph labels */ int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0; int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0; int fill=0, fill_last; int leg_c = 0; int leg_x = border, leg_y = im->yimg; int leg_y_prev = im->yimg; int leg_cc; int glue = 0; int i,ii, mark = 0; char prt_fctn; /*special printfunctions */ int *legspace; if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) { if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){ rrd_set_error("malloc for legspace"); return -1; } for(i=0;igdes_c;i++){ fill_last = fill; /* hid legends for rules which are not displayed */ if(!(im->extra_flags & FORCE_RULES_LEGEND)) { if (im->gdes[i].gf == GF_HRULE && (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval)) im->gdes[i].legend[0] = '\0'; if (im->gdes[i].gf == GF_VRULE && (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end)) im->gdes[i].legend[0] = '\0'; } leg_cc = strlen(im->gdes[i].legend); /* is there a controle code ant the end of the legend string ? */ /* and it is not a tab \\t */ if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') { prt_fctn = im->gdes[i].legend[leg_cc-1]; leg_cc -= 2; im->gdes[i].legend[leg_cc] = '\0'; } else { prt_fctn = '\0'; } /* only valid control codes */ if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */ prt_fctn != 'r' && prt_fctn != 'j' && prt_fctn != 'c' && prt_fctn != 's' && prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g' ) { free(legspace); rrd_set_error("Unknown control code at the end of '%s\\%c'",im->gdes[i].legend,prt_fctn); return -1; } /* remove exess space */ if ( prt_fctn == 'n' ){ prt_fctn='l'; } while (prt_fctn=='g' && leg_cc > 0 && im->gdes[i].legend[leg_cc-1]==' '){ leg_cc--; im->gdes[i].legend[leg_cc]='\0'; } if (leg_cc != 0 ){ legspace[i]=(prt_fctn=='g' ? 0 : interleg); if (fill > 0){ /* no interleg space if string ends in \g */ fill += legspace[i]; } fill += gfx_get_text_width(im->canvas, fill+border, im->text_prop[TEXT_PROP_LEGEND].font, im->text_prop[TEXT_PROP_LEGEND].size, im->tabwidth, im->gdes[i].legend, 0); leg_c++; } else { legspace[i]=0; } /* who said there was a special tag ... ?*/ if (prt_fctn=='g') { prt_fctn = '\0'; } if (prt_fctn == '\0') { if (i == im->gdes_c -1 ) prt_fctn ='l'; /* is it time to place the legends ? */ if (fill > im->ximg - 2*border){ if (leg_c > 1) { /* go back one */ i--; fill = fill_last; leg_c--; prt_fctn = 'j'; } else { prt_fctn = 'l'; } } } if (prt_fctn != '\0'){ leg_x = border; if (leg_c >= 2 && prt_fctn == 'j') { glue = (im->ximg - fill - 2* border) / (leg_c-1); } else { glue = 0; } if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0; if (prt_fctn =='r') leg_x = im->ximg - fill - border; for(ii=mark;ii<=i;ii++){ if(im->gdes[ii].legend[0]=='\0') continue; /* skip empty legends */ im->gdes[ii].leg_x = leg_x; im->gdes[ii].leg_y = leg_y; leg_x += gfx_get_text_width(im->canvas, leg_x, im->text_prop[TEXT_PROP_LEGEND].font, im->text_prop[TEXT_PROP_LEGEND].size, im->tabwidth, im->gdes[ii].legend, 0) + legspace[ii] + glue; } leg_y_prev = leg_y; /* only add y space if there was text on the line */ if (leg_x > border || prt_fctn == 's') leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8; if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size; fill = 0; leg_c = 0; mark = ii; } } im->yimg = leg_y_prev; /* if we did place some legends we have to add vertical space */ if (leg_y != im->yimg){ im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8; } free(legspace); } return 0; } /* create a grid on the graph. it determines what to do from the values of xsize, start and end */ /* the xaxis labels are determined from the number of seconds per pixel in the requested graph */ int calc_horizontal_grid(image_desc_t *im) { double range; double scaledrange; int pixel,i; int gridind=0; int decimals, fractionals; im->ygrid_scale.labfact=2; range = im->maxval - im->minval; scaledrange = range / im->magfact; /* does the scale of this graph make it impossible to put lines on it? If so, give up. */ if (isnan(scaledrange)) { return 0; } /* find grid spaceing */ pixel=1; if(isnan(im->ygridstep)){ if(im->extra_flags & ALTYGRID) { /* find the value with max number of digits. Get number of digits */ decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact)); if(decimals <= 0) /* everything is small. make place for zero */ decimals = 1; im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact; if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */ im->ygrid_scale.gridstep = 0.1; /* should have at least 5 lines but no more then 15 */ if(range/im->ygrid_scale.gridstep < 5) im->ygrid_scale.gridstep /= 10; if(range/im->ygrid_scale.gridstep > 15) im->ygrid_scale.gridstep *= 10; if(range/im->ygrid_scale.gridstep > 5) { im->ygrid_scale.labfact = 1; if(range/im->ygrid_scale.gridstep > 8) im->ygrid_scale.labfact = 2; } else { im->ygrid_scale.gridstep /= 5; im->ygrid_scale.labfact = 5; } fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact)); if(fractionals < 0) { /* small amplitude. */ int len = decimals - fractionals + 1; if (im->unitslength < len+2) im->unitslength = len+2; sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : "")); } else { int len = decimals + 1; if (im->unitslength < len+2) im->unitslength = len+2; sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" )); } } else { for(i=0;ylab[i].grid > 0;i++){ pixel = im->ysize / (scaledrange / ylab[i].grid); gridind = i; if (pixel > 7) break; } for(i=0; i<4;i++) { if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) { im->ygrid_scale.labfact = ylab[gridind].lfac[i]; break; } } im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact; } } else { im->ygrid_scale.gridstep = im->ygridstep; im->ygrid_scale.labfact = im->ylabfact; } return 1; } int draw_horizontal_grid(image_desc_t *im) { int i; double scaledstep; char graph_label[100]; int nlabels=0; double X0=im->xorigin; double X1=im->xorigin+im->xsize; int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1); int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1); double MaxY; scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor; MaxY = scaledstep*(double)egrid; for (i = sgrid; i <= egrid; i++){ double Y0=ytr(im,im->ygrid_scale.gridstep*i); double YN=ytr(im,im->ygrid_scale.gridstep*(i+1)); if ( Y0 >= im->yorigin-im->ysize && Y0 <= im->yorigin){ /* Make sure at least 2 grid labels are shown, even if it doesn't agree with the chosen settings. Add a label if required by settings, or if there is only one label so far and the next grid line is out of bounds. */ if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){ if (im->symbol == ' ') { if(im->extra_flags & ALTYGRID) { sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i); } else { if(MaxY < 10) { sprintf(graph_label,"%4.1f",scaledstep*(double)i); } else { sprintf(graph_label,"%4.0f",scaledstep*(double)i); } } }else { char sisym = ( i == 0 ? ' ' : im->symbol); if(im->extra_flags & ALTYGRID) { sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym); } else { if(MaxY < 10){ sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym); } else { sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym); } } } nlabels++; gfx_new_text ( im->canvas, X0-im->text_prop[TEXT_PROP_AXIS].size, Y0, im->graph_col[GRC_FONT], im->text_prop[TEXT_PROP_AXIS].font, im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label ); gfx_new_dashed_line ( im->canvas, X0-2,Y0, X1+2,Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID], im->grid_dash_on, im->grid_dash_off); } else if (!(im->extra_flags & NOMINOR)) { gfx_new_dashed_line ( im->canvas, X0-1,Y0, X1+1,Y0, GRIDWIDTH, im->graph_col[GRC_GRID], im->grid_dash_on, im->grid_dash_off); } } } return 1; } /* this is frexp for base 10 */ double frexp10(double, double *); double frexp10(double x, double *e) { double mnt; int iexp; iexp = floor(log(fabs(x)) / log(10)); mnt = x / pow(10.0, iexp); if(mnt >= 10.0) { iexp++; mnt = x / pow(10.0, iexp); } *e = iexp; return mnt; } static int AlmostEqual2sComplement (float A, float B, int maxUlps) { int aInt = *(int*)&A; int bInt = *(int*)&B; int intDiff; /* Make sure maxUlps is non-negative and small enough that the default NAN won't compare as equal to anything. */ /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */ /* Make aInt lexicographically ordered as a twos-complement int */ if (aInt < 0) aInt = 0x80000000l - aInt; /* Make bInt lexicographically ordered as a twos-complement int */ if (bInt < 0) bInt = 0x80000000l - bInt; intDiff = abs(aInt - bInt); if (intDiff <= maxUlps) return 1; return 0; } /* logaritmic horizontal grid */ int horizontal_log_grid(image_desc_t *im) { double yloglab[][10] = { {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0}, {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0}, {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.}, {0,0,0,0,0, 0,0,0,0,0} /* last line */ }; int i, j, val_exp, min_exp; double nex; /* number of decades in data */ double logscale; /* scale in logarithmic space */ int exfrac = 1; /* decade spacing */ int mid = -1; /* row in yloglab for major grid */ double mspac; /* smallest major grid spacing (pixels) */ int flab; /* first value in yloglab to use */ double value, tmp, pre_value; double X0,X1,Y0; char graph_label[100]; nex = log10(im->maxval / im->minval); logscale = im->ysize / nex; /* major spacing for data with high dynamic range */ while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) { if(exfrac == 1) exfrac = 3; else exfrac += 3; } /* major spacing for less dynamic data */ do { /* search best row in yloglab */ mid++; for(i = 0; yloglab[mid][i + 1] < 10.0; i++); mspac = logscale * log10(10.0 / yloglab[mid][i]); } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0); if(mid) mid--; /* find first value in yloglab */ for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++); if(yloglab[mid][flab] == 10.0) { tmp += 1.0; flab = 0; } val_exp = tmp; if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac); X0=im->xorigin; X1=im->xorigin+im->xsize; /* draw grid */ pre_value = DNAN; while(1) { value = yloglab[mid][flab] * pow(10.0, val_exp); if ( AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */ pre_value = value; Y0 = ytr(im, value); if(Y0 <= im->yorigin - im->ysize) break; /* major grid line */ gfx_new_dashed_line ( im->canvas, X0-2,Y0, X1+2,Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID], im->grid_dash_on, im->grid_dash_off); /* label */ if (im->extra_flags & FORCE_UNITS_SI) { int scale; double pvalue; char symbol; scale = floor(val_exp / 3.0); if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3); else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2); pvalue *= yloglab[mid][flab]; if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) && ((scale+si_symbcenter) >= 0) ) symbol = si_symbol[scale+si_symbcenter]; else symbol = '?'; sprintf(graph_label,"%3.0f %c", pvalue, symbol); } else sprintf(graph_label,"%3.0e", value); gfx_new_text ( im->canvas, X0-im->text_prop[TEXT_PROP_AXIS].size, Y0, im->graph_col[GRC_FONT], im->text_prop[TEXT_PROP_AXIS].font, im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label ); /* minor grid */ if(mid < 4 && exfrac == 1) { /* find first and last minor line behind current major line * i is the first line and j tha last */ if(flab == 0) { min_exp = val_exp - 1; for(i = 1; yloglab[mid][i] < 10.0; i++); i = yloglab[mid][i - 1] + 1; j = 10; } else { min_exp = val_exp; i = yloglab[mid][flab - 1] + 1; j = yloglab[mid][flab]; } /* draw minor lines below current major line */ for(; i < j; i++) { value = i * pow(10.0, min_exp); if(value < im->minval) continue; Y0 = ytr(im, value); if(Y0 <= im->yorigin - im->ysize) break; /* draw lines */ gfx_new_dashed_line ( im->canvas, X0-1,Y0, X1+1,Y0, GRIDWIDTH, im->graph_col[GRC_GRID], im->grid_dash_on, im->grid_dash_off); } } else if(exfrac > 1) { for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) { value = pow(10.0, i); if(value < im->minval) continue; Y0 = ytr(im, value); if(Y0 <= im->yorigin - im->ysize) break; /* draw lines */ gfx_new_dashed_line ( im->canvas, X0-1,Y0, X1+1,Y0, GRIDWIDTH, im->graph_col[GRC_GRID], im->grid_dash_on, im->grid_dash_off); } } /* next decade */ if(yloglab[mid][++flab] == 10.0) { flab = 0; val_exp += exfrac; } } /* draw minor lines after highest major line */ if(mid < 4 && exfrac == 1) { /* find first and last minor line below current major line * i is the first line and j tha last */ if(flab == 0) { min_exp = val_exp - 1; for(i = 1; yloglab[mid][i] < 10.0; i++); i = yloglab[mid][i - 1] + 1; j = 10; } else { min_exp = val_exp; i = yloglab[mid][flab - 1] + 1; j = yloglab[mid][flab]; } /* draw minor lines below current major line */ for(; i < j; i++) { value = i * pow(10.0, min_exp); if(value < im->minval) continue; Y0 = ytr(im, value); if(Y0 <= im->yorigin - im->ysize) break; /* draw lines */ gfx_new_dashed_line ( im->canvas, X0-1,Y0, X1+1,Y0, GRIDWIDTH, im->graph_col[GRC_GRID], im->grid_dash_on, im->grid_dash_off); } } /* fancy minor gridlines */ else if(exfrac > 1) { for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) { value = pow(10.0, i); if(value < im->minval) continue; Y0 = ytr(im, value); if(Y0 <= im->yorigin - im->ysize) break; /* draw lines */ gfx_new_dashed_line ( im->canvas, X0-1,Y0, X1+1,Y0, GRIDWIDTH, im->graph_col[GRC_GRID], im->grid_dash_on, im->grid_dash_off); } } return 1; } void vertical_grid( image_desc_t *im ) { int xlab_sel; /* which sort of label and grid ? */ time_t ti, tilab, timajor; long factor; char graph_label[100]; double X0,Y0,Y1; /* points for filled graph and more*/ struct tm tm; /* the type of time grid is determined by finding the number of seconds per pixel in the graph */ if(im->xlab_user.minsec == -1){ factor=(im->end - im->start)/im->xsize; xlab_sel=0; while ( xlab[xlab_sel+1].minsec != -1 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */ while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */ im->xlab_user.gridtm = xlab[xlab_sel].gridtm; im->xlab_user.gridst = xlab[xlab_sel].gridst; im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm; im->xlab_user.mgridst = xlab[xlab_sel].mgridst; im->xlab_user.labtm = xlab[xlab_sel].labtm; im->xlab_user.labst = xlab[xlab_sel].labst; im->xlab_user.precis = xlab[xlab_sel].precis; im->xlab_user.stst = xlab[xlab_sel].stst; } /* y coords are the same for every line ... */ Y0 = im->yorigin; Y1 = im->yorigin-im->ysize; /* paint the minor grid */ if (!(im->extra_flags & NOMINOR)) { for(ti = find_first_time(im->start, im->xlab_user.gridtm, im->xlab_user.gridst), timajor = find_first_time(im->start, im->xlab_user.mgridtm, im->xlab_user.mgridst); ti < im->end; ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst) ){ /* are we inside the graph ? */ if (ti < im->start || ti > im->end) continue; while (timajor < ti) { timajor = find_next_time(timajor, im->xlab_user.mgridtm, im->xlab_user.mgridst); } if (ti == timajor) continue; /* skip as falls on major grid line */ X0 = xtr(im,ti); gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH, im->graph_col[GRC_GRID], im->grid_dash_on, im->grid_dash_off); } } /* paint the major grid */ for(ti = find_first_time(im->start, im->xlab_user.mgridtm, im->xlab_user.mgridst); ti < im->end; ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst) ){ /* are we inside the graph ? */ if (ti < im->start || ti > im->end) continue; X0 = xtr(im,ti); gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID], im->grid_dash_on, im->grid_dash_off); } /* paint the labels below the graph */ for(ti = find_first_time(im->start - im->xlab_user.precis/2, im->xlab_user.labtm, im->xlab_user.labst); ti <= im->end - im->xlab_user.precis/2; ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst) ){ tilab= ti + im->xlab_user.precis/2; /* correct time for the label */ /* are we inside the graph ? */ if (tilab < im->start || tilab > im->end) continue; #if HAVE_STRFTIME localtime_r(&tilab, &tm); strftime(graph_label,99,im->xlab_user.stst, &tm); #else # error "your libc has no strftime I guess we'll abort the exercise here." #endif gfx_new_text ( im->canvas, xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5, im->graph_col[GRC_FONT], im->text_prop[TEXT_PROP_AXIS].font, im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM, graph_label ); } } void axis_paint( image_desc_t *im ) { /* draw x and y axis */ /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin, im->xorigin+im->xsize,im->yorigin-im->ysize, GRIDWIDTH, im->graph_col[GRC_AXIS]); gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize, im->xorigin+im->xsize,im->yorigin-im->ysize, GRIDWIDTH, im->graph_col[GRC_AXIS]); */ gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin, im->xorigin+im->xsize+4,im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]); gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4, im->xorigin,im->yorigin-im->ysize-4, MGRIDWIDTH, im->graph_col[GRC_AXIS]); /* arrow for X and Y axis direction */ gfx_new_area ( im->canvas, im->xorigin+im->xsize+2, im->yorigin-2, im->xorigin+im->xsize+2, im->yorigin+3, im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */ im->graph_col[GRC_ARROW]); gfx_new_area ( im->canvas, im->xorigin-2, im->yorigin-im->ysize-2, im->xorigin+3, im->yorigin-im->ysize-2, im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */ im->graph_col[GRC_ARROW]); } void grid_paint(image_desc_t *im) { long i; int res=0; double X0,Y0; /* points for filled graph and more*/ gfx_node_t *node; /* draw 3d border */ node = gfx_new_area (im->canvas, 0,im->yimg, 2,im->yimg-2, 2,2,im->graph_col[GRC_SHADEA]); gfx_add_point( node , im->ximg - 2, 2 ); gfx_add_point( node , im->ximg, 0 ); gfx_add_point( node , 0,0 ); /* gfx_add_point( node , 0,im->yimg ); */ node = gfx_new_area (im->canvas, 2,im->yimg-2, im->ximg-2,im->yimg-2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]); gfx_add_point( node , im->ximg,0); gfx_add_point( node , im->ximg,im->yimg); gfx_add_point( node , 0,im->yimg); /* gfx_add_point( node , 0,im->yimg ); */ if (im->draw_x_grid == 1 ) vertical_grid(im); if (im->draw_y_grid == 1){ if(im->logarithmic){ res = horizontal_log_grid(im); } else { res = draw_horizontal_grid(im); } /* dont draw horizontal grid if there is no min and max val */ if (! res ) { char *nodata = "No Data found"; gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2, im->graph_col[GRC_FONT], im->text_prop[TEXT_PROP_AXIS].font, im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata ); } } /* yaxis unit description */ gfx_new_text( im->canvas, 10, (im->yorigin - im->ysize/2), im->graph_col[GRC_FONT], im->text_prop[TEXT_PROP_UNIT].font, im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth, RRDGRAPH_YLEGEND_ANGLE, GFX_H_LEFT, GFX_V_CENTER, im->ylegend); /* graph title */ gfx_new_text( im->canvas, im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4, im->graph_col[GRC_FONT], im->text_prop[TEXT_PROP_TITLE].font, im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER, im->title); /* rrdtool 'logo' */ gfx_new_text( im->canvas, im->ximg-7, 7, ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044, im->text_prop[TEXT_PROP_AXIS].font, 5.5, im->tabwidth, 270, GFX_H_RIGHT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER"); /* graph watermark */ if(im->watermark[0] != '\0') { gfx_new_text( im->canvas, im->ximg/2, im->yimg-6, ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044, im->text_prop[TEXT_PROP_AXIS].font, 5.5, im->tabwidth, 0, GFX_H_CENTER, GFX_V_BOTTOM, im->watermark); } /* graph labels */ if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) { for(i=0;igdes_c;i++){ if(im->gdes[i].legend[0] =='\0') continue; /* im->gdes[i].leg_y is the bottom of the legend */ X0 = im->gdes[i].leg_x; Y0 = im->gdes[i].leg_y; gfx_new_text ( im->canvas, X0, Y0, im->graph_col[GRC_FONT], im->text_prop[TEXT_PROP_LEGEND].font, im->text_prop[TEXT_PROP_LEGEND].size, im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend ); /* The legend for GRAPH items starts with "M " to have enough space for the box */ if ( im->gdes[i].gf != GF_PRINT && im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) { int boxH, boxV; boxH = gfx_get_text_width(im->canvas, 0, im->text_prop[TEXT_PROP_LEGEND].font, im->text_prop[TEXT_PROP_LEGEND].size, im->tabwidth,"o", 0) * 1.2; boxV = boxH*1.1; /* make sure transparent colors show up the same way as in the graph */ node = gfx_new_area(im->canvas, X0,Y0-boxV, X0,Y0, X0+boxH,Y0, im->graph_col[GRC_BACK]); gfx_add_point ( node, X0+boxH, Y0-boxV ); node = gfx_new_area(im->canvas, X0,Y0-boxV, X0,Y0, X0+boxH,Y0, im->gdes[i].col); gfx_add_point ( node, X0+boxH, Y0-boxV ); node = gfx_new_line(im->canvas, X0,Y0-boxV, X0,Y0, 1.0,im->graph_col[GRC_FRAME]); gfx_add_point(node,X0+boxH,Y0); gfx_add_point(node,X0+boxH,Y0-boxV); gfx_close_path(node); } } } } /***************************************************** * lazy check make sure we rely need to create this graph *****************************************************/ int lazy_check(image_desc_t *im){ FILE *fd = NULL; int size = 1; struct stat imgstat; if (im->lazy == 0) return 0; /* no lazy option */ if (stat(im->graphfile,&imgstat) != 0) return 0; /* can't stat */ /* one pixel in the existing graph is more then what we would change here ... */ if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize) return 0; if ((fd = fopen(im->graphfile,"rb")) == NULL) return 0; /* the file does not exist */ switch (im->canvas->imgformat) { case IF_PNG: size = PngSize(fd,&(im->ximg),&(im->yimg)); break; default: size = 1; } fclose(fd); return size; } #ifdef WITH_PIECHART void pie_part(image_desc_t *im, gfx_color_t color, double PieCenterX, double PieCenterY, double Radius, double startangle, double endangle) { gfx_node_t *node; double angle; double step=M_PI/50; /* Number of iterations for the circle; ** 10 is definitely too low, more than ** 50 seems to be overkill */ /* Strange but true: we have to work clockwise or else ** anti aliasing nor transparency don't work. ** ** This test is here to make sure we do it right, also ** this makes the for...next loop more easy to implement. ** The return will occur if the user enters a negative number ** (which shouldn't be done according to the specs) or if the ** programmers do something wrong (which, as we all know, never ** happens anyway :) */ if (endangle