From: <sa...@us...> - 2003-10-22 15:23:44
|
Update of /cvsroot/jrobin/src/jrobin/core In directory sc8-pr-cvs1:/tmp/cvs-serv12502/jrobin/core Modified Files: FetchData.java RrdDb.java Added Files: RpnCalculator.java Log Message: Added aggregation functions (with RPN pre-processing) to FetchData class to easily obtain MIN, MAX, LAST or AVERAGE values of the fetched data... --- NEW FILE: RpnCalculator.java --- package jrobin.core; import java.util.StringTokenizer; class RpnCalculator { static final String VAR_PLACEHOLDER = "value"; private static final byte TOK_VAR = 0; private static final byte TOK_NUM = 1; private static final byte TOK_PLUS = 2; private static final byte TOK_MINUS = 3; private static final byte TOK_MULT = 4; private static final byte TOK_DIV = 5; private static final byte TOK_MOD = 6; private static final byte TOK_SIN = 7; private static final byte TOK_COS = 8; private static final byte TOK_LOG = 9; private static final byte TOK_EXP = 10; private static final byte TOK_FLOOR = 11; private static final byte TOK_CEIL = 12; private static final byte TOK_ROUND = 13; private static final byte TOK_POW = 14; private static final byte TOK_ABS = 15; private static final byte TOK_SQRT = 16; private static final byte TOK_RANDOM = 17; private static final byte TOK_LT = 18; private static final byte TOK_LE = 19; private static final byte TOK_GT = 20; private static final byte TOK_GE = 21; private static final byte TOK_EQ = 22; private static final byte TOK_IF = 23; private static final byte TOK_MIN = 24; private static final byte TOK_MAX = 25; private static final byte TOK_LIMIT = 26; private static final byte TOK_DUP = 27; private static final byte TOK_EXC = 28; private static final byte TOK_POP = 29; private static final byte TOK_UN = 30; private static final byte TOK_UNKN = 31; // private static final byte TOK_NOW = 32; // private static final byte TOK_TIME = 33; private static final byte TOK_PI = 34; private static final byte TOK_E = 35; private static final byte TOK_AND = 36; private static final byte TOK_OR = 37; private static final byte TOK_XOR = 38; private String[] tokens; private byte[] tokenCodes; private double[] parsedDoubles; private RpnStack stack = new RpnStack(); private String rpnExpression; private double value; // private long timestamp; RpnCalculator(String rpnExpression) throws RrdException { this.rpnExpression = rpnExpression; createTokens(); } void setValue(double value) { this.value = value; } /* not supported yet public void setTimestamp(long timestamp) { this.timestamp = timestamp; } */ private void createTokens() throws RrdException { StringTokenizer st = new StringTokenizer(rpnExpression, ","); int count = st.countTokens(); tokens = new String[count]; tokenCodes = new byte[count]; parsedDoubles = new double[count]; for(int i = 0; st.hasMoreTokens(); i++) { String token = st.nextToken(); tokens[i] = token; byte tokenCode = findTokenCode(token); tokenCodes[i] = tokenCode; if(tokenCode == TOK_NUM) { parsedDoubles[i] = Double.parseDouble(token); } } } private byte findTokenCode(String token) throws RrdException { if(isVariable(token)) { return TOK_VAR; } else if(isNumber(token)) { return TOK_NUM; } else if(token.equals("+")) { return TOK_PLUS; } else if(token.equals("-")) { return TOK_MINUS; } else if(token.equals("*")) { return TOK_MULT; } else if(token.equals("/")) { return TOK_DIV; } else if(token.equals("%")) { return TOK_MOD; } else if(token.equals("SIN")) { return TOK_SIN; } else if(token.equals("COS")) { return TOK_COS; } else if(token.equals("LOG")) { return TOK_LOG; } else if(token.equals("EXP")) { return TOK_EXP; } else if(token.equals("FLOOR")) { return TOK_FLOOR; } else if(token.equals("CEIL")) { return TOK_CEIL; } else if(token.equals("ROUND")) { return TOK_ROUND; } else if(token.equals("POW")) { return TOK_POW; } else if(token.equals("ABS")) { return TOK_ABS; } else if(token.equals("SQRT")) { return TOK_SQRT; } else if(token.equals("RANDOM")) { return TOK_RANDOM; } else if(token.equals("LT")) { return TOK_LT; } else if(token.equals("LE")) { return TOK_LE; } else if(token.equals("GT")) { return TOK_GT; } else if(token.equals("GE")) { return TOK_GE; } else if(token.equals("EQ")) { return TOK_EQ; } else if(token.equals("IF")) { return TOK_IF; } else if(token.equals("MIN")) { return TOK_MIN; } else if(token.equals("MAX")) { return TOK_MAX; } else if(token.equals("LIMIT")) { return TOK_LIMIT; } else if(token.equals("DUP")) { return TOK_DUP; } else if(token.equals("EXC")) { return TOK_EXC; } else if(token.equals("POP")) { return TOK_POP; } else if(token.equals("UN")) { return TOK_UN; } else if(token.equals("UNKN")) { return TOK_UNKN; } /* not supported yet else if(token.equals("NOW")) { return TOK_NOW; } else if(token.equals("TIME")) { return TOK_TIME; } */ else if(token.equals("PI")) { return TOK_PI; } else if(token.equals("E")) { return TOK_E; } else if(token.equals("AND")) { return TOK_AND; } else if(token.equals("OR")) { return TOK_OR; } else if(token.equals("XOR")) { return TOK_XOR; } else { throw new RrdException("Unknown RPN token encountered: " + token); } } private static boolean isNumber(String token) { try { Double.parseDouble(token); return true; } catch(NumberFormatException nfe) { return false; } } private static boolean isVariable(String token) { return token.equals(VAR_PLACEHOLDER); } double calculate() throws RrdException { resetCalculator(); for(int i = 0; i < tokenCodes.length; i++) { byte tokenCode = tokenCodes[i]; double x1, x2, x3; switch(tokenCode) { case TOK_NUM: push(parsedDoubles[i]); break; case TOK_VAR: push(value); break; case TOK_PLUS: push(pop() + pop()); break; case TOK_MINUS: x2 = pop(); x1 = pop(); push(x1 - x2); break; case TOK_MULT: push(pop() * pop()); break; case TOK_DIV: x2 = pop(); x1 = pop(); push(x1 / x2); break; case TOK_MOD: x2 = pop(); x1 = pop(); push(x1 % x2); break; case TOK_SIN: push(Math.sin(pop())); break; case TOK_COS: push(Math.cos(pop())); break; case TOK_LOG: push(Math.log(pop())); break; case TOK_EXP: push(Math.exp(pop())); break; case TOK_FLOOR: push(Math.floor(pop())); break; case TOK_CEIL: push(Math.ceil(pop())); break; case TOK_ROUND: push(Math.round(pop())); break; case TOK_POW: x2 = pop(); x1 = pop(); push(Math.pow(x1, x2)); break; case TOK_ABS: push(Math.abs(pop())); break; case TOK_SQRT: push(Math.sqrt(pop())); break; case TOK_RANDOM: push(Math.random()); break; case TOK_LT: x2 = pop(); x1 = pop(); push(x1 < x2? 1: 0); break; case TOK_LE: x2 = pop(); x1 = pop(); push(x1 <= x2? 1: 0); break; case TOK_GT: x2 = pop(); x1 = pop(); push(x1 > x2? 1: 0); break; case TOK_GE: x2 = pop(); x1 = pop(); push(x1 >= x2? 1: 0); break; case TOK_EQ: x2 = pop(); x1 = pop(); push(x1 == x2? 1: 0); break; case TOK_IF: x3 = pop(); x2 = pop(); x1 = pop(); push(x1 != 0? x2: x3); break; case TOK_MIN: push(Math.min(pop(), pop())); break; case TOK_MAX: push(Math.max(pop(), pop())); break; case TOK_LIMIT: x3 = pop(); x2 = pop(); x1 = pop(); push(x1 < x2 || x1 > x3? Double.NaN: x1); break; case TOK_DUP: x1 = pop(); push(x1); push(x1); break; case TOK_EXC: x2 = pop(); x1 = pop(); push(x2); push(x1); break; case TOK_POP: pop(); break; case TOK_UN: push(Double.isNaN(pop())? 1: 0); break; case TOK_UNKN: push(Double.NaN); break; /* not supported yet case TOK_NOW: push(Util.getTime()); break; case TOK_TIME: push(timestamp); break; */ case TOK_PI: push(Math.PI); break; case TOK_E: push(Math.E); break; case TOK_AND: x2 = pop(); x1 = pop(); push((x1 != 0 && x2 != 0)? 1: 0); break; case TOK_OR: x2 = pop(); x1 = pop(); push((x1 != 0 || x2 != 0)? 1: 0); break; case TOK_XOR: x2 = pop(); x1 = pop(); push(((x1 != 0 && x2 == 0) || (x1 == 0 && x2 != 0))? 1: 0); break; default: throw new RrdException("Unexpected RPN token encountered [" + tokenCode + "]"); } } double retVal = pop(); if(!isStackEmpty()) { throw new RrdException("Stack not empty at the end of calculation. " + "Probably bad RPN expression"); } return retVal; } void push(double x) throws RrdException { stack.push(x); } double pop() throws RrdException { return stack.pop(); } void resetCalculator() { stack.reset(); } boolean isStackEmpty() { return stack.isEmpty(); } class RpnStack { static final int MAX_STACK_SIZE = 1000; private double[] stack = new double[MAX_STACK_SIZE]; private int pos = 0; void push(double x) throws RrdException { if(pos >= MAX_STACK_SIZE) { throw new RrdException( "PUSH failed, RPN stack full [" + MAX_STACK_SIZE + "]"); } stack[pos++] = x; } double pop() throws RrdException { if(pos <= 0) { throw new RrdException("POP failed, RPN stack is empty "); } return stack[--pos]; } void reset() { pos = 0; } boolean isEmpty() { return pos == 0; } } public static void main(String[] args) throws RrdException { RpnCalculator c = new RpnCalculator("2,3,/,value,+"); c.setValue(5); System.out.println(c.calculate()); } } Index: FetchData.java =================================================================== RCS file: /cvsroot/jrobin/src/jrobin/core/FetchData.java,v retrieving revision 1.2 retrieving revision 1.3 diff -C2 -d -r1.2 -r1.3 *** FetchData.java 22 Oct 2003 08:55:10 -0000 1.2 --- FetchData.java 22 Oct 2003 13:47:09 -0000 1.3 *************** *** 196,198 **** --- 196,317 ---- } + /** + * Returns aggregated value from the fetched data for a single datasource. + * @param dsName Datasource name + * @param consolFun Consolidation function to be applied to fetched datasource values. + * Valid consolidation functions are MIN, MAX, LAST and AVERAGE + * @return MIN, MAX, LAST or AVERAGE value calculated from the fetched data + * for the given datasource name + * @throws RrdException Thrown if the given datasource name cannot be found in fetched data. + */ + public double getAggregate(String dsName, String consolFun) throws RrdException { + return getAggregate(dsName, consolFun, null); + } + + /** + * Returns aggregated value from the fetched data for a single datasource. + * Before applying aggrregation functions, specified RPN expression is applied to fetched + * data. For example, if you have a gauge datasource named 'foots' but you wont to + * find the maximum fetched value in meters use something like:</p> + * <code>getAggregate("foots", "MAX", "value,0.3048,*");</code> + * Note that 'value' in the RPN expression is a reserved word and stands for the + * original value (value fetched from RRD file)</p> + * @param dsName Datasource name + * @param consolFun Consolidation function to be applied to fetched datasource values. + * Valid consolidation functions are MIN, MAX, LAST and AVERAGE + * @return MIN, MAX, LAST or AVERAGE value calculated from the fetched data + * for the given datasource name + * @throws RrdException Thrown if the given datasource name cannot be found in fetched data. + */ + public double getAggregate(String dsName, String consolFun, String rpnExpression) + throws RrdException { + if(consolFun.equals("MAX")) { + return getMax(dsName, rpnExpression); + } + else if(consolFun.equals("MIN")) { + return getMin(dsName, rpnExpression); + } + else if(consolFun.equals("LAST")) { + return getLast(dsName, rpnExpression); + } + else if(consolFun.equals("AVERAGE")) { + return getAverage(dsName, rpnExpression); + } + else { + throw new RrdException("Unsupported consolidation function [" + consolFun + "]"); + } + } + + private double getMax(String dsName, String rpnExpression) throws RrdException { + RpnCalculator rpnCalculator = null; + if(rpnExpression != null) { + rpnCalculator = new RpnCalculator(rpnExpression); + } + double vals[] = getValues(dsName), max = Double.NaN; + for(int i = 0; i < vals.length - 1; i++) { + double value = vals[i + 1]; + if(rpnCalculator != null) { + rpnCalculator.setValue(value); + value = rpnCalculator.calculate(); + } + max = Util.max(max, value); + } + return max; + } + + private double getMin(String dsName, String rpnExpression) throws RrdException { + RpnCalculator rpnCalculator = null; + if(rpnExpression != null) { + rpnCalculator = new RpnCalculator(rpnExpression); + } + double vals[] = getValues(dsName), min = Double.NaN; + for(int i = 0; i < vals.length - 1; i++) { + double value = vals[i + 1]; + if(rpnCalculator != null) { + rpnCalculator.setValue(value); + value = rpnCalculator.calculate(); + } + min = Util.min(min, value); + } + return min; + } + + private double getLast(String dsName, String rpnExpression) throws RrdException { + RpnCalculator rpnCalculator = null; + if(rpnExpression != null) { + rpnCalculator = new RpnCalculator(rpnExpression); + } + double vals[] = getValues(dsName); + double value = vals[vals.length - 1]; + if(rpnCalculator != null) { + rpnCalculator.setValue(value); + value = rpnCalculator.calculate(); + } + return value; + } + + private double getAverage(String dsName, String rpnExpression) throws RrdException { + RpnCalculator rpnCalculator = null; + if(rpnExpression != null) { + rpnCalculator = new RpnCalculator(rpnExpression); + } + double vals[] = getValues(dsName); + double totalVal = 0; + long totalSecs = 0; + for(int i = 0; i < vals.length - 1; i++) { + long t1 = Math.max(request.getFetchStart(), timestamps[i]); + long t2 = Math.min(request.getFetchEnd(), timestamps[i + 1]); + double value = vals[i + 1]; + if(rpnCalculator != null) { + rpnCalculator.setValue(value); + value = rpnCalculator.calculate(); + } + if(!Double.isNaN(value)) { + totalSecs += (t2 - t1); + totalVal += (t2 - t1) * value; + } + } + return totalSecs > 0? totalVal / totalSecs: Double.NaN; + } + } Index: RrdDb.java =================================================================== RCS file: /cvsroot/jrobin/src/jrobin/core/RrdDb.java,v retrieving revision 1.10 retrieving revision 1.11 diff -C2 -d -r1.10 -r1.11 *** RrdDb.java 22 Oct 2003 08:55:10 -0000 1.10 --- RrdDb.java 22 Oct 2003 13:47:09 -0000 1.11 *************** *** 643,645 **** --- 643,659 ---- close(); } + + public static void main(String[] args) throws RrdException, IOException { + RrdDb rrd = new RrdDb("demo.rrd"); + long t1 = Util.getTimestamp(2003, 4, 1); + long t2 = Util.getTimestamp(2003, 4, 2); + FetchData data = rrd.createFetchRequest("AVERAGE", t1, t2).fetchData(); + data.dump(); + System.out.println("MAX : " + data.getAggregate("sun", "MAX")); + System.out.println("MAX2: " + data.getAggregate("sun", "MAX", "value,2,*")); + System.out.println("MIN : " + data.getAggregate("sun", "MIN")); + System.out.println("MIN2: " + data.getAggregate("sun", "MIN", "value,2,*")); + System.out.println("AVG : " + data.getAggregate("sun", "AVERAGE")); + System.out.println("AVG2: " + data.getAggregate("sun", "AVERAGE", "value,2,*")); + } } |