X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Frrd_graph.c;h=9be24e8aaba270e273b746c936deb88251ab016e;hb=2e6c98e893777e4abf7b05cb4ecf81dde088cdb8;hp=bb31289e3df9a07415c97d090fe1d5cb9be870f6;hpb=8fdbb7f866597d29e970168781f4883fa8a9116e;p=rrdtool.git diff --git a/src/rrd_graph.c b/src/rrd_graph.c index bb31289..9be24e8 100644 --- a/src/rrd_graph.c +++ b/src/rrd_graph.c @@ -1,5 +1,5 @@ /**************************************************************************** - * RRDtool 1.2.11 Copyright by Tobi Oetiker, 1997-2005 + * RRDtool 1.2.19 Copyright by Tobi Oetiker, 1997-2007 **************************************************************************** * rrd__graph.c produce graphs from data in rrdfiles ****************************************************************************/ @@ -7,9 +7,12 @@ #include +#ifdef WIN32 +#include "strftime.h" +#endif #include "rrd_tool.h" -#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__) +#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__) #include #include #endif @@ -42,37 +45,29 @@ text_prop_t text_prop[] = { }; xlab_t xlab[] = { - {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"}, - {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"}, - {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"}, - {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"}, - {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"}, - {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"}, - {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"}, - /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/ - {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"}, - {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"}, - {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"}, - {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"}, - {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"}, - {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"}, - {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"}, - {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""} + {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 logarithmic y label intervals ... - the first element of each row defines the possible starting points on the - y axis ... the other specify the */ - -double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */ - { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 }, - { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 }, - { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }}; - /* sensible y label intervals ...*/ ylab_t ylab[]= { @@ -176,7 +171,7 @@ enum gf_en gf_conv(char *string){ conv_if(VRULE,GF_VRULE) conv_if(LINE,GF_LINE) conv_if(AREA,GF_AREA) - conv_if(STACK,GF_STACK) + conv_if(STACK,GF_STACK) conv_if(TICK,GF_TICK) conv_if(DEF,GF_DEF) conv_if(CDEF,GF_CDEF) @@ -309,14 +304,8 @@ auto_scale( } -/* find SI magnitude symbol for the numbers on the y-axis*/ -void -si_unit( - image_desc_t *im /* image description */ -) -{ - - char symbol[] = {'a', /* 10e-18 Atto */ +static char si_symbol[] = { + 'a', /* 10e-18 Atto */ 'f', /* 10e-15 Femto */ 'p', /* 10e-12 Pico */ 'n', /* 10e-9 Nano */ @@ -328,9 +317,17 @@ si_unit( 'G', /* 10e9 Giga */ 'T', /* 10e12 Tera */ 'P', /* 10e15 Peta */ - 'E'};/* 10e18 Exa */ + '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 */ +) +{ - int symbcenter = 6; double digits,viewdigits=0; digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base)); @@ -350,9 +347,9 @@ si_unit( im->viewfactor = im->magfact / pow((double)im->base , viewdigits); - if ( ((viewdigits+symbcenter) < sizeof(symbol)) && - ((viewdigits+symbcenter) >= 0) ) - im->symbol = symbol[(int)viewdigits+symbcenter]; + if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) && + ((viewdigits+si_symbcenter) >= 0) ) + im->symbol = si_symbol[(int)viewdigits+si_symbcenter]; else im->symbol = '?'; } @@ -688,7 +685,7 @@ data_fetch(image_desc_t *im ) int i,ii; int skip; - /* pull the data from the log files ... */ + /* pull the data from the rrd files ... */ for (i=0;i< (int)im->gdes_c;i++){ /* only GF_DEF elements fetch data */ if (im->gdes[i].gf != GF_DEF) @@ -702,9 +699,9 @@ data_fetch(image_desc_t *im ) 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 == im->gdes[ii].start) - && (im->gdes[i].end == im->gdes[ii].end) - && (im->gdes[i].step == im->gdes[ii].step)) { + && (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 */ @@ -721,7 +718,7 @@ data_fetch(image_desc_t *im ) break; } if (! skip) { - unsigned long ft_step = im->gdes[i].step ; + 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, @@ -734,7 +731,6 @@ data_fetch(image_desc_t *im ) return -1; } im->gdes[i].data_first = 1; - im->gdes[i].step = im->step; if (ft_step < im->gdes[i].step) { reduce_data(im->gdes[i].cf_reduce, @@ -1017,8 +1013,7 @@ data_proc( image_desc_t *im ){ 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) || - (im->gdes[i].gf==GF_STACK)) { + (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"); @@ -1040,7 +1035,6 @@ data_proc( image_desc_t *im ){ case GF_TICK: if (!im->gdes[ii].stack) paintval = 0.0; - case GF_STACK: value = im->gdes[ii].yrule; if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) { /* The time of the data doesn't necessarily match @@ -1070,8 +1064,9 @@ data_proc( image_desc_t *im ){ ** relevant for min and max */ if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) { - if (isnan(minval) || paintval < minval) - minval = paintval; + if ((isnan(minval) || paintval < minval ) && + ! (im->logarithmic && paintval <= 0.0)) + minval = paintval; if (isnan(maxval) || paintval > maxval) maxval = paintval; } @@ -1079,6 +1074,10 @@ data_proc( image_desc_t *im ){ 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; } @@ -1089,20 +1088,30 @@ data_proc( image_desc_t *im ){ there was no data in the graph ... this is not good ... lets set these to dummy values then ... */ - if (isnan(minval)) minval = 0.0; - if (isnan(maxval)) maxval = 1.0; + 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 */ - || ((!im->logarithmic && !im->rigid) && im->minval > minval) - ) - im->minval = 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 * 1.1; + im->maxval = maxval * 2.0; else im->maxval = maxval; } @@ -1223,7 +1232,7 @@ print_calc(image_desc_t *im, char ***prdata) { long i,ii,validsteps; double printval; - time_t printtime; + struct tm tmvdef; int graphelement = 0; long vidx; int max_ii; @@ -1231,6 +1240,9 @@ print_calc(image_desc_t *im, char ***prdata) 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++){ switch(im->gdes[i].gf){ @@ -1248,7 +1260,7 @@ print_calc(image_desc_t *im, char ***prdata) vidx = im->gdes[i].vidx; if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */ printval = im->gdes[vidx].vf.val; - printtime = im->gdes[vidx].vf.when; + 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) @@ -1294,21 +1306,6 @@ print_calc(image_desc_t *im, char ***prdata) } } /* prepare printval */ - if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */ - char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */ - int iii=0; - ctime_r(&printtime,ctime_buf); - while(isprint(ctime_buf[iii])){iii++;} - ctime_buf[iii]='\0'; - if (im->gdes[i].gf == GF_PRINT){ - (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char)); - sprintf((*prdata)[prlines-2],"%s (%lu)",ctime_buf,printtime); - (*prdata)[prlines-1] = NULL; - } else { - sprintf(im->gdes[i].legend,"%s (%lu)",ctime_buf,printtime); - graphelement = 1; - } - } else { 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. @@ -1327,40 +1324,57 @@ print_calc(image_desc_t *im, char ***prdata) auto_scale(im,&printval,&si_symb,&magfact); } - if (im->gdes[i].gf == GF_PRINT){ + if (im->gdes[i].gf == GF_PRINT){ (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char)); (*prdata)[prlines-1] = NULL; - if (bad_format(im->gdes[i].format)) { - rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format); + 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); + 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); + sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb); #endif - } else { + } + } else { /* GF_GPRINT */ - if (bad_format(im->gdes[i].format)) { + 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); + 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); + sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb); #endif - graphelement = 1; - } - } + } + graphelement = 1; + } break; case GF_LINE: case GF_AREA: case GF_TICK: - case GF_STACK: + 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[im->gdes[i].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[im->gdes[i].vidx].vf.when; + }; graphelement = 1; break; case GF_COMMENT: @@ -1373,6 +1387,10 @@ print_calc(image_desc_t *im, char ***prdata) 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; @@ -1389,6 +1407,7 @@ leg_place(image_desc_t *im) 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; @@ -1427,7 +1446,27 @@ leg_place(image_desc_t *im) } 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]==' '){ @@ -1497,14 +1536,22 @@ leg_place(image_desc_t *im) + legspace[ii] + glue; } - leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8; - if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size; + 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; + 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; @@ -1524,11 +1571,10 @@ calc_horizontal_grid(image_desc_t *im) double range; double scaledrange; int pixel,i; - int gridind; + int gridind=0; int decimals, fractionals; im->ygrid_scale.labfact=2; - gridind=-1; range = im->maxval - im->minval; scaledrange = range / im->magfact; @@ -1579,17 +1625,16 @@ calc_horizontal_grid(image_desc_t *im) else { for(i=0;ylab[i].grid > 0;i++){ pixel = im->ysize / (scaledrange / ylab[i].grid); - if (pixel > 7) { - gridind = i; - break; - } + 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]; + im->ygrid_scale.labfact = ylab[gridind].lfac[i]; break; - } + } } im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact; @@ -1606,6 +1651,7 @@ 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; @@ -1616,9 +1662,13 @@ int draw_horizontal_grid(image_desc_t *im) 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){ - if(i % im->ygrid_scale.labfact == 0){ + /* 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); @@ -1641,6 +1691,7 @@ int draw_horizontal_grid(image_desc_t *im) } } } + nlabels++; gfx_new_text ( im->canvas, X0-im->text_prop[TEXT_PROP_AXIS].size, Y0, @@ -1668,84 +1719,260 @@ int draw_horizontal_grid(image_desc_t *im) 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 pixpex; - int ii,i; - int minoridx=0, majoridx=0; - char graph_label[100]; - double X0,X1,Y0; - double value, pixperstep, minstep; + 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]; - /* find grid spaceing */ - pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval)); + nex = log10(im->maxval / im->minval); + logscale = im->ysize / nex; - if (isnan(pixpex)) { - return 0; - } + /* 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; + } - for(i=0;yloglab[i][0] > 0;i++){ - minstep = log10(yloglab[i][0]); - for(ii=1;yloglab[i][ii+1] > 0;ii++){ - if(yloglab[i][ii+2]==0){ - minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]); - break; + /* 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); } } - pixperstep = pixpex * minstep; - if(pixperstep > 5){minoridx = i;} - if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;} + + /* next decade */ + if(yloglab[mid][++flab] == 10.0) { + flab = 0; + val_exp += exfrac; + } } - - X0=im->xorigin; - X1=im->xorigin+im->xsize; - /* paint minor grid */ - for (value = pow((double)10, log10(im->minval) - - fmod(log10(im->minval),log10(yloglab[minoridx][0]))); - value <= im->maxval; - value *= yloglab[minoridx][0]){ - if (value < im->minval) continue; - i=0; - while(yloglab[minoridx][++i] > 0){ - Y0 = ytr(im,value * yloglab[minoridx][i]); - if (Y0 <= im->yorigin - im->ysize) break; - 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); + + /* 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); } } - /* paint major grid and labels*/ - for (value = pow((double)10, log10(im->minval) - - fmod(log10(im->minval),log10(yloglab[majoridx][0]))); - value <= im->maxval; - value *= yloglab[majoridx][0]){ - if (value < im->minval) continue; - i=0; - while(yloglab[majoridx][++i] > 0){ - Y0 = ytr(im,value * yloglab[majoridx][i]); - if (Y0 <= im->yorigin - im->ysize) break; - 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); - - sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]); - 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 ); - } - } - return 1; + return 1; } @@ -1768,7 +1995,9 @@ vertical_grid( 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++; } + && 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; @@ -1969,6 +2198,17 @@ grid_paint(image_desc_t *im) 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) ) { @@ -2132,6 +2372,8 @@ graph_size_location(image_desc_t *im, int elements ** |v+--+-------------------------------+--------+ ** | |..............legends......................| ** +-+-------------------------------------------+ + ** | watermark | + ** +---------------------------------------------+ */ int Xvertical=0, Ytitle =0, @@ -2144,7 +2386,9 @@ graph_size_location(image_desc_t *im, int elements #if 0 Xlegend =0, Ylegend =0, #endif - Xspacing =15, Yspacing =15; + Xspacing =15, Yspacing =15, + + Ywatermark =4; if (im->extra_flags & ONLY_GRAPH) { im->xorigin =0; @@ -2231,12 +2475,13 @@ graph_size_location(image_desc_t *im, int elements xtr(im,0); /* The vertical size is interesting... we need to compare - ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical - ** however we need to know {Ytitle+Ymain+Yxlabel} in order to - ** start even thinking about Ylegend. + ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with + ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel} + ** in order to start even thinking about Ylegend or Ywatermark. ** ** Do it in three portions: First calculate the inner part, - ** then do the legend, then adjust the total height of the img. + ** then do the legend, then adjust the total height of the img, + ** adding space for a watermark if one exists; */ /* reserve space for main and/or pie */ @@ -2265,7 +2510,10 @@ graph_size_location(image_desc_t *im, int elements */ if(leg_place(im)==-1) return -1; - + + if (im->watermark[0] != '\0') { + im->yimg += Ywatermark; + } #if 0 if (Xlegend > im->ximg) { @@ -2292,6 +2540,11 @@ graph_size_location(image_desc_t *im, int elements return 0; } +/* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */ +/* yes we are loosing precision by doing tos with floats instead of doubles + but it seems more stable this way. */ + + /* draw that picture thing ... */ int graph_paint(image_desc_t *im, char ***calcpr) @@ -2306,7 +2559,6 @@ graph_paint(image_desc_t *im, char ***calcpr) gfx_node_t *node; double areazero = 0.0; - enum gf_en stack_gf = GF_PRINT; graph_desc_t *lastgdes = NULL; /* if we are lazy and there is nothing to PRINT ... quit now */ @@ -2430,22 +2682,27 @@ graph_paint(image_desc_t *im, char ***calcpr) for (ii = 0; ii < im->xsize; ii++) { if (!isnan(im->gdes[i].p_data[ii]) && - im->gdes[i].p_data[ii] > 0.0) - { - /* generate a tick */ - gfx_new_line(im->canvas, im -> xorigin + ii, - im -> yorigin - (im -> gdes[i].yrule * im -> ysize), - im -> xorigin + ii, - im -> yorigin, - 1.0, - im -> gdes[i].col ); - } + im->gdes[i].p_data[ii] != 0.0) + { + if (im -> gdes[i].yrule > 0 ) { + gfx_new_line(im->canvas, + im -> xorigin + ii, im->yorigin, + im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize, + 1.0, + im -> gdes[i].col ); + } else if ( im -> gdes[i].yrule < 0 ) { + gfx_new_line(im->canvas, + im -> xorigin + ii, im->yorigin - im -> ysize, + im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize, + 1.0, + im -> gdes[i].col ); + + } + } } break; case GF_LINE: case GF_AREA: - stack_gf = im->gdes[i].gf; - case GF_STACK: /* fix data points at oo and -oo */ for(ii=0;iixsize;ii++){ if (isinf(im->gdes[i].p_data[ii])){ @@ -2471,32 +2728,36 @@ graph_paint(image_desc_t *im, char ***calcpr) ********************************************************* */ if (im->gdes[i].col != 0x0){ /* GF_LINE and friend */ - if(stack_gf == GF_LINE ){ + if(im->gdes[i].gf == GF_LINE ){ + double last_y=0.0; node = NULL; - for(ii=1;iixsize;ii++){ + for(ii=1;iixsize;ii++){ if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){ node = NULL; continue; } if ( node == NULL ) { + last_y = ytr(im,im->gdes[i].p_data[ii]); if ( im->slopemode == 0 ){ node = gfx_new_line(im->canvas, - ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]), - ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]), + ii-1+im->xorigin,last_y, + ii+im->xorigin,last_y, im->gdes[i].linewidth, im->gdes[i].col); } else { node = gfx_new_line(im->canvas, ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]), - ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]), + ii+im->xorigin,last_y, im->gdes[i].linewidth, im->gdes[i].col); } } else { - if ( im->slopemode==0 ){ - gfx_add_point(node,ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii])); + double new_y = ytr(im,im->gdes[i].p_data[ii]); + if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){ + gfx_add_point(node,ii-1+im->xorigin,new_y); }; - gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii])); + last_y = new_y; + gfx_add_point(node,ii+im->xorigin,new_y); }; } @@ -2512,7 +2773,7 @@ graph_paint(image_desc_t *im, char ***calcpr) if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){ int cntI=1; int lastI=0; - while (cntI < idxI && foreY[lastI] == foreY[cntI] && foreY[lastI] == foreY[cntI+1]){cntI++;} + while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;} node = gfx_new_area(im->canvas, backX[0],backY[0], foreX[0],foreY[0], @@ -2520,14 +2781,14 @@ graph_paint(image_desc_t *im, char ***calcpr) while (cntI < idxI) { lastI = cntI; cntI++; - while ( cntI < idxI && foreY[lastI] == foreY[cntI] && foreY[lastI] == foreY[cntI+1]){cntI++;} + while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;} gfx_add_point(node,foreX[cntI],foreY[cntI]); } gfx_add_point(node,backX[idxI],backY[idxI]); while (idxI > 1){ lastI = idxI; idxI--; - while ( idxI > 1 && backY[lastI] == backY[idxI] && backY[lastI] == backY[idxI-1]){idxI--;} + while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;} gfx_add_point(node,backX[idxI],backY[idxI]); } idxI=-1; @@ -2611,6 +2872,10 @@ graph_paint(image_desc_t *im, char ***calcpr) } break; #endif + case GF_STACK: + rrd_set_error("STACK should already be turned into LINE or AREA here"); + return -1; + break; } /* switch */ } @@ -2635,9 +2900,6 @@ graph_paint(image_desc_t *im, char ***calcpr) switch(im->gdes[i].gf){ case GF_HRULE: - if(isnan(im->gdes[i].yrule)) { /* fetch variable */ - im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val; - }; if(im->gdes[i].yrule >= im->minval && im->gdes[i].yrule <= im->maxval) gfx_new_line(im->canvas, @@ -2646,9 +2908,6 @@ graph_paint(image_desc_t *im, char ***calcpr) 1.0,im->gdes[i].col); break; case GF_VRULE: - if(im->gdes[i].xrule == 0) { /* fetch variable */ - im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when; - }; if(im->gdes[i].xrule >= im->start && im->gdes[i].xrule <= im->end) gfx_new_line(im->canvas, @@ -2698,10 +2957,14 @@ gdes_alloc(image_desc_t *im){ im->gdes[im->gdes_c-1].step=im->step; + im->gdes[im->gdes_c-1].step_orig=im->step; im->gdes[im->gdes_c-1].stack=0; + im->gdes[im->gdes_c-1].linewidth=0; im->gdes[im->gdes_c-1].debug=0; im->gdes[im->gdes_c-1].start=im->start; + im->gdes[im->gdes_c-1].start_orig=im->start; im->gdes[im->gdes_c-1].end=im->end; + im->gdes[im->gdes_c-1].end_orig=im->end; im->gdes[im->gdes_c-1].vname[0]='\0'; im->gdes[im->gdes_c-1].data=NULL; im->gdes[im->gdes_c-1].ds_namv=NULL; @@ -2712,8 +2975,11 @@ gdes_alloc(image_desc_t *im){ im->gdes[im->gdes_c-1].col = 0x0; im->gdes[im->gdes_c-1].legend[0]='\0'; im->gdes[im->gdes_c-1].format[0]='\0'; + im->gdes[im->gdes_c-1].strftm=0; im->gdes[im->gdes_c-1].rrd[0]='\0'; im->gdes[im->gdes_c-1].ds=-1; + im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE; + im->gdes[im->gdes_c-1].cf=CF_AVERAGE; im->gdes[im->gdes_c-1].p_data=NULL; im->gdes[im->gdes_c-1].yrule=DNAN; im->gdes[im->gdes_c-1].xrule=0; @@ -2723,7 +2989,7 @@ gdes_alloc(image_desc_t *im){ /* copies input untill the first unescaped colon is found or until input ends. backslashes have to be escaped as well */ int -scan_for_col(char *input, int len, char *output) +scan_for_col(const char *const input, int len, char *const output) { int inp,outp=0; for (inp=0; @@ -2829,6 +3095,9 @@ rrd_graph_init(image_desc_t *im) #endif #ifdef HAVE_SETLOCALE setlocale(LC_TIME,""); +#ifdef HAVE_MBSTOWCS + setlocale(LC_CTYPE,""); +#endif #endif im->yorigin=0; im->xorigin=0; @@ -2841,6 +3110,7 @@ rrd_graph_init(image_desc_t *im) im->step = 0; im->ylegend[0] = '\0'; im->title[0] = '\0'; + im->watermark[0] = '\0'; im->minval = DNAN; im->maxval = DNAN; im->unitsexponent= 9999; @@ -2876,8 +3146,8 @@ rrd_graph_init(image_desc_t *im) windir = getenv("windir"); /* %windir% is something like D:\windows or C:\winnt */ if (windir != NULL) { - strncpy(rrd_win_default_font,windir,999); - rrd_win_default_font[999] = '\0'; + strncpy(rrd_win_default_font,windir,500); + rrd_win_default_font[500] = '\0'; strcat(rrd_win_default_font,"\\fonts\\"); strcat(rrd_win_default_font,RRD_DEFAULT_FONT); for(i=0;iextra_flags |= FORCE_RULES_LEGEND; break; + case LONGOPT_UNITS_SI: + if(im->extra_flags & FORCE_UNITS) { + rrd_set_error("--units can only be used once!"); + return; + } + if(strcmp(optarg,"si")==0) + im->extra_flags |= FORCE_UNITS_SI; + else { + rrd_set_error("invalid argument for --units: %s", optarg ); + return; + } + break; case 'X': im->unitsexponent = atoi(optarg); break; @@ -3133,8 +3421,6 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im) case 'o': im->logarithmic = 1; - if (isnan(im->minval)) - im->minval=1; break; case 'c': if(sscanf(optarg, @@ -3238,6 +3524,11 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im) im->canvas->font_aa_threshold = atof(optarg); break; + case 'W': + strncpy(im->watermark,optarg,100); + im->watermark[99]='\0'; + break; + case '?': if (optopt != 0) rrd_set_error("unknown option '%c'", optopt); @@ -3252,7 +3543,7 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im) return; } - if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){ + if (im->logarithmic == 1 && im->minval <= 0){ rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0"); return; } @@ -3343,12 +3634,17 @@ int bad_format(char *fmt) { /* '%s', '%S' and '%%' are allowed */ if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++; + /* %c is allowed (but use only with vdef!) */ + else if (*ptr == 'c') { + ptr++; + n=1; + } + /* or else '% 6.2lf' and such are allowed */ else { - /* optional padding character */ if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++; - + /* This should take care of 'm.n' with all three optional */ while (*ptr >= '0' && *ptr <= '9') ptr++; if (*ptr == '.') ptr++; @@ -3369,7 +3665,7 @@ int bad_format(char *fmt) { int vdef_parse(gdes,str) struct graph_desc_t *gdes; -char *str; +const char *const str; { /* A VDEF currently is either "func" or "param,func" * so the parsing is rather simple. Change if needed. @@ -3402,6 +3698,9 @@ char *str; else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL; else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST; else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST; + else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE; + else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT; + else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL; else { rrd_set_error("Unknown function '%s' in VDEF '%s'\n" ,func @@ -3437,6 +3736,9 @@ char *str; case VDEF_TOTAL: case VDEF_FIRST: case VDEF_LAST: + case VDEF_LSLSLOPE: + case VDEF_LSLINT: + case VDEF_LSLCORREL: if (isnan(param)) { gdes->vf.param = DNAN; gdes->vf.val = DNAN; @@ -3534,7 +3836,7 @@ printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' '); if (cnt) { if (dst->vf.op == VDEF_TOTAL) { dst->vf.val = sum*src->step; - dst->vf.when = cnt*src->step; /* not really "when" */ + dst->vf.when = 0; /* no time component */ } else { dst->vf.val = sum/cnt; dst->vf.when = 0; /* no time component */ @@ -3595,6 +3897,48 @@ printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' '); dst->vf.when = src->start + (step+1)*src->step; } break; + case VDEF_LSLSLOPE: + case VDEF_LSLINT: + case VDEF_LSLCORREL:{ + /* Bestfit line by linear least squares method */ + + int cnt=0; + double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ; + SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0; + + for (step=0;stepds_cnt])) { + cnt++; + SUMx += step; + SUMxx += step * step; + SUMxy += step * data[step*src->ds_cnt]; + SUMy += data[step*src->ds_cnt]; + SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt]; + }; + } + + slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx ); + y_intercept = ( SUMy - slope*SUMx ) / cnt; + correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt)); + + if (cnt) { + if (dst->vf.op == VDEF_LSLSLOPE) { + dst->vf.val = slope; + dst->vf.when = 0; + } else if (dst->vf.op == VDEF_LSLINT) { + dst->vf.val = y_intercept; + dst->vf.when = 0; + } else if (dst->vf.op == VDEF_LSLCORREL) { + dst->vf.val = correl; + dst->vf.when = 0; + }; + + } else { + dst->vf.val = DNAN; + dst->vf.when = 0; + } + } + break; } return 0; }