]> git.ipfire.org Git - thirdparty/rrdtool-1.x.git/commitdiff
add new RPN functions: AVG, SMIN, SMAX, STDEV, POW, MEDIAN
authorTobias Oetiker <tobi@oetiker.ch>
Mon, 18 Apr 2016 15:13:15 +0000 (17:13 +0200)
committerTobias Oetiker <tobi@oetiker.ch>
Mon, 18 Apr 2016 15:13:15 +0000 (17:13 +0200)
doc/rrdgraph_rpn.pod
src/rrd_rpncalc.c
src/rrd_rpncalc.h
tests/rpn2

index 2cbc422c4d439a998e6676cabb864434b08b51ae..51a0bf1b54cc663a7bb55896a015361de728eae1 100644 (file)
@@ -60,7 +60,7 @@ B<UN, ISINF>
 Pop one element from the stack, compare this to I<unknown> respectively
 to I<positive or negative infinity>. Returns 1 for true or 0 for false.
 
-B<IF>
+I<then>,I<else>,I<condition>,B<IF>
 
 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<unknown>
 then the result of the operation will be the other one. If both are
 I<unknown>, then the result of the operation is I<unknown>.
 
-B<LIMIT>
+I<lower-limit>,I<upper-limit>,B<LIMIT>
 
 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<ADDNAN>
 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<value>,I<power>,B<POW>
+
+Raise I<value> to the power of I<power>.
+
 B<SIN, COS, LOG, EXP, SQRT>
 
 Sine and cosine (input in radians), log and exp (natural logarithm),
@@ -147,24 +152,36 @@ Take the absolute value.
 
 =item Set Operations
 
-B<SORT, REV>
+I<count>,B<SORT>
+
+Pop one element from the stack.  This is the I<count> of items to be sorted.  The top I<count> 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<count> of items to be sorted
-(or reversed).  The top I<count> 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<count>,B<REV>
+
+Reverse the number
 
 Example: C<CDEF:x=v1,v2,v3,v4,v5,v6,6,SORT,POP,5,REV,POP,+,+,+,4,/> will
 compute the average of the values v1 to v6 after removing the smallest and
 largest.
 
-B<AVG>
+I<count>,B<AVG>
 
 Pop one element (I<count>) from the stack. Now pop I<count> elements and build the
 average, ignoring all UNKNOWN values in the process.
 
 Example: C<CDEF:x=a,b,c,d,4,AVG>
 
-B<MEDIAN>
+I<count>,B<SMIN> and
+I<count>,B<SMAX>
+
+Pop one element (I<count>) from the stack. Now pop I<count> elements and push the minimum/maximum back onto the stack.
+
+Example: C<CDEF:x=a,b,c,d,4,AVG>
+
+I<count>,B<MEDIAN>
 
 pop one element (I<count>) from the stack. Now pop I<count> 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<CDEF:x=a,b,c,d,4,MEDIAN>
 
+I<count>,B<STDEV>
+
+pop one element (I<count>) from the stack. Now pop I<count> elements and calculate the standard deviation over these values (ignoring any NAN values). Push the result back on to the stack.
+
+Example: C<CDEF:x=a,b,c,d,4,STDEV>
+
+I<percent>,I<count>,B<PERCENT>
+
+pop two elements (I<count>,I<percent>) from the stack. Now pop I<count> 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<percent> of the elements
+are equal then the one picked. Push the result back on to the stack.
+
+Example: C<CDEF:x=a,b,c,d,95,4,PERCENT>
 
-B<TREND, TRENDNAN>
+I<count>,B<TREND, TRENDNAN>
 
 Create a "sliding window" average of another data series.
 
index e76bdff56b8fd7b5a42850c9de0aabeb7a1f328c..a4359aaa0821e84e434d7572ba6393c5eb948b90 100644 (file)
@@ -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);
             {
index cdadf4d728ab054d42cfa9277d1b0524d843ab5f..972bc6001df89e8c8f37349bd1fabc1d6c6c2ccf 100644 (file)
@@ -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 {
index b1fe41291198108dbec0b55266b7958cb4785fb5..9b8309adbacc6b9641c7bf00d92a514692bb6461 100755 (executable)
@@ -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"