/******************************************************************************* * Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. * Copyright (c) 2011 The OpenNMS Group, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *******************************************************************************/ package org.jrobin.data; import java.util.Arrays; import org.jrobin.core.RrdException; import org.jrobin.core.Util; import java.util.Calendar; import java.util.StringTokenizer; import java.util.TimeZone; class RpnCalculator { private static final byte TKN_VAR = 0; private static final byte TKN_NUM = 1; private static final byte TKN_PLUS = 2; private static final byte TKN_MINUS = 3; private static final byte TKN_MULT = 4; private static final byte TKN_DIV = 5; private static final byte TKN_MOD = 6; private static final byte TKN_SIN = 7; private static final byte TKN_COS = 8; private static final byte TKN_LOG = 9; private static final byte TKN_EXP = 10; private static final byte TKN_FLOOR = 11; private static final byte TKN_CEIL = 12; private static final byte TKN_ROUND = 13; private static final byte TKN_POW = 14; private static final byte TKN_ABS = 15; private static final byte TKN_SQRT = 16; private static final byte TKN_RANDOM = 17; private static final byte TKN_LT = 18; private static final byte TKN_LE = 19; private static final byte TKN_GT = 20; private static final byte TKN_GE = 21; private static final byte TKN_EQ = 22; private static final byte TKN_IF = 23; private static final byte TKN_MIN = 24; private static final byte TKN_MAX = 25; private static final byte TKN_LIMIT = 26; private static final byte TKN_DUP = 27; private static final byte TKN_EXC = 28; private static final byte TKN_POP = 29; private static final byte TKN_UN = 30; private static final byte TKN_UNKN = 31; private static final byte TKN_NOW = 32; private static final byte TKN_TIME = 33; private static final byte TKN_PI = 34; private static final byte TKN_E = 35; private static final byte TKN_AND = 36; private static final byte TKN_OR = 37; private static final byte TKN_XOR = 38; private static final byte TKN_PREV = 39; private static final byte TKN_INF = 40; private static final byte TKN_NEGINF = 41; private static final byte TKN_STEP = 42; private static final byte TKN_YEAR = 43; private static final byte TKN_MONTH = 44; private static final byte TKN_DATE = 45; private static final byte TKN_HOUR = 46; private static final byte TKN_MINUTE = 47; private static final byte TKN_SECOND = 48; private static final byte TKN_WEEK = 49; private static final byte TKN_SIGN = 50; private static final byte TKN_RND = 51; private static final byte TKN_ADDNAN = 52; private static final byte TKN_NE = 53; private static final byte TKN_ISINF = 54; private static final byte TKN_ATAN = 55; private static final byte TKN_ATAN2 = 56; private static final byte TKN_DEG2RAD = 57; private static final byte TKN_RAD2DEG = 58; private static final byte TKN_COUNT = 59; private static final byte TKN_SORT = 60; private static final byte TKN_REV = 61; private static final byte TKN_AVG = 62; private static final byte TKN_LTIME = 63; private static final byte TKN_TREND = 64; private static final byte TKN_TRENDNAN = 65; private static final byte TKN_PREDICT = 66; private static final byte TKN_PREDICTSIGMA = 67; private String rpnExpression; private String sourceName; private DataProcessor dataProcessor; private Token[] tokens; private RpnStack stack = new RpnStack(); private double[] calculatedValues; private long[] timestamps; private double timeStep; RpnCalculator(String rpnExpression, String sourceName, DataProcessor dataProcessor) throws RrdException { this.rpnExpression = rpnExpression; this.sourceName = sourceName; this.dataProcessor = dataProcessor; this.timestamps = dataProcessor.getTimestamps(); this.timeStep = this.timestamps[1] - this.timestamps[0]; this.calculatedValues = new double[this.timestamps.length]; StringTokenizer st = new StringTokenizer(rpnExpression, ", "); tokens = new Token[st.countTokens()]; for (int i = 0; st.hasMoreTokens(); i++) { tokens[i] = createToken(st.nextToken()); } } private Token createToken(String parsedText) throws RrdException { Token token = new Token(); if (Util.isDouble(parsedText)) { token.id = TKN_NUM; token.number = Util.parseDouble(parsedText); } else if (parsedText.equals("+")) { token.id = TKN_PLUS; } else if (parsedText.equals("-")) { token.id = TKN_MINUS; } else if (parsedText.equals("*")) { token.id = TKN_MULT; } else if (parsedText.equals("/")) { token.id = TKN_DIV; } else if (parsedText.equals("%")) { token.id = TKN_MOD; } else if (parsedText.equals("SIN")) { token.id = TKN_SIN; } else if (parsedText.equals("COS")) { token.id = TKN_COS; } else if (parsedText.equals("LOG")) { token.id = TKN_LOG; } else if (parsedText.equals("EXP")) { token.id = TKN_EXP; } else if (parsedText.equals("FLOOR")) { token.id = TKN_FLOOR; } else if (parsedText.equals("CEIL")) { token.id = TKN_CEIL; } else if (parsedText.equals("ROUND")) { token.id = TKN_ROUND; } else if (parsedText.equals("POW")) { token.id = TKN_POW; } else if (parsedText.equals("ABS")) { token.id = TKN_ABS; } else if (parsedText.equals("SQRT")) { token.id = TKN_SQRT; } else if (parsedText.equals("RANDOM")) { token.id = TKN_RANDOM; } else if (parsedText.equals("LT")) { token.id = TKN_LT; } else if (parsedText.equals("LE")) { token.id = TKN_LE; } else if (parsedText.equals("GT")) { token.id = TKN_GT; } else if (parsedText.equals("GE")) { token.id = TKN_GE; } else if (parsedText.equals("EQ")) { token.id = TKN_EQ; } else if (parsedText.equals("IF")) { token.id = TKN_IF; } else if (parsedText.equals("MIN")) { token.id = TKN_MIN; } else if (parsedText.equals("MAX")) { token.id = TKN_MAX; } else if (parsedText.equals("LIMIT")) { token.id = TKN_LIMIT; } else if (parsedText.equals("DUP")) { token.id = TKN_DUP; } else if (parsedText.equals("EXC")) { token.id = TKN_EXC; } else if (parsedText.equals("POP")) { token.id = TKN_POP; } else if (parsedText.equals("UN")) { token.id = TKN_UN; } else if (parsedText.equals("UNKN")) { token.id = TKN_UNKN; } else if (parsedText.equals("NOW")) { token.id = TKN_NOW; } else if (parsedText.equals("TIME")) { token.id = TKN_TIME; } else if (parsedText.equals("LTIME")) { token.id = TKN_LTIME; } else if (parsedText.equals("PI")) { token.id = TKN_PI; } else if (parsedText.equals("E")) { token.id = TKN_E; } else if (parsedText.equals("AND")) { token.id = TKN_AND; } else if (parsedText.equals("OR")) { token.id = TKN_OR; } else if (parsedText.equals("XOR")) { token.id = TKN_XOR; } else if (parsedText.equals("PREV")) { token.id = TKN_PREV; token.variable = sourceName; token.values = calculatedValues; } else if (parsedText.startsWith("PREV(") && parsedText.endsWith(")")) { token.id = TKN_PREV; token.variable = parsedText.substring(5, parsedText.length() - 1); token.values = dataProcessor.getValues(token.variable); } else if (parsedText.equals("INF")) { token.id = TKN_INF; } else if (parsedText.equals("NEGINF")) { token.id = TKN_NEGINF; } else if (parsedText.equals("STEP")) { token.id = TKN_STEP; } else if (parsedText.equals("YEAR")) { token.id = TKN_YEAR; } else if (parsedText.equals("MONTH")) { token.id = TKN_MONTH; } else if (parsedText.equals("DATE")) { token.id = TKN_DATE; } else if (parsedText.equals("HOUR")) { token.id = TKN_HOUR; } else if (parsedText.equals("MINUTE")) { token.id = TKN_MINUTE; } else if (parsedText.equals("SECOND")) { token.id = TKN_SECOND; } else if (parsedText.equals("WEEK")) { token.id = TKN_WEEK; } else if (parsedText.equals("SIGN")) { token.id = TKN_SIGN; } else if (parsedText.equals("RND")) { token.id = TKN_RND; } else if (parsedText.equals("ADDNAN")) { token.id = TKN_ADDNAN; } else if (parsedText.equals("NE")) { token.id = TKN_NE; } else if (parsedText.equals("ISINF")) { token.id = TKN_ISINF; } else if (parsedText.equals("ATAN")) { token.id = TKN_ATAN; } else if (parsedText.equals("ATAN2")) { token.id = TKN_ATAN2; } else if (parsedText.equals("DEG2RAD")) { token.id = TKN_DEG2RAD; } else if (parsedText.equals("RAD2DEG")) { token.id = TKN_RAD2DEG; } else if (parsedText.equals("COUNT")) { token.id = TKN_COUNT; } else if (parsedText.equals("SORT")) { token.id = TKN_SORT; } else if (parsedText.equals("REV")) { token.id = TKN_REV; } else if (parsedText.equals("AVG")) { token.id = TKN_AVG; } else if (parsedText.equals("TREND")) { token.id = TKN_TREND; } else if (parsedText.equals("TRENDNAN")) { token.id = TKN_TRENDNAN; } else if (parsedText.equals("PREDICT")) { token.id = TKN_PREDICT; } else if (parsedText.equals("PREDICTSIGMA")) { token.id = TKN_PREDICTSIGMA; } else { token.id = TKN_VAR; token.variable = parsedText; token.values = dataProcessor.getValues(token.variable); } return token; } double[] calculateValues() throws RrdException { TimeZone tz = TimeZone.getDefault(); for (int slot = 0; slot < timestamps.length; slot++) { resetStack(); int token_rpi = -1; for (int rpi = 0; rpi < tokens.length; rpi++) { Token token = tokens[rpi]; double x1, x2, x3; switch (token.id) { case TKN_NUM: push(token.number); break; case TKN_VAR: push(token.values[slot]); token_rpi = rpi; break; case TKN_COUNT: push(slot+1); break; case TKN_PLUS: push(pop() + pop()); break; case TKN_MINUS: x2 = pop(); x1 = pop(); push(x1 - x2); break; case TKN_MULT: push(pop() * pop()); break; case TKN_DIV: x2 = pop(); x1 = pop(); push(x1 / x2); break; case TKN_MOD: x2 = pop(); x1 = pop(); push(x1 % x2); break; case TKN_SIN: push(Math.sin(pop())); break; case TKN_COS: push(Math.cos(pop())); break; case TKN_ATAN: push(Math.atan(pop())); break; case TKN_ATAN2: x2 = pop(); x1 = pop(); push(Math.atan2(x1, x2)); break; case TKN_LOG: push(Math.log(pop())); break; case TKN_EXP: push(Math.exp(pop())); break; case TKN_FLOOR: push(Math.floor(pop())); break; case TKN_CEIL: push(Math.ceil(pop())); break; case TKN_ROUND: push(Math.round(pop())); break; case TKN_POW: x2 = pop(); x1 = pop(); push(Math.pow(x1, x2)); break; case TKN_ABS: push(Math.abs(pop())); break; case TKN_SQRT: push(Math.sqrt(pop())); break; case TKN_RANDOM: push(Math.random()); break; case TKN_LT: x2 = pop(); x1 = pop(); push(x1 < x2 ? 1 : 0); break; case TKN_LE: x2 = pop(); x1 = pop(); push(x1 <= x2 ? 1 : 0); break; case TKN_GT: x2 = pop(); x1 = pop(); push(x1 > x2 ? 1 : 0); break; case TKN_GE: x2 = pop(); x1 = pop(); push(x1 >= x2 ? 1 : 0); break; case TKN_EQ: x2 = pop(); x1 = pop(); push(x1 == x2 ? 1 : 0); break; case TKN_NE: x2 = pop(); x1 = pop(); push(x1 != x2 ? 1 : 0); break; case TKN_IF: x3 = pop(); x2 = pop(); x1 = pop(); push(x1 != 0 ? x2 : x3); break; case TKN_MIN: push(Math.min(pop(), pop())); break; case TKN_MAX: push(Math.max(pop(), pop())); break; case TKN_LIMIT: x3 = pop(); x2 = pop(); x1 = pop(); push(x1 < x2 || x1 > x3 ? Double.NaN : x1); break; case TKN_DUP: push(peek()); break; case TKN_EXC: x2 = pop(); x1 = pop(); push(x2); push(x1); break; case TKN_POP: pop(); break; case TKN_UN: push(Double.isNaN(pop()) ? 1 : 0); break; case TKN_ISINF: push(Double.isInfinite(pop()) ? 1 : 0); break; case TKN_UNKN: push(Double.NaN); break; case TKN_NOW: push(Util.getTime()); break; case TKN_TIME: push(timestamps[slot]); break; case TKN_LTIME: push(timestamps[slot] + (tz.getOffset(timestamps[slot]) / 1000L)); break; case TKN_PI: push(Math.PI); break; case TKN_E: push(Math.E); break; case TKN_AND: x2 = pop(); x1 = pop(); push((x1 != 0 && x2 != 0) ? 1 : 0); break; case TKN_OR: x2 = pop(); x1 = pop(); push((x1 != 0 || x2 != 0) ? 1 : 0); break; case TKN_XOR: x2 = pop(); x1 = pop(); push(((x1 != 0 && x2 == 0) || (x1 == 0 && x2 != 0)) ? 1 : 0); break; case TKN_PREV: push((slot == 0) ? Double.NaN : token.values[slot - 1]); break; case TKN_INF: push(Double.POSITIVE_INFINITY); break; case TKN_NEGINF: push(Double.NEGATIVE_INFINITY); break; case TKN_STEP: push(timeStep); break; case TKN_YEAR: push(getCalendarField(pop(), Calendar.YEAR)); break; case TKN_MONTH: push(getCalendarField(pop(), Calendar.MONTH)); break; case TKN_DATE: push(getCalendarField(pop(), Calendar.DAY_OF_MONTH)); break; case TKN_HOUR: push(getCalendarField(pop(), Calendar.HOUR_OF_DAY)); break; case TKN_MINUTE: push(getCalendarField(pop(), Calendar.MINUTE)); break; case TKN_SECOND: push(getCalendarField(pop(), Calendar.SECOND)); break; case TKN_WEEK: push(getCalendarField(pop(), Calendar.WEEK_OF_YEAR)); break; case TKN_SIGN: x1 = pop(); push(Double.isNaN(x1) ? Double.NaN : x1 > 0 ? +1 : x1 < 0 ? -1 : 0); break; case TKN_RND: push(Math.floor(pop() * Math.random())); break; case TKN_ADDNAN: x2 = pop(); x1 = pop(); if (Double.isNaN(x1)) { push(x2); } else if (Double.isNaN(x2)) { push(x1); } else { push(x1+x2); } break; case TKN_DEG2RAD: push(Math.toRadians(pop())); break; case TKN_RAD2DEG: push(Math.toDegrees(pop())); break; case TKN_SORT: { int n = (int) pop(); double[] array = new double[n]; for(int i = 0; i < n; i++) { array[i] = pop(); } Arrays.sort(array); for (int i = 0; i < n; i++) { push(array[i]); } } break; case TKN_REV: { int n = (int) pop(); double[] array = new double[n]; for(int i = 0; i < n; i++) { array[i] = pop(); } for (int i = 0; i < n; i++) { push(array[i]); } } break; case TKN_AVG: { int count = 0; int n = (int) pop(); double sum = 0.0; while (n > 0) { x1 = pop(); n--; if (Double.isNaN(x1)) { continue; } sum += x1; count++; } if (count > 0) { push(sum / count); } else { push(Double.NaN); } } break; case TKN_TREND: case TKN_TRENDNAN: { int dur = (int) pop(); pop(); /* * OK, so to match the output from rrdtool, we have to go *forward* 2 timeperiods. * So at t[59] we use the average of t[1]..t[61] * */ if ((slot+1) < Math.ceil(dur / timeStep)) { push(Double.NaN); } else { double[] vals = dataProcessor.getValues(tokens[token_rpi].variable); boolean ignorenan = token.id == TKN_TRENDNAN; double accum = 0.0; int count = 0; int start = (int) (Math.ceil(dur / timeStep)); int row = 2; while ((slot + row) > vals.length) { row --; } for(; start > 0; start--) { double val = vals[slot + row - start]; if (ignorenan || !Double.isNaN(val)) { accum = Util.sum(accum, val); ++count; } } //System.err.printf("t[%d]: %1.10e / %d\n", slot, (count == 0) ? Double.NaN : (accum / count), count); push((count == 0) ? Double.NaN : (accum / count)); } } break; case TKN_PREDICT: case TKN_PREDICTSIGMA: { pop(); // Clear the value of our variable /* the local averaging window (similar to trend, but better here, as we get better statistics thru numbers)*/ int locstepsize = (int) pop(); /* the number of shifts and range-checking*/ int num_shifts = (int) pop(); double[] multipliers; // handle negative shifts special if (num_shifts < 0) { multipliers = new double[1]; multipliers[0] = pop(); } else { multipliers = new double[num_shifts]; for(int i = 0; i < num_shifts; i++) { multipliers[i] = pop(); } } /* the real calculation */ double val = Double.NaN; /* the info on the datasource */ double[] vals = dataProcessor.getValues(tokens[rpi-1].variable); int locstep = (int) Math.ceil((float) locstepsize / (float) timeStep); /* the sums */ double sum = 0; double sum2 = 0; int count = 0; /* now loop for each position */ int doshifts = Math.abs(num_shifts); for (int loop = 0; loop < doshifts; loop++) { /* calculate shift step */ int shiftstep = 1; if (num_shifts < 0) { shiftstep = loop * (int) multipliers[0]; } else { shiftstep = (int) multipliers[loop]; } if (shiftstep < 0) { throw new RrdException("negative shift step not allowed: " + shiftstep); } shiftstep = (int) Math.ceil((float) shiftstep / (float) timeStep); /* loop all local shifts */ for (int i = 0; i <= locstep; i++) { int offset = shiftstep + i; if ((offset >= 0) && (offset < slot)) { /* get the value */ val = vals[slot - offset]; /* and handle the non NAN case only*/ if (!Double.isNaN(val)) { sum = Util.sum(sum, val); sum2 = Util.sum(sum2, val * val); count++; } } } } /* do the final calculations */ val = Double.NaN; if (token.id == TKN_PREDICT) { /* the average */ if (count > 0) { val = sum / (double) count; } } else { if (count > 1) { /* the sigma case */ val = count * sum2 - sum * sum; if (val < 0) { val = Double.NaN; } else { val = Math.sqrt(val / ((float) count * ((float) count - 1.0))); } } } push(val); } break; default: throw new RrdException("Unexpected RPN token encountered, token.id=" + token.id); } } calculatedValues[slot] = pop(); // check if stack is empty only on the first try if (slot == 0 && !isStackEmpty()) { throw new RrdException("Stack not empty at the end of calculation. " + "Probably bad RPN expression [" + rpnExpression + "]"); } } return calculatedValues; } private double getCalendarField(double timestamp, int field) { Calendar calendar = Util.getCalendar((long) timestamp); return calendar.get(field); } private void push(double x) throws RrdException { stack.push(x); } private double pop() throws RrdException { return stack.pop(); } private double peek() throws RrdException { return stack.peek(); } private void resetStack() { stack.reset(); } private boolean isStackEmpty() { return stack.isEmpty(); } private static final class RpnStack { private static final int MAX_STACK_SIZE = 1000; private double[] stack = new double[MAX_STACK_SIZE]; private int pos = 0; void push(final 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]; } double peek() throws RrdException { if (pos <= 0) { throw new RrdException("PEEK failed, RPN stack is empty "); } return stack[pos - 1]; } void reset() { pos = 0; } boolean isEmpty() { return pos <= 0; } } private static final class Token { byte id = -1; double number = Double.NaN; String variable = null; double[] values = null; } }