From: Tobias Oetiker Date: Mon, 18 Apr 2016 15:13:15 +0000 (+0200) Subject: add new RPN functions: AVG, SMIN, SMAX, STDEV, POW, MEDIAN X-Git-Tag: v1.6.0~5^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=37706dbfe098912e0575af61820df59f05d419fe;p=thirdparty%2Frrdtool-1.x.git add new RPN functions: AVG, SMIN, SMAX, STDEV, POW, MEDIAN --- diff --git a/doc/rrdgraph_rpn.pod b/doc/rrdgraph_rpn.pod index 2cbc422c..51a0bf1b 100644 --- a/doc/rrdgraph_rpn.pod +++ b/doc/rrdgraph_rpn.pod @@ -60,7 +60,7 @@ B Pop one element from the stack, compare this to I respectively to I. Returns 1 for true or 0 for false. -B +I,I,I,B Pops three elements from the stack. If the element popped last is 0 (false), the value popped first is pushed back onto the stack, @@ -86,7 +86,7 @@ NAN-safe version of MIN and MAX. If one of the input numbers is I then the result of the operation will be the other one. If both are I, then the result of the operation is I. -B +I,I,B Pops two elements from the stack and uses them to define a range. Then it pops another element and if it falls inside the range, it @@ -113,6 +113,11 @@ B NAN-safe addition. If one parameter is NAN/UNKNOWN it'll be treated as zero. If both parameters are NAN/UNKNOWN, NAN/UNKNOWN will be returned. + +I,I,B + +Raise I to the power of I. + B Sine and cosine (input in radians), log and exp (natural logarithm), @@ -147,24 +152,36 @@ Take the absolute value. =item Set Operations -B +I,B + +Pop one element from the stack. This is the I of items to be sorted. The top I of the remaining elements are then sorted +from the smallest to the largest, in place on the stack. -Pop one element from the stack. This is the I of items to be sorted -(or reversed). The top I of the remaining elements are then sorted -(or reversed) in place on the stack. + 4,3,22.1,1,4,SORT -> 1,3,4,22.1 + +I,B + +Reverse the number Example: C will compute the average of the values v1 to v6 after removing the smallest and largest. -B +I,B Pop one element (I) from the stack. Now pop I elements and build the average, ignoring all UNKNOWN values in the process. Example: C -B +I,B and +I,B + +Pop one element (I) from the stack. Now pop I elements and push the minimum/maximum back onto the stack. + +Example: C + +I,B pop one element (I) from the stack. Now pop I elements and find the median, ignoring all UNKNOWN values in the process. If there are an even @@ -173,8 +190,22 @@ the stack. Example: C +I,B + +pop one element (I) from the stack. Now pop I elements and calculate the standard deviation over these values (ignoring any NAN values). Push the result back on to the stack. + +Example: C + +I,I,B + +pop two elements (I,I) from the stack. Now pop I element, order them by size +(while the smalles elements are -INF, the largest are INF and NaN is larger than -INF but smaller +than anything else. No pick the element from the ordered list where I of the elements +are equal then the one picked. Push the result back on to the stack. + +Example: C -B +I,B Create a "sliding window" average of another data series. diff --git a/src/rrd_rpncalc.c b/src/rrd_rpncalc.c index e76bdff5..a4359aaa 100644 --- a/src/rrd_rpncalc.c +++ b/src/rrd_rpncalc.c @@ -188,6 +188,7 @@ void rpn_compact2str( add_op(OP_ATAN, ATAN) add_op(OP_SQRT, SQRT) add_op(OP_SORT, SORT) + add_op(OP_COUNT, COUNT) add_op(OP_REV, REV) add_op(OP_TREND, TREND) add_op(OP_TRENDNAN, TRENDNAN) @@ -201,10 +202,16 @@ void rpn_compact2str( add_op(OP_ADDNAN, ADDNAN) add_op(OP_MINNAN, MINNAN) add_op(OP_MAXNAN, MAXNAN) + add_op(OP_MEDIAN, MEDIAN) + add_op(OP_PERCENT, PERCENT) + add_op(OP_SMAX, SMAX) + add_op(OP_SMIN, SMIN) + add_op(OP_STDEV, STDEV) add_op(OP_DEPTH, DEPTH) add_op(OP_COPY, COPY) add_op(OP_ROLL, ROLL) add_op(OP_INDEX, INDEX) + add_op(OP_POW, POW) #undef add_op } (*str)[offset] = '\0'; @@ -430,6 +437,11 @@ rpnp_t *rpn_parse( match_op(OP_COPY, COPY) match_op(OP_ROLL, ROLL) match_op(OP_INDEX, INDEX) + match_op(OP_SMAX, SMAX) + match_op(OP_SMIN, SMIN) + match_op(OP_STDEV, STDEV) + match_op(OP_PERCENT, PERCENT) + match_op(OP_POW, POW) #undef match_op else if ((sscanf(expr, DEF_NAM_FMT "%n", vname, &pos) == 1) @@ -500,8 +512,22 @@ static int rpn_compare_double( const void *x, const void *y) { - double diff = *((const double *) x) - *((const double *) y); - + /* First catch NaN values. They are smallest */ + if (isnan(*(double *) x) && isnan(*(double *) y)) + return 0; + if (isnan(*(double *) x)) + return -1; + if (isnan(*(double *) y)) + return 1; + /* NaN doesn't reach this part so INF and -INF are extremes. + * The sign from isinf() is compatible with the sign we return + */ + if (isinf(*(double *) x)) + return isinf(*(double *) x); + if (isinf(*(double *) y)) + return isinf(*(double *) y); + + double diff = *((const double *) x) - *((const double *) y); return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; } @@ -725,6 +751,12 @@ short rpn_calc( , rpnstack->s[stptr]); stptr--; break; + case OP_POW: + stackunderflow(1); + rpnstack->s[stptr - 1] = pow(rpnstack->s[stptr - 1] + , rpnstack->s[stptr]); + stptr--; + break; case OP_SIN: stackunderflow(0); rpnstack->s[stptr] = sin(rpnstack->s[stptr]); @@ -1184,6 +1216,76 @@ short rpn_calc( } } break; + case OP_STDEV: + stackunderflow(0); + { + int elements = (int) rpnstack->s[stptr--]; + stackunderflow(elements-1); + int n = 0; + rrd_value_t mean = 0; + rrd_value_t mean2 = 0; + while (elements--){ + rrd_value_t datum = rpnstack->s[stptr--]; + rrd_value_t delta; + if (isnan(datum)){ + continue; + } + n++; + delta = datum - mean; + mean += delta / n; + mean2 += delta * (datum - mean); + } + rpnstack->s[++stptr] = n < 2 ? DNAN : sqrt(mean2 / ( n - 1)); + } + break; + case OP_PERCENT: + stackunderflow(2); + { + int elements = (int) rpnstack->s[stptr--]; + double percent = rpnstack->s[stptr--]; + if (! (percent >= 0 && percent <=100)){ + rrd_set_error("percentile argument must be between 0 and 100"); + return -1; + } + + stackunderflow(elements - 1); + qsort(rpnstack->s + stptr - elements + 1, elements, sizeof(double), + rpn_compare_double); + stptr -= elements; + rpnstack->s[stptr+1] = rpnstack->s[stptr+(int)round(percent*(double)(elements)/100.0)]; + stptr++; + } + break; + case OP_SMAX: + stackunderflow(0); + { + rrd_value_t ximum = DNAN; + int elements = (int) rpnstack->s[stptr--]; + stackunderflow(elements - 1); + while(elements--){ + rrd_value_t element = rpnstack->s[stptr--]; + if (isnan(ximum) || element > ximum) { + ximum = element; + } + } + rpnstack->s[++stptr] = ximum; + } + break; + case OP_SMIN: + stackunderflow(0); + { + rrd_value_t ximum = DNAN; + int elements = (int) rpnstack->s[stptr--]; + stackunderflow(elements - 1); + while(elements--){ + rrd_value_t element = rpnstack->s[stptr--]; + if (isnan(ximum) || element < ximum) { + ximum = element; + } + } + rpnstack->s[++stptr] = ximum; + } + break; case OP_ROLL: stackunderflow(1); { diff --git a/src/rrd_rpncalc.h b/src/rrd_rpncalc.h index cdadf4d7..972bc600 100644 --- a/src/rrd_rpncalc.h +++ b/src/rrd_rpncalc.h @@ -23,7 +23,8 @@ enum op_en { OP_NUMBER = 0, OP_VARIABLE, OP_INF, OP_PREV, OP_NEGINF, OP_MINNAN, OP_MAXNAN, OP_MEDIAN, OP_PREDICTPERC, OP_DEPTH, OP_COPY, OP_ROLL, OP_INDEX, OP_STEPWIDTH, - OP_NEWDAY, OP_NEWWEEK, OP_NEWMONTH, OP_NEWYEAR + OP_NEWDAY, OP_NEWWEEK, OP_NEWMONTH, OP_NEWYEAR, + OP_SMIN, OP_SMAX, OP_STDEV, OP_PERCENT, OP_POW }; typedef struct rpnp_t { diff --git a/tests/rpn2 b/tests/rpn2 index b1fe4129..9b8309ad 100755 --- a/tests/rpn2 +++ b/tests/rpn2 @@ -25,10 +25,27 @@ LC_TIME=C TZ=Europe/Zurich $RRDTOOL xport \ CDEF:week=myspeed,STEPWIDTH,*,NEWWEEK,0,PREV,IF,ADDNAN \ CDEF:month=myspeed,STEPWIDTH,*,NEWMONTH,0,PREV,IF,ADDNAN \ CDEF:year=myspeed,STEPWIDTH,*,NEWYEAR,0,PREV,IF,ADDNAN \ + CDEF:avg=myspeed,POP,1,1,1,17,4,AVG \ + CDEF:smin=myspeed,POP,3,4,5,2.2,4,SMIN \ + CDEF:smax=myspeed,POP,3,4,5,2.2,4,SMAX \ + CDEF:med1=myspeed,POP,3,4,5,2.2,4,MEDIAN \ + CDEF:med2=myspeed,POP,3,4,5,2.2,11,5,MEDIAN \ + CDEF:stdev=myspeed,POP,3,4,5,2.2,4,STDEV \ + CDEF:p9=myspeed,POP,0.5,1,1,1,1,1,1,1,5,10,100,10,PERCENT \ + CDEF:pow=myspeed,2,POW \ XPORT:myspeed:myspeed \ XPORT:day:day XPORT:rday:rday \ XPORT:week:week XPORT:rweek:rweek \ XPORT:month:month XPORT:rmonth:rmonth \ - XPORT:year:year XPORT:ryear:ryear |\ + XPORT:year:year XPORT:ryear:ryear \ + XPORT:avg:avg \ + XPORT:smin:smin \ + XPORT:smax:smax \ + XPORT:med1:med1 \ + XPORT:med2:med2 \ + XPORT:p9:p9 \ + XPORT:pow:pow \ + XPORT:stdev:stdev |\ + $DIFF9 - $BASEDIR/rpn2.output report "xport"