diff --git a/LICENSE.txt b/LICENSE.txt index 2741818ee..a6406bb6b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -217,7 +217,9 @@ Applications: See licenses/LICENSE-ECLIPSE-1.0.html See licenses/NOTICE-Commons-Logging.txt - JRobin 1.5.9.1: + JRobin 1.6.0-1: + Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. + Copyright (c) 2011 The OpenNMS Group, Inc. See licenses/LICENSE-LGPLv2.1.txt Ministreaming Lib: diff --git a/apps/jrobin/java/build.xml b/apps/jrobin/java/build.xml new file mode 100644 index 000000000..3d481f99e --- /dev/null +++ b/apps/jrobin/java/build.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/jrobin/java/src/org/jrobin/core/ArcDef.java b/apps/jrobin/java/src/org/jrobin/core/ArcDef.java new file mode 100644 index 000000000..0c420193a --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/ArcDef.java @@ -0,0 +1,181 @@ +/******************************************************************************* + * 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.core; + +/** + * Class to represent single archive definition within the RRD. + * Archive definition consists of the following four elements: + *

+ *

+ *

+ * For the complete explanation of all archive definition parameters, see RRDTool's + * rrdcreate man page. + * + * @author Sasa Markovic + */ + +public class ArcDef implements ConsolFuns { + /** + * array of valid consolidation function names + */ + public static final String CONSOL_FUNS[] = {CF_AVERAGE, CF_MAX, CF_MIN, CF_LAST}; + + private String consolFun; + private double xff; + private int steps, rows; + + /** + * Creates new archive definition object. This object should be passed as argument to + * {@link RrdDef#addArchive(ArcDef) addArchive()} method of + * {@link RrdDb RrdDb} object. + *

+ *

For the complete explanation of all archive definition parameters, see RRDTool's + * rrdcreate man page

+ * + * @param consolFun Consolidation function. Allowed values are "AVERAGE", "MIN", + * "MAX" and "LAST" (these string constants are conveniently defined in the + * {@link ConsolFuns} class). + * @param xff X-files factor, between 0 and 1. + * @param steps Number of archive steps. + * @param rows Number of archive rows. + * @throws RrdException Thrown if any parameter has illegal value. + */ + public ArcDef(final String consolFun, final double xff, final int steps, final int rows) throws RrdException { + this.consolFun = consolFun; + this.xff = xff; + this.steps = steps; + this.rows = rows; + validate(); + } + + /** + * Returns consolidation function. + * + * @return Consolidation function. + */ + public String getConsolFun() { + return consolFun; + } + + /** + * Returns the X-files factor. + * + * @return X-files factor value. + */ + public double getXff() { + return xff; + } + + /** + * Returns the number of primary RRD steps which complete a single archive step. + * + * @return Number of steps. + */ + public int getSteps() { + return steps; + } + + /** + * Returns the number of rows (aggregated values) stored in the archive. + * + * @return Number of rows. + */ + public int getRows() { + return rows; + } + + private void validate() throws RrdException { + if (!isValidConsolFun(consolFun)) { + throw new RrdException("Invalid consolidation function specified: " + consolFun); + } + if (Double.isNaN(xff) || xff < 0.0 || xff >= 1.0) { + throw new RrdException("Invalid xff, must be >= 0 and < 1: " + xff); + } + if (steps < 1 || rows < 2) { + throw new RrdException("Invalid steps/rows settings: " + steps + "/" + rows + + ". Minimal values allowed are steps=1, rows=2"); + } + } + + /** + * Returns string representing archive definition (RRDTool format). + * + * @return String containing all archive definition parameters. + */ + public String dump() { + return "RRA:" + consolFun + ":" + xff + ":" + steps + ":" + rows; + } + + /** + * Checks if two archive definitions are equal. + * Archive definitions are considered equal if they have the same number of steps + * and the same consolidation function. It is not possible to create RRD with two + * equal archive definitions. + * + * @param obj Archive definition to compare with. + * @return true if archive definitions are equal, + * false otherwise. + */ + public boolean equals(final Object obj) { + if (obj instanceof ArcDef) { + final ArcDef arcObj = (ArcDef) obj; + return consolFun.equals(arcObj.consolFun) && steps == arcObj.steps; + } + return false; + } + + public int hashCode() { + return (consolFun.hashCode() + steps) * 53; + } + + /** + * Checks if function argument represents valid consolidation function name. + * + * @param consolFun Consolidation function to be checked + * @return true if consolFun is valid consolidation function, + * false otherwise. + */ + public static boolean isValidConsolFun(final String consolFun) { + for (final String cFun : CONSOL_FUNS) { + if (cFun.equals(consolFun)) { + return true; + } + } + return false; + } + + void setRows(final int rows) { + this.rows = rows; + } + + boolean exactlyEqual(final ArcDef def) { + return consolFun.equals(def.consolFun) && xff == def.xff && + steps == def.steps && rows == def.rows; + } + + public String toString() { + return "ArcDef@" + Integer.toHexString(hashCode()) + "[consolFun=" + consolFun + ",xff=" + xff + ",steps=" + steps + ",rows=" + rows + "]"; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/ArcState.java b/apps/jrobin/java/src/org/jrobin/core/ArcState.java new file mode 100644 index 000000000..71f6dd297 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/ArcState.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Class to represent internal RRD archive state for a single datasource. Objects of this + * class are never manipulated directly, it's up to JRobin framework to manage + * internal arcihve states.

+ * + * @author Sasa Markovic + */ +public class ArcState implements RrdUpdater { + private Archive parentArc; + + private RrdDouble accumValue; + private RrdLong nanSteps; + + ArcState(Archive parentArc, boolean shouldInitialize) throws IOException { + this.parentArc = parentArc; + accumValue = new RrdDouble(this); + nanSteps = new RrdLong(this); + if (shouldInitialize) { + Header header = parentArc.getParentDb().getHeader(); + long step = header.getStep(); + long lastUpdateTime = header.getLastUpdateTime(); + long arcStep = parentArc.getArcStep(); + long initNanSteps = (Util.normalize(lastUpdateTime, step) - + Util.normalize(lastUpdateTime, arcStep)) / step; + accumValue.set(Double.NaN); + nanSteps.set(initNanSteps); + } + } + + String dump() throws IOException { + return "accumValue:" + accumValue.get() + " nanSteps:" + nanSteps.get() + "\n"; + } + + void setNanSteps(long value) throws IOException { + nanSteps.set(value); + } + + /** + * Returns the number of currently accumulated NaN steps. + * + * @return Number of currently accumulated NaN steps. + * @throws IOException Thrown in case of I/O error + */ + public long getNanSteps() throws IOException { + return nanSteps.get(); + } + + void setAccumValue(double value) throws IOException { + accumValue.set(value); + } + + /** + * Returns the value accumulated so far. + * + * @return Accumulated value + * @throws IOException Thrown in case of I/O error + */ + public double getAccumValue() throws IOException { + return accumValue.get(); + } + + /** + * Returns the Archive object to which this ArcState object belongs. + * + * @return Parent Archive object. + */ + public Archive getParent() { + return parentArc; + } + + void appendXml(XmlWriter writer) throws IOException { + writer.startTag("ds"); + writer.writeTag("value", accumValue.get()); + writer.writeTag("unknown_datapoints", nanSteps.get()); + writer.closeTag(); // ds + } + + /** + * Copies object's internal state to another ArcState object. + * + * @param other New ArcState object to copy state to + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if supplied argument is not an ArcState object + */ + public void copyStateTo(RrdUpdater other) throws IOException, RrdException { + if (!(other instanceof ArcState)) { + throw new RrdException( + "Cannot copy ArcState object to " + other.getClass().getName()); + } + ArcState arcState = (ArcState) other; + arcState.accumValue.set(accumValue.get()); + arcState.nanSteps.set(nanSteps.get()); + } + + /** + * Returns the underlying storage (backend) object which actually performs all + * I/O operations. + * + * @return I/O backend object + */ + public RrdBackend getRrdBackend() { + return parentArc.getRrdBackend(); + } + + /** + * Required to implement RrdUpdater interface. You should never call this method directly. + * + * @return Allocator object + */ + public RrdAllocator getRrdAllocator() { + return parentArc.getRrdAllocator(); + } + + public String toString() { + return "ArcState@" + Integer.toHexString(hashCode()) + "[parentArc=" + parentArc + ",accumValue=" + accumValue + ",nanSteps=" + nanSteps + "]"; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/Archive.java b/apps/jrobin/java/src/org/jrobin/core/Archive.java new file mode 100644 index 000000000..470bc75eb --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/Archive.java @@ -0,0 +1,418 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Class to represent single RRD archive in a RRD with its internal state. + * Normally, you don't need methods to manipulate archive objects directly + * because JRobin framework does it automatically for you. + *

+ * Each archive object consists of three parts: archive definition, archive state objects + * (one state object for each datasource) and round robin archives (one round robin for + * each datasource). API (read-only) is provided to access each of theese parts. + * + * @author Sasa Markovic + */ +public class Archive implements RrdUpdater, ConsolFuns { + private RrdDb parentDb; + // definition + private RrdString consolFun; + private RrdDouble xff; + private RrdInt steps, rows; + // state + private Robin[] robins; + private ArcState[] states; + + Archive(final RrdDb parentDb, final ArcDef arcDef) throws IOException { + final boolean shouldInitialize = arcDef != null; + this.parentDb = parentDb; + consolFun = new RrdString(this, true); // constant, may be cached + xff = new RrdDouble(this); + steps = new RrdInt(this, true); // constant, may be cached + rows = new RrdInt(this, true); // constant, may be cached + if (shouldInitialize) { + consolFun.set(arcDef.getConsolFun()); + xff.set(arcDef.getXff()); + steps.set(arcDef.getSteps()); + rows.set(arcDef.getRows()); + } + final int dsCount = parentDb.getHeader().getDsCount(); + states = new ArcState[dsCount]; + robins = new Robin[dsCount]; + final int numRows = rows.get(); + for (int i = 0; i < dsCount; i++) { + states[i] = new ArcState(this, shouldInitialize); + robins[i] = new Robin(this, numRows, shouldInitialize); + } + } + + // read from XML + Archive(final RrdDb parentDb, final DataImporter reader, final int arcIndex) throws IOException, RrdException,RrdException { + this(parentDb, new ArcDef( + reader.getConsolFun(arcIndex), reader.getXff(arcIndex), + reader.getSteps(arcIndex), reader.getRows(arcIndex))); + final int dsCount = parentDb.getHeader().getDsCount(); + for (int i = 0; i < dsCount; i++) { + // restore state + states[i].setAccumValue(reader.getStateAccumValue(arcIndex, i)); + states[i].setNanSteps(reader.getStateNanSteps(arcIndex, i)); + // restore robins + double[] values = reader.getValues(arcIndex, i); + robins[i].update(values); + } + } + + /** + * Returns archive time step in seconds. Archive step is equal to RRD step + * multiplied with the number of archive steps. + * + * @return Archive time step in seconds + * @throws IOException Thrown in case of I/O error. + */ + public long getArcStep() throws IOException { + final long step = parentDb.getHeader().getStep(); + return step * steps.get(); + } + + String dump() throws IOException { + final StringBuffer buffer = new StringBuffer("== ARCHIVE ==\n"); + buffer.append("RRA:").append(consolFun.get()).append(":").append(xff.get()).append(":").append(steps.get()). + append(":").append(rows.get()).append("\n"); + buffer.append("interval [").append(getStartTime()).append(", ").append(getEndTime()).append("]" + "\n"); + for (int i = 0; i < robins.length; i++) { + buffer.append(states[i].dump()); + buffer.append(robins[i].dump()); + } + return buffer.toString(); + } + + RrdDb getParentDb() { + return parentDb; + } + + public void archive(final int dsIndex, final double value, final long numStepUpdates) throws IOException { + final Robin robin = robins[dsIndex]; + final ArcState state = states[dsIndex]; + final long step = parentDb.getHeader().getStep(); + final long lastUpdateTime = parentDb.getHeader().getLastUpdateTime(); + long updateTime = Util.normalize(lastUpdateTime, step) + step; + final long arcStep = getArcStep(); + final String consolFunString = consolFun.get(); + final int numSteps = steps.get(); + final int numRows = rows.get(); + final double xffValue = xff.get(); + + // finish current step + long numUpdates = numStepUpdates; + while (numUpdates > 0) { + accumulate(state, value, consolFunString); + numUpdates--; + if (updateTime % arcStep == 0) { + finalizeStep(state, robin, consolFunString, numSteps, xffValue); + break; + } + else { + updateTime += step; + } + } + // update robin in bulk + final int bulkUpdateCount = (int) Math.min(numUpdates / numSteps, (long) numRows); + robin.bulkStore(value, bulkUpdateCount); + // update remaining steps + final long remainingUpdates = numUpdates % numSteps; + for (long i = 0; i < remainingUpdates; i++) { + accumulate(state, value, consolFunString); + } + } + + private void accumulate(final ArcState state, final double value, String consolFunString) throws IOException { + if (Double.isNaN(value)) { + state.setNanSteps(state.getNanSteps() + 1); + } + else { + final double accumValue = state.getAccumValue(); + if (consolFunString.equals(CF_MIN)) { + final double minValue = Util.min(accumValue, value); + if (minValue != accumValue) { + state.setAccumValue(minValue); + } + } + else if (consolFunString.equals(CF_MAX)) { + final double maxValue = Util.max(accumValue, value); + if (maxValue != accumValue) { + state.setAccumValue(maxValue); + } + } + else if (consolFunString.equals(CF_LAST)) { + state.setAccumValue(value); + } + else if (consolFunString.equals(CF_AVERAGE)) { + state.setAccumValue(Util.sum(accumValue, value)); + } + } + } + + private void finalizeStep(final ArcState state, final Robin robin, final String consolFunString, final long numSteps, final double xffValue) throws IOException { + final long nanSteps = state.getNanSteps(); + //double nanPct = (double) nanSteps / (double) arcSteps; + double accumValue = state.getAccumValue(); + if (nanSteps <= xffValue * numSteps && !Double.isNaN(accumValue)) { + if (consolFunString.equals(CF_AVERAGE)) { + accumValue /= (numSteps - nanSteps); + } + robin.store(accumValue); + } else { + robin.store(Double.NaN); + } + state.setAccumValue(Double.NaN); + state.setNanSteps(0); + } + + /** + * Returns archive consolidation function ("AVERAGE", "MIN", "MAX" or "LAST"). + * + * @return Archive consolidation function. + * @throws IOException Thrown in case of I/O error. + */ + public String getConsolFun() throws IOException { + return consolFun.get(); + } + + /** + * Returns archive X-files factor. + * + * @return Archive X-files factor (between 0 and 1). + * @throws IOException Thrown in case of I/O error. + */ + public double getXff() throws IOException { + return xff.get(); + } + + /** + * Returns the number of archive steps. + * + * @return Number of archive steps. + * @throws IOException Thrown in case of I/O error. + */ + public int getSteps() throws IOException { + return steps.get(); + } + + /** + * Returns the number of archive rows. + * + * @return Number of archive rows. + * @throws IOException Thrown in case of I/O error. + */ + public int getRows() throws IOException { + return rows.get(); + } + + /** + * Returns current starting timestamp. This value is not constant. + * + * @return Timestamp corresponding to the first archive row + * @throws IOException Thrown in case of I/O error. + */ + public long getStartTime() throws IOException { + final long endTime = getEndTime(); + final long arcStep = getArcStep(); + final long numRows = rows.get(); + return endTime - (numRows - 1) * arcStep; + } + + /** + * Returns current ending timestamp. This value is not constant. + * + * @return Timestamp corresponding to the last archive row + * @throws IOException Thrown in case of I/O error. + */ + public long getEndTime() throws IOException { + final long arcStep = getArcStep(); + final long lastUpdateTime = parentDb.getHeader().getLastUpdateTime(); + return Util.normalize(lastUpdateTime, arcStep); + } + + /** + * Returns the underlying archive state object. Each datasource has its + * corresponding ArcState object (archive states are managed independently + * for each RRD datasource). + * + * @param dsIndex Datasource index + * @return Underlying archive state object + */ + public ArcState getArcState(final int dsIndex) { + return states[dsIndex]; + } + + /** + * Returns the underlying round robin archive. Robins are used to store actual + * archive values on a per-datasource basis. + * + * @param dsIndex Index of the datasource in the RRD. + * @return Underlying round robin archive for the given datasource. + */ + public Robin getRobin(final int dsIndex) { + return robins[dsIndex]; + } + + FetchData fetchData(final FetchRequest request) throws IOException, RrdException { + final long arcStep = getArcStep(); + final long fetchStart = Util.normalize(request.getFetchStart(), arcStep); + long fetchEnd = Util.normalize(request.getFetchEnd(), arcStep); + if (fetchEnd < request.getFetchEnd()) { + fetchEnd += arcStep; + } + final long startTime = getStartTime(); + final long endTime = getEndTime(); + String[] dsToFetch = request.getFilter(); + if (dsToFetch == null) { + dsToFetch = parentDb.getDsNames(); + } + final int dsCount = dsToFetch.length; + final int ptsCount = (int) ((fetchEnd - fetchStart) / arcStep + 1); + final long[] timestamps = new long[ptsCount]; + final double[][] values = new double[dsCount][ptsCount]; + final long matchStartTime = Math.max(fetchStart, startTime); + final long matchEndTime = Math.min(fetchEnd, endTime); + double[][] robinValues = null; + if (matchStartTime <= matchEndTime) { + // preload robin values + final int matchCount = (int) ((matchEndTime - matchStartTime) / arcStep + 1); + final int matchStartIndex = (int) ((matchStartTime - startTime) / arcStep); + robinValues = new double[dsCount][]; + for (int i = 0; i < dsCount; i++) { + final int dsIndex = parentDb.getDsIndex(dsToFetch[i]); + robinValues[i] = robins[dsIndex].getValues(matchStartIndex, matchCount); + } + } + for (int ptIndex = 0; ptIndex < ptsCount; ptIndex++) { + final long time = fetchStart + ptIndex * arcStep; + timestamps[ptIndex] = time; + for (int i = 0; i < dsCount; i++) { + double value = Double.NaN; + if (time >= matchStartTime && time <= matchEndTime) { + // inbound time + final int robinValueIndex = (int) ((time - matchStartTime) / arcStep); + assert robinValues != null; + value = robinValues[i][robinValueIndex]; + } + values[i][ptIndex] = value; + } + } + final FetchData fetchData = new FetchData(this, request); + fetchData.setTimestamps(timestamps); + fetchData.setValues(values); + return fetchData; + } + + void appendXml(final XmlWriter writer) throws IOException { + writer.startTag("rra"); + writer.writeTag("cf", consolFun.get()); + writer.writeComment(getArcStep() + " seconds"); + writer.writeTag("pdp_per_row", steps.get()); + writer.writeTag("xff", xff.get()); + writer.startTag("cdp_prep"); + for (final ArcState state : states) { + state.appendXml(writer); + } + writer.closeTag(); // cdp_prep + writer.startTag("database"); + final long startTime = getStartTime(); + for (int i = 0; i < rows.get(); i++) { + final long time = startTime + i * getArcStep(); + writer.writeComment(Util.getDate(time) + " / " + time); + writer.startTag("row"); + for (final Robin robin : robins) { + writer.writeTag("v", robin.getValue(i)); + } + writer.closeTag(); // row + } + writer.closeTag(); // database + writer.closeTag(); // rra + } + + /** + * Copies object's internal state to another Archive object. + * + * @param other New Archive object to copy state to + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if supplied argument is not an Archive object + */ + public void copyStateTo(final RrdUpdater other) throws IOException, RrdException { + if (!(other instanceof Archive)) { + throw new RrdException("Cannot copy Archive object to " + other.getClass().getName()); + } + final Archive arc = (Archive) other; + if (!arc.consolFun.get().equals(consolFun.get())) { + throw new RrdException("Incompatible consolidation functions"); + } + if (arc.steps.get() != steps.get()) { + throw new RrdException("Incompatible number of steps"); + } + final int count = parentDb.getHeader().getDsCount(); + for (int i = 0; i < count; i++) { + final int j = Util.getMatchingDatasourceIndex(parentDb, i, arc.parentDb); + if (j >= 0) { + states[i].copyStateTo(arc.states[j]); + robins[i].copyStateTo(arc.robins[j]); + } + } + } + + /** + * Sets X-files factor to a new value. + * + * @param xff New X-files factor value. Must be >= 0 and < 1. + * @throws RrdException Thrown if invalid value is supplied + * @throws IOException Thrown in case of I/O error + */ + public void setXff(final double xff) throws RrdException, IOException { + if (xff < 0D || xff >= 1D) { + throw new RrdException("Invalid xff supplied (" + xff + "), must be >= 0 and < 1"); + } + this.xff.set(xff); + } + + /** + * Returns the underlying storage (backend) object which actually performs all + * I/O operations. + * + * @return I/O backend object + */ + public RrdBackend getRrdBackend() { + return parentDb.getRrdBackend(); + } + + /** + * Required to implement RrdUpdater interface. You should never call this method directly. + * + * @return Allocator object + */ + public RrdAllocator getRrdAllocator() { + return parentDb.getRrdAllocator(); + } + + public String toString() { + return "Archive@" + Integer.toHexString(hashCode()) + "[parentDb=" + parentDb + ",consolFun=" + consolFun + ",xff=" + xff + ",steps=" + steps + ",rows=" + rows + ",robins=" + robins + ",states=" + states + "]"; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/ConsolFuns.java b/apps/jrobin/java/src/org/jrobin/core/ConsolFuns.java new file mode 100644 index 000000000..ec9f4bd72 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/ConsolFuns.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * 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.core; + +/** + * Simple interface to represent available consolidation functions + */ +public interface ConsolFuns { + /** + * Constant to represent AVERAGE consolidation function + */ + public static final String CF_AVERAGE = "AVERAGE"; + + /** + * Constant to represent MIN consolidation function + */ + public static final String CF_MIN = "MIN"; + + /** + * Constant to represent MAX consolidation function + */ + public static final String CF_MAX = "MAX"; + + /** + * Constant to represent LAST consolidation function + */ + public static final String CF_LAST = "LAST"; + + /** + * Constant to represent FIRST consolidation function + */ + public static final String CF_FIRST = "FIRST"; + + /** + * Constant to represent TOTAL consolidation function + */ + public static final String CF_TOTAL = "TOTAL"; +} diff --git a/apps/jrobin/java/src/org/jrobin/core/DataImporter.java b/apps/jrobin/java/src/org/jrobin/core/DataImporter.java new file mode 100644 index 000000000..905500583 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/DataImporter.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +import org.jrobin.core.RrdException; + +abstract class DataImporter { + + // header + abstract String getVersion() throws RrdException, IOException; + + abstract long getLastUpdateTime() throws RrdException, IOException; + + abstract long getStep() throws RrdException, IOException; + + abstract int getDsCount() throws RrdException, IOException; + + abstract int getArcCount() throws RrdException, IOException; + + // datasource + abstract String getDsName(int dsIndex) throws RrdException, IOException; + + abstract String getDsType(int dsIndex) throws RrdException, IOException; + + abstract long getHeartbeat(int dsIndex) throws RrdException, IOException; + + abstract double getMinValue(int dsIndex) throws RrdException, IOException; + + abstract double getMaxValue(int dsIndex) throws RrdException, IOException; + + // datasource state + abstract double getLastValue(int dsIndex) throws RrdException, IOException; + + abstract double getAccumValue(int dsIndex) throws RrdException, IOException; + + abstract long getNanSeconds(int dsIndex) throws RrdException, IOException; + + // archive + abstract String getConsolFun(int arcIndex) throws RrdException, IOException; + + abstract double getXff(int arcIndex) throws RrdException, IOException; + + abstract int getSteps(int arcIndex) throws RrdException, IOException; + + abstract int getRows(int arcIndex) throws RrdException, IOException; + + // archive state + abstract double getStateAccumValue(int arcIndex, int dsIndex) throws RrdException, IOException; + + abstract int getStateNanSteps(int arcIndex, int dsIndex) throws RrdException, IOException; + + abstract double[] getValues(int arcIndex, int dsIndex) throws RrdException, IOException,RrdException; + + long getEstimatedSize() throws RrdException, IOException { + int dsCount = getDsCount(); + int arcCount = getArcCount(); + int rowCount = 0; + for (int i = 0; i < arcCount; i++) { + rowCount += getRows(i); + } + return RrdDef.calculateSize(dsCount, arcCount, rowCount); + } + + void release() throws RrdException, IOException { + // NOP + } + +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/core/Datasource.java b/apps/jrobin/java/src/org/jrobin/core/Datasource.java new file mode 100644 index 000000000..3aca8d3d4 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/Datasource.java @@ -0,0 +1,497 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Class to represent single datasource within RRD. Each datasource object holds the + * following information: datasource definition (once set, never changed) and + * datasource state variables (changed whenever RRD gets updated). + *

+ * Normally, you don't need to manipluate Datasource objects directly, it's up to + * JRobin framework to do it for you. + * + * @author Sasa Markovic + */ + +public class Datasource implements RrdUpdater, DsTypes { + private static final double MAX_32_BIT = Math.pow(2, 32); + private static final double MAX_64_BIT = Math.pow(2, 64); + + private RrdDb parentDb; + // definition + private RrdString dsName, dsType; + private RrdLong heartbeat; + private RrdDouble minValue, maxValue; + + // cache + private String m_primitiveDsName = null; + private String m_primitiveDsType = null; + + // state variables + private RrdDouble lastValue; + private RrdLong nanSeconds; + private RrdDouble accumValue; + + Datasource(final RrdDb parentDb, final DsDef dsDef) throws IOException { + boolean shouldInitialize = dsDef != null; + this.parentDb = parentDb; + dsName = new RrdString(this); + dsType = new RrdString(this); + heartbeat = new RrdLong(this); + minValue = new RrdDouble(this); + maxValue = new RrdDouble(this); + lastValue = new RrdDouble(this); + accumValue = new RrdDouble(this); + nanSeconds = new RrdLong(this); + if (shouldInitialize) { + dsName.set(dsDef.getDsName()); + m_primitiveDsName = null; + dsType.set(dsDef.getDsType()); + m_primitiveDsType = null; + heartbeat.set(dsDef.getHeartbeat()); + minValue.set(dsDef.getMinValue()); + maxValue.set(dsDef.getMaxValue()); + lastValue.set(Double.NaN); + accumValue.set(0.0); + final Header header = parentDb.getHeader(); + nanSeconds.set(header.getLastUpdateTime() % header.getStep()); + } + } + + Datasource(final RrdDb parentDb, final DataImporter reader, final int dsIndex) throws IOException, RrdException { + this(parentDb, null); + dsName.set(reader.getDsName(dsIndex)); + m_primitiveDsName = null; + dsType.set(reader.getDsType(dsIndex)); + m_primitiveDsType = null; + heartbeat.set(reader.getHeartbeat(dsIndex)); + minValue.set(reader.getMinValue(dsIndex)); + maxValue.set(reader.getMaxValue(dsIndex)); + lastValue.set(reader.getLastValue(dsIndex)); + accumValue.set(reader.getAccumValue(dsIndex)); + nanSeconds.set(reader.getNanSeconds(dsIndex)); + } + + String dump() throws IOException { + return "== DATASOURCE ==\n" + + "DS:" + dsName.get() + ":" + dsType.get() + ":" + + heartbeat.get() + ":" + minValue.get() + ":" + + maxValue.get() + "\nlastValue:" + lastValue.get() + + " nanSeconds:" + nanSeconds.get() + + " accumValue:" + accumValue.get() + "\n"; + } + + /** + * Returns datasource name. + * + * @return Datasource name + * @throws IOException Thrown in case of I/O error + */ + public String getDsName() throws IOException { + if (m_primitiveDsName == null) { + m_primitiveDsName = dsName.get(); + } + return m_primitiveDsName; + } + + /** + * Returns datasource type (GAUGE, COUNTER, DERIVE, ABSOLUTE). + * + * @return Datasource type. + * @throws IOException Thrown in case of I/O error + */ + public String getDsType() throws IOException { + if (m_primitiveDsType == null) { + m_primitiveDsType = dsType.get(); + } + return m_primitiveDsType; + } + + /** + * Returns datasource heartbeat + * + * @return Datasource heartbeat + * @throws IOException Thrown in case of I/O error + */ + + public long getHeartbeat() throws IOException { + return heartbeat.get(); + } + + /** + * Returns mimimal allowed value for this datasource. + * + * @return Minimal value allowed. + * @throws IOException Thrown in case of I/O error + */ + public double getMinValue() throws IOException { + return minValue.get(); + } + + /** + * Returns maximal allowed value for this datasource. + * + * @return Maximal value allowed. + * @throws IOException Thrown in case of I/O error + */ + public double getMaxValue() throws IOException { + return maxValue.get(); + } + + /** + * Returns last known value of the datasource. + * + * @return Last datasource value. + * @throws IOException Thrown in case of I/O error + */ + public double getLastValue() throws IOException { + return lastValue.get(); + } + + /** + * Returns value this datasource accumulated so far. + * + * @return Accumulated datasource value. + * @throws IOException Thrown in case of I/O error + */ + public double getAccumValue() throws IOException { + return accumValue.get(); + } + + /** + * Returns the number of accumulated NaN seconds. + * + * @return Accumulated NaN seconds. + * @throws IOException Thrown in case of I/O error + */ + public long getNanSeconds() throws IOException { + return nanSeconds.get(); + } + + void process(final long newTime, final double newValue) throws IOException, RrdException { + final Header header = parentDb.getHeader(); + final long step = header.getStep(); + final long oldTime = header.getLastUpdateTime(); + final long startTime = Util.normalize(oldTime, step); + final long endTime = startTime + step; + final double oldValue = lastValue.get(); + final double updateValue = calculateUpdateValue(oldTime, oldValue, newTime, newValue); + if (newTime < endTime) { + accumulate(oldTime, newTime, updateValue); + } + else { + // should store something + final long boundaryTime = Util.normalize(newTime, step); + accumulate(oldTime, boundaryTime, updateValue); + final double value = calculateTotal(startTime, boundaryTime); + // how many updates? + final long numSteps = (boundaryTime - endTime) / step + 1L; + // ACTION! + parentDb.archive(this, value, numSteps); + // cleanup + nanSeconds.set(0); + accumValue.set(0.0); + accumulate(boundaryTime, newTime, updateValue); + } + } + + private double calculateUpdateValue(final long oldTime, final double oldValue, final long newTime, final double newValue) throws IOException { + double updateValue = Double.NaN; + if (newTime - oldTime <= heartbeat.get()) { + final String type = dsType.get(); + if (type.equals(DT_GAUGE)) { + updateValue = newValue; + } + else if (type.equals(DT_ABSOLUTE)) { + if (!Double.isNaN(newValue)) { + updateValue = newValue / (newTime - oldTime); + } + } + else if (type.equals(DT_DERIVE)) { + if (!Double.isNaN(newValue) && !Double.isNaN(oldValue)) { + updateValue = (newValue - oldValue) / (newTime - oldTime); + } + } + else if (type.equals(DT_COUNTER)) { + if (!Double.isNaN(newValue) && !Double.isNaN(oldValue)) { + double diff = newValue - oldValue; + if (diff < 0) { + diff += MAX_32_BIT; + } + if (diff < 0) { + diff += MAX_64_BIT - MAX_32_BIT; + } + if (diff >= 0) { + updateValue = diff / (newTime - oldTime); + } + } + } + if (!Double.isNaN(updateValue)) { + final double minVal = minValue.get(); + final double maxVal = maxValue.get(); + if (!Double.isNaN(minVal) && updateValue < minVal) { + updateValue = Double.NaN; + } + if (!Double.isNaN(maxVal) && updateValue > maxVal) { + updateValue = Double.NaN; + } + } + } + lastValue.set(newValue); + return updateValue; + } + + private void accumulate(final long oldTime, final long newTime, final double updateValue) throws IOException { + if (Double.isNaN(updateValue)) { + nanSeconds.set(nanSeconds.get() + (newTime - oldTime)); + } + else { + accumValue.set(accumValue.get() + updateValue * (newTime - oldTime)); + } + } + + private double calculateTotal(final long startTime, final long boundaryTime) throws IOException { + double totalValue = Double.NaN; + final long validSeconds = boundaryTime - startTime - nanSeconds.get(); + if (nanSeconds.get() <= heartbeat.get() && validSeconds > 0) { + totalValue = accumValue.get() / validSeconds; + } + // IMPORTANT: + // if datasource name ends with "!", we'll send zeros instead of NaNs + // this might be handy from time to time + if (Double.isNaN(totalValue) && dsName.get().endsWith(DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX)) { + totalValue = 0D; + } + return totalValue; + } + + void appendXml(final XmlWriter writer) throws IOException { + writer.startTag("ds"); + writer.writeTag("name", dsName.get()); + writer.writeTag("type", dsType.get()); + writer.writeTag("minimal_heartbeat", heartbeat.get()); + writer.writeTag("min", minValue.get()); + writer.writeTag("max", maxValue.get()); + writer.writeComment("PDP Status"); + writer.writeTag("last_ds", lastValue.get(), "UNKN"); + writer.writeTag("value", accumValue.get()); + writer.writeTag("unknown_sec", nanSeconds.get()); + writer.closeTag(); // ds + } + + /** + * Copies object's internal state to another Datasource object. + * + * @param other New Datasource object to copy state to + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if supplied argument is not a Datasource object + */ + public void copyStateTo(final RrdUpdater other) throws IOException, RrdException { + if (!(other instanceof Datasource)) { + throw new RrdException("Cannot copy Datasource object to " + other.getClass().getName()); + } + final Datasource datasource = (Datasource) other; + if (!datasource.dsName.get().equals(dsName.get())) { + throw new RrdException("Incomaptible datasource names"); + } + if (!datasource.dsType.get().equals(dsType.get())) { + throw new RrdException("Incomaptible datasource types"); + } + datasource.lastValue.set(lastValue.get()); + datasource.nanSeconds.set(nanSeconds.get()); + datasource.accumValue.set(accumValue.get()); + } + + /** + * Returns index of this Datasource object in the RRD. + * + * @return Datasource index in the RRD. + * @throws IOException Thrown in case of I/O error + */ + public int getDsIndex() throws IOException { + try { + return parentDb.getDsIndex(dsName.get()); + } + catch (final RrdException e) { + return -1; + } + } + + /** + * Sets datasource heartbeat to a new value. + * + * @param heartbeat New heartbeat value + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if invalid (non-positive) heartbeat value is specified. + */ + public void setHeartbeat(final long heartbeat) throws RrdException, IOException { + if (heartbeat < 1L) { + throw new RrdException("Invalid heartbeat specified: " + heartbeat); + } + this.heartbeat.set(heartbeat); + } + + /** + * Sets datasource name to a new value + * + * @param newDsName New datasource name + * @throws RrdException Thrown if invalid data source name is specified (name too long, or + * name already defined in the RRD + * @throws IOException Thrown in case of I/O error + */ + public void setDsName(final String newDsName) throws RrdException, IOException { + if (newDsName.length() > RrdString.STRING_LENGTH) { + throw new RrdException("Invalid datasource name specified: " + newDsName); + } + if (parentDb.containsDs(newDsName)) { + throw new RrdException("Datasource already defined in this RRD: " + newDsName); + } + dsName.set(newDsName); + m_primitiveDsName = null; + } + + public void setDsType(final String newDsType) throws RrdException, IOException { + if (!DsDef.isValidDsType(newDsType)) { + throw new RrdException("Invalid datasource type: " + newDsType); + } + // set datasource type + this.dsType.set(newDsType); + m_primitiveDsType = null; + // reset datasource status + lastValue.set(Double.NaN); + accumValue.set(0.0); + // reset archive status + final int dsIndex = parentDb.getDsIndex(dsName.get()); + final Archive[] archives = parentDb.getArchives(); + for (final Archive archive : archives) { + archive.getArcState(dsIndex).setAccumValue(Double.NaN); + } + } + + /** + * Sets minimum allowed value for this datasource. If filterArchivedValues + * argment is set to true, all archived values less then minValue will + * be fixed to NaN. + * + * @param minValue New minimal value. Specify Double.NaN if no minimal + * value should be set + * @param filterArchivedValues true, if archived datasource values should be fixed; + * false, otherwise. + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if invalid minValue was supplied (not less then maxValue) + */ + public void setMinValue(final double minValue, final boolean filterArchivedValues) throws IOException, RrdException { + final double maxValue = this.maxValue.get(); + if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) { + throw new RrdException("Invalid min/max values: " + minValue + "/" + maxValue); + } + this.minValue.set(minValue); + if (!Double.isNaN(minValue) && filterArchivedValues) { + final int dsIndex = getDsIndex(); + final Archive[] archives = parentDb.getArchives(); + for (final Archive archive : archives) { + archive.getRobin(dsIndex).filterValues(minValue, Double.NaN); + } + } + } + + /** + * Sets maximum allowed value for this datasource. If filterArchivedValues + * argment is set to true, all archived values greater then maxValue will + * be fixed to NaN. + * + * @param maxValue New maximal value. Specify Double.NaN if no max + * value should be set. + * @param filterArchivedValues true, if archived datasource values should be fixed; + * false, otherwise. + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if invalid maxValue was supplied (not greater then minValue) + */ + public void setMaxValue(final double maxValue, final boolean filterArchivedValues) throws IOException, RrdException { + final double minValue = this.minValue.get(); + if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) { + throw new RrdException("Invalid min/max values: " + minValue + "/" + maxValue); + } + this.maxValue.set(maxValue); + if (!Double.isNaN(maxValue) && filterArchivedValues) { + final int dsIndex = getDsIndex(); + final Archive[] archives = parentDb.getArchives(); + for (final Archive archive : archives) { + archive.getRobin(dsIndex).filterValues(Double.NaN, maxValue); + } + } + } + + /** + * Sets min/max values allowed for this datasource. If filterArchivedValues + * argment is set to true, all archived values less then minValue or + * greater then maxValue will be fixed to NaN. + * + * @param minValue New minimal value. Specify Double.NaN if no min + * value should be set. + * @param maxValue New maximal value. Specify Double.NaN if no max + * value should be set. + * @param filterArchivedValues true, if archived datasource values should be fixed; + * false, otherwise. + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if invalid min/max values were supplied + */ + public void setMinMaxValue(final double minValue, final double maxValue, final boolean filterArchivedValues) throws IOException, RrdException { + if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) { + throw new RrdException("Invalid min/max values: " + minValue + "/" + maxValue); + } + this.minValue.set(minValue); + this.maxValue.set(maxValue); + if (!(Double.isNaN(minValue) && Double.isNaN(maxValue)) && filterArchivedValues) { + final int dsIndex = getDsIndex(); + final Archive[] archives = parentDb.getArchives(); + for (final Archive archive : archives) { + archive.getRobin(dsIndex).filterValues(minValue, maxValue); + } + } + } + + /** + * Returns the underlying storage (backend) object which actually performs all + * I/O operations. + * + * @return I/O backend object + */ + public RrdBackend getRrdBackend() { + return parentDb.getRrdBackend(); + } + + /** + * Required to implement RrdUpdater interface. You should never call this method directly. + * + * @return Allocator object + */ + public RrdAllocator getRrdAllocator() { + return parentDb.getRrdAllocator(); + } + + public String toString() { + return getClass().getName() + "@" + Integer.toHexString(hashCode()) + "[parentDb=" + parentDb + + ",dsName=" + dsName + ",dsType=" + dsType + ",heartbeat=" + heartbeat + + ",minValue=" + minValue + ",maxValue=" + maxValue + "]"; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/DsDef.java b/apps/jrobin/java/src/org/jrobin/core/DsDef.java new file mode 100644 index 000000000..362c9d373 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/DsDef.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * 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.core; + +/** + * Class to represent single data source definition within the RRD. + * Datasource definition consists of the following five elements: + *

+ *

+ *

+ * For the complete explanation of all source definition parameters, see RRDTool's + * rrdcreate man page. + * + * @author Sasa Markovic + */ +public class DsDef implements DsTypes { + /** + * array of valid source types + */ + public static final String[] DS_TYPES = {DT_GAUGE, DT_COUNTER, DT_DERIVE, DT_ABSOLUTE}; + static final String FORCE_ZEROS_FOR_NANS_SUFFIX = "!"; + + private String dsName, dsType; + private long heartbeat; + private double minValue, maxValue; + + /** + * Creates new data source definition object. This object should be passed as argument + * to {@link RrdDef#addDatasource(DsDef) addDatasource()} + * method of {@link RrdDb RrdDb} object. + *

+ * For the complete explanation of all source definition parameters, see RRDTool's + * rrdcreate man page. + *

+ * IMPORTANT NOTE: If datasource name ends with '!', corresponding archives will never + * store NaNs as datasource values. In that case, NaN datasource values will be silently + * replaced with zeros by the framework. + * + * @param dsName Data source name. + * @param dsType Data source type. Valid values are "COUNTER", "GAUGE", "DERIVE" + * and "ABSOLUTE" (these string constants are conveniently defined in the + * {@link DsTypes} class). + * @param heartbeat Hearbeat + * @param minValue Minimal value. Use Double.NaN if unknown. + * @param maxValue Maximal value. Use Double.NaN if unknown. + * @throws RrdException Thrown if any parameter has illegal value. + */ + public DsDef(final String dsName, final String dsType, final long heartbeat, final double minValue, final double maxValue) throws RrdException { + this.dsName = dsName; + this.dsType = dsType; + this.heartbeat = heartbeat; + this.minValue = minValue; + this.maxValue = maxValue; + validate(); + } + + /** + * Returns data source name. + * + * @return Data source name. + */ + public String getDsName() { + return dsName; + } + + /** + * Returns source type. + * + * @return Source type ("COUNTER", "GAUGE", "DERIVE" or "ABSOLUTE"). + */ + public String getDsType() { + return dsType; + } + + /** + * Returns source heartbeat. + * + * @return Source heartbeat. + */ + public long getHeartbeat() { + return heartbeat; + } + + /** + * Returns minimal calculated source value. + * + * @return Minimal value. + */ + public double getMinValue() { + return minValue; + } + + /** + * Returns maximal calculated source value. + * + * @return Maximal value. + */ + public double getMaxValue() { + return maxValue; + } + + private void validate() throws RrdException { + if (dsName == null) { + throw new RrdException("Null datasource name specified"); + } + if (dsName.length() == 0) { + throw new RrdException("Datasource name length equal to zero"); + } + if (dsName.length() > RrdPrimitive.STRING_LENGTH) { + throw new RrdException("Datasource name [" + dsName + "] to long (" + + dsName.length() + " chars found, only " + RrdPrimitive.STRING_LENGTH + " allowed"); + } + if (!isValidDsType(dsType)) { + throw new RrdException("Invalid datasource type specified: " + dsType); + } + if (heartbeat <= 0) { + throw new RrdException("Invalid heartbeat, must be positive: " + heartbeat); + } + if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) { + throw new RrdException("Invalid min/max values specified: " + + minValue + "/" + maxValue); + } + } + + /** + * Checks if function argument represents valid source type. + * + * @param dsType Source type to be checked. + * @return true if dsType is valid type, + * false otherwise. + */ + public static boolean isValidDsType(final String dsType) { + for (final String type : DS_TYPES) { + if (type.equals(dsType)) { + return true; + } + } + return false; + } + + /** + * Returns string representing source definition (RRDTool format). + * + * @return String containing all data source definition parameters. + */ + public String dump() { + return "DS:" + dsName + ":" + dsType + ":" + heartbeat + + ":" + Util.formatDouble(minValue, "U", false) + + ":" + Util.formatDouble(maxValue, "U", false); + } + + /** + * Checks if two datasource definitions are equal. + * Source definitions are treated as equal if they have the same source name. + * It is not possible to create RRD with two equal archive definitions. + * + * @param obj Archive definition to compare with. + * @return true if archive definitions are equal, + * false otherwise. + */ + public boolean equals(final Object obj) { + if (obj instanceof DsDef) { + final DsDef dsObj = (DsDef) obj; + return dsName.equals(dsObj.dsName); + } + return false; + } + + public int hashCode() { + return dsName.hashCode() * 47; + } + + boolean exactlyEqual(final DsDef def) { + return dsName.equals(def.dsName) && dsType.equals(def.dsType) && + heartbeat == def.heartbeat && Util.equal(minValue, def.minValue) && + Util.equal(maxValue, def.maxValue); + } + + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + "[" + dump() + "]"; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/DsTypes.java b/apps/jrobin/java/src/org/jrobin/core/DsTypes.java new file mode 100644 index 000000000..31330b043 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/DsTypes.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * 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.core; + +/** + * Simple interface to represent available datasource types. + */ +public interface DsTypes { + /** + * Constant to represent GAUGE datasource type + */ + public static final String DT_GAUGE = "GAUGE"; + + /** + * Constant to represent COUNTER datasource type + */ + public static final String DT_COUNTER = "COUNTER"; + + /** + * Constant to represent DERIVE datasource type + */ + public static final String DT_DERIVE = "DERIVE"; + + /** + * Constant to represent ABSOLUTE datasource type + */ + public static final String DT_ABSOLUTE = "ABSOLUTE"; +} diff --git a/apps/jrobin/java/src/org/jrobin/core/FetchData.java b/apps/jrobin/java/src/org/jrobin/core/FetchData.java new file mode 100644 index 000000000..cc57a87a4 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/FetchData.java @@ -0,0 +1,529 @@ +/******************************************************************************* + * 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.core; + +import org.jrobin.data.Aggregates; +import org.jrobin.data.DataProcessor; + +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Class used to represent data fetched from the RRD. + * Object of this class is created when the method + * {@link FetchRequest#fetchData() fetchData()} is + * called on a {@link FetchRequest FetchRequest} object. + *

+ * Data returned from the RRD is, simply, just one big table filled with + * timestamps and corresponding datasource values. + * Use {@link #getRowCount() getRowCount()} method to count the number + * of returned timestamps (table rows). + *

+ * The first table column is filled with timestamps. Time intervals + * between consecutive timestamps are guaranteed to be equal. Use + * {@link #getTimestamps() getTimestamps()} method to get an array of + * timestamps returned. + *

+ * Remaining columns are filled with datasource values for the whole timestamp range, + * on a column-per-datasource basis. Use {@link #getColumnCount() getColumnCount()} to find + * the number of datasources and {@link #getValues(int) getValues(i)} method to obtain + * all values for the i-th datasource. Returned datasource values correspond to + * the values returned with {@link #getTimestamps() getTimestamps()} method. + */ +public class FetchData implements ConsolFuns { + // anything fuuny will do + private static final String RPN_SOURCE_NAME = "WHERE THE SPEECHLES UNITE IN A SILENT ACCORD"; + + private FetchRequest request; + private String[] dsNames; + private long[] timestamps; + private double[][] values; + + private Archive matchingArchive; + private long arcStep; + private long arcEndTime; + + FetchData(Archive matchingArchive, FetchRequest request) throws IOException { + this.matchingArchive = matchingArchive; + this.arcStep = matchingArchive.getArcStep(); + this.arcEndTime = matchingArchive.getEndTime(); + this.dsNames = request.getFilter(); + if (this.dsNames == null) { + this.dsNames = matchingArchive.getParentDb().getDsNames(); + } + this.request = request; + } + + void setTimestamps(long[] timestamps) { + this.timestamps = timestamps; + } + + void setValues(double[][] values) { + this.values = values; + } + + /** + * Returns the number of rows fetched from the corresponding RRD. + * Each row represents datasource values for the specific timestamp. + * + * @return Number of rows. + */ + public int getRowCount() { + return timestamps.length; + } + + /** + * Returns the number of columns fetched from the corresponding RRD. + * This number is always equal to the number of datasources defined + * in the RRD. Each column represents values of a single datasource. + * + * @return Number of columns (datasources). + */ + public int getColumnCount() { + return dsNames.length; + } + + /** + * Returns an array of timestamps covering the whole range specified in the + * {@link FetchRequest FetchReguest} object. + * + * @return Array of equidistant timestamps. + */ + public long[] getTimestamps() { + return timestamps; + } + + /** + * Returns the step with which this data was fetched. + * + * @return Step as long. + */ + public long getStep() { + return timestamps[1] - timestamps[0]; + } + + /** + * Returns all archived values for a single datasource. + * Returned values correspond to timestamps + * returned with {@link #getTimestamps() getTimestamps()} method. + * + * @param dsIndex Datasource index. + * @return Array of single datasource values. + */ + public double[] getValues(int dsIndex) { + return values[dsIndex]; + } + + /** + * Returns all archived values for all datasources. + * Returned values correspond to timestamps + * returned with {@link #getTimestamps() getTimestamps()} method. + * + * @return Two-dimensional aray of all datasource values. + */ + public double[][] getValues() { + return values; + } + + /** + * Returns all archived values for a single datasource. + * Returned values correspond to timestamps + * returned with {@link #getTimestamps() getTimestamps()} method. + * + * @param dsName Datasource name. + * @return Array of single datasource values. + * @throws RrdException Thrown if no matching datasource name is found. + */ + public double[] getValues(String dsName) throws RrdException { + for (int dsIndex = 0; dsIndex < getColumnCount(); dsIndex++) { + if (dsName.equals(dsNames[dsIndex])) { + return getValues(dsIndex); + } + } + throw new RrdException("Datasource [" + dsName + "] not found"); + } + + /** + * Returns a set of values created by applying RPN expression to the fetched data. + * For example, if you have two datasources named x and y + * in this FetchData and you want to calculate values for (x+y)/2 use something like: + *

+ * getRpnValues("x,y,+,2,/"); + * + * @param rpnExpression RRDTool-like RPN expression + * @return Calculated values + * @throws RrdException Thrown if invalid RPN expression is supplied + */ + public double[] getRpnValues(String rpnExpression) throws RrdException { + DataProcessor dataProcessor = createDataProcessor(rpnExpression); + return dataProcessor.getValues(RPN_SOURCE_NAME); + } + + /** + * Returns {@link FetchRequest FetchRequest} object used to create this FetchData object. + * + * @return Fetch request object. + */ + public FetchRequest getRequest() { + return request; + } + + /** + * Returns the first timestamp in this FetchData object. + * + * @return The smallest timestamp. + */ + public long getFirstTimestamp() { + return timestamps[0]; + } + + /** + * Returns the last timestamp in this FecthData object. + * + * @return The biggest timestamp. + */ + public long getLastTimestamp() { + return timestamps[timestamps.length - 1]; + } + + /** + * Returns Archive object which is determined to be the best match for the + * timestamps specified in the fetch request. All datasource values are obtained + * from round robin archives belonging to this archive. + * + * @return Matching archive. + */ + public Archive getMatchingArchive() { + return matchingArchive; + } + + /** + * Returns array of datasource names found in the corresponding RRD. If the request + * was filtered (data was fetched only for selected datasources), only datasources selected + * for fetching are returned. + * + * @return Array of datasource names. + */ + public String[] getDsNames() { + return dsNames; + } + + /** + * Retrieve the table index number of a datasource by name. Names are case sensitive. + * + * @param dsName Name of the datasource for which to find the index. + * @return Index number of the datasources in the value table. + */ + public int getDsIndex(String dsName) { + // Let's assume the table of dsNames is always small, so it is not necessary to use a hashmap for lookups + for (int i = 0; i < dsNames.length; i++) { + if (dsNames[i].equals(dsName)) { + return i; + } + } + return -1; // Datasource not found ! + } + + /** + * Dumps the content of the whole FetchData object. Useful for debugging. + * + * @return String containing the contents of this object, for debugging. + */ + public String dump() { + StringBuffer buffer = new StringBuffer(""); + for (int row = 0; row < getRowCount(); row++) { + buffer.append(timestamps[row]); + buffer.append(": "); + for (int dsIndex = 0; dsIndex < getColumnCount(); dsIndex++) { + buffer.append(Util.formatDouble(values[dsIndex][row], true)); + buffer.append(" "); + } + buffer.append("\n"); + } + return buffer.toString(); + } + + /** + * Returns string representing fetched data in a RRDTool-like form. + * + * @return Fetched data as a string in a rrdfetch-like output form. + */ + public String toString() { + // print header row + StringBuffer buff = new StringBuffer(); + buff.append(padWithBlanks("", 10)); + buff.append(" "); + for (String dsName : dsNames) { + buff.append(padWithBlanks(dsName, 18)); + } + buff.append("\n \n"); + for (int i = 0; i < timestamps.length; i++) { + buff.append(padWithBlanks("" + timestamps[i], 10)); + buff.append(":"); + for (int j = 0; j < dsNames.length; j++) { + double value = values[j][i]; + String valueStr = Double.isNaN(value) ? "nan" : Util.formatDouble(value); + buff.append(padWithBlanks(valueStr, 18)); + } + buff.append("\n"); + } + return buff.toString(); + } + + private static String padWithBlanks(String input, int width) { + StringBuffer buff = new StringBuffer(""); + int diff = width - input.length(); + while (diff-- > 0) { + buff.append(' '); + } + buff.append(input); + return buff.toString(); + } + + /** + * Returns single 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", "FIRST", "AVERAGE" and "TOTAL" + * (these string constants are conveniently defined in the {@link ConsolFuns} class) + * @return MIN, MAX, LAST, FIRST, AVERAGE or TOTAL 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 { + DataProcessor dp = createDataProcessor(null); + return dp.getAggregate(dsName, consolFun); + } + + /** + * Returns aggregated value from the fetched data for a single datasource. + * Before applying aggregation functions, specified RPN expression is applied to fetched data. + * For example, if you have a gauge datasource named 'foots' but you want to find the maximum + * fetched value in meters use something like: + *

+ * getAggregate("foots", "MAX", "foots,0.3048,*"); + * + * @param dsName Datasource name + * @param consolFun Consolidation function (MIN, MAX, LAST, FIRST, AVERAGE or TOTAL) + * @param rpnExpression RRDTool-like RPN expression + * @return Aggregated value + * @throws RrdException Thrown if the given datasource name cannot be found in fetched data, or if + * invalid RPN expression is supplied + * @throws IOException Thrown in case of I/O error (unlikely to happen) + * @deprecated This method is preserved just for backward compatibility. + */ + public double getAggregate(String dsName, String consolFun, String rpnExpression) + throws RrdException, IOException { + // for backward compatibility + rpnExpression = rpnExpression.replaceAll("value", dsName); + return getRpnAggregate(rpnExpression, consolFun); + } + + /** + * Returns aggregated value for a set of values calculated by applying an RPN expression to the + * fetched data. For example, if you have two datasources named x and y + * in this FetchData and you want to calculate MAX value of (x+y)/2 use something like: + *

+ * getRpnAggregate("x,y,+,2,/", "MAX"); + * + * @param rpnExpression RRDTool-like RPN expression + * @param consolFun Consolidation function (MIN, MAX, LAST, FIRST, AVERAGE or TOTAL) + * @return Aggregated value + * @throws RrdException Thrown if invalid RPN expression is supplied + */ + public double getRpnAggregate(String rpnExpression, String consolFun) throws RrdException { + DataProcessor dataProcessor = createDataProcessor(rpnExpression); + return dataProcessor.getAggregate(RPN_SOURCE_NAME, consolFun); + } + + /** + * Returns all aggregated values (MIN, MAX, LAST, FIRST, AVERAGE or TOTAL) calculated from the fetched data + * for a single datasource. + * + * @param dsName Datasource name. + * @return Simple object containing all aggregated values. + * @throws RrdException Thrown if the given datasource name cannot be found in the fetched data. + */ + public Aggregates getAggregates(String dsName) throws RrdException { + DataProcessor dataProcessor = createDataProcessor(null); + return dataProcessor.getAggregates(dsName); + } + + /** + * Returns all aggregated values for a set of values calculated by applying an RPN expression to the + * fetched data. For example, if you have two datasources named x and y + * in this FetchData and you want to calculate MIN, MAX, LAST, FIRST, AVERAGE and TOTAL value + * of (x+y)/2 use something like: + *

+ * getRpnAggregates("x,y,+,2,/"); + * + * @param rpnExpression RRDTool-like RPN expression + * @return Object containing all aggregated values + * @throws RrdException Thrown if invalid RPN expression is supplied + * @throws IOException Thrown in case of I/O error + */ + public Aggregates getRpnAggregates(String rpnExpression) throws RrdException, IOException { + DataProcessor dataProcessor = createDataProcessor(rpnExpression); + return dataProcessor.getAggregates(RPN_SOURCE_NAME); + } + + /** + * Used by ISPs which charge for bandwidth utilization on a "95th percentile" basis. + *

+ * The 95th percentile is the highest source value left when the top 5% of a numerically sorted set + * of source data is discarded. It is used as a measure of the peak value used when one discounts + * a fair amount for transitory spikes. This makes it markedly different from the average. + *

+ * Read more about this topic at: + * Rednet or
+ * Bytemark. + * + * @param dsName Datasource name + * @return 95th percentile of fetched source values + * @throws RrdException Thrown if invalid source name is supplied + */ + public double get95Percentile(String dsName) throws RrdException { + DataProcessor dataProcessor = createDataProcessor(null); + return dataProcessor.get95Percentile(dsName); + } + + /** + * Same as {@link #get95Percentile(String)}, but for a set of values calculated with the given + * RPN expression. + * + * @param rpnExpression RRDTool-like RPN expression + * @return 95-percentile + * @throws RrdException Thrown if invalid RPN expression is supplied + */ + public double getRpn95Percentile(String rpnExpression) throws RrdException { + DataProcessor dataProcessor = createDataProcessor(rpnExpression); + return dataProcessor.get95Percentile(RPN_SOURCE_NAME); + } + + /** + * Dumps fetch data to output stream in XML format. + * + * @param outputStream Output stream to dump fetch data to + * @throws IOException Thrown in case of I/O error + */ + public void exportXml(OutputStream outputStream) throws IOException { + XmlWriter writer = new XmlWriter(outputStream); + writer.startTag("fetch_data"); + writer.startTag("request"); + writer.writeTag("file", request.getParentDb().getPath()); + writer.writeComment(Util.getDate(request.getFetchStart())); + writer.writeTag("start", request.getFetchStart()); + writer.writeComment(Util.getDate(request.getFetchEnd())); + writer.writeTag("end", request.getFetchEnd()); + writer.writeTag("resolution", request.getResolution()); + writer.writeTag("cf", request.getConsolFun()); + writer.closeTag(); // request + writer.startTag("datasources"); + for (String dsName : dsNames) { + writer.writeTag("name", dsName); + } + writer.closeTag(); // datasources + writer.startTag("data"); + for (int i = 0; i < timestamps.length; i++) { + writer.startTag("row"); + writer.writeComment(Util.getDate(timestamps[i])); + writer.writeTag("timestamp", timestamps[i]); + writer.startTag("values"); + for (int j = 0; j < dsNames.length; j++) { + writer.writeTag("v", values[j][i]); + } + writer.closeTag(); // values + writer.closeTag(); // row + } + writer.closeTag(); // data + writer.closeTag(); // fetch_data + writer.flush(); + } + + /** + * Dumps fetch data to file in XML format. + * + * @param filepath Path to destination file + * @throws IOException Thrown in case of I/O error + */ + public void exportXml(String filepath) throws IOException { + OutputStream outputStream = null; + try { + outputStream = new FileOutputStream(filepath); + exportXml(outputStream); + } + finally { + if (outputStream != null) { + outputStream.close(); + } + } + } + + /** + * Dumps fetch data in XML format. + * + * @return String containing XML formatted fetch data + * @throws IOException Thrown in case of I/O error + */ + public String exportXml() throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + exportXml(outputStream); + return outputStream.toString(); + } + + /** + * Returns the step of the corresponding RRA archive + * + * @return Archive step in seconds + */ + public long getArcStep() { + return arcStep; + } + + /** + * Returns the timestamp of the last populated slot in the corresponding RRA archive + * + * @return Timestamp in seconds + */ + public long getArcEndTime() { + return arcEndTime; + } + + private DataProcessor createDataProcessor(String rpnExpression) throws RrdException { + DataProcessor dataProcessor = new DataProcessor(request.getFetchStart(), request.getFetchEnd()); + for (String dsName : dsNames) { + dataProcessor.addDatasource(dsName, this); + } + if (rpnExpression != null) { + dataProcessor.addDatasource(RPN_SOURCE_NAME, rpnExpression); + try { + dataProcessor.processData(); + } + catch (IOException ioe) { + // highly unlikely, since all datasources have already calculated values + throw new RuntimeException("Impossible error: " + ioe); + } + } + return dataProcessor; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/FetchRequest.java b/apps/jrobin/java/src/org/jrobin/core/FetchRequest.java new file mode 100644 index 000000000..5722acd97 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/FetchRequest.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; +import java.util.Set; + +/** + * Class to represent fetch request. For the complete explanation of all + * fetch parameters consult RRDTool's + * rrdfetch man page. + *

+ * You cannot create FetchRequest directly (no public constructor + * is provided). Use {@link RrdDb#createFetchRequest(String, long, long, long) + * createFetchRequest()} method of your {@link RrdDb RrdDb} object. + * + * @author Sasa Markovic + */ +public class FetchRequest { + private RrdDb parentDb; + private String consolFun; + private long fetchStart; + private long fetchEnd; + private long resolution; + private String[] filter; + + public FetchRequest(RrdDb parentDb, String consolFun, long fetchStart, long fetchEnd, long resolution) throws RrdException { + this.parentDb = parentDb; + this.consolFun = consolFun; + this.fetchStart = fetchStart; + this.fetchEnd = fetchEnd; + this.resolution = resolution; + validate(); + } + + /** + * Sets request filter in order to fetch data only for + * the specified array of datasources (datasource names). + * If not set (or set to null), fetched data will + * containt values of all datasources defined in the corresponding RRD. + * To fetch data only from selected + * datasources, specify an array of datasource names as method argument. + * + * @param filter Array of datsources (datsource names) to fetch data from. + */ + public void setFilter(String[] filter) { + this.filter = filter; + } + + /** + * Sets request filter in order to fetch data only for + * the specified set of datasources (datasource names). + * If the filter is not set (or set to null), fetched data will + * containt values of all datasources defined in the corresponding RRD. + * To fetch data only from selected + * datasources, specify a set of datasource names as method argument. + * + * @param filter Set of datsource names to fetch data for. + */ + public void setFilter(Set filter) { + this.filter = filter.toArray(new String[0]); + } + + /** + * Sets request filter in order to fetch data only for + * a single datasource (datasource name). + * If not set (or set to null), fetched data will + * containt values of all datasources defined in the corresponding RRD. + * To fetch data for a single datasource only, + * specify an array of datasource names as method argument. + * + * @param filter Array of datsources (datsource names) to fetch data from. + */ + public void setFilter(String filter) { + this.filter = (filter == null) ? null : (new String[] {filter}); + } + + /** + * Returns request filter. See {@link #setFilter(String[]) setFilter()} for + * complete explanation. + * + * @return Request filter (array of datasource names), null if not set. + */ + public String[] getFilter() { + return filter; + } + + /** + * Returns consolitation function to be used during the fetch process. + * + * @return Consolidation function. + */ + public String getConsolFun() { + return consolFun; + } + + /** + * Returns starting timestamp to be used for the fetch request. + * + * @return Starting timstamp in seconds. + */ + public long getFetchStart() { + return fetchStart; + } + + /** + * Returns ending timestamp to be used for the fetch request. + * + * @return Ending timestamp in seconds. + */ + public long getFetchEnd() { + return fetchEnd; + } + + /** + * Returns fetch resolution to be used for the fetch request. + * + * @return Fetch resolution in seconds. + */ + public long getResolution() { + return resolution; + } + + private void validate() throws RrdException { + if (!ArcDef.isValidConsolFun(consolFun)) { + throw new RrdException("Invalid consolidation function in fetch request: " + consolFun); + } + if (fetchStart < 0) { + throw new RrdException("Invalid start time in fetch request: " + fetchStart); + } + if (fetchEnd < 0) { + throw new RrdException("Invalid end time in fetch request: " + fetchEnd); + } + if (fetchStart > fetchEnd) { + throw new RrdException("Invalid start/end time in fetch request: " + fetchStart + + " > " + fetchEnd); + } + if (resolution <= 0) { + throw new RrdException("Invalid resolution in fetch request: " + resolution); + } + } + + /** + * Dumps the content of fetch request using the syntax of RRDTool's fetch command. + * + * @return Fetch request dump. + */ + public String dump() { + return "fetch \"" + parentDb.getRrdBackend().getPath() + + "\" " + consolFun + " --start " + fetchStart + " --end " + fetchEnd + + (resolution > 1 ? " --resolution " + resolution : ""); + } + + String getRrdToolCommand() { + return dump(); + } + + /** + * Returns data from the underlying RRD and puts it in a single + * {@link FetchData FetchData} object. + * + * @return FetchData object filled with timestamps and datasource values. + * @throws RrdException Thrown in case of JRobin specific error. + * @throws IOException Thrown in case of I/O error. + */ + public FetchData fetchData() throws RrdException, IOException { + return parentDb.fetchData(this); + } + + /** + * Returns the underlying RrdDb object. + * + * @return RrdDb object used to create this FetchRequest object. + */ + public RrdDb getParentDb() { + return parentDb; + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/core/Header.java b/apps/jrobin/java/src/org/jrobin/core/Header.java new file mode 100644 index 000000000..faecb02a8 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/Header.java @@ -0,0 +1,222 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Class to represent RRD header. Header information is mainly static (once set, it + * cannot be changed), with the exception of last update time (this value is changed whenever + * RRD gets updated). + *

+ * Normally, you don't need to manipulate the Header object directly - JRobin framework + * does it for you. + * + * @author Sasa Markovic* + */ +public class Header implements RrdUpdater { + static final int SIGNATURE_LENGTH = 2; + static final String SIGNATURE = "JR"; + + static final String DEFAULT_SIGNATURE = "JRobin, version 0.1"; + static final String RRDTOOL_VERSION = "0001"; + + private RrdDb parentDb; + + private RrdString signature; + private RrdLong step; + private RrdInt dsCount, arcCount; + private RrdLong lastUpdateTime; + private Long m_primitiveStep = null; + private Integer m_primitiveDsCount = null; + private Integer m_primitiveArcCount = null; + + Header(final RrdDb parentDb, final RrdDef rrdDef) throws IOException { + final boolean shouldInitialize = rrdDef != null; + this.parentDb = parentDb; + signature = new RrdString(this); // NOT constant, may NOT be cached + step = new RrdLong(this, true); // constant, may be cached + dsCount = new RrdInt(this, true); // constant, may be cached + arcCount = new RrdInt(this, true); // constant, may be cached + lastUpdateTime = new RrdLong(this); + if (shouldInitialize) { + signature.set(DEFAULT_SIGNATURE); + step.set(rrdDef.getStep()); + dsCount.set(rrdDef.getDsCount()); + arcCount.set(rrdDef.getArcCount()); + lastUpdateTime.set(rrdDef.getStartTime()); + } + } + + Header(final RrdDb parentDb, final DataImporter reader) throws IOException, RrdException { + this(parentDb, (RrdDef) null); + final String version = reader.getVersion(); + final int intVersion = Integer.parseInt(version); + if (intVersion > 3) { + throw new RrdException("Could not unserialize xml version " + version); + } + signature.set(DEFAULT_SIGNATURE); + step.set(reader.getStep()); + dsCount.set(reader.getDsCount()); + arcCount.set(reader.getArcCount()); + lastUpdateTime.set(reader.getLastUpdateTime()); + } + + /** + * Returns RRD signature. Initially, the returned string will be + * of the form JRobin, version x.x. Note: RRD format did not + * change since Jrobin 1.0.0 release (and probably never will). + * + * @return RRD signature + * @throws IOException Thrown in case of I/O error + */ + public String getSignature() throws IOException { + return signature.get(); + } + + public String getInfo() throws IOException { + return getSignature().substring(SIGNATURE_LENGTH); + } + + public void setInfo(String info) throws IOException { + if (info != null && info.length() > 0) { + signature.set(SIGNATURE + info); + } + else { + signature.set(SIGNATURE); + } + } + + /** + * Returns the last update time of the RRD. + * + * @return Timestamp (Unix epoch, no milliseconds) corresponding to the last update time. + * @throws IOException Thrown in case of I/O error + */ + public long getLastUpdateTime() throws IOException { + return lastUpdateTime.get(); + } + + /** + * Returns primary RRD time step. + * + * @return Primary time step in seconds + * @throws IOException Thrown in case of I/O error + */ + public long getStep() throws IOException { + if (m_primitiveStep == null) { + m_primitiveStep = step.get(); + } + return m_primitiveStep; + } + + /** + * Returns the number of datasources defined in the RRD. + * + * @return Number of datasources defined + * @throws IOException Thrown in case of I/O error + */ + public int getDsCount() throws IOException { + if (m_primitiveDsCount == null) { + m_primitiveDsCount = dsCount.get(); + } + return m_primitiveDsCount; + } + + /** + * Returns the number of archives defined in the RRD. + * + * @return Number of archives defined + * @throws IOException Thrown in case of I/O error + */ + public int getArcCount() throws IOException { + if (m_primitiveArcCount == null) { + m_primitiveArcCount = arcCount.get(); + } + return m_primitiveArcCount; + } + + public void setLastUpdateTime(final long lastUpdateTime) throws IOException { + this.lastUpdateTime.set(lastUpdateTime); + } + + String dump() throws IOException { + return "== HEADER ==\n" + + "signature:" + getSignature() + + " lastUpdateTime:" + getLastUpdateTime() + + " step:" + getStep() + + " dsCount:" + getDsCount() + + " arcCount:" + getArcCount() + "\n"; + } + + void appendXml(XmlWriter writer) throws IOException { + writer.writeComment(signature.get()); + writer.writeTag("version", RRDTOOL_VERSION); + writer.writeComment("Seconds"); + writer.writeTag("step", step.get()); + writer.writeComment(Util.getDate(lastUpdateTime.get())); + writer.writeTag("lastupdate", lastUpdateTime.get()); + } + + /** + * Copies object's internal state to another Header object. + * + * @param other New Header object to copy state to + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if supplied argument is not a Header object + */ + public void copyStateTo(final RrdUpdater other) throws IOException, RrdException { + if (!(other instanceof Header)) { + throw new RrdException( "Cannot copy Header object to " + other.getClass().getName()); + } + final Header header = (Header) other; + header.signature.set(signature.get()); + header.lastUpdateTime.set(lastUpdateTime.get()); + } + + /** + * Returns the underlying storage (backend) object which actually performs all + * I/O operations. + * + * @return I/O backend object + */ + public RrdBackend getRrdBackend() { + return parentDb.getRrdBackend(); + } + + boolean isJRobinHeader() throws IOException { + return signature.get().startsWith(SIGNATURE); + } + + void validateHeader() throws IOException, RrdException { + if (!isJRobinHeader()) { + throw new RrdException("Invalid file header. File [" + parentDb.getCanonicalPath() + "] is not a JRobin RRD file"); + } + } + + /** + * Required to implement RrdUpdater interface. You should never call this method directly. + * + * @return Allocator object + */ + public RrdAllocator getRrdAllocator() { + return parentDb.getRrdAllocator(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/Robin.java b/apps/jrobin/java/src/org/jrobin/core/Robin.java new file mode 100644 index 000000000..623bc80a9 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/Robin.java @@ -0,0 +1,266 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Class to represent archive values for a single datasource. Robin class is the heart of + * the so-called "round robin database" concept. Basically, each Robin object is a + * fixed length array of double values. Each double value reperesents consolidated, archived + * value for the specific timestamp. When the underlying array of double values gets completely + * filled, new values will replace the oldest ones. + *

+ * Robin object does not hold values in memory - such object could be quite large. + * Instead of it, Robin reads them from the backend I/O only when necessary. + * + * @author Sasa Markovic + */ +public class Robin implements RrdUpdater { + private Archive parentArc; + private RrdInt pointer; + private RrdDoubleArray values; + private int rows; + + Robin(Archive parentArc, int rows, boolean shouldInitialize) throws IOException { + this.parentArc = parentArc; + this.pointer = new RrdInt(this); + this.values = new RrdDoubleArray(this, rows); + this.rows = rows; + if (shouldInitialize) { + pointer.set(0); + values.set(0, Double.NaN, rows); + } + } + + /** + * Fetches all archived values. + * + * @return Array of double archive values, starting from the oldest one. + * @throws IOException Thrown in case of I/O specific error. + */ + public double[] getValues() throws IOException { + return getValues(0, rows); + } + + // stores single value + void store(double newValue) throws IOException { + int position = pointer.get(); + values.set(position, newValue); + pointer.set((position + 1) % rows); + } + + // stores the same value several times + void bulkStore(double newValue, int bulkCount) throws IOException { + assert bulkCount <= rows: "Invalid number of bulk updates: " + bulkCount + + " rows=" + rows; + int position = pointer.get(); + // update tail + int tailUpdateCount = Math.min(rows - position, bulkCount); + values.set(position, newValue, tailUpdateCount); + pointer.set((position + tailUpdateCount) % rows); + // do we need to update from the start? + int headUpdateCount = bulkCount - tailUpdateCount; + if (headUpdateCount > 0) { + values.set(0, newValue, headUpdateCount); + pointer.set(headUpdateCount); + } + } + + void update(double[] newValues) throws IOException { + assert rows == newValues.length: "Invalid number of robin values supplied (" + newValues.length + + "), exactly " + rows + " needed"; + pointer.set(0); + values.writeDouble(0, newValues); + } + + /** + * Updates archived values in bulk. + * + * @param newValues Array of double values to be stored in the archive + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if the length of the input array is different from the length of + * this archive + */ + public void setValues(double[] newValues) throws IOException, RrdException { + if (rows != newValues.length) { + throw new RrdException("Invalid number of robin values supplied (" + newValues.length + + "), exactly " + rows + " needed"); + } + update(newValues); + } + + /** + * (Re)sets all values in this archive to the same value. + * + * @param newValue New value + * @throws IOException Thrown in case of I/O error + */ + public void setValues(double newValue) throws IOException { + double[] values = new double[rows]; + for (int i = 0; i < values.length; i++) { + values[i] = newValue; + } + update(values); + } + + String dump() throws IOException { + StringBuffer buffer = new StringBuffer("Robin " + pointer.get() + "/" + rows + ": "); + double[] values = getValues(); + for (double value : values) { + buffer.append(Util.formatDouble(value, true)).append(" "); + } + buffer.append("\n"); + return buffer.toString(); + } + + /** + * Returns the i-th value from the Robin archive. + * + * @param index Value index + * @return Value stored in the i-th position (the oldest value has zero index) + * @throws IOException Thrown in case of I/O specific error. + */ + public double getValue(int index) throws IOException { + int arrayIndex = (pointer.get() + index) % rows; + return values.get(arrayIndex); + } + + /** + * Sets the i-th value in the Robin archive. + * + * @param index index in the archive (the oldest value has zero index) + * @param value value to be stored + * @throws IOException Thrown in case of I/O specific error. + */ + public void setValue(int index, double value) throws IOException { + int arrayIndex = (pointer.get() + index) % rows; + values.set(arrayIndex, value); + } + + double[] getValues(int index, int count) throws IOException { + assert count <= rows: "Too many values requested: " + count + " rows=" + rows; + int startIndex = (pointer.get() + index) % rows; + int tailReadCount = Math.min(rows - startIndex, count); + double[] tailValues = values.get(startIndex, tailReadCount); + if (tailReadCount < count) { + int headReadCount = count - tailReadCount; + double[] headValues = values.get(0, headReadCount); + double[] values = new double[count]; + int k = 0; + for (double tailValue : tailValues) { + values[k++] = tailValue; + } + for (double headValue : headValues) { + values[k++] = headValue; + } + return values; + } + else { + return tailValues; + } + } + + /** + * Returns the Archive object to which this Robin object belongs. + * + * @return Parent Archive object + */ + public Archive getParent() { + return parentArc; + } + + /** + * Returns the size of the underlying array of archived values. + * + * @return Number of stored values + */ + public int getSize() { + return rows; + } + + /** + * Copies object's internal state to another Robin object. + * + * @param other New Robin object to copy state to + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if supplied argument is not a Robin object + */ + public void copyStateTo(RrdUpdater other) throws IOException, RrdException { + if (!(other instanceof Robin)) { + throw new RrdException( + "Cannot copy Robin object to " + other.getClass().getName()); + } + Robin robin = (Robin) other; + int rowsDiff = rows - robin.rows; + if (rowsDiff == 0) { + // Identical dimensions. Do copy in BULK to speed things up + robin.pointer.set(pointer.get()); + robin.values.writeBytes(values.readBytes()); + } + else { + // different sizes + for (int i = 0; i < robin.rows; i++) { + int j = i + rowsDiff; + robin.store(j >= 0 ? getValue(j) : Double.NaN); + } + } + } + + /** + * Filters values stored in this archive based on the given boundary. + * Archived values found to be outside of [minValue, maxValue] interval (inclusive) + * will be silently replaced with NaN. + * + * @param minValue lower boundary + * @param maxValue upper boundary + * @throws IOException Thrown in case of I/O error + */ + public void filterValues(double minValue, double maxValue) throws IOException { + for (int i = 0; i < rows; i++) { + double value = values.get(i); + if (!Double.isNaN(minValue) && !Double.isNaN(value) && minValue > value) { + values.set(i, Double.NaN); + } + if (!Double.isNaN(maxValue) && !Double.isNaN(value) && maxValue < value) { + values.set(i, Double.NaN); + } + } + } + + /** + * Returns the underlying storage (backend) object which actually performs all + * I/O operations. + * + * @return I/O backend object + */ + public RrdBackend getRrdBackend() { + return parentArc.getRrdBackend(); + } + + /** + * Required to implement RrdUpdater interface. You should never call this method directly. + * + * @return Allocator object + */ + public RrdAllocator getRrdAllocator() { + return parentArc.getRrdAllocator(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdAllocator.java b/apps/jrobin/java/src/org/jrobin/core/RrdAllocator.java new file mode 100644 index 000000000..f3b205863 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdAllocator.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +class RrdAllocator { + private long allocationPointer = 0L; + + long allocate(long byteCount) throws IOException { + long pointer = allocationPointer; + allocationPointer += byteCount; + return pointer; + } +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdBackend.java b/apps/jrobin/java/src/org/jrobin/core/RrdBackend.java new file mode 100644 index 000000000..d15c5857b --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdBackend.java @@ -0,0 +1,341 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Base implementation class for all backend classes. Each Round Robin Database object + * ({@link RrdDb} object) is backed with a single RrdBackend object which performs + * actual I/O operations on the underlying storage. JRobin supports + * three different bakcends out of the box: + *

+ *

+ *

+ * To create your own backend in order to provide some custom type of RRD storage, + * you should do the following: + *

+ *

+ */ +public abstract class RrdBackend { + private static boolean s_instanceCreated = false; + private String m_path = null; + private boolean m_readOnly = false; + + /** + * Creates backend for a RRD storage with the given path. + * + * @param path String identifying RRD storage. For files on the disk, this + * argument should represent file path. Other storage types might interpret + * this argument differently. + */ + protected RrdBackend(final String path) { + this(path, false); + } + + protected RrdBackend(final String path, final boolean readOnly) { + m_path = path; + m_readOnly = readOnly; + RrdBackend.setInstanceCreated(); + } + + /** + * Returns path to the storage. + * + * @return Storage path + */ + public String getPath() { + return m_path; + } + + /** + * Is the RRD ReadOnly? + * + * @return True if the RRD is read only, false if not. + */ + public boolean isReadOnly() { + return m_readOnly; + } + + /** + * Writes an array of bytes to the underlying storage starting from the given + * storage offset. + * + * @param offset Storage offset. + * @param b Array of bytes that should be copied to the underlying storage + * @throws IOException Thrown in case of I/O error + */ + protected abstract void write(long offset, byte[] b) throws IOException; + + /** + * Reads an array of bytes from the underlying storage starting from the given + * storage offset. + * + * @param offset Storage offset. + * @param b Array which receives bytes from the underlying storage + * @throws IOException Thrown in case of I/O error + */ + protected abstract void read(long offset, byte[] b) throws IOException; + + /** + * Returns the number of RRD bytes in the underlying storage. + * + * @return Number of RRD bytes in the storage. + * @throws IOException Thrown in case of I/O error. + */ + public abstract long getLength() throws IOException; + + /** + * Sets the number of bytes in the underlying RRD storage. + * This method is called only once, immediately after a new RRD storage gets created. + * + * @param length Length of the underlying RRD storage in bytes. + * @throws IOException Thrown in case of I/O error. + */ + protected abstract void setLength(long length) throws IOException; + + /** + * Closes the underlying backend. + * @throws IOException Thrown in case of I/O error. + */ + public void close() throws IOException { + } + + /** + * This method suggests the caching policy to the JRobin frontend (high-level) classes. If true + * is returned, frontent classes will cache frequently used parts of a RRD file in memory to improve + * performance. If false is returned, high level classes will never cache RRD file sections + * in memory. + * + * @return true if file caching is enabled, false otherwise. By default, the + * method returns true but it can be overriden in subclasses. + */ + protected boolean isCachingAllowed() { + return true; + } + + /** + * Reads all RRD bytes from the underlying storage + * + * @return RRD bytes + * @throws IOException Thrown in case of I/O error + */ + public final byte[] readAll() throws IOException { + final byte[] b = new byte[(int) getLength()]; + read(0, b); + return b; + } + + final void writeInt(final long offset, final int value) throws IOException { + write(offset, getIntBytes(value)); + } + + final void writeLong(final long offset, final long value) throws IOException { + write(offset, getLongBytes(value)); + } + + final void writeDouble(final long offset, final double value) throws IOException { + write(offset, getDoubleBytes(value)); + } + + final void writeDouble(final long offset, final double value, final int count) throws IOException { + final byte[] b = getDoubleBytes(value); + final byte[] image = new byte[8 * count]; + for (int i = 0, k = 0; i < count; i++) { + image[k++] = b[0]; + image[k++] = b[1]; + image[k++] = b[2]; + image[k++] = b[3]; + image[k++] = b[4]; + image[k++] = b[5]; + image[k++] = b[6]; + image[k++] = b[7]; + } + write(offset, image); + } + + final void writeDouble(final long offset, final double[] values) throws IOException { + final int count = values.length; + final byte[] image = new byte[8 * count]; + for (int i = 0, k = 0; i < count; i++) { + final byte[] b = getDoubleBytes(values[i]); + image[k++] = b[0]; + image[k++] = b[1]; + image[k++] = b[2]; + image[k++] = b[3]; + image[k++] = b[4]; + image[k++] = b[5]; + image[k++] = b[6]; + image[k++] = b[7]; + } + write(offset, image); + } + + final void writeString(final long offset, final String rawValue) throws IOException { + final String value = rawValue.trim(); + final byte[] b = new byte[RrdPrimitive.STRING_LENGTH * 2]; + for (int i = 0, k = 0; i < RrdPrimitive.STRING_LENGTH; i++) { + final char c = (i < value.length()) ? value.charAt(i) : ' '; + final byte[] cb = getCharBytes(c); + b[k++] = cb[0]; + b[k++] = cb[1]; + } + write(offset, b); + } + + final int readInt(final long offset) throws IOException { + final byte[] b = new byte[4]; + read(offset, b); + return getInt(b); + } + + final long readLong(final long offset) throws IOException { + final byte[] b = new byte[8]; + read(offset, b); + return getLong(b); + } + + final double readDouble(final long offset) throws IOException { + final byte[] b = new byte[8]; + read(offset, b); + return getDouble(b); + } + + final double[] readDouble(final long offset, final int count) throws IOException { + final int byteCount = 8 * count; + final byte[] image = new byte[byteCount]; + read(offset, image); + final double[] values = new double[count]; + for (int i = 0, k = -1; i < count; i++) { + final byte[] b = new byte[] { + image[++k], image[++k], image[++k], image[++k], + image[++k], image[++k], image[++k], image[++k] + }; + values[i] = getDouble(b); + } + return values; + } + + final String readString(final long offset) throws IOException { + final byte[] b = new byte[RrdPrimitive.STRING_LENGTH * 2]; + final char[] c = new char[RrdPrimitive.STRING_LENGTH]; + read(offset, b); + for (int i = 0, k = -1; i < RrdPrimitive.STRING_LENGTH; i++) { + final byte[] cb = new byte[] {b[++k], b[++k]}; + c[i] = getChar(cb); + } + return new String(c).trim(); + } + + // static helper methods + + private static byte[] getIntBytes(final int value) { + final byte[] b = new byte[4]; + b[0] = (byte) ((value >>> 24) & 0xFF); + b[1] = (byte) ((value >>> 16) & 0xFF); + b[2] = (byte) ((value >>> 8) & 0xFF); + b[3] = (byte) ((value) & 0xFF); + return b; + } + + private static byte[] getLongBytes(final long value) { + final byte[] b = new byte[8]; + b[0] = (byte) ((int) (value >>> 56) & 0xFF); + b[1] = (byte) ((int) (value >>> 48) & 0xFF); + b[2] = (byte) ((int) (value >>> 40) & 0xFF); + b[3] = (byte) ((int) (value >>> 32) & 0xFF); + b[4] = (byte) ((int) (value >>> 24) & 0xFF); + b[5] = (byte) ((int) (value >>> 16) & 0xFF); + b[6] = (byte) ((int) (value >>> 8) & 0xFF); + b[7] = (byte) ((int) (value) & 0xFF); + return b; + } + + private static byte[] getCharBytes(final char value) { + final byte[] b = new byte[2]; + b[0] = (byte) ((value >>> 8) & 0xFF); + b[1] = (byte) ((value) & 0xFF); + return b; + } + + private static byte[] getDoubleBytes(final double value) { + return getLongBytes(Double.doubleToLongBits(value)); + } + + private static int getInt(final byte[] b) { + assert b.length == 4: "Invalid number of bytes for integer conversion"; + return ((b[0] << 24) & 0xFF000000) + ((b[1] << 16) & 0x00FF0000) + + ((b[2] << 8) & 0x0000FF00) + (b[3] & 0x000000FF); + } + + private static long getLong(final byte[] b) { + assert b.length == 8: "Invalid number of bytes for long conversion"; + int high = getInt(new byte[] {b[0], b[1], b[2], b[3]}); + int low = getInt(new byte[] {b[4], b[5], b[6], b[7]}); + return ((long) (high) << 32) + (low & 0xFFFFFFFFL); + } + + private static char getChar(final byte[] b) { + assert b.length == 2: "Invalid number of bytes for char conversion"; + return (char) (((b[0] << 8) & 0x0000FF00) + + (b[1] & 0x000000FF)); + } + + private static double getDouble(final byte[] b) { + assert b.length == 8: "Invalid number of bytes for double conversion"; + return Double.longBitsToDouble(getLong(b)); + } + + private static void setInstanceCreated() { + s_instanceCreated = true; + } + + static boolean isInstanceCreated() { + return s_instanceCreated; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdBackendFactory.java b/apps/jrobin/java/src/org/jrobin/core/RrdBackendFactory.java new file mode 100644 index 000000000..42ebbd761 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdBackendFactory.java @@ -0,0 +1,221 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; +import java.util.HashMap; + +/** + * Base (abstract) backend factory class which holds references to all concrete + * backend factories and defines abstract methods which must be implemented in + * all concrete factory implementations. + *

+ * Factory classes are used to create concrete {@link RrdBackend} implementations. + * Each factory creates unlimited number of specific backend objects. + *

+ * JRobin supports four different backend types (backend factories) out of the box:

+ *

+ *

+ * Each backend factory is identifed by its {@link #getFactoryName() name}. Constructors + * are provided in the {@link RrdDb} class to create RrdDb objects (RRD databases) + * backed with a specific backend. + *

+ * See javadoc for {@link RrdBackend} to find out how to create your custom backends. + */ +public abstract class RrdBackendFactory { + private static final HashMap factories = new HashMap(); + private static RrdBackendFactory defaultFactory; + + static { + try { + RrdFileBackendFactory fileFactory = new RrdFileBackendFactory(); + registerFactory(fileFactory); + RrdJRobin14FileBackendFactory jrobin14Factory = new RrdJRobin14FileBackendFactory(); + registerFactory(jrobin14Factory); + RrdMemoryBackendFactory memoryFactory = new RrdMemoryBackendFactory(); + registerFactory(memoryFactory); + RrdNioBackendFactory nioFactory = new RrdNioBackendFactory(); + registerFactory(nioFactory); + RrdSafeFileBackendFactory safeFactory = new RrdSafeFileBackendFactory(); + registerFactory(safeFactory); + RrdNioByteBufferBackendFactory nioByteBufferFactory = new RrdNioByteBufferBackendFactory(); + registerFactory(nioByteBufferFactory); + selectDefaultFactory(); + } + catch (RrdException e) { + throw new RuntimeException("FATAL: Cannot register RRD backend factories: " + e); + } + } + + private static void selectDefaultFactory() throws RrdException { + setDefaultFactory("FILE"); + } + + /** + * Returns backend factory for the given backend factory name. + * + * @param name Backend factory name. Initially supported names are:

+ *

+ * @return Backend factory for the given factory name + * @throws RrdException Thrown if no factory with the given name + * is available. + */ + public static synchronized RrdBackendFactory getFactory(final String name) throws RrdException { + final RrdBackendFactory factory = factories.get(name); + if (factory != null) { + return factory; + } + throw new RrdException("No backend factory found with the name specified [" + name + "]"); + } + + /** + * Registers new (custom) backend factory within the JRobin framework. + * + * @param factory Factory to be registered + * @throws RrdException Thrown if the name of the specified factory is already + * used. + */ + public static synchronized void registerFactory(final RrdBackendFactory factory) throws RrdException { + final String name = factory.getFactoryName(); + if (!factories.containsKey(name)) { + factories.put(name, factory); + } + else { + throw new RrdException("Backend factory of this name2 (" + name + ") already exists and cannot be registered"); + } + } + + /** + * Registers new (custom) backend factory within the JRobin framework and sets this + * factory as the default. + * + * @param factory Factory to be registered and set as default + * @throws RrdException Thrown if the name of the specified factory is already + * used. + */ + public static synchronized void registerAndSetAsDefaultFactory(final RrdBackendFactory factory) throws RrdException { + registerFactory(factory); + setDefaultFactory(factory.getFactoryName()); + } + + /** + * Returns the defaul backend factory. This factory is used to construct + * {@link RrdDb} objects if no factory is specified in the RrdDb constructor. + * + * @return Default backend factory. + */ + public static RrdBackendFactory getDefaultFactory() { + return defaultFactory; + } + + /** + * Replaces the default backend factory with a new one. This method must be called before + * the first RRD gets created.

+ * + * @param factoryName Name of the default factory. Out of the box, JRobin supports four + * different RRD backends: "FILE" (java.io.* based), "SAFE" (java.io.* based - use this + * backend if RRD files may be accessed from several JVMs at the same time), + * "NIO" (java.nio.* based) and "MEMORY" (byte[] based). + * @throws RrdException Thrown if invalid factory name is supplied or not called before + * the first RRD is created. + */ + public static void setDefaultFactory(final String factoryName) throws RrdException { + // We will allow this only if no RRDs are created + if (!RrdBackend.isInstanceCreated()) { + defaultFactory = getFactory(factoryName); + } + else { + throw new RrdException("Could not change the default backend factory. This method must be called before the first RRD gets created"); + } + } + + /** + * Whether or not the RRD backend has created an instance yet. + * + * @return True if the backend instance is created, false if not. + */ + public static boolean isInstanceCreated() { + return RrdBackend.isInstanceCreated(); + } + + /** + * Creates RrdBackend object for the given storage path. + * + * @param path Storage path + * @param readOnly True, if the storage should be accessed in read/only mode. + * False otherwise. + * @return Backend object which handles all I/O operations for the given storage path + * @throws IOException Thrown in case of I/O error. + */ + protected abstract RrdBackend open(String path, boolean readOnly) throws IOException; + + /** + * Method to determine if a storage with the given path already exists. + * + * @param path Storage path + * @return True, if such storage exists, false otherwise. + * @throws IOException Thrown in case of I/O error. + */ + protected abstract boolean exists(String path) throws IOException; + + /** + * Returns the name (primary ID) for the factory. + * + * @return Name of the factory. + */ + public abstract String getFactoryName(); + + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + "[name=" + getFactoryName() + "]"; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdDb.java b/apps/jrobin/java/src/org/jrobin/core/RrdDb.java new file mode 100644 index 000000000..64544dc77 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdDb.java @@ -0,0 +1,1166 @@ +/******************************************************************************* + * 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.core; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; + +/** + * Main class used to create and manipulate round robin databases (RRDs). Use this class to perform + * update and fetch operations on exisiting RRDs, to create new RRD from + * the definition (object of class {@link org.jrobin.core.RrdDef RrdDef}) or + * from XML file (dumped content of RRDTool's or JRobin's RRD file). + *

+ * Each RRD is backed with some kind of storage. For example, RRDTool supports only one kind of + * storage (disk file). On the contrary, JRobin gives you freedom to use other storage (backend) types + * even to create your own backend types for some special purposes. JRobin by default stores + * RRD data in files (as RRDTool), but you might choose to store RRD data in memory (this is + * supported in JRobin), to use java.nio.* instead of java.io.* package for file manipulation + * (also supported) or to store whole RRDs in the SQL database + * (you'll have to extend some classes to do this). + *

+ * Note that JRobin uses binary format different from RRDTool's format. You cannot + * use this class to manipulate RRD files created with RRDTool. However, if you perform + * the same sequence of create, update and fetch operations, you will get exactly the same + * results from JRobin and RRDTool. + *

+ * You will not be able to use JRobin API if you are not familiar with + * basic RRDTool concepts. Good place to start is the + * official RRD tutorial + * and relevant RRDTool man pages: rrdcreate, + * rrdupdate, + * rrdfetch and + * rrdgraph. + * For RRDTool's advanced graphing capabilities (RPN extensions), also supported in JRobin, + * there is an excellent + * CDEF tutorial. + *

+ * + * @see RrdBackend + * @see RrdBackendFactory + */ +public class RrdDb implements RrdUpdater { + /** + * prefix to identify external XML file source used in various RrdDb constructors + */ + public static final String PREFIX_XML = "xml:/"; + /** + * prefix to identify external RRDTool file source used in various RrdDb constructors + */ + public static final String PREFIX_RRDTool = "rrdtool:/"; + + // static final String RRDTOOL = "rrdtool"; + static final int XML_INITIAL_BUFFER_CAPACITY = 100000; // bytes + + private RrdBackend backend; + private RrdAllocator allocator = new RrdAllocator(); + + private Header header; + private Datasource[] datasources; + private Archive[] archives; + + private boolean closed = false; + + /** + * Constructor used to create new RRD object from the definition. This RRD object will be backed + * with a storage (backend) of the default type. Initially, storage type defaults to "NIO" + * (RRD bytes will be put in a file on the disk). Default storage type can be changed with a static + * {@link RrdBackendFactory#setDefaultFactory(String)} method call. + *

+ * New RRD file structure is specified with an object of class + * {@link org.jrobin.core.RrdDef RrdDef}. The underlying RRD storage is created as soon + * as the constructor returns. + *

+ * Typical scenario: + *

+ *

+	 * // create new RRD definition
+	 * RrdDef def = new RrdDef("test.rrd", 300);
+	 * def.addDatasource("input", DsTypes.DT_COUNTER, 600, 0, Double.NaN);
+	 * def.addDatasource("output", DsTypes.DT_COUNTER, 600, 0, Double.NaN);
+	 * def.addArchive(ConsolFuns.CF_AVERAGE, 0.5, 1, 600);
+	 * def.addArchive(ConsolFuns.CF_AVERAGE, 0.5, 6, 700);
+	 * def.addArchive(ConsolFuns.CF_AVERAGE, 0.5, 24, 797);
+	 * def.addArchive(ConsolFuns.CF_AVERAGE, 0.5, 288, 775);
+	 * def.addArchive(ConsolFuns.CF_MAX, 0.5, 1, 600);
+	 * def.addArchive(ConsolFuns.CF_MAX, 0.5, 6, 700);
+	 * def.addArchive(ConsolFuns.CF_MAX, 0.5, 24, 797);
+	 * def.addArchive(ConsolFuns.CF_MAX, 0.5, 288, 775);
+	 *
+	 * // RRD definition is now completed, create the database!
+	 * RrdDb rrd = new RrdDb(def);
+	 * // new RRD file has been created on your disk
+	 * 
+ * + * @param rrdDef Object describing the structure of the new RRD file. + * @throws IOException Thrown in case of I/O error. + * @throws RrdException Thrown if invalid RrdDef object is supplied. + */ + public RrdDb(RrdDef rrdDef) throws RrdException, IOException { + this(rrdDef, RrdFileBackendFactory.getDefaultFactory()); + } + + /** + * Constructor used to create new RRD object from the definition object but with a storage + * (backend) different from default. + *

+ * JRobin uses factories to create RRD backend objecs. There are three different + * backend factories supplied with JRobin, and each factory has its unique name: + *

+ *

+ *

+ * For example, to create RRD in memory, use the following code: + *

+	 * RrdBackendFactory factory = RrdBackendFactory.getFactory("MEMORY");
+	 * RrdDb rrdDb = new RrdDb(rrdDef, factory);
+	 * rrdDb.close();
+	 * 
+ *

+ * New RRD file structure is specified with an object of class + * {@link org.jrobin.core.RrdDef RrdDef}. The underlying RRD storage is created as soon + * as the constructor returns. + * + * @param rrdDef RRD definition object + * @param factory The factory which will be used to create storage for this RRD + * @throws RrdException Thrown if invalid factory or definition is supplied + * @throws IOException Thrown in case of I/O error + * @see RrdBackendFactory + */ + public RrdDb(RrdDef rrdDef, RrdBackendFactory factory) throws RrdException, IOException { + rrdDef.validate(); + String path = rrdDef.getPath(); + backend = factory.open(path, false); + try { + backend.setLength(rrdDef.getEstimatedSize()); + // create header + header = new Header(this, rrdDef); + // create datasources + DsDef[] dsDefs = rrdDef.getDsDefs(); + datasources = new Datasource[dsDefs.length]; + for (int i = 0; i < dsDefs.length; i++) { + datasources[i] = new Datasource(this, dsDefs[i]); + } + // create archives + ArcDef[] arcDefs = rrdDef.getArcDefs(); + archives = new Archive[arcDefs.length]; + for (int i = 0; i < arcDefs.length; i++) { + archives[i] = new Archive(this, arcDefs[i]); + } + } + catch (IOException e) { + backend.close(); + throw e; + } + } + + /** + * Constructor used to open already existing RRD. This RRD object will be backed + * with a storage (backend) of the default type (file on the disk). Constructor + * obtains read or read/write access to this RRD. + * + * @param path Path to existing RRD. + * @param readOnly Should be set to false if you want to update + * the underlying RRD. If you want just to fetch data from the RRD file + * (read-only access), specify true. If you try to update RRD file + * open in read-only mode (m_readOnly set to true), + * IOException will be thrown. + * @throws IOException Thrown in case of I/O error. + * @throws RrdException Thrown in case of JRobin specific error. + */ + public RrdDb(String path, boolean readOnly) throws IOException, RrdException { + this(path, readOnly, RrdBackendFactory.getDefaultFactory()); + } + + /** + * Constructor used to open already existing RRD backed + * with a storage (backend) different from default. Constructor + * obtains read or read/write access to this RRD. + * + * @param path Path to existing RRD. + * @param readOnly Should be set to false if you want to update + * the underlying RRD. If you want just to fetch data from the RRD file + * (read-only access), specify true. If you try to update RRD file + * open in read-only mode (m_readOnly set to true), + * IOException will be thrown. + * @param factory Backend factory which will be used for this RRD. + * @throws FileNotFoundException Thrown if the requested file does not exist. + * @throws IOException Thrown in case of general I/O error (bad RRD file, for example). + * @throws RrdException Thrown in case of JRobin specific error. + * @see RrdBackendFactory + */ + public RrdDb(String path, boolean readOnly, RrdBackendFactory factory) + throws FileNotFoundException, IOException, RrdException { + // opens existing RRD file - throw exception if the file does not exist... + if (!factory.exists(path)) { + throw new FileNotFoundException("Could not open " + path + " [non existent]"); + } + backend = factory.open(path, readOnly); + try { + // restore header + header = new Header(this, (RrdDef) null); + header.validateHeader(); + // restore datasources + int dsCount = header.getDsCount(); + datasources = new Datasource[dsCount]; + for (int i = 0; i < dsCount; i++) { + datasources[i] = new Datasource(this, null); + } + // restore archives + int arcCount = header.getArcCount(); + archives = new Archive[arcCount]; + for (int i = 0; i < arcCount; i++) { + archives[i] = new Archive(this, null); + } + } + catch (RrdException e) { + backend.close(); + throw e; + } + catch (IOException e) { + backend.close(); + throw e; + } + } + + /** + *

Constructor used to open already existing RRD in R/W mode, with a default storage + * (backend) type (file on the disk). + * + * @param path Path to existing RRD. + * @throws IOException Thrown in case of I/O error. + * @throws RrdException Thrown in case of JRobin specific error. + */ + public RrdDb(String path) throws IOException, RrdException { + this(path, false); + } + + /** + *

Constructor used to open already existing RRD in R/W mode with a storage (backend) type + * different from default.

+ * + * @param path Path to existing RRD. + * @param factory Backend factory used to create this RRD. + * @throws IOException Thrown in case of I/O error. + * @throws RrdException Thrown in case of JRobin specific error. + * @see RrdBackendFactory + */ + public RrdDb(String path, RrdBackendFactory factory) throws IOException, RrdException { + this(path, false, factory); + } + + /** + * Constructor used to create RRD files from external file sources. + * Supported external file sources are: + *

+ *

+ *

+ * Newly created RRD will be backed with a default storage (backend) type + * (file on the disk). + *

+ * JRobin and RRDTool use the same format for XML dump and this constructor should be used to + * (re)create JRobin RRD files from XML dumps. First, dump the content of a RRDTool + * RRD file (use command line): + *

+ *

+	 * rrdtool dump original.rrd > original.xml
+	 * 
+ *

+ * Than, use the file original.xml to create JRobin RRD file named + * copy.rrd: + *

+ *

+	 * RrdDb rrd = new RrdDb("copy.rrd", "original.xml");
+	 * 
+ *

+ * or: + *

+ *

+	 * RrdDb rrd = new RrdDb("copy.rrd", "xml:/original.xml");
+	 * 
+ *

+ * See documentation for {@link #dumpXml(java.lang.String) dumpXml()} method + * to see how to convert JRobin files to RRDTool's format. + *

+ * To read RRDTool files directly, specify rrdtool:/ prefix in the + * externalPath argument. For example, to create JRobin compatible file named + * copy.rrd from the file original.rrd created with RRDTool, use + * the following code: + *

+ *

+	 * RrdDb rrd = new RrdDb("copy.rrd", "rrdtool:/original.rrd");
+	 * 
+ *

+ * Note that the prefix xml:/ or rrdtool:/ is necessary to distinguish + * between XML and RRDTool's binary sources. If no prefix is supplied, XML format is assumed. + * + * @param rrdPath Path to a RRD file which will be created + * @param externalPath Path to an external file which should be imported, with an optional + * xml:/ or rrdtool:/ prefix. + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public RrdDb(String rrdPath, String externalPath) throws IOException, RrdException, RrdException { + this(rrdPath, externalPath, RrdBackendFactory.getDefaultFactory()); + } + + /** + * Constructor used to create RRD files from external file sources with a backend type + * different from default. Supported external file sources are: + *

+ *

+ *

+ * JRobin and RRDTool use the same format for XML dump and this constructor should be used to + * (re)create JRobin RRD files from XML dumps. First, dump the content of a RRDTool + * RRD file (use command line): + *

+ *

+	 * rrdtool dump original.rrd > original.xml
+	 * 
+ *

+ * Than, use the file original.xml to create JRobin RRD file named + * copy.rrd: + *

+ *

+	 * RrdDb rrd = new RrdDb("copy.rrd", "original.xml");
+	 * 
+ *

+ * or: + *

+ *

+	 * RrdDb rrd = new RrdDb("copy.rrd", "xml:/original.xml");
+	 * 
+ *

+ * See documentation for {@link #dumpXml(java.lang.String) dumpXml()} method + * to see how to convert JRobin files to RRDTool's format. + *

+ * To read RRDTool files directly, specify rrdtool:/ prefix in the + * externalPath argument. For example, to create JRobin compatible file named + * copy.rrd from the file original.rrd created with RRDTool, use + * the following code: + *

+ *

+	 * RrdDb rrd = new RrdDb("copy.rrd", "rrdtool:/original.rrd");
+	 * 
+ *

+ * Note that the prefix xml:/ or rrdtool:/ is necessary to distinguish + * between XML and RRDTool's binary sources. If no prefix is supplied, XML format is assumed. + * + * @param rrdPath Path to RRD which will be created + * @param externalPath Path to an external file which should be imported, with an optional + * xml:/ or rrdtool:/ prefix. + * @param factory Backend factory which will be used to create storage (backend) for this RRD. + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + * @see RrdBackendFactory + */ + public RrdDb(String rrdPath, String externalPath, RrdBackendFactory factory) + throws IOException, RrdException { + DataImporter reader; + if (externalPath.startsWith(PREFIX_RRDTool)) { + String rrdToolPath = externalPath.substring(PREFIX_RRDTool.length()); + reader = new RrdToolReader(rrdToolPath); + } + else if (externalPath.startsWith(PREFIX_XML)) { + externalPath = externalPath.substring(PREFIX_XML.length()); + reader = new XmlReader(externalPath); + } + else { + reader = new XmlReader(externalPath); + } + backend = factory.open(rrdPath, false); + try { + backend.setLength(reader.getEstimatedSize()); + // create header + header = new Header(this, reader); + // create datasources + datasources = new Datasource[reader.getDsCount()]; + for (int i = 0; i < datasources.length; i++) { + datasources[i] = new Datasource(this, reader, i); + } + // create archives + archives = new Archive[reader.getArcCount()]; + for (int i = 0; i < archives.length; i++) { + archives[i] = new Archive(this, reader, i); + } + reader.release(); + } + catch (RrdException e) { + backend.close(); + throw e; + } + catch (IOException e) { + backend.close(); + throw e; + } + } + + public RrdDb(final File file) throws IOException, RrdException { + this(file.getPath()); + } + + public RrdDb(final File file, final boolean readOnly) throws IOException, RrdException { + this(file.getPath(), readOnly); + } + + /** + * Closes RRD. No further operations are allowed on this RrdDb object. + * + * @throws IOException Thrown in case of I/O related error. + */ + public synchronized void close() throws IOException { + if (!closed) { + closed = true; + backend.close(); + } + } + + /** + * Returns true if the RRD is closed. + * + * @return true if closed, false otherwise + */ + public boolean isClosed() { + return closed; + } + + /** + * Returns RRD header. + * + * @return Header object + */ + public Header getHeader() { + return header; + } + + /** + * Returns Datasource object for the given datasource index. + * + * @param dsIndex Datasource index (zero based) + * @return Datasource object + */ + public Datasource getDatasource(int dsIndex) { + return datasources[dsIndex]; + } + + /** + * Returns Archive object for the given archive index. + * + * @param arcIndex Archive index (zero based) + * @return Archive object + */ + public Archive getArchive(int arcIndex) { + return archives[arcIndex]; + } + + /** + * Returns an array of datasource names defined in RRD. + * + * @return Array of datasource names. + * @throws IOException Thrown in case of I/O error. + */ + public String[] getDsNames() throws IOException { + int n = datasources.length; + String[] dsNames = new String[n]; + for (int i = 0; i < n; i++) { + dsNames[i] = datasources[i].getDsName(); + } + return dsNames; + } + + /** + * Creates new sample with the given timestamp and all datasource values set to + * 'unknown'. Use returned Sample object to specify + * datasource values for the given timestamp. See documentation for + * {@link org.jrobin.core.Sample Sample} for an explanation how to do this. + *

+ * Once populated with data source values, call Sample's + * {@link org.jrobin.core.Sample#update() update()} method to actually + * store sample in the RRD associated with it. + * + * @param time Sample timestamp rounded to the nearest second (without milliseconds). + * @return Fresh sample with the given timestamp and all data source values set to 'unknown'. + * @throws IOException Thrown in case of I/O error. + */ + public Sample createSample(long time) throws IOException { + return new Sample(this, time); + } + + /** + * Creates new sample with the current timestamp and all data source values set to + * 'unknown'. Use returned Sample object to specify + * datasource values for the current timestamp. See documentation for + * {@link org.jrobin.core.Sample Sample} for an explanation how to do this. + *

+ * Once populated with data source values, call Sample's + * {@link org.jrobin.core.Sample#update() update()} method to actually + * store sample in the RRD associated with it. + * + * @return Fresh sample with the current timestamp and all + * data source values set to 'unknown'. + * @throws IOException Thrown in case of I/O error. + */ + public Sample createSample() throws IOException { + return createSample(Util.getTime()); + } + + /** + *

Prepares fetch request to be executed on this RRD. Use returned + * FetchRequest object and its {@link org.jrobin.core.FetchRequest#fetchData() fetchData()} + * method to actually fetch data from the RRD file.

+ * + * @param consolFun Consolidation function to be used in fetch request. Allowed values are + * "AVERAGE", "MIN", "MAX" and "LAST" (these constants are conveniently defined in the + * {@link ConsolFuns} class). + * @param fetchStart Starting timestamp for fetch request. + * @param fetchEnd Ending timestamp for fetch request. + * @param resolution Fetch resolution (see RRDTool's + * rrdfetch man page for an + * explanation of this parameter. + * @return Request object that should be used to actually fetch data from RRD. + * @throws RrdException In case of JRobin related error (invalid consolidation function or + * invalid time span). + */ + public FetchRequest createFetchRequest(String consolFun, long fetchStart, long fetchEnd, + long resolution) throws RrdException { + return new FetchRequest(this, consolFun, fetchStart, fetchEnd, resolution); + } + + /** + *

Prepares fetch request to be executed on this RRD. Use returned + * FetchRequest object and its {@link org.jrobin.core.FetchRequest#fetchData() fetchData()} + * method to actually fetch data from this RRD. Data will be fetched with the smallest + * possible resolution (see RRDTool's + * rrdfetch man page + * for the explanation of the resolution parameter).

+ * + * @param consolFun Consolidation function to be used in fetch request. Allowed values are + * "AVERAGE", "MIN", "MAX" and "LAST" (these constants are conveniently defined in the + * {@link ConsolFuns} class). + * @param fetchStart Starting timestamp for fetch request. + * @param fetchEnd Ending timestamp for fetch request. + * @return Request object that should be used to actually fetch data from RRD. + * @throws RrdException In case of JRobin related error (invalid consolidation function or + * invalid time span). + */ + public FetchRequest createFetchRequest(String consolFun, long fetchStart, long fetchEnd) + throws RrdException { + return createFetchRequest(consolFun, fetchStart, fetchEnd, 1); + } + + synchronized void store(Sample sample) throws IOException, RrdException { + if (closed) { + throw new RrdException("RRD already closed, cannot store this sample"); + } + long newTime = sample.getTime(); + long lastTime = header.getLastUpdateTime(); + if (lastTime >= newTime) { + throw new RrdException("Bad sample timestamp " + newTime + + ". Last update time was " + lastTime + ", at least one second step is required"); + } + double[] newValues = sample.getValues(); + for (int i = 0; i < datasources.length; i++) { + double newValue = newValues[i]; + datasources[i].process(newTime, newValue); + } + header.setLastUpdateTime(newTime); + } + + synchronized FetchData fetchData(FetchRequest request) throws IOException, RrdException { + if (closed) { + throw new RrdException("RRD already closed, cannot fetch data"); + } + Archive archive = findMatchingArchive(request); + return archive.fetchData(request); + } + + public Archive findMatchingArchive(FetchRequest request) throws RrdException, IOException { + String consolFun = request.getConsolFun(); + long fetchStart = request.getFetchStart(); + long fetchEnd = request.getFetchEnd(); + long resolution = request.getResolution(); + Archive bestFullMatch = null, bestPartialMatch = null; + long bestStepDiff = 0, bestMatch = 0; + for (Archive archive : archives) { + if (archive.getConsolFun().equals(consolFun)) { + long arcStep = archive.getArcStep(); + long arcStart = archive.getStartTime() - arcStep; + long arcEnd = archive.getEndTime(); + long fullMatch = fetchEnd - fetchStart; + if (arcEnd >= fetchEnd && arcStart <= fetchStart) { + long tmpStepDiff = Math.abs(archive.getArcStep() - resolution); + + if (tmpStepDiff < bestStepDiff || bestFullMatch == null) { + bestStepDiff = tmpStepDiff; + bestFullMatch = archive; + } + } + else { + long tmpMatch = fullMatch; + + if (arcStart > fetchStart) { + tmpMatch -= (arcStart - fetchStart); + } + if (arcEnd < fetchEnd) { + tmpMatch -= (fetchEnd - arcEnd); + } + if (bestPartialMatch == null || bestMatch < tmpMatch) { + bestPartialMatch = archive; + bestMatch = tmpMatch; + } + } + } + } + if (bestFullMatch != null) { + return bestFullMatch; + } + else if (bestPartialMatch != null) { + return bestPartialMatch; + } + else { + throw new RrdException("RRD file does not contain RRA:" + consolFun + " archive"); + } + } + + /** + * Finds the archive that best matches to the start time (time period being start-time until now) + * and requested resolution. + * + * @param consolFun Consolidation function of the datasource. + * @param startTime Start time of the time period in seconds. + * @param resolution Requested fetch resolution. + * @return Reference to the best matching archive. + * @throws IOException Thrown in case of I/O related error. + */ + public Archive findStartMatchArchive(String consolFun, long startTime, long resolution) throws IOException { + long arcStep, diff; + int fallBackIndex = 0; + int arcIndex = -1; + long minDiff = Long.MAX_VALUE; + long fallBackDiff = Long.MAX_VALUE; + + for (int i = 0; i < archives.length; i++) { + if (archives[i].getConsolFun().equals(consolFun)) { + arcStep = archives[i].getArcStep(); + diff = Math.abs(resolution - arcStep); + + // Now compare start time, see if this archive encompasses the requested interval + if (startTime >= archives[i].getStartTime()) { + if (diff == 0) // Best possible match either way + { + return archives[i]; + } + else if (diff < minDiff) { + minDiff = diff; + arcIndex = i; + } + } + else if (diff < fallBackDiff) { + fallBackDiff = diff; + fallBackIndex = i; + } + } + } + + return (arcIndex >= 0 ? archives[arcIndex] : archives[fallBackIndex]); + } + + /** + *

Returns string representing complete internal RRD state. The returned + * string can be printed to stdout and/or used for debugging purposes.

+ * + * @return String representing internal RRD state. + * @throws IOException Thrown in case of I/O related error. + */ + public synchronized String dump() throws IOException { + StringBuffer buffer = new StringBuffer(); + buffer.append(header.dump()); + for (Datasource datasource : datasources) { + buffer.append(datasource.dump()); + } + for (Archive archive : archives) { + buffer.append(archive.dump()); + } + return buffer.toString(); + } + + void archive(Datasource datasource, double value, long numUpdates) + throws IOException, RrdException { + int dsIndex = getDsIndex(datasource.getDsName()); + for (Archive archive : archives) { + archive.archive(dsIndex, value, numUpdates); + } + } + + /** + *

Returns internal index number for the given datasource name. This index is heavily + * used by jrobin.graph package and has no value outside of it.

+ * + * @param dsName Data source name. + * @return Internal index of the given data source name in this RRD. + * @throws RrdException Thrown in case of JRobin related error (invalid data source name, + * for example) + * @throws IOException Thrown in case of I/O error. + */ + public int getDsIndex(String dsName) throws RrdException, IOException { + for (int i = 0; i < datasources.length; i++) { + if (datasources[i].getDsName().equals(dsName)) { + return i; + } + } + throw new RrdException("Unknown datasource name: " + dsName); + } + + /** + * Checks presence of a specific datasource. + * + * @param dsName Datasource name to check + * @return true if datasource is present in this RRD, false otherwise + * @throws IOException Thrown in case of I/O error. + */ + public boolean containsDs(String dsName) throws IOException { + for (Datasource datasource : datasources) { + if (datasource.getDsName().equals(dsName)) { + return true; + } + } + return false; + } + + Datasource[] getDatasources() { + return datasources; + } + + Archive[] getArchives() { + return archives; + } + + /** + * Writes the RRD content to OutputStream using XML format. This format + * is fully compatible with RRDTool's XML dump format and can be used for conversion + * purposes or debugging. + * + * @param destination Output stream to receive XML data + * @throws IOException Thrown in case of I/O related error + */ + public synchronized void dumpXml(OutputStream destination) throws IOException { + XmlWriter writer = new XmlWriter(destination); + writer.startTag("rrd"); + // dump header + header.appendXml(writer); + // dump datasources + for (Datasource datasource : datasources) { + datasource.appendXml(writer); + } + // dump archives + for (Archive archive : archives) { + archive.appendXml(writer); + } + writer.closeTag(); + writer.flush(); + } + + /** + * This method is just an alias for {@link #dumpXml(OutputStream) dumpXml} method. + * + * @param destination Output stream to receive XML data + * @throws IOException Thrown in case of I/O related error + */ + public synchronized void exportXml(OutputStream destination) throws IOException { + dumpXml(destination); + } + + /** + * Returns string representing internal RRD state in XML format. This format + * is fully compatible with RRDTool's XML dump format and can be used for conversion + * purposes or debugging. + * + * @return Internal RRD state in XML format. + * @throws IOException Thrown in case of I/O related error + * @throws RrdException Thrown in case of JRobin specific error + */ + public synchronized String getXml() throws IOException, RrdException { + ByteArrayOutputStream destination = new ByteArrayOutputStream(XML_INITIAL_BUFFER_CAPACITY); + dumpXml(destination); + return destination.toString(); + } + + /** + * This method is just an alias for {@link #getXml() getXml} method. + * + * @return Internal RRD state in XML format. + * @throws IOException Thrown in case of I/O related error + * @throws RrdException Thrown in case of JRobin specific error + */ + public synchronized String exportXml() throws IOException, RrdException { + return getXml(); + } + + /** + * Dumps internal RRD state to XML file. + * Use this XML file to convert your JRobin RRD to RRDTool format. + *

+ * Suppose that you have a JRobin RRD file original.rrd and you want + * to convert it to RRDTool format. First, execute the following java code: + *

+ * RrdDb rrd = new RrdDb("original.rrd"); + * rrd.dumpXml("original.xml"); + *

+ * Use original.xml file to create the corresponding RRDTool file + * (from your command line): + *

+ * rrdtool restore copy.rrd original.xml + * + * @param filename Path to XML file which will be created. + * @throws IOException Thrown in case of I/O related error. + * @throws RrdException Thrown in case of JRobin related error. + */ + public synchronized void dumpXml(String filename) throws IOException, RrdException { + OutputStream outputStream = null; + try { + outputStream = new FileOutputStream(filename, false); + dumpXml(outputStream); + } + finally { + if (outputStream != null) { + outputStream.close(); + } + } + } + + /** + * This method is just an alias for {@link #dumpXml(String) dumpXml(String)} method. + * + * @param filename Path to XML file which will be created. + * @throws IOException Thrown in case of I/O related error + * @throws RrdException Thrown in case of JRobin specific error + */ + public synchronized void exportXml(String filename) throws IOException, RrdException { + dumpXml(filename); + } + + /** + * Returns time of last update operation as timestamp (in seconds). + * + * @return Last update time (in seconds). + * @throws IOException Thrown in case of I/O related error + */ + public synchronized long getLastUpdateTime() throws IOException { + return header.getLastUpdateTime(); + } + + /** + * Returns RRD definition object which can be used to create new RRD + * with the same creation parameters but with no data in it. + *

+ * Example: + *

+ *

+	 * RrdDb rrd1 = new RrdDb("original.rrd");
+	 * RrdDef def = rrd1.getRrdDef();
+	 * // fix path
+	 * def.setPath("empty_copy.rrd");
+	 * // create new RRD file
+	 * RrdDb rrd2 = new RrdDb(def);
+	 * 
+ * + * @return RRD definition. + * @throws IOException Thrown in case of I/O related error. + * @throws RrdException Thrown in case of JRobin specific error. + */ + public synchronized RrdDef getRrdDef() throws RrdException, IOException { + // set header + long startTime = header.getLastUpdateTime(); + long step = header.getStep(); + String path = backend.getPath(); + RrdDef rrdDef = new RrdDef(path, startTime, step); + // add datasources + for (Datasource datasource : datasources) { + DsDef dsDef = new DsDef(datasource.getDsName(), + datasource.getDsType(), datasource.getHeartbeat(), + datasource.getMinValue(), datasource.getMaxValue()); + rrdDef.addDatasource(dsDef); + } + // add archives + for (Archive archive : archives) { + ArcDef arcDef = new ArcDef(archive.getConsolFun(), + archive.getXff(), archive.getSteps(), archive.getRows()); + rrdDef.addArchive(arcDef); + } + return rrdDef; + } + + protected void finalize() throws Throwable { + super.finalize(); + close(); + } + + /** + * Copies object's internal state to another RrdDb object. + * + * @param other New RrdDb object to copy state to + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if supplied argument is not a compatible RrdDb object + */ + public synchronized void copyStateTo(RrdUpdater other) throws IOException, RrdException { + if (!(other instanceof RrdDb)) { + throw new RrdException("Cannot copy RrdDb object to " + other.getClass().getName()); + } + RrdDb otherRrd = (RrdDb) other; + header.copyStateTo(otherRrd.header); + for (int i = 0; i < datasources.length; i++) { + int j = Util.getMatchingDatasourceIndex(this, i, otherRrd); + if (j >= 0) { + datasources[i].copyStateTo(otherRrd.datasources[j]); + } + } + for (int i = 0; i < archives.length; i++) { + int j = Util.getMatchingArchiveIndex(this, i, otherRrd); + if (j >= 0) { + archives[i].copyStateTo(otherRrd.archives[j]); + } + } + } + + /** + * Returns Datasource object corresponding to the given datasource name. + * + * @param dsName Datasource name + * @return Datasource object corresponding to the give datasource name or null + * if not found. + * @throws IOException Thrown in case of I/O error + */ + public Datasource getDatasource(String dsName) throws IOException { + try { + return getDatasource(getDsIndex(dsName)); + } + catch (RrdException e) { + return null; + } + } + + /** + * Returns index of Archive object with the given consolidation function and the number + * of steps. Exception is thrown if such archive could not be found. + * + * @param consolFun Consolidation function + * @param steps Number of archive steps + * @return Requested Archive object + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if no such archive could be found + */ + public int getArcIndex(String consolFun, int steps) throws RrdException, IOException { + for (int i = 0; i < archives.length; i++) { + if (archives[i].getConsolFun().equals(consolFun) && + archives[i].getSteps() == steps) { + return i; + } + } + throw new RrdException("Could not find archive " + consolFun + "/" + steps); + } + + /** + * Returns Archive object with the given consolidation function and the number + * of steps. + * + * @param consolFun Consolidation function + * @param steps Number of archive steps + * @return Requested Archive object or null if no such archive could be found + * @throws IOException Thrown in case of I/O error + */ + public Archive getArchive(String consolFun, int steps) throws IOException { + try { + return getArchive(getArcIndex(consolFun, steps)); + } + catch (RrdException e) { + return null; + } + } + + /** + * Returns canonical path to the underlying RRD file. Note that this method makes sense just for + * ordinary RRD files created on the disk - an exception will be thrown for RRD objects created in + * memory or with custom backends. + * + * @return Canonical path to RRD file; + * @throws IOException Thrown in case of I/O error or if the underlying backend is + * not derived from RrdFileBackend. + */ + public String getCanonicalPath() throws IOException { + if (backend instanceof RrdFileBackend) { + return ((RrdFileBackend) backend).getCanonicalPath(); + } + else { + throw new IOException("The underlying backend has no canonical path"); + } + } + + /** + * Returns path to this RRD. + * + * @return Path to this RRD. + */ + public String getPath() { + return backend.getPath(); + } + + /** + * Returns backend object for this RRD which performs actual I/O operations. + * + * @return RRD backend for this RRD. + */ + public RrdBackend getRrdBackend() { + return backend; + } + + /** + * Required to implement RrdUpdater interface. You should never call this method directly. + * + * @return Allocator object + */ + public RrdAllocator getRrdAllocator() { + return allocator; + } + + /** + * Returns an array of bytes representing the whole RRD. + * + * @return All RRD bytes + * @throws IOException Thrown in case of I/O related error. + */ + public synchronized byte[] getBytes() throws IOException { + return backend.readAll(); + } + + /** + * Sets default backend factory to be used. This method is just an alias for + * {@link RrdBackendFactory#setDefaultFactory(java.lang.String)}.

+ * + * @param factoryName Name of the backend factory to be set as default. + * @throws RrdException Thrown if invalid factory name is supplied, or not called + * before the first backend object (before the first RrdDb object) is created. + */ + public static void setDefaultFactory(String factoryName) throws RrdException { + RrdBackendFactory.setDefaultFactory(factoryName); + } + + /** + * Returns an array of last datasource values. The first value in the array corresponds + * to the first datasource defined in the RrdDb and so on. + * + * @return Array of last datasource values + * @throws IOException Thrown in case of I/O error + */ + public synchronized double[] getLastDatasourceValues() throws IOException { + double[] values = new double[datasources.length]; + for (int i = 0; i < values.length; i++) { + values[i] = datasources[i].getLastValue(); + } + return values; + } + + /** + * Returns the last stored value for the given datasource. + * + * @param dsName Datasource name + * @return Last stored value for the given datasource + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown if no datasource in this RrdDb matches the given datasource name + */ + public synchronized double getLastDatasourceValue(String dsName) throws IOException, RrdException { + int dsIndex = getDsIndex(dsName); + return datasources[dsIndex].getLastValue(); + } + + /** + * Returns the number of datasources defined in the file + * + * @return The number of datasources defined in the file + */ + public int getDsCount() { + return datasources.length; + } + + /** + * Returns the number of RRA arcihves defined in the file + * + * @return The number of RRA arcihves defined in the file + */ + public int getArcCount() { + return archives.length; + } + + /** + * Returns the last time when some of the archives in this RRD was updated. This time is not the + * same as the {@link #getLastUpdateTime()} since RRD file can be updated without updating any of + * the archives. + * + * @return last time when some of the archives in this RRD was updated + * @throws IOException Thrown in case of I/O error + */ + public long getLastArchiveUpdateTime() throws IOException { + long last = 0; + for (Archive archive : archives) { + last = Math.max(last, archive.getEndTime()); + } + return last; + } + + public synchronized String getInfo() throws IOException { + return header.getInfo(); + } + + public synchronized void setInfo(String info) throws IOException { + header.setInfo(info); + } + + public static void main(String[] args) { + System.out.println("JRobin Java Library :: RRDTool choice for the Java world"); + System.out.println("=================================================================="); + System.out.println("JRobin base directory: " + Util.getJRobinHomeDirectory()); + long time = Util.getTime(); + System.out.println("Current timestamp: " + time + ": " + new Date(time * 1000L)); + System.out.println("------------------------------------------------------------------"); + System.out.println("For the latest information visit: http://www.jrobin.org"); + System.out.println("(C) 2003-2005 Sasa Markovic. All rights reserved."); + } + + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + "[" + new File(getPath()).getName() + "]"; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdDbPool.java b/apps/jrobin/java/src/org/jrobin/core/RrdDbPool.java new file mode 100644 index 000000000..3be890f3e --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdDbPool.java @@ -0,0 +1,932 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; +import java.util.HashMap; + +import org.jrobin.core.RrdException; + +/** + * This class should be used to synchronize access to RRD files + * in a multithreaded environment. This class should be also used to prevent openning of + * too many RRD files at the same time (thus avoiding operating system limits) + */ + +public class RrdDbPool { + /** + * Initial capacity of the pool i.e. maximum number of simultaneously open RRD files. The pool will + * never open too many RRD files at the same time. + */ + public static final int INITIAL_CAPACITY = 200; + private static RrdDbPool instance; + + private int capacity = INITIAL_CAPACITY; + private HashMap rrdMap = new HashMap(INITIAL_CAPACITY); + + /** + * Creates a single instance of the class on the first call, or returns already existing one. + * + * @return Single instance of this class + * @throws RrdException Thrown if the default RRD backend is not derived from the {@link RrdFileBackendFactory} + */ + public synchronized static RrdDbPool getInstance() throws RrdException { + if (instance == null) { + instance = new RrdDbPool(); + } + return instance; + } + + private RrdDbPool() throws RrdException { + RrdBackendFactory factory = RrdBackendFactory.getDefaultFactory(); + if (!(factory instanceof RrdFileBackendFactory)) { + throw new RrdException("Cannot create instance of " + getClass().getName() + " with " + + "a default backend factory not derived from RrdFileBackendFactory"); + } + } + + /** + * Requests a RrdDb reference for the given RRD file path.

+ *

+ * + * @param path Path to existing RRD file + * @return reference for the give RRD file + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public synchronized RrdDb requestRrdDb(String path) throws IOException, RrdException { + String canonicalPath = Util.getCanonicalPath(path); + while (!rrdMap.containsKey(canonicalPath) && rrdMap.size() >= capacity) { + try { + wait(); + } + catch (InterruptedException e) { + throw new RrdException(e); + } + } + if (rrdMap.containsKey(canonicalPath)) { + // already open, just increase usage count + RrdEntry entry = rrdMap.get(canonicalPath); + entry.count++; + return entry.rrdDb; + } + else { + // not open, open it now and add to the map + RrdDb rrdDb = new RrdDb(canonicalPath); + rrdMap.put(canonicalPath, new RrdEntry(rrdDb)); + return rrdDb; + } + } + + /** + * Requests a RrdDb reference for the given RRD file definition object.

+ *

+ * + * @param rrdDef Definition of the RRD file to be created + * @return Reference to the newly created RRD file + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public synchronized RrdDb requestRrdDb(RrdDef rrdDef) throws IOException, RrdException { + String canonicalPath = Util.getCanonicalPath(rrdDef.getPath()); + while (rrdMap.containsKey(canonicalPath) || rrdMap.size() >= capacity) { + try { + wait(); + } + catch (InterruptedException e) { + throw new RrdException(e); + } + } + RrdDb rrdDb = new RrdDb(rrdDef); + rrdMap.put(canonicalPath, new RrdEntry(rrdDb)); + return rrdDb; + } + + /** + * Requests a RrdDb reference for the given path. The file will be created from + * external data (from XML dump, RRD file or RRDTool's binary RRD file).

+ *

+ * + * @param path Path to RRD file which should be created + * @param sourcePath Path to external data which is to be converted to JRobin's native RRD file format + * @return Reference to the newly created RRD file + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public synchronized RrdDb requestRrdDb(String path, String sourcePath) + throws IOException, RrdException,RrdException { + String canonicalPath = Util.getCanonicalPath(path); + while (rrdMap.containsKey(canonicalPath) || rrdMap.size() >= capacity) { + try { + wait(); + } + catch (InterruptedException e) { + throw new RrdException(e); + } + } + RrdDb rrdDb = new RrdDb(canonicalPath, sourcePath); + rrdMap.put(canonicalPath, new RrdEntry(rrdDb)); + return rrdDb; + } + + /** + * Releases RrdDb reference previously obtained from the pool. When a reference is released, its usage + * count is decremented by one. If usage count drops to zero, the underlying RRD file will be closed. + * + * @param rrdDb RrdDb reference to be returned to the pool + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public synchronized void release(RrdDb rrdDb) throws IOException, RrdException { + // null pointer should not kill the thread, just ignore it + if (rrdDb == null) { + return; + } + String canonicalPath = Util.getCanonicalPath(rrdDb.getPath()); + if (!rrdMap.containsKey(canonicalPath)) { + throw new RrdException("Could not release [" + canonicalPath + "], the file was never requested"); + } + RrdEntry entry = rrdMap.get(canonicalPath); + if (--entry.count <= 0) { + // no longer used + rrdMap.remove(canonicalPath); + notifyAll(); + entry.rrdDb.close(); + } + } + + /** + * Returns the maximum number of simultaneously open RRD files. + * + * @return maximum number of simultaneously open RRD files + */ + public synchronized int getCapacity() { + return capacity; + } + + /** + * Sets the maximum number of simultaneously open RRD files. + * + * @param capacity Maximum number of simultaneously open RRD files. + */ + public synchronized void setCapacity(int capacity) { + this.capacity = capacity; + } + + /** + * Returns an array of open file names. + * + * @return Array with canonical paths to open RRD files held in the pool. + */ + public synchronized String[] getOpenFiles() { + return rrdMap.keySet().toArray(new String[0]); + } + + /** + * Returns the number of open RRD files. + * + * @return Number of currently open RRD files held in the pool. + */ + public synchronized int getOpenFileCount() { + return rrdMap.size(); + } + + private final static class RrdEntry { + RrdDb rrdDb; + int count; + + RrdEntry(final RrdDb rrdDb) { + this.rrdDb = rrdDb; + this.count = 1; + } + } +} + +// OLDER VERSION IS HERE + +///* ============================================================ +// * JRobin : Pure java implementation of RRDTool's functionality +// * ============================================================ +// * +// * Project Info: http://www.jrobin.org +// * Project Lead: Sasa Markovic (saxon@jrobin.org); +// * +// * (C) Copyright 2003-2005, by Sasa Markovic. +// * +// * Developers: Sasa Markovic (saxon@jrobin.org) +// * +// * +// * 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., 59 Temple Place, Suite 330, +// * Boston, MA 02111-1307, USA. +// */ +//package org.jrobin.core; +// +//import java.io.IOException; +//import java.util.HashMap; +//import java.util.Iterator; +//import java.util.LinkedHashMap; +//import java.util.Set; +// +///** +// * Class to represent the pool of open RRD files.

+// * +// * To open already existing RRD file with JRobin, you have to create a +// * {@link org.jrobin.core.RrdDb RrdDb} object by specifying RRD file path +// * as constructor argument. This operation can be time consuming +// * especially with large RRD files with many datasources and +// * several long archives.

+// * +// * In a multithreaded environment you might probably need a reference to the +// * same RRD file from two different threads (RRD file updates are performed in +// * one thread but data fetching and graphing is performed in another one). To make +// * the RrdDb construction process more efficient it might be convenient to open all +// * RRD files in a centralized place. That's the purpose of RrdDbPool class.

+// * +// * How does it work? The typical usage scenario goes like this:

+// * +// *

+// * // obtain instance to RrdDbPool object
+// * RrdDbPool pool = RrdDbPool.getInstance();
+// *
+// * // request a reference to RrdDb object
+// * String path = "some_relative_or_absolute_path_to_any_RRD_file";
+// * RrdDb rrdDb = RrdDbPool.requestRrdDb(path);
+// *
+// * // reference obtained, do whatever you want with it...
+// * ...
+// * ...
+// *
+// * // once you don't need the reference, release it.
+// * // DO NOT CALL rrdDb.close() - files no longer in use are eventually closed by the pool
+// * pool.release(rrdDb);
+// * 
+// * +// * It's that simple. When the reference is requested for the first time, RrdDbPool will open the RRD file +// * for you and make some internal note that the RRD file is used only once. When the reference +// * to the same file (same RRD file path) is requested for the second time, the same RrdDb +// * reference will be returned, and its usage count will be increased by one. When the +// * reference is released its usage count will be decremented by one.

+// * +// * When the reference count drops to zero, RrdDbPool will not close the underlying +// * RRD file immediatelly. Instead of it, it will be marked as 'eligible for closing'. +// * If someone request the same RRD file again (before it gets closed), the same +// * reference will be returned again.

+// * +// * RrdDbPool has a 'garbage collector' which runs in a separate, low-priority +// * thread and gets activated only when the number of RRD files kept in the +// * pool is too big (greater than number returned from {@link #getCapacity getCapacity()}). +// * Only RRD files with a reference count equal to zero +// * will be eligible for closing. Unreleased RrdDb references are never invalidated. +// * RrdDbPool object keeps track of the time when each RRD file +// * becomes eligible for closing so that the oldest RRD file gets closed first.

+// * +// * Initial RrdDbPool capacity is set to {@link #INITIAL_CAPACITY}. Use {@link #setCapacity(int)} +// * method to change it at any time.

+// * +// * WARNING:Never use close() method on the reference returned from the pool. +// * When the reference is no longer needed, return it to the pool with the +// * {@link #release(RrdDb) release()} method.

+// * +// * However, you are not forced to use RrdDbPool methods to obtain RrdDb references +// * to RRD files, 'ordinary' RrdDb constructors are still available. But RrdDbPool class +// * offers serious performance improvement especially in complex applications with many +// * threads and many simultaneously open RRD files.

+// * +// * The pool is thread-safe. Not that the {@link RrdDb} objects returned from the pool are +// * also thread-safe

+// * +// * You should know that each operating system has its own internal limit on the number +// * of simultaneously open files. The capacity of your RrdDbPool should be +// * reasonably smaller than the limit imposed by your operating system.

+// * +// * WARNING: The pool cannot be used to manipulate RrdDb objects +// * with {@link RrdBackend backends} different from default.

+// */ +//public class RrdDbPool implements Runnable { +// static final String GC_THREAD_NAME = "RrdDbPool GC thread"; +// static final String CLOSING_THREAD_NAME = "RrdDbPool closing thread"; +// private static final boolean DEBUG = false; +// +// // singleton pattern +// private static RrdDbPool ourInstance; +// private boolean closingOnExit = true; +// +// private Thread shutdownHook = new Thread(CLOSING_THREAD_NAME) { +// public void run() { +// try { +// close(); +// } +// catch (IOException e) { +// e.printStackTrace(); +// } +// } +// }; +// +// /** +// * Constant to represent the maximum number of internally open RRD files +// * which still does not force garbage collector (the process which closes RRD files) to run. +// */ +// public static final int INITIAL_CAPACITY = 500; +// private int capacity = INITIAL_CAPACITY, maxUsedCapacity; +// private boolean active = true; +// +// /** +// * Constant to represent the internal behaviour of the pool. +// * Defaults to true but can be changed at runtime. See +// * {@link #setLimitedCapacity(boolean)} for more information. +// */ +// public static final boolean LIMITED_CAPACITY = false; +// private boolean limitedCapacity = LIMITED_CAPACITY; +// +// /** +// * Constant to represent the priority of the background thread which closes excessive RRD files +// * which are no longer in use. +// */ +// public static final int GC_THREAD_PRIORITY = /** Thread.NORM_PRIORITY - */ 1; +// +// private HashMap rrdMap = new HashMap(INITIAL_CAPACITY); +// private LinkedHashMap rrdIdleMap = new LinkedHashMap(INITIAL_CAPACITY); +// private RrdBackendFactory factory; +// private int poolHitsCount = 0, poolRequestsCount = 0; +// +// /** +// * Returns an instance to RrdDbPool object. Only one such object may exist in each JVM. +// * +// * @return Instance to RrdDbPool object. +// */ +// public synchronized static RrdDbPool getInstance() { +// if (ourInstance == null) { +// ourInstance = new RrdDbPool(); +// ourInstance.startGarbageCollector(); +// } +// return ourInstance; +// } +// +// private RrdDbPool() { +// setClosingOnExit(closingOnExit); +// } +// +// /** +// * Checks the exiting behaviour of RrdDbPool. +// * @return True, if all RRD files are to be closed +// * when application invokes System.exit(). +// * False otherwise. The default behaviour is true +// * (all RRD files will be closed on exit). +// */ +// public synchronized boolean isClosingOnExit() { +// return closingOnExit; +// } +// +// /** +// * Sets the exiting behaviour of RrdDbPool. +// * @param closingOnExit True, if all RRD files are to be closed +// * when application invokes System.exit(). +// * False otherwise. The default behaviour is true +// * (all RRD files will be closed on exit). +// */ +// public synchronized void setClosingOnExit(boolean closingOnExit) { +// Runtime runtime = Runtime.getRuntime(); +// runtime.removeShutdownHook(shutdownHook); +// if(closingOnExit) { +// runtime.addShutdownHook(shutdownHook); +// } +// this.closingOnExit = closingOnExit; +// } +// +// private void startGarbageCollector() { +// Thread gcThread = new Thread(this, GC_THREAD_NAME); +// gcThread.setPriority(GC_THREAD_PRIORITY); +// gcThread.setDaemon(true); +// gcThread.start(); +// } +// +// /** +// * Returns a reference to an existing RRD file with the specified path. +// * If the file is already open in the pool, existing reference to it will be returned. +// * Otherwise, the file is open and a newly created reference to it is returned. +// * +// * @param path Relative or absolute path to a RRD file. +// * @return Reference to a RrdDb object (RRD file). +// * @throws IOException Thrown in case of I/O error. +// * @throws RrdException Thrown in case of JRobin specific error. +// */ +// public synchronized RrdDb requestRrdDb(String path) throws IOException, RrdException { +// proveActive(); +// poolRequestsCount++; +// String canonicalPath = getCanonicalPath(path); +// for(;;) { +// RrdEntry rrdEntry = rrdMap.get(canonicalPath); +// if (rrdEntry != null) { +// // already open, use it! +// reportUsage(canonicalPath, rrdEntry); +// poolHitsCount++; +//// debug("CACHED: " + rrdEntry.dump()); +// return rrdEntry.getRrdDb(); +// } +// else if(!limitedCapacity || rrdMap.size() < capacity) { +// // not found, open it +// RrdDb rrdDb = createRrdDb(path, null); +// rrdEntry = new RrdEntry(rrdDb); +// addRrdEntry(canonicalPath, rrdEntry); +//// debug("ADDED: " + rrdEntry.dump()); +// return rrdDb; +// } +// else { +// // we have to wait +// try { +// wait(); +// } +// catch (InterruptedException e) { +// throw new RrdException("Request for file '" + path + "' was interrupted"); +// } +// } +// } +// } +// +// /** +// * Returns a reference to a new RRD file. The new file will have the specified +// * relative or absolute path, and its contents will be provided from the specified +// * XML file (RRDTool comaptible). +// * +// * @param path Relative or absolute path to a new RRD file. +// * @param xmlPath Relative or absolute path to an existing XML dump file (RRDTool comaptible) +// * @return Reference to a RrdDb object (RRD file). +// * @throws IOException Thrown in case of I/O error. +// * @throws RrdException Thrown in case of JRobin specific error. +// */ +// public synchronized RrdDb requestRrdDb(String path, String xmlPath) +// throws IOException, RrdException { +// return requestNewRrdDb(path, xmlPath); +// } +// +// /** +// * Returns a reference to a new RRD file. The new file will be created based on the +// * definition contained in a RrdDef object. +// * +// * @param rrdDef RRD definition object +// * @return Reference to a RrdDb object (RRD file). +// * @throws IOException Thrown in case of I/O error. +// * @throws RrdException Thrown in case of JRobin specific error. +// */ +// public synchronized RrdDb requestRrdDb(RrdDef rrdDef) throws IOException, RrdException { +// return requestNewRrdDb(rrdDef.getPath(), rrdDef); +// } +// +// private RrdDb requestNewRrdDb(String path, Object creationDef) throws IOException, RrdException { +// proveActive(); +// poolRequestsCount++; +// String canonicalPath = getCanonicalPath(path); +// for(;;) { +// RrdEntry rrdEntry = rrdMap.get(canonicalPath); +// if(rrdEntry != null) { +// // already open +// removeIfIdle(canonicalPath, rrdEntry); +// } +// else if(!limitedCapacity || rrdMap.size() < capacity) { +// RrdDb rrdDb = createRrdDb(path, creationDef); +// RrdEntry newRrdEntry = new RrdEntry(rrdDb); +// addRrdEntry(canonicalPath, newRrdEntry); +//// debug("ADDED: " + newRrdEntry.dump()); +// return rrdDb; +// } +// else { +// // we have to wait +// try { +// wait(); +// } +// catch (InterruptedException e) { +// throw new RrdException("Request for file '" + path + "' was interrupted"); +// } +// } +// } +// } +// +// private RrdDb createRrdDb(String path, Object creationDef) throws RrdException, IOException { +// if(creationDef == null) { +// // existing RRD +// return new RrdDb(path, getFactory()); +// } +// else if(creationDef instanceof String) { +// // XML input +// return new RrdDb(path, (String) creationDef, getFactory()); +// } +// else if(creationDef instanceof RrdDef) { +// // RrdDef +// return new RrdDb((RrdDef) creationDef, getFactory()); +// } +// else { +// throw new RrdException("Unexpected input object type: " + +// creationDef.getClass().getName()); +// } +// } +// +// private void reportUsage(String canonicalPath, RrdEntry rrdEntry) { +// if (rrdEntry.reportUsage() == 1) { +// // must not be garbage collected +// rrdIdleMap.remove(canonicalPath); +// } +// } +// +// private void reportRelease(String canonicalPath, RrdEntry rrdEntry) { +// if (rrdEntry.reportRelease() == 0) { +// // ready to be garbage collected +// rrdIdleMap.put(canonicalPath, rrdEntry); +// } +// } +// +// private void addRrdEntry(String canonicalPath, RrdEntry newRrdEntry) { +// rrdMap.put(canonicalPath, newRrdEntry); +// maxUsedCapacity = Math.max(rrdMap.size(), maxUsedCapacity); +// // notify waiting threads +// notifyAll(); +// } +// +// private void removeIfIdle(String canonicalPath, RrdEntry rrdEntry) +// throws RrdException, IOException { +// // already open, check if active (not released) +// if (rrdEntry.isInUse()) { +// // not released, not allowed here +// throw new RrdException("Cannot create new RrdDb file: " + +// "File '" + canonicalPath + "' already in use"); +// } else { +// // open but released... safe to close it +//// debug("WILL BE RECREATED: " + rrdEntry.dump()); +// removeRrdEntry(canonicalPath, rrdEntry); +// } +// } +// +// private void removeRrdEntry(String canonicalPath, RrdEntry rrdEntry) throws IOException { +// rrdEntry.closeRrdDb(); +// rrdMap.remove(canonicalPath); +// rrdIdleMap.remove(canonicalPath); +//// debug("REMOVED: " + rrdEntry.dump()); +// } +// +// /** +// * Method used to report that the reference to a RRD file is no longer needed. File that +// * is no longer needed (all references to it are released) is marked 'eligible for +// * closing'. It will be eventually closed by the pool when the number of open RRD files +// * becomes too big. Most recently released files will be closed last. +// * +// * @param rrdDb Reference to RRD file that is no longer needed. +// * @throws IOException Thrown in case of I/O error. +// * @throws RrdException Thrown in case of JRobin specific error. +// */ +// public synchronized void release(RrdDb rrdDb) throws IOException, RrdException { +// proveActive(); +// if (rrdDb == null) { +// // we don't want NullPointerException +// return; +// } +// if (rrdDb.isClosed()) { +// throw new RrdException("File " + rrdDb.getPath() + " already closed"); +// } +// String canonicalPath = getCanonicalPath(rrdDb.getPath()); +// if (rrdMap.containsKey(canonicalPath)) { +// RrdEntry rrdEntry = rrdMap.get(canonicalPath); +// reportRelease(canonicalPath, rrdEntry); +//// debug("RELEASED: " + rrdEntry.dump()); +// } else { +// throw new RrdException("RRD file " + rrdDb.getPath() + " not in the pool"); +// } +// // notify waiting threads +// notifyAll(); +// } +// +// /** +// * This method runs garbage collector in a separate thread. If the number of +// * open RRD files kept in the pool is too big (greater than number +// * returned from {@link #getCapacity getCapacity()}), garbage collector will try +// * to close and remove RRD files with a reference count equal to zero. +// * Never call this method directly. +// */ +// public void run() { +//// debug("GC: started"); +// while (active) { +// synchronized (this) { +// if (rrdMap.size() >= capacity && rrdIdleMap.size() > 0) { +// try { +// String canonicalPath = rrdIdleMap.keySet().iterator().next(); +// RrdEntry rrdEntry = rrdIdleMap.get(canonicalPath); +//// debug("GC: closing " + rrdEntry.dump()); +// removeRrdEntry(canonicalPath, rrdEntry); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// notifyAll(); +// } +// else { +// try { +//// debug("GC: waiting"); +// wait(); +//// debug("GC: running"); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } +// } +// } +// } +// +// protected void finalize() throws IOException { +// close(); +// } +// +// /** +// * Clears the internal state of the pool entirely. All open RRD files are closed. +// * +// * @throws IOException Thrown in case of I/O related error. +// */ +// public synchronized void reset() throws IOException { +// Iterator it = rrdMap.values().iterator(); +// while (it.hasNext()) { +// RrdEntry rrdEntry = it.next(); +// rrdEntry.closeRrdDb(); +// } +// rrdMap.clear(); +// rrdIdleMap.clear(); +//// debug("Pool cleared"); +// } +// +// /** +// * Closes the pool and all RRD files currently held in the pool. +// * No further operations on the pool are allowed. +// * @throws IOException Thrown in case of I/O error. +// */ +// public synchronized void close() throws IOException { +// if(active) { +// active = false; +// reset(); +//// debug("The pool is closed."); +// } +// else { +//// debug("The pool is already closed!"); +// } +// } +// +// private static String getCanonicalPath(String path) throws IOException { +// return Util.getCanonicalPath(path); +// } +// +// static void debug(String msg) { +// if (DEBUG) { +// System.out.println("POOL: " + msg); +// } +// } +// +// /** +// * Returns the internal state of the pool. Useful for debugging purposes. +// * +// * @param dumpFiles true, if dumped information should contain paths to open files +// * currently held in the pool, false otherwise +// * @return Internal pool state (with an optional list of open RRD files and +// * the current number of usages for each one). +// * @throws IOException Thrown in case of I/O error. +// */ +// public synchronized String dump(boolean dumpFiles) throws IOException { +// StringBuffer buff = new StringBuffer(); +// buff.append("==== POOL DUMP ===========================\n"); +// buff.append("open=" + rrdMap.size() + ", idle=" + rrdIdleMap.size() + "\n"); +// buff.append("capacity=" + capacity + ", " + "maxUsedCapacity=" + maxUsedCapacity + "\n"); +// buff.append("hits=" + poolHitsCount + ", " + "requests=" + poolRequestsCount + "\n"); +// buff.append("efficiency=" + getPoolEfficiency() + "\n"); +// if(dumpFiles) { +// buff.append("---- CACHED FILES ------------------------\n"); +// Iterator it = rrdMap.values().iterator(); +// while (it.hasNext()) { +// RrdEntry rrdEntry = it.next(); +// buff.append(rrdEntry.dump() + "\n"); +// } +// } +// return buff.toString(); +// } +// +// /** +// * Returns the complete internal state of the pool. Useful for debugging purposes. +// * +// * @return Internal pool state (with a list of open RRD files and the current number of +// * usages for each one). +// * @throws IOException Thrown in case of I/O error. +// */ +// public synchronized String dump() throws IOException { +// return dump(true); +// } +// +// /** +// * Returns paths to all open files currently held in the pool. +// * @return An array containing open file paths. +// */ +// public synchronized String[] getCachedFilePaths() { +// Set keySet = rrdMap.keySet(); +// int n = keySet.size(), i = 0; +// String[] files = new String[n]; +// Iterator it = keySet.iterator(); +// while(it.hasNext()) { +// files[i++] = it.next(); +// } +// return files; +// } +// +// /** +// * Returns maximum number of internally open RRD files +// * which still does not force garbage collector to run. +// * +// * @return Desired nuber of open files held in the pool. +// */ +// public synchronized int getCapacity() { +// return capacity; +// } +// +// /** +// * Sets maximum number of internally open RRD files +// * which still does not force garbage collector to run. +// * +// * @param capacity Desired number of open files to hold in the pool +// */ +// public synchronized void setCapacity(int capacity) { +// this.capacity = capacity; +//// debug("Capacity set to: " + capacity); +// } +// +// private RrdBackendFactory getFactory() throws RrdException { +// if (factory == null) { +// factory = RrdBackendFactory.getDefaultFactory(); +// if (!(factory instanceof RrdFileBackendFactory)) { +// factory = null; +// throw new RrdException( +// "RrdDbPool cannot work with factories not derived from RrdFileBackendFactory"); +// } +// } +// return factory; +// } +// +// private class RrdEntry { +// private RrdDb rrdDb; +// private int usageCount = 1; +// +// public RrdEntry(RrdDb rrdDb) { +// this.rrdDb = rrdDb; +// } +// +// RrdDb getRrdDb() { +// return rrdDb; +// } +// +// int reportUsage() { +// assert usageCount >= 0: "Unexpected reportUsage count: " + usageCount; +// return ++usageCount; +// } +// +// int reportRelease() { +// assert usageCount > 0: "Unexpected reportRelease count: " + usageCount; +// return --usageCount; +// } +// +// boolean isInUse() { +// return usageCount > 0; +// } +// +// void closeRrdDb() throws IOException { +// rrdDb.close(); +// } +// +// String dump() throws IOException { +// String canonicalPath = getCanonicalPath(rrdDb.getPath()); +// return canonicalPath + " [" + usageCount + "]"; +// } +// } +// +// /** +// * Calculates pool's efficency ratio. The ratio is obtained by dividing the number of +// * RrdDb requests served from the internal pool of open RRD files +// * with the number of total RrdDb requests. +// * +// * @return Pool's efficiency ratio as a double between 1 (best) and 0 (worst). +// * If no RrdDb reference was ever requested, 1 would be returned. +// */ +// public synchronized double getPoolEfficiency() { +// if (poolRequestsCount == 0) { +// return 1.0; +// } +// double ratio = (double) poolHitsCount / (double) poolRequestsCount; +// // round to 3 decimal digits +// return Math.round(ratio * 1000.0) / 1000.0; +// } +// +// /** +// * Returns the number of RRD requests served from the internal pool of open RRD files +// * +// * @return The number of pool "hits". +// */ +// public synchronized int getPoolHitsCount() { +// return poolHitsCount; +// } +// +// /** +// * Returns the total number of RRD requests successfully served by this pool. +// * +// * @return Total number of RRD requests +// */ +// public synchronized int getPoolRequestsCount() { +// return poolRequestsCount; +// } +// +// /** +// * Returns the maximum number of open RRD files over the lifetime +// * of the pool. +// * @return maximum number of open RRD files. +// */ +// public synchronized int getMaxUsedCapacity() { +// return maxUsedCapacity; +// } +// +// /** +// * Checks the internal behaviour of the pool. See {@link #setLimitedCapacity(boolean)} for +// * more information. +// * +// * @return true if the pool is 'flexible' (by not imposing the strict +// * limit on the number of simultaneously open files), false otherwise. +// */ +// public synchronized boolean isLimitedCapacity() { +// return limitedCapacity; +// } +// +// /** +// * Sets the behaviour of the pool. If true is passed as argument, the pool will never +// * open more than {@link #getCapacity()} files at any time. If set to false, +// * the pool might keep more open files, but only for a short period of time. This method might be +// * useful if you want avoid OS limits when it comes to the number of simultaneously open files.

+// * +// * By default, the pool behaviour is 'flexible' (limitedCapacity property defaults +// * to false

+// * +// * @param limitedCapacity true if the pool should be 'flexible' (not imposing the strict +// * limit on the number of simultaneously open files), false otherwise. +// */ +// public synchronized void setLimitedCapacity(boolean limitedCapacity) { +// this.limitedCapacity = limitedCapacity; +// } +// +// private void proveActive() throws IOException { +// if(!active) { +// throw new IOException("RrdDbPool is already closed"); +// } +// } +// +// /** +// * Checks if the pool is active. You can request RrdDb references only from the active pool. The +// * pool is deactived when the {@link #close()} method is called. +// * @return true if active, false otherwise. +// */ +// public synchronized boolean isActive() { +// return active; +// } +//} +// diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdDef.java b/apps/jrobin/java/src/org/jrobin/core/RrdDef.java new file mode 100644 index 000000000..e5369761d --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdDef.java @@ -0,0 +1,694 @@ +/******************************************************************************* + * 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.core; + +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.*; + +/** + * Class to represent definition of new Round Robin Database (RRD). + * Object of this class is used to create + * new RRD from scratch - pass its reference as a RrdDb constructor + * argument (see documentation for {@link RrdDb RrdDb} class). RrdDef + * object does not actually create new RRD. It just holds all necessary + * information which will be used during the actual creation process. + *

+ * RRD definition (RrdDef object) consists of the following elements: + *

+ *

+ * RrdDef provides API to set all these elements. For the complete explanation of all + * RRD definition parameters, see RRDTool's + * rrdcreate man page. + *

+ * + * @author Sasa Markovic + */ +public class RrdDef { + /** + * default RRD step to be used if not specified in constructor (300 seconds) + */ + public static final long DEFAULT_STEP = 300L; + /** + * if not specified in constructor, starting timestamp will be set to the + * current timestamp plus DEFAULT_INITIAL_SHIFT seconds (-10) + */ + public static final long DEFAULT_INITIAL_SHIFT = -10L; + + private String path; + private long startTime = Util.getTime() + DEFAULT_INITIAL_SHIFT; + private long step = DEFAULT_STEP; + private ArrayList dsDefs = new ArrayList(); + private ArrayList arcDefs = new ArrayList(); + + /** + *

Creates new RRD definition object with the given path. + * When this object is passed to + * RrdDb constructor, new RRD will be created using the + * specified path.

+ * + * @param path Path to new RRD. + * @throws RrdException Thrown if name is invalid (null or empty). + */ + public RrdDef(final String path) throws RrdException { + if (path == null || path.length() == 0) { + throw new RrdException("No path specified"); + } + this.path = path; + } + + /** + *

Creates new RRD definition object with the given path and step.

+ * + * @param path Path to new RRD. + * @param step RRD step. + * @throws RrdException Thrown if supplied parameters are invalid. + */ + public RrdDef(final String path, final long step) throws RrdException { + this(path); + if (step <= 0) { + throw new RrdException("Invalid RRD step specified: " + step); + } + this.step = step; + } + + /** + *

Creates new RRD definition object with the given path, starting timestamp + * and step.

+ * + * @param path Path to new RRD. + * @param startTime RRD starting timestamp. + * @param step RRD step. + * @throws RrdException Thrown if supplied parameters are invalid. + */ + public RrdDef(final String path, final long startTime, final long step) throws RrdException { + this(path, step); + if (startTime < 0) { + throw new RrdException("Invalid RRD start time specified: " + startTime); + } + this.startTime = startTime; + } + + /** + * Returns path for the new RRD + * + * @return path to the new RRD which should be created + */ + public String getPath() { + return path; + } + + /** + * Returns starting timestamp for the RRD that should be created. + * + * @return RRD starting timestamp + */ + public long getStartTime() { + return startTime; + } + + /** + * Returns time step for the RRD that will be created. + * + * @return RRD step + */ + public long getStep() { + return step; + } + + /** + * Sets path to RRD. + * + * @param path to new RRD. + */ + public void setPath(final String path) { + this.path = path; + } + + /** + * Sets RRD's starting timestamp. + * + * @param startTime starting timestamp. + */ + public void setStartTime(final long startTime) { + this.startTime = startTime; + } + + /** + * Sets RRD's starting timestamp. + * + * @param date starting date + */ + public void setStartTime(final Date date) { + this.startTime = Util.getTimestamp(date); + } + + /** + * Sets RRD's starting timestamp. + * + * @param gc starting date + */ + public void setStartTime(final Calendar gc) { + this.startTime = Util.getTimestamp(gc); + } + + /** + * Sets RRD's time step. + * + * @param step RRD time step. + */ + public void setStep(final long step) { + this.step = step; + } + + /** + * Adds single datasource definition represented with object of class DsDef. + * + * @param dsDef Datasource definition. + * @throws RrdException Thrown if new datasource definition uses already used data + * source name. + */ + public void addDatasource(final DsDef dsDef) throws RrdException { + if (dsDefs.contains(dsDef)) { + throw new RrdException("Datasource already defined: " + dsDef.dump()); + } + dsDefs.add(dsDef); + } + + /** + * Adds single datasource to RRD definition by specifying its data source name, source type, + * heartbeat, minimal and maximal value. For the complete explanation of all data + * source definition parameters see RRDTool's + * rrdcreate man page. + *

+ * IMPORTANT NOTE: If datasource name ends with '!', corresponding archives will never + * store NaNs as datasource values. In that case, NaN datasource values will be silently + * replaced with zeros by the framework. + * + * @param dsName Data source name. + * @param dsType Data source type. Valid types are "COUNTER", + * "GAUGE", "DERIVE" and "ABSOLUTE" (these string constants are conveniently defined in + * the {@link DsTypes} class). + * @param heartbeat Data source heartbeat. + * @param minValue Minimal acceptable value. Use Double.NaN if unknown. + * @param maxValue Maximal acceptable value. Use Double.NaN if unknown. + * @throws RrdException Thrown if new datasource definition uses already used data + * source name. + */ + public void addDatasource(final String dsName, final String dsType, final long heartbeat, final double minValue, final double maxValue) throws RrdException { + addDatasource(new DsDef(dsName, dsType, heartbeat, minValue, maxValue)); + } + + /** + * Adds single datasource to RRD definition from a RRDTool-like + * datasource definition string. The string must have six elements separated with colons + * (:) in the following order: + *

+ *

+	 * DS:name:type:heartbeat:minValue:maxValue
+	 * 
+ * For example: + *

+ *

+	 * DS:input:COUNTER:600:0:U
+	 * 
+ * For more information on datasource definition parameters see rrdcreate + * man page. + * + * @param rrdToolDsDef Datasource definition string with the syntax borrowed from RRDTool. + * @throws RrdException Thrown if invalid string is supplied. + */ + public void addDatasource(final String rrdToolDsDef) throws RrdException { + final RrdException rrdException = new RrdException("Wrong rrdtool-like datasource definition: " + rrdToolDsDef); + final StringTokenizer tokenizer = new StringTokenizer(rrdToolDsDef, ":"); + if (tokenizer.countTokens() != 6) { + throw rrdException; + } + final String[] tokens = new String[6]; + for (int curTok = 0; tokenizer.hasMoreTokens(); curTok++) { + tokens[curTok] = tokenizer.nextToken(); + } + if (!tokens[0].equalsIgnoreCase("DS")) { + throw rrdException; + } + final String dsName = tokens[1]; + final String dsType = tokens[2]; + long dsHeartbeat; + try { + dsHeartbeat = Long.parseLong(tokens[3]); + } + catch (final NumberFormatException nfe) { + throw rrdException; + } + double minValue = Double.NaN; + if (!tokens[4].equalsIgnoreCase("U")) { + try { + minValue = Double.parseDouble(tokens[4]); + } + catch (final NumberFormatException nfe) { + throw rrdException; + } + } + double maxValue = Double.NaN; + if (!tokens[5].equalsIgnoreCase("U")) { + try { + maxValue = Double.parseDouble(tokens[5]); + } + catch (final NumberFormatException nfe) { + throw rrdException; + } + } + addDatasource(new DsDef(dsName, dsType, dsHeartbeat, minValue, maxValue)); + } + + /** + * Adds data source definitions to RRD definition in bulk. + * + * @param dsDefs Array of data source definition objects. + * @throws RrdException Thrown if duplicate data source name is used. + */ + public void addDatasource(final DsDef[] dsDefs) throws RrdException { + for (final DsDef dsDef : dsDefs) { + addDatasource(dsDef); + } + } + + /** + * Adds single archive definition represented with object of class ArcDef. + * + * @param arcDef Archive definition. + * @throws RrdException Thrown if archive with the same consolidation function + * and the same number of steps is already added. + */ + public void addArchive(final ArcDef arcDef) throws RrdException { + if (arcDefs.contains(arcDef)) { + throw new RrdException("Archive already defined: " + arcDef.dump()); + } + arcDefs.add(arcDef); + } + + /** + * Adds archive definitions to RRD definition in bulk. + * + * @param arcDefs Array of archive definition objects + * @throws RrdException Thrown if RRD definition already contains archive with + * the same consolidation function and the same number of steps. + */ + public void addArchive(final ArcDef[] arcDefs) throws RrdException { + for (final ArcDef arcDef : arcDefs) { + addArchive(arcDef); + } + } + + /** + * Adds single archive definition by specifying its consolidation function, X-files factor, + * number of steps and rows. For the complete explanation of all archive + * definition parameters see RRDTool's + * rrdcreate man page. + *

+ * + * @param consolFun Consolidation function. Valid values are "AVERAGE", + * "MIN", "MAX" and "LAST" (these constants are conveniently defined in the + * {@link ConsolFuns} class) + * @param xff X-files factor. Valid values are between 0 and 1. + * @param steps Number of archive steps + * @param rows Number of archive rows + * @throws RrdException Thrown if archive with the same consolidation function + * and the same number of steps is already added. + */ + public void addArchive(final String consolFun, final double xff, final int steps, final int rows) throws RrdException { + addArchive(new ArcDef(consolFun, xff, steps, rows)); + } + + /** + * Adds single archive to RRD definition from a RRDTool-like + * archive definition string. The string must have five elements separated with colons + * (:) in the following order: + *

+ *

+	 * RRA:consolidationFunction:XFilesFactor:steps:rows
+	 * 
+ * For example: + *

+ *

+	 * RRA:AVERAGE:0.5:10:1000
+	 * 
+ * For more information on archive definition parameters see rrdcreate + * man page. + * + * @param rrdToolArcDef Archive definition string with the syntax borrowed from RRDTool. + * @throws RrdException Thrown if invalid string is supplied. + */ + public void addArchive(final String rrdToolArcDef) throws RrdException { + final RrdException rrdException = new RrdException("Wrong rrdtool-like archive definition: " + rrdToolArcDef); + final StringTokenizer tokenizer = new StringTokenizer(rrdToolArcDef, ":"); + if (tokenizer.countTokens() != 5) { + throw rrdException; + } + final String[] tokens = new String[5]; + for (int curTok = 0; tokenizer.hasMoreTokens(); curTok++) { + tokens[curTok] = tokenizer.nextToken(); + } + if (!tokens[0].equalsIgnoreCase("RRA")) { + throw rrdException; + } + final String consolFun = tokens[1]; + double xff; + try { + xff = Double.parseDouble(tokens[2]); + } + catch (final NumberFormatException nfe) { + throw rrdException; + } + int steps; + try { + steps = Integer.parseInt(tokens[3]); + } + catch (final NumberFormatException nfe) { + throw rrdException; + } + int rows; + try { + rows = Integer.parseInt(tokens[4]); + } + catch (final NumberFormatException nfe) { + throw rrdException; + } + addArchive(new ArcDef(consolFun, xff, steps, rows)); + } + + void validate() throws RrdException { + if (dsDefs.size() == 0) { + throw new RrdException("No RRD datasource specified. At least one is needed."); + } + if (arcDefs.size() == 0) { + throw new RrdException("No RRD archive specified. At least one is needed."); + } + } + + /** + * Returns all data source definition objects specified so far. + * + * @return Array of data source definition objects + */ + public DsDef[] getDsDefs() { + return dsDefs.toArray(new DsDef[0]); + } + + /** + * Returns all archive definition objects specified so far. + * + * @return Array of archive definition objects. + */ + public ArcDef[] getArcDefs() { + return arcDefs.toArray(new ArcDef[0]); + } + + /** + * Returns number of defined datasources. + * + * @return Number of defined datasources. + */ + public int getDsCount() { + return dsDefs.size(); + } + + /** + * Returns number of defined archives. + * + * @return Number of defined archives. + */ + public int getArcCount() { + return arcDefs.size(); + } + + /** + * Returns string that represents all specified RRD creation parameters. Returned string + * has the syntax of RRDTool's create command. + * + * @return Dumped content of RrdDb object. + */ + public String dump() { + final StringBuffer buffer = new StringBuffer("create \""); + buffer.append(path).append("\""); + buffer.append(" --start ").append(getStartTime()); + buffer.append(" --step ").append(getStep()).append(" "); + for (final DsDef dsDef : dsDefs) { + buffer.append(dsDef.dump()).append(" "); + } + for (final ArcDef arcDef : arcDefs) { + buffer.append(arcDef.dump()).append(" "); + } + return buffer.toString().trim(); + } + + String getRrdToolCommand() { + return dump(); + } + + void removeDatasource(final String dsName) throws RrdException { + for (int i = 0; i < dsDefs.size(); i++) { + final DsDef dsDef = dsDefs.get(i); + if (dsDef.getDsName().equals(dsName)) { + dsDefs.remove(i); + return; + } + } + throw new RrdException("Could not find datasource named '" + dsName + "'"); + } + + void saveSingleDatasource(final String dsName) { + final Iterator it = dsDefs.iterator(); + while (it.hasNext()) { + final DsDef dsDef = it.next(); + if (!dsDef.getDsName().equals(dsName)) { + it.remove(); + } + } + } + + void removeArchive(final String consolFun, final int steps) throws RrdException { + final ArcDef arcDef = findArchive(consolFun, steps); + if (!arcDefs.remove(arcDef)) { + throw new RrdException("Could not remove archive " + consolFun + "/" + steps); + } + } + + ArcDef findArchive(final String consolFun, final int steps) throws RrdException { + for (final ArcDef arcDef : arcDefs) { + if (arcDef.getConsolFun().equals(consolFun) && arcDef.getSteps() == steps) { + return arcDef; + } + } + throw new RrdException("Could not find archive " + consolFun + "/" + steps); + } + + /** + * Exports RrdDef object to output stream in XML format. Generated XML code can be parsed + * with {@link RrdDefTemplate} class. + * + * @param out Output stream + */ + public void exportXmlTemplate(final OutputStream out) { + final XmlWriter xml = new XmlWriter(out); + xml.startTag("rrd_def"); + xml.writeTag("path", getPath()); + xml.writeTag("step", getStep()); + xml.writeTag("start", getStartTime()); + for (final DsDef dsDef : getDsDefs()) { + xml.startTag("datasource"); + xml.writeTag("name", dsDef.getDsName()); + xml.writeTag("type", dsDef.getDsType()); + xml.writeTag("heartbeat", dsDef.getHeartbeat()); + xml.writeTag("min", dsDef.getMinValue(), "U"); + xml.writeTag("max", dsDef.getMaxValue(), "U"); + xml.closeTag(); // datasource + } + for (ArcDef arcDef : getArcDefs()) { + xml.startTag("archive"); + xml.writeTag("cf", arcDef.getConsolFun()); + xml.writeTag("xff", arcDef.getXff()); + xml.writeTag("steps", arcDef.getSteps()); + xml.writeTag("rows", arcDef.getRows()); + xml.closeTag(); // archive + } + xml.closeTag(); // rrd_def + xml.flush(); + } + + /** + * Exports RrdDef object to string in XML format. Generated XML string can be parsed + * with {@link RrdDefTemplate} class. + * + * @return XML formatted string representing this RrdDef object + */ + public String exportXmlTemplate() { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + exportXmlTemplate(out); + return out.toString(); + } + + /** + * Exports RrdDef object to a file in XML format. Generated XML code can be parsed + * with {@link RrdDefTemplate} class. + * + * @param filePath path to the file + * @throws IOException if an I/O error occurs. + */ + public void exportXmlTemplate(final String filePath) throws IOException { + final FileOutputStream out = new FileOutputStream(filePath, false); + exportXmlTemplate(out); + out.close(); + } + + /** + * Returns the number of storage bytes required to create RRD from this + * RrdDef object. + * + * @return Estimated byte count of the underlying RRD storage. + */ + public long getEstimatedSize() { + final int dsCount = dsDefs.size(); + final int arcCount = arcDefs.size(); + int rowsCount = 0; + for (final ArcDef arcDef : arcDefs) { + rowsCount += arcDef.getRows(); + } + return calculateSize(dsCount, arcCount, rowsCount); + } + + static long calculateSize(final int dsCount, final int arcCount, final int rowsCount) { + return (24L + 48L * dsCount + 16L * arcCount + + 20L * dsCount * arcCount + 8L * dsCount * rowsCount) + + (1L + 2L * dsCount + arcCount) * 2L * RrdPrimitive.STRING_LENGTH; + } + + /** + * Compares the current RrdDef with another. RrdDefs are considered equal if:

+ *

+ * + * @param obj The second RrdDef object + * @return true if RrdDefs match exactly, false otherwise + */ + public boolean equals(final Object obj) { + if (obj == null || !(obj instanceof RrdDef)) { + return false; + } + final RrdDef rrdDef2 = (RrdDef) obj; + // check primary RRD step + if (step != rrdDef2.step) { + return false; + } + // check datasources + final DsDef[] dsDefs = getDsDefs(); + final DsDef[] dsDefs2 = rrdDef2.getDsDefs(); + if (dsDefs.length != dsDefs2.length) { + return false; + } + for (final DsDef dsDef : dsDefs) { + boolean matched = false; + for (final DsDef dsDef2 : dsDefs2) { + if (dsDef.exactlyEqual(dsDef2)) { + matched = true; + break; + } + } + // this datasource could not be matched + if (!matched) { + return false; + } + } + // check archives + final ArcDef[] arcDefs = getArcDefs(); + final ArcDef[] arcDefs2 = rrdDef2.getArcDefs(); + if (arcDefs.length != arcDefs2.length) { + return false; + } + for (final ArcDef arcDef : arcDefs) { + boolean matched = false; + for (final ArcDef arcDef2 : arcDefs2) { + if (arcDef.exactlyEqual(arcDef2)) { + matched = true; + break; + } + } + // this archive could not be matched + if (!matched) { + return false; + } + } + // everything matches + return true; + } + + public int hashCode() { + int hashCode = (int)step; + for (final DsDef dsDef : dsDefs) { + hashCode *= dsDef.hashCode(); + } + for (final ArcDef arcDef : arcDefs) { + hashCode *= arcDef.hashCode(); + } + return hashCode; + } + + /** + * Removes all datasource definitions. + */ + public void removeDatasources() { + dsDefs.clear(); + } + + /** + * Removes all RRA archive definitions. + */ + public void removeArchives() { + arcDefs.clear(); + } + + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + "[arcDefs=[" + join(getArcDefs()) + "],dsDefs=[" + join(getDsDefs()) + "]]"; + } + + private String join(final Object[] objs) { + final StringBuffer sb = new StringBuffer(); + for (int i = 0; i < objs.length; i++) { + sb.append(objs[i]); + if (i != (objs.length - 1)) { + sb.append(","); + } + } + return sb.toString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdDefTemplate.java b/apps/jrobin/java/src/org/jrobin/core/RrdDefTemplate.java new file mode 100644 index 000000000..7e667a807 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdDefTemplate.java @@ -0,0 +1,229 @@ +/******************************************************************************* + * 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.core; + +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +import java.io.File; +import java.io.IOException; +import java.util.Calendar; + +/** + * Class used to create an arbitrary number of {@link RrdDef} (RRD definition) objects + * from a single XML template. XML template can be supplied as an XML InputSource, + * XML file or XML formatted string. + *

+ * Here is an example of a properly formatted XML template with all available + * options in it (unwanted options can be removed): + *

+ * <rrd_def>
+ *     <path>test.rrd</path>
+ *     <!-- not mandatory -->
+ *     <start>1000123456</start>
+ *     <!-- not mandatory -->
+ *     <step>300</step>
+ *     <!-- at least one datasource must be supplied -->
+ *     <datasource>
+ *         <name>input</name>
+ *         <type>COUNTER</type>
+ *         <heartbeat>300</heartbeat>
+ *         <min>0</min>
+ *         <max>U</max>
+ *     </datasource>
+ *     <datasource>
+ *         <name>temperature</name>
+ *         <type>GAUGE</type>
+ *         <heartbeat>400</heartbeat>
+ *         <min>U</min>
+ *         <max>1000</max>
+ *     </datasource>
+ *     <!-- at least one archive must be supplied -->
+ *     <archive>
+ *         <cf>AVERAGE</cf>
+ *         <xff>0.5</xff>
+ *         <steps>1</steps>
+ *         <rows>600</rows>
+ *     </archive>
+ *     <archive>
+ *         <cf>MAX</cf>
+ *         <xff>0.6</xff>
+ *         <steps>6</steps>
+ *         <rows>7000</rows>
+ *     </archive>
+ * </rrd_def>
+ * 
+ * Notes on the template syntax:

+ *

+ * Any template value (text between <some_tag> and + * </some_tag>) can be replaced with + * a variable of the following form: ${variable_name}. Use + * {@link XmlTemplate#setVariable(String, String) setVariable()} + * methods from the base class to replace template variables with real values + * at runtime. + *

+ * Typical usage scenario:

+ *

+ * You should create new RrdDefTemplate object only once for each XML template. Single template + * object can be reused to create as many RrdDef objects as needed, with different values + * specified for template variables. XML synatax check is performed only once - the first + * definition object gets created relatively slowly, but it will be created much faster next time. + */ +public class RrdDefTemplate extends XmlTemplate { + /** + * Creates RrdDefTemplate object from any parsable XML input source. Read general information + * for this class to find an example of a properly formatted RrdDef XML source. + * + * @param xmlInputSource Xml input source + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of XML related error (parsing error, for example) + */ + public RrdDefTemplate(InputSource xmlInputSource) throws IOException, RrdException { + super(xmlInputSource); + } + + /** + * Creates RrdDefTemplate object from the string containing XML template. + * Read general information for this class to see an example of a properly formatted XML source. + * + * @param xmlString String containing XML template + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of XML related error (parsing error, for example) + */ + public RrdDefTemplate(String xmlString) throws IOException, RrdException { + super(xmlString); + } + + /** + * Creates RrdDefTemplate object from the file containing XML template. + * Read general information for this class to see an example of a properly formatted XML source. + * + * @param xmlFile File object representing file with XML template + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of XML related error (parsing error, for example) + */ + public RrdDefTemplate(File xmlFile) throws IOException, RrdException { + super(xmlFile); + } + + /** + * Returns RrdDef object constructed from the underlying XML template. Before this method + * is called, values for all non-optional placeholders must be supplied. To specify + * placeholder values at runtime, use some of the overloaded + * {@link XmlTemplate#setVariable(String, String) setVariable()} methods. Once this method + * returns, all placeholder values are preserved. To remove them all, call inhereted + * {@link XmlTemplate#clearValues() clearValues()} method explicitly.

+ * + * @return RrdDef object constructed from the underlying XML template, + * with all placeholders replaced with real values. This object can be passed to the constructor + * of the new RrdDb object. + * @throws RrdException Thrown (in most cases) if the value for some placeholder + * was not supplied through {@link XmlTemplate#setVariable(String, String) setVariable()} + * method call + */ + public RrdDef getRrdDef() throws RrdException { + if (!root.getTagName().equals("rrd_def")) { + throw new RrdException("XML definition must start with "); + } + validateTagsOnlyOnce(root, new String[] { + "path", "start", "step", "datasource*", "archive*" + }); + // PATH must be supplied or exception is thrown + String path = getChildValue(root, "path"); + RrdDef rrdDef = new RrdDef(path); + try { + String startStr = getChildValue(root, "start"); + Calendar startGc = Util.getCalendar(startStr); + rrdDef.setStartTime(startGc); + } + catch (RrdException e) { + // START is not mandatory + } + try { + long step = getChildValueAsLong(root, "step"); + rrdDef.setStep(step); + } + catch (RrdException e) { + // STEP is not mandatory + } + // datsources + Node[] dsNodes = getChildNodes(root, "datasource"); + for (Node dsNode : dsNodes) { + validateTagsOnlyOnce(dsNode, new String[] { + "name", "type", "heartbeat", "min", "max" + }); + String name = getChildValue(dsNode, "name"); + String type = getChildValue(dsNode, "type"); + long heartbeat = getChildValueAsLong(dsNode, "heartbeat"); + double min = getChildValueAsDouble(dsNode, "min"); + double max = getChildValueAsDouble(dsNode, "max"); + rrdDef.addDatasource(name, type, heartbeat, min, max); + } + // archives + Node[] arcNodes = getChildNodes(root, "archive"); + for (Node arcNode : arcNodes) { + validateTagsOnlyOnce(arcNode, new String[] { + "cf", "xff", "steps", "rows" + }); + String consolFun = getChildValue(arcNode, "cf"); + double xff = getChildValueAsDouble(arcNode, "xff"); + int steps = getChildValueAsInt(arcNode, "steps"); + int rows = getChildValueAsInt(arcNode, "rows"); + rrdDef.addArchive(consolFun, xff, steps, rows); + } + return rrdDef; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdDouble.java b/apps/jrobin/java/src/org/jrobin/core/RrdDouble.java new file mode 100644 index 000000000..06b27d76b --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdDouble.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +class RrdDouble extends RrdPrimitive { + private double cache; + private boolean cached = false; + + RrdDouble(final RrdUpdater updater, final boolean isConstant) throws IOException { + super(updater, RrdDouble.RRD_DOUBLE, isConstant); + } + + RrdDouble(final RrdUpdater updater) throws IOException { + super(updater, RrdDouble.RRD_DOUBLE, false); + } + + void set(final double value) throws IOException { + if (!isCachingAllowed()) { + writeDouble(value); + } + // caching allowed + else if (!cached || !Util.equal(cache, value)) { + // update cache + writeDouble(cache = value); + cached = true; + } + } + + double get() throws IOException { + return cached ? cache : readDouble(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdDoubleArray.java b/apps/jrobin/java/src/org/jrobin/core/RrdDoubleArray.java new file mode 100644 index 000000000..03f71dc8d --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdDoubleArray.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +class RrdDoubleArray extends RrdPrimitive { + private int length; + + RrdDoubleArray(final RrdUpdater updater, final int length) throws IOException { + super(updater, RrdPrimitive.RRD_DOUBLE, length, false); + this.length = length; + } + + void set(final int index, final double value) throws IOException { + set(index, value, 1); + } + + void set(final int index, final double value, final int count) throws IOException { + // rollovers not allowed! + assert index + count <= length: "Invalid robin index supplied: index=" + index +", count=" + count + ", length=" + length; + writeDouble(index, value, count); + } + + double get(final int index) throws IOException { + assert index < length: "Invalid index supplied: " + index + ", length=" + length; + return readDouble(index); + } + + double[] get(final int index, final int count) throws IOException { + assert index + count <= length: "Invalid index/count supplied: " + index + "/" + count + " (length=" + length + ")"; + return readDouble(index, count); + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdException.java b/apps/jrobin/java/src/org/jrobin/core/RrdException.java new file mode 100644 index 000000000..3ba6ae4d6 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdException.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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.core; + +/** + * Class to represent various JRobin checked exceptions. + * JRobin code can throw only RrdException + * (for various JRobin related errors) or IOException + * (for various I/O errors). + * + * @author Sasa Markovic + */ +public class RrdException extends Exception { + private static final long serialVersionUID = 6999702149227009855L; + + public RrdException() { + super(); + } + + public RrdException(final String message) { + super(message); + } + + public RrdException(final Throwable cause) { + super(cause); + } + + public RrdException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdFileBackend.java b/apps/jrobin/java/src/org/jrobin/core/RrdFileBackend.java new file mode 100644 index 000000000..ee213f6a1 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdFileBackend.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * JRobin backend which is used to store RRD data to ordinary files on the disk. This was the + * default factory before 1.4.0 version. + *

+ * This backend is based on the RandomAccessFile class (java.io.* package). + */ +public class RrdFileBackend extends RrdBackend { + /** + * radnom access file handle + */ + protected RandomAccessFile file; + + /** + * Creates RrdFileBackend object for the given file path, backed by RandomAccessFile object. + * + * @param path Path to a file + * @param readOnly True, if file should be open in a read-only mode. False otherwise + * @throws IOException Thrown in case of I/O error + */ + protected RrdFileBackend(final String path, final boolean readOnly) throws IOException { + super(path, readOnly); + this.file = new RandomAccessFile(path, readOnly ? "r" : "rw"); + } + + /** + * Closes the underlying RRD file. + * + * @throws IOException Thrown in case of I/O error + */ + public void close() throws IOException { + file.close(); + } + + /** + * Returns canonical path to the file on the disk. + * + * @param path File path + * @return Canonical file path + * @throws IOException Thrown in case of I/O error + */ + public static String getCanonicalPath(String path) throws IOException { + return Util.getCanonicalPath(path); + } + + /** + * Returns canonical path to the file on the disk. + * + * @return Canonical file path + * @throws IOException Thrown in case of I/O error + */ + public String getCanonicalPath() throws IOException { + return RrdFileBackend.getCanonicalPath(getPath()); + } + + /** + * Writes bytes to the underlying RRD file on the disk + * + * @param offset Starting file offset + * @param b Bytes to be written. + * @throws IOException Thrown in case of I/O error + */ + protected void write(long offset, byte[] b) throws IOException { + file.seek(offset); + file.write(b); + } + + /** + * Reads a number of bytes from the RRD file on the disk + * + * @param offset Starting file offset + * @param b Buffer which receives bytes read from the file. + * @throws IOException Thrown in case of I/O error. + */ + protected void read(long offset, byte[] b) throws IOException { + file.seek(offset); + if (file.read(b) != b.length) { + throw new IOException("Not enough bytes available in file " + getPath()); + } + } + + /** + * Returns RRD file length. + * + * @return File length. + * @throws IOException Thrown in case of I/O error. + */ + public long getLength() throws IOException { + return file.length(); + } + + /** + * Sets length of the underlying RRD file. This method is called only once, immediately + * after a new RRD file gets created. + * + * @param length Length of the RRD file + * @throws IOException Thrown in case of I/O error. + */ + protected void setLength(long length) throws IOException { + file.setLength(length); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdFileBackendFactory.java b/apps/jrobin/java/src/org/jrobin/core/RrdFileBackendFactory.java new file mode 100644 index 000000000..e89959616 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdFileBackendFactory.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Factory class which creates actual {@link RrdFileBackend} objects. This was the default + * backend factory in JRobin before 1.4.0 release. + */ +public class RrdFileBackendFactory extends RrdBackendFactory { + /** + * factory name, "FILE" + */ + public static final String NAME = "FILE"; + + /** + * Creates RrdFileBackend object for the given file path. + * + * @param path File path + * @param readOnly True, if the file should be accessed in read/only mode. + * False otherwise. + * @return RrdFileBackend object which handles all I/O operations for the given file path + * @throws IOException Thrown in case of I/O error. + */ + protected RrdBackend open(String path, boolean readOnly) throws IOException { + return new RrdFileBackend(path, readOnly); + } + + /** + * Method to determine if a file with the given path already exists. + * + * @param path File path + * @return True, if such file exists, false otherwise. + */ + protected boolean exists(String path) { + return Util.fileExists(path); + } + + /** + * Returns the name of this factory. + * + * @return Factory name (equals to string "FILE") + */ + public String getFactoryName() { + return NAME; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdInt.java b/apps/jrobin/java/src/org/jrobin/core/RrdInt.java new file mode 100644 index 000000000..3213d541c --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdInt.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +class RrdInt extends RrdPrimitive { + private int cache; + private boolean cached = false; + + RrdInt(final RrdUpdater updater, final boolean isConstant) throws IOException { + super(updater, RrdPrimitive.RRD_INT, isConstant); + } + + RrdInt(final RrdUpdater updater) throws IOException { + this(updater, false); + } + + void set(final int value) throws IOException { + if (!isCachingAllowed()) { + writeInt(value); + } + // caching allowed + else if (!cached || cache != value) { + // update cache + writeInt(cache = value); + cached = true; + } + } + + int get() throws IOException { + return cached ? cache : readInt(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdJRobin14FileBackend.java b/apps/jrobin/java/src/org/jrobin/core/RrdJRobin14FileBackend.java new file mode 100644 index 000000000..7e8ba8b09 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdJRobin14FileBackend.java @@ -0,0 +1,207 @@ +/* ============================================================ + * JRobin : Pure java implementation of RRDTool's functionality + * ============================================================ + * + * Project Info: http://www.jrobin.org + * Project Lead: Sasa Markovic (saxon@jrobin.org); + * + * (C) Copyright 2003, by Sasa Markovic. + * + * Developers: Sasa Markovic (saxon@jrobin.org) + * Arne Vandamme (cobralord@jrobin.org) + * + * 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +package org.jrobin.core; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileLock; +import java.util.HashSet; +import java.util.Set; + +/** + * JRobin backend which is used to store RRD data to ordinary files on the disk. This was the + * default factory before 1.4.0 version. + *

+ * This backend is based on the RandomAccessFile class (java.io.* package). + */ +public class RrdJRobin14FileBackend extends RrdBackend { + private static final long LOCK_DELAY = 100; // 0.1sec + + private static Set m_openFiles = new HashSet(); + + public static enum LockMode { + EXCEPTION_IF_LOCKED, + WAIT_IF_LOCKED, + NO_LOCKS + }; + + /** locking mode */ + protected LockMode m_lockMode; + + /** random access file handle */ + protected RandomAccessFile m_file; + /** file lock */ + protected FileLock m_fileLock; + + /** + * Creates RrdFileBackend object for the given file path, backed by RandomAccessFile object. + * @param path Path to a file + * @param readOnly True, if file should be open in a read-only mode. False otherwise + * @param lockMode Locking mode: Exception if locked, Wait if locked, or no locks. + * @throws IOException Thrown in case of I/O error + */ + protected RrdJRobin14FileBackend(String path, boolean readOnly, LockMode lockMode) throws IOException { + super(path, readOnly); + m_lockMode = lockMode; + m_file = new RandomAccessFile(path, readOnly ? "r" : "rw"); + try { + lockFile(); + registerWriter(); + } catch(final IOException ioe) { + close(); + throw ioe; + } + System.err.println(String.format("backend initialized with path=%s, readOnly=%s, lockMode=%s", path, Boolean.valueOf(readOnly), lockMode)); + } + + private void lockFile() throws IOException { + switch (m_lockMode) { + case EXCEPTION_IF_LOCKED: + m_fileLock = m_file.getChannel().tryLock(); + if (m_fileLock == null) { + // could not obtain lock + throw new IOException("Access denied. " + "File [" + getPath() + "] already locked"); + } + break; + case WAIT_IF_LOCKED: + while (m_fileLock == null) { + m_fileLock = m_file.getChannel().tryLock(); + if (m_fileLock == null) { + // could not obtain lock, wait a little, than try again + try { + Thread.sleep(LOCK_DELAY); + } + catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + break; + case NO_LOCKS: + break; + } + } + + private void registerWriter() throws IOException { + if (!isReadOnly()) { + final String canonicalPath = Util.getCanonicalPath(getPath()); + synchronized (m_openFiles) { + if (m_openFiles.contains(canonicalPath)) { + throw new IOException("File \"" + getPath() + "\" already open for R/W access. " + + "You cannot open the same file for R/W access twice"); + } + else { + m_openFiles.add(canonicalPath); + } + } + } + } + + /** + * Closes the underlying RRD file. + * + * @throws IOException Thrown in case of I/O error + */ + public void close() throws IOException { + unregisterWriter(); + try { + unlockFile(); + } finally { + m_file.close(); + } + } + + private void unlockFile() throws IOException { + if (m_fileLock != null) { + m_fileLock.release(); + } + } + + private void unregisterWriter() throws IOException { + if (!isReadOnly()) { + synchronized (m_openFiles) { + m_openFiles.remove(Util.getCanonicalPath(getPath())); + } + } + } + + /** + * Returns canonical path to the file on the disk. + * + * @return Canonical file path + * @throws IOException Thrown in case of I/O error + */ + public String getCanonicalPath() throws IOException { + return Util.getCanonicalPath(getPath()); + } + + /** + * Writes bytes to the underlying RRD file on the disk + * + * @param offset Starting file offset + * @param b Bytes to be written. + * @throws IOException Thrown in case of I/O error + */ + protected void write(final long offset, final byte[] b) throws IOException { + m_file.seek(offset); + m_file.write(b); + } + + /** + * Reads a number of bytes from the RRD file on the disk + * + * @param offset Starting file offset + * @param b Buffer which receives bytes read from the file. + * @throws IOException Thrown in case of I/O error. + */ + protected void read(final long offset, final byte[] b) throws IOException { + m_file.seek(offset); + if (m_file.read(b) != b.length) { + throw new IOException("Not enough bytes available in file " + getPath()); + } + } + + /** + * Returns RRD file length. + * + * @return File length. + * @throws IOException Thrown in case of I/O error. + */ + public long getLength() throws IOException { + return m_file.length(); + } + + /** + * Sets length of the underlying RRD file. This method is called only once, immediately + * after a new RRD file gets created. + * + * @param length Length of the RRD file + * @throws IOException Thrown in case of I/O error. + */ + protected void setLength(final long length) throws IOException { + m_file.setLength(length); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdJRobin14FileBackendFactory.java b/apps/jrobin/java/src/org/jrobin/core/RrdJRobin14FileBackendFactory.java new file mode 100644 index 000000000..910394e71 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdJRobin14FileBackendFactory.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +import org.jrobin.core.RrdJRobin14FileBackend.LockMode; + +/** + * Factory class which creates actual {@link RrdFileBackend} objects. This was the default + * backend factory in JRobin before 1.4.0 release. + */ +public class RrdJRobin14FileBackendFactory extends RrdBackendFactory { + /** + * factory name, "FILE" + */ + public static final String NAME = "14FILE"; + private LockMode m_lockMode = LockMode.NO_LOCKS; + + public RrdJRobin14FileBackendFactory() { + super(); + } + + public RrdJRobin14FileBackendFactory(final LockMode lockMode) { + super(); + m_lockMode = lockMode; + } + + /** + * Creates RrdFileBackend object for the given file path. + * + * @param path File path + * @param readOnly True, if the file should be accessed in read/only mode. + * False otherwise. + * @return RrdFileBackend object which handles all I/O operations for the given file path + * @throws IOException Thrown in case of I/O error. + */ + protected RrdBackend open(final String path, final boolean readOnly) throws IOException { + return new RrdJRobin14FileBackend(path, readOnly, m_lockMode); + } + + /** + * Method to determine if a file with the given path already exists. + * + * @param path File path + * @return True, if such file exists, false otherwise. + */ + protected boolean exists(final String path) { + return Util.fileExists(path); + } + + /** + * Returns the name of this factory. + * + * @return Factory name (equals to string "FILE") + */ + public String getFactoryName() { + return NAME; + } + + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + "[name=" + getFactoryName() + ",lockMode=" + m_lockMode + "]"; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdLong.java b/apps/jrobin/java/src/org/jrobin/core/RrdLong.java new file mode 100644 index 000000000..7b07b1730 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdLong.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +class RrdLong extends RrdPrimitive { + private long cache; + private boolean cached = false; + + RrdLong(final RrdUpdater updater, final boolean isConstant) throws IOException { + super(updater, RrdPrimitive.RRD_LONG, isConstant); + } + + RrdLong(final RrdUpdater updater) throws IOException { + this(updater, false); + } + + void set(final long value) throws IOException { + if (!isCachingAllowed()) { + writeLong(value); + } + // caching allowed + else if (!cached || cache != value) { + // update cache + writeLong(cache = value); + cached = true; + } + } + + long get() throws IOException { + return cached ? cache : readLong(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdMemoryBackend.java b/apps/jrobin/java/src/org/jrobin/core/RrdMemoryBackend.java new file mode 100644 index 000000000..9387c00cd --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdMemoryBackend.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Backend to be used to store all RRD bytes in memory.

+ */ +public class RrdMemoryBackend extends RrdBackend { + private static final ReadWriteLock m_readWritelock = new ReentrantReadWriteLock(); + private static final Lock m_readLock = m_readWritelock.readLock(); + private static final Lock m_writeLock = m_readWritelock.writeLock(); + + private byte[] buffer = new byte[0]; + + protected RrdMemoryBackend(String path) { + super(path); + } + + protected void write(final long offset, final byte[] b) { + m_writeLock.lock(); + try { + int pos = (int) offset; + for (final byte singleByte : b) { + buffer[pos++] = singleByte; + } + } finally { + m_writeLock.unlock(); + } + } + + protected void read(final long offset, final byte[] b) throws IOException { + m_readLock.lock(); + try { + int pos = (int) offset; + if (pos + b.length <= buffer.length) { + for (int i = 0; i < b.length; i++) { + b[i] = buffer[pos++]; + } + } + else { + throw new IOException("Not enough bytes available in memory " + getPath()); + } + } finally { + m_readLock.unlock(); + } + } + + /** + * Returns the number of RRD bytes held in memory. + * + * @return Number of all RRD bytes. + */ + public long getLength() { + m_readLock.lock(); + try { + return buffer.length; + } finally { + m_readLock.unlock(); + } + } + + /** + * Reserves a memory section as a RRD storage. + * + * @param newLength Number of bytes held in memory. + * @throws IOException Thrown in case of I/O error. + */ + protected void setLength(final long newLength) throws IOException { + m_writeLock.lock(); + try { + if (newLength > Integer.MAX_VALUE) { + throw new IOException("Cannot create this big memory backed RRD"); + } + buffer = new byte[(int) newLength]; + } finally { + m_writeLock.unlock(); + } + } + + /** + * This method is required by the base class definition, but it does not + * releases any memory resources at all. + */ + public void close() { + // NOP + } + + /** + * This method is overridden to disable high-level caching in frontend JRobin classes. + * + * @return Always returns false. There is no need to cache anything in high-level classes + * since all RRD bytes are already in memory. + */ + protected boolean isCachingAllowed() { + return false; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdMemoryBackendFactory.java b/apps/jrobin/java/src/org/jrobin/core/RrdMemoryBackendFactory.java new file mode 100644 index 000000000..e326deecd --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdMemoryBackendFactory.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * 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.core; + +import java.util.HashMap; + +/** + * Factory class which creates actual {@link RrdMemoryBackend} objects. JRobin's support + * for in-memory RRDs is still experimental. You should know that all active RrdMemoryBackend + * objects are held in memory, each backend object stores RRD data in one big byte array. This + * implementation is therefore quite basic and memory hungry but runs very fast. + *

+ * Calling {@link RrdDb#close() close()} on RrdDb objects does not release any memory at all + * (RRD data must be available for the next new RrdDb(path) call. To release allocated + * memory, you'll have to call {@link #delete(String) delete(path)} method of this class. + */ +public class RrdMemoryBackendFactory extends RrdBackendFactory { + /** + * factory name, "MEMORY" + */ + public static final String NAME = "MEMORY"; + private HashMap backends = new HashMap(); + + /** + * Creates RrdMemoryBackend object. + * + * @param id Since this backend holds all data in memory, this argument is interpreted + * as an ID for this memory-based storage. + * @param readOnly This parameter is ignored + * @return RrdMemoryBackend object which handles all I/O operations + */ + protected synchronized RrdBackend open(String id, boolean readOnly) { + RrdMemoryBackend backend; + if (backends.containsKey(id)) { + backend = backends.get(id); + } + else { + backend = new RrdMemoryBackend(id); + backends.put(id, backend); + } + return backend; + } + + /** + * Method to determine if a memory storage with the given ID already exists. + * + * @param id Memory storage ID. + * @return True, if such storage exists, false otherwise. + */ + protected synchronized boolean exists(String id) { + return backends.containsKey(id); + } + + /** + * Removes the storage with the given ID from the memory. + * + * @param id Storage ID + * @return True, if the storage with the given ID is deleted, false otherwise. + */ + public boolean delete(String id) { + if (backends.containsKey(id)) { + backends.remove(id); + return true; + } + return false; + } + + /** + * Returns the name of this factory. + * + * @return Factory name (equals to "MEMORY"). + */ + public String getFactoryName() { + return NAME; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdNioBackend.java b/apps/jrobin/java/src/org/jrobin/core/RrdNioBackend.java new file mode 100644 index 000000000..a88dbfdc7 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdNioBackend.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; + + +import sun.nio.ch.DirectBuffer; + +/** + * JRobin backend which is used to store RRD data to ordinary disk files by + * using fast java.nio.* package. This is the default backend engine since + * JRobin 1.4.0. + */ +@SuppressWarnings("restriction") +public class RrdNioBackend extends RrdFileBackend { + private final SyncManager m_syncManager; + private MappedByteBuffer m_byteBuffer = null; + + /** + * Creates RrdFileBackend object for the given file path, backed by + * java.nio.* classes. This constructor will create a + * {@link SyncManager} for each instance, which is very inefficient. + * It is recommended that you instead use the + * {@link #RrdNioBackend(String, boolean, SyncManager)} + * constructor instead. + * + * @param path + * Path to a JRB file. + * @param readOnly + * True, if file should be open in a read-only mode. False + * otherwise + * @param syncPeriod + * How often (in seconds) to sync MMAP'd RRD data to disk + * @throws IOException + * Thrown in case of I/O error + */ + protected RrdNioBackend(final String path, final boolean readOnly, final int syncPeriod) throws IOException { + this(path, readOnly, new SyncManager(syncPeriod)); + } + + /** + * Creates RrdFileBackend object for the given file path, backed by + * java.nio.* classes. + * + * @param path + * Path to a file + * @param readOnly + * True, if file should be open in a read-only mode. False + * otherwise. + * @param syncManager + * An object for managing synchronization of NIO-backed RRDs, + * generally owned by the backend factory. If null, MMAP'd + * data will only be synchronized to disk upon unmap. Note + * that if the file is opened read-only, the SyncManager is + * ignored. {@link RrdNioBackend#unmapFile() unmapFile()} + * @throws IOException + * Thrown in case of I/O error + */ + protected RrdNioBackend(final String path, final boolean readOnly, final SyncManager syncManager) throws IOException { + super(path, readOnly); + m_syncManager = syncManager; + + try { + mapFile(); + } catch (final IOException ioe) { + stopSchedule(); + super.close(); + throw ioe; + } + } + + private void mapFile() throws IOException { + if (!isReadOnly()) { + startSchedule(); + } + final long length = getLength(); + if (length > 0) { + final FileChannel.MapMode mapMode = isReadOnly() ? FileChannel.MapMode.READ_ONLY : FileChannel.MapMode.READ_WRITE; + m_byteBuffer = file.getChannel().map(mapMode, 0, length); + } + } + + private void unmapFile() { + if (!isReadOnly()) { + stopSchedule(); + } + if (m_byteBuffer != null) { + if (m_byteBuffer instanceof DirectBuffer) { + ((DirectBuffer) m_byteBuffer).cleaner().clean(); + } + m_byteBuffer = null; + } + } + + private void startSchedule() { + if (m_syncManager != null) { + m_syncManager.add(this); + } + } + + private synchronized void stopSchedule() { + if (m_syncManager != null) { + m_syncManager.remove(this); + } + sync(); + } + + @Override + protected void finalize() throws Throwable { + stopSchedule(); + super.finalize(); + } + + /** + * Sets length of the underlying RRD file. This method is called only + * once, immediately after a new RRD file gets created. + * + * @param newLength + * Length of the RRD file + * @throws IOException + * Thrown in case of I/O error. + */ + protected synchronized void setLength(final long newLength) throws IOException { + unmapFile(); + super.setLength(newLength); + mapFile(); + } + + /** + * Writes bytes to the underlying RRD file on the disk + * + * @param offset + * Starting file offset + * @param b + * Bytes to be written. + */ + protected synchronized void write(final long offset, final byte[] b) throws IOException { + if (m_byteBuffer != null) { + m_byteBuffer.position((int) offset); + m_byteBuffer.put(b); + } else { + throw new IOException("Write failed, file " + getPath() + " not mapped for I/O"); + } + } + + /** + * Reads a number of bytes from the RRD file on the disk + * + * @param offset + * Starting file offset + * @param b + * Buffer which receives bytes read from the file. + */ + protected synchronized void read(final long offset, final byte[] b) throws IOException { + if (m_byteBuffer != null) { + m_byteBuffer.position((int) offset); + m_byteBuffer.get(b); + } else { + throw new IOException("Read failed, file " + getPath() + " not mapped for I/O"); + } + } + + /** + * Closes the underlying RRD file. + * + * @throws IOException + * Thrown in case of I/O error + */ + public synchronized void close() throws IOException { + // cancel synchronization + try { + unmapFile(); + } finally { + super.close(); + } + } + + /** + * This method forces all data cached in memory but not yet stored in the + * file, to be stored in it. + */ + protected synchronized void sync() { + if (m_byteBuffer != null) { + m_byteBuffer.force(); + } + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdNioBackendFactory.java b/apps/jrobin/java/src/org/jrobin/core/RrdNioBackendFactory.java new file mode 100644 index 000000000..2855cf374 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdNioBackendFactory.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Factory class which creates actual {@link RrdNioBackend} objects. This is + * the default factory since 1.4.0 version + */ +public class RrdNioBackendFactory extends RrdFileBackendFactory { + /** + * Period in seconds between consecutive synchronizations when sync-mode + * is set to SYNC_BACKGROUND. By default in-memory cache will be + * transferred to the disc every 300 seconds (5 minutes). Default value + * can be changed via {@link #setSyncPeriod(int)} method. + */ + public static final int DEFAULT_SYNC_PERIOD = 300; // seconds + + private static SyncManager s_syncManager = new SyncManager(DEFAULT_SYNC_PERIOD); + + /** + * factory name, "NIO" + */ + public static final String NAME = "NIO"; + + /** + * Returns time between two consecutive background synchronizations. If + * not changed via {@link #setSyncPeriod(int)} method call, defaults to + * {@link #DEFAULT_SYNC_PERIOD}. See {@link #setSyncPeriod(int)} for more + * information. + * + * @return Time in seconds between consecutive background + * synchronizations. + */ + public static int getSyncPeriod() { + return s_syncManager.getSyncPeriod(); + } + + /** + * Sets time between consecutive background synchronizations. + * + * @param syncPeriod + * Time in seconds between consecutive background + * synchronizations. + */ + public synchronized static void setSyncPeriod(final int syncPeriod) { + s_syncManager.setSyncPeriod(syncPeriod); + } + + /** + * Creates RrdNioBackend object for the given file path. + * + * @param path + * File path + * @param readOnly + * True, if the file should be accessed in read/only mode. + * False otherwise. + * @return RrdNioBackend object which handles all I/O operations for the + * given file path + * @throws IOException + * Thrown in case of I/O error. + */ + protected RrdBackend open(final String path, final boolean readOnly) throws IOException { + return new RrdNioBackend(path, readOnly, s_syncManager); + } + + public void shutdown() { + s_syncManager.shutdown(); + } + + /** + * Returns the name of this factory. + * + * @return Factory name (equals to string "NIO") + */ + public String getFactoryName() { + return NAME; + } + + @Override + protected void finalize() throws Throwable { + shutdown(); + super.finalize(); + } + + SyncManager getSyncManager() { + return s_syncManager; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdNioByteBufferBackend.java b/apps/jrobin/java/src/org/jrobin/core/RrdNioByteBufferBackend.java new file mode 100644 index 000000000..b9dd5fca7 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdNioByteBufferBackend.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * JRobin backend which is used to store RRD data to ordinary disk files + * by using fast java.nio.* package. This is the default backend engine since JRobin 1.4.0. + */ +public class RrdNioByteBufferBackend extends RrdFileBackend { + + private ByteBuffer m_byteBuffer; + + private FileChannel m_ch; + + private static final ReadWriteLock m_readWritelock = new ReentrantReadWriteLock(); + private static final Lock m_readLock = m_readWritelock.readLock(); + private static final Lock m_writeLock = m_readWritelock.writeLock(); + + /** + * Creates RrdFileBackend object for the given file path, backed by java.nio.* classes. + * + * @param path Path to a file + * @param readOnly True, if file should be open in a read-only mode. False otherwise + * @throws IOException Thrown in case of I/O error + */ + protected RrdNioByteBufferBackend(final String path, final boolean readOnly) throws IOException, IllegalStateException { + super(path, readOnly); + + if (file != null) { + m_ch = file.getChannel(); + m_byteBuffer = ByteBuffer.allocate((int) m_ch.size()); + m_ch.read(m_byteBuffer, 0); + } else { + throw new IllegalStateException("File in base class is null."); + } + } + + /** + * Sets length of the underlying RRD file. This method is called only once, immediately + * after a new RRD file gets created. + * + * @param newLength Length of the RRD file + * @throws IOException Thrown in case of I/O error. + */ + @Override + protected void setLength(final long newLength) throws IOException { + m_writeLock.lock(); + try { + super.setLength(newLength); + m_ch = file.getChannel(); + m_byteBuffer = ByteBuffer.allocate((int) newLength); + m_ch.read(m_byteBuffer, 0); + m_byteBuffer.position(0); + } finally { + m_writeLock.unlock(); + } + } + + /** + * Writes bytes to the underlying RRD file on the disk + * + * @param offset Starting file offset + * @param b Bytes to be written. + */ + @Override + protected void write(final long offset, final byte[] b) { + m_writeLock.lock(); + try { + m_byteBuffer.position((int) offset); + m_byteBuffer.put(b); + } finally { + m_writeLock.unlock(); + } + } + + /** + * Reads a number of bytes from the RRD file on the disk + * + * @param offset Starting file offset + * @param b Buffer which receives bytes read from the file. + */ + @Override + protected void read(final long offset, final byte[] b) { + m_readLock.lock(); + try { + m_byteBuffer.position((int) offset); + m_byteBuffer.get(b); + } finally { + m_readLock.unlock(); + } + } + + /** + * Closes the underlying RRD file. + * + * @throws IOException Thrown in case of I/O error + */ + public void close() throws IOException { + m_writeLock.lock(); + try { + m_byteBuffer.position(0); + + if (!isReadOnly()) m_ch.write(m_byteBuffer, 0); + //just calling close here because the super calls close + //on the File object and Java calls close on the channel + super.close(); + } finally { + m_writeLock.unlock(); + } + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdNioByteBufferBackendFactory.java b/apps/jrobin/java/src/org/jrobin/core/RrdNioByteBufferBackendFactory.java new file mode 100644 index 000000000..9f63fdfca --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdNioByteBufferBackendFactory.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Factory class which creates actual {@link RrdNioBackend} objects. + */ +public class RrdNioByteBufferBackendFactory extends RrdFileBackendFactory { + + public static final String NAME = "MNIO"; + + /** + * Creates RrdNioByteBufferBackend object for the given file path. + * + * @param path File path + * @param readOnly True, if the file should be accessed in read/only mode. + * False otherwise. + * @return RrdNioBackend object which handles all I/O operations for the given file path + * @throws IOException Thrown in case of I/O error. + */ + @Override + protected RrdBackend open(String path, boolean readOnly) throws IOException { + return new RrdNioByteBufferBackend(path, readOnly); + } + + /** + * Returns the name of this factory. + * + * @return Factory name (equals to string "NIOBB") + */ + @Override + public String getFactoryName() { + return NAME; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdPrimitive.java b/apps/jrobin/java/src/org/jrobin/core/RrdPrimitive.java new file mode 100644 index 000000000..714804010 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdPrimitive.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +abstract class RrdPrimitive { + static final int STRING_LENGTH = 20; + static final int RRD_INT = 0, RRD_LONG = 1, RRD_DOUBLE = 2, RRD_STRING = 3; + static final int[] RRD_PRIM_SIZES = {4, 8, 8, 2 * STRING_LENGTH}; + + private RrdBackend backend; + private int byteCount; + private final long pointer; + private final boolean cachingAllowed; + + RrdPrimitive(final RrdUpdater updater, final int type, final boolean isConstant) throws IOException { + this(updater, type, 1, isConstant); + } + + RrdPrimitive(final RrdUpdater updater, final int type, final int count, final boolean isConstant) throws IOException { + this.backend = updater.getRrdBackend(); + this.byteCount = RRD_PRIM_SIZES[type] * count; + this.pointer = updater.getRrdAllocator().allocate(byteCount); + this.cachingAllowed = isConstant || backend.isCachingAllowed(); + } + + final byte[] readBytes() throws IOException { + final byte[] b = new byte[byteCount]; + backend.read(pointer, b); + return b; + } + + final void writeBytes(final byte[] b) throws IOException { + assert b.length == byteCount: "Invalid number of bytes supplied to RrdPrimitive.write method"; + backend.write(pointer, b); + } + + final int readInt() throws IOException { + return backend.readInt(pointer); + } + + final void writeInt(final int value) throws IOException { + backend.writeInt(pointer, value); + } + + final long readLong() throws IOException { + return backend.readLong(pointer); + } + + final void writeLong(final long value) throws IOException { + backend.writeLong(pointer, value); + } + + final double readDouble() throws IOException { + return backend.readDouble(pointer); + } + + final double readDouble(final int index) throws IOException { + final long offset = pointer + ((long)index * (long)RRD_PRIM_SIZES[RRD_DOUBLE]); + return backend.readDouble(offset); + } + + final double[] readDouble(final int index, final int count) throws IOException { + final long offset = pointer + ((long)index * (long)RRD_PRIM_SIZES[RRD_DOUBLE]); + return backend.readDouble(offset, count); + } + + final void writeDouble(final double value) throws IOException { + backend.writeDouble(pointer, value); + } + + final void writeDouble(final int index, final double value, final int count) throws IOException { + final long offset = pointer + ((long)index * (long)RRD_PRIM_SIZES[RRD_DOUBLE]); + backend.writeDouble(offset, value, count); + } + + final void writeDouble(final int index, final double[] values) throws IOException { + final long offset = pointer + ((long)index * (long)RRD_PRIM_SIZES[RRD_DOUBLE]); + backend.writeDouble(offset, values); + } + + final String readString() throws IOException { + return backend.readString(pointer); + } + + final void writeString(final String value) throws IOException { + backend.writeString(pointer, value); + } + + final boolean isCachingAllowed() { + return cachingAllowed; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdSafeFileBackend.java b/apps/jrobin/java/src/org/jrobin/core/RrdSafeFileBackend.java new file mode 100644 index 000000000..3be8bcb00 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdSafeFileBackend.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; +import java.nio.channels.FileLock; +import java.nio.channels.FileChannel; + +/** + * JRobin backend which is used to store RRD data to ordinary files on the disk. This backend + * is SAFE: it locks the underlying RRD file during update/fetch operations, and caches only static + * parts of a RRD file in memory. Therefore, this backend is safe to be used when RRD files should + * be shared between several JVMs at the same time. However, this backend is a little bit slow + * since it does not use fast java.nio.* package (it's still based on the RandomAccessFile class). + */ +public class RrdSafeFileBackend extends RrdFileBackend { + private static final Counters counters = new Counters(); + + private FileLock m_lock; + + /** + * Creates RrdFileBackend object for the given file path, backed by RandomAccessFile object. + * + * @param path Path to a file + * @param lockWaitTime lock waiting time in milliseconds + * @param lockRetryPeriod lock retry period in milliseconds + * @throws IOException Thrown in case of I/O error + */ + public RrdSafeFileBackend(final String path, final long lockWaitTime, final long lockRetryPeriod) throws IOException { + super(path, false); + try { + lockFile(lockWaitTime, lockRetryPeriod); + } + catch (final IOException ioe) { + super.close(); + throw ioe; + } + } + + private void lockFile(final long lockWaitTime, final long lockRetryPeriod) throws IOException { + final long entryTime = System.currentTimeMillis(); + final FileChannel channel = file.getChannel(); + m_lock = channel.tryLock(0, Long.MAX_VALUE, false); + if (m_lock != null) { + counters.registerQuickLock(); + return; + } + do { + try { + Thread.sleep(lockRetryPeriod); + } + catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + m_lock = channel.tryLock(0, Long.MAX_VALUE, false); + if (m_lock != null) { + counters.registerDelayedLock(); + return; + } + } while (System.currentTimeMillis() - entryTime <= lockWaitTime); + counters.registerError(); + throw new IOException("Could not obtain exclusive m_lock on file: " + getPath() + "] after " + lockWaitTime + " milliseconds"); + } + + public void close() throws IOException { + try { + if (m_lock != null) { + m_lock.release(); + m_lock = null; + counters.registerUnlock(); + } + } + finally { + super.close(); + } + } + + /** + * Defines the caching policy for this backend. + * + * @return false + */ + protected boolean isCachingAllowed() { + return false; + } + + public static String getLockInfo() { + return counters.getInfo(); + } + + static class Counters { + long locks, quickLocks, unlocks, locked, errors; + + synchronized void registerQuickLock() { + locks++; + quickLocks++; + locked++; + } + + synchronized void registerDelayedLock() { + locks++; + locked++; + } + + synchronized void registerUnlock() { + unlocks++; + locked--; + } + + synchronized void registerError() { + errors++; + } + + synchronized String getInfo() { + return "LOCKS=" + locks + ", " + "UNLOCKS=" + unlocks + ", " + + "DELAYED_LOCKS=" + (locks - quickLocks) + ", " + "LOCKED=" + locked + ", " + + "ERRORS=" + errors; + } + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdSafeFileBackendFactory.java b/apps/jrobin/java/src/org/jrobin/core/RrdSafeFileBackendFactory.java new file mode 100644 index 000000000..ae6fb4e9c --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdSafeFileBackendFactory.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +/** + * Factory class which creates actual {@link RrdSafeFileBackend} objects. + */ +public class RrdSafeFileBackendFactory extends RrdFileBackendFactory { + /** + * Default time (in milliseconds) this backend will wait for a file lock. + */ + public static final long LOCK_WAIT_TIME = 3000L; + private static long lockWaitTime = LOCK_WAIT_TIME; + + /** + * Default time between two consecutive file locking attempts. + */ + public static final long LOCK_RETRY_PERIOD = 50L; + private static long lockRetryPeriod = LOCK_RETRY_PERIOD; + + /** + * factory name, "SAFE" + */ + public static final String NAME = "SAFE"; + + /** + * Creates RrdSafeFileBackend object for the given file path. + * + * @param path File path + * @param readOnly This parameter is ignored + * @return RrdSafeFileBackend object which handles all I/O operations for the given file path + * @throws IOException Thrown in case of I/O error. + */ + protected RrdBackend open(String path, boolean readOnly) throws IOException { + return new RrdSafeFileBackend(path, lockWaitTime, lockRetryPeriod); + } + + /** + * Returns the name of this factory. + * + * @return Factory name (equals to string "SAFE") + */ + public String getFactoryName() { + return NAME; + } + + /** + * Returns time this backend will wait for a file lock. + * + * @return Time (in milliseconds) this backend will wait for a file lock. + */ + public static long getLockWaitTime() { + return lockWaitTime; + } + + /** + * Sets time this backend will wait for a file lock. + * + * @param lockWaitTime Maximum lock wait time (in milliseconds) + */ + public static void setLockWaitTime(long lockWaitTime) { + RrdSafeFileBackendFactory.lockWaitTime = lockWaitTime; + } + + /** + * Returns time between two consecutive file locking attempts. + * + * @return Time (im milliseconds) between two consecutive file locking attempts. + */ + public static long getLockRetryPeriod() { + return lockRetryPeriod; + } + + /** + * Sets time between two consecutive file locking attempts. + * + * @param lockRetryPeriod time (in milliseconds) between two consecutive file locking attempts. + */ + public static void setLockRetryPeriod(long lockRetryPeriod) { + RrdSafeFileBackendFactory.lockRetryPeriod = lockRetryPeriod; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdString.java b/apps/jrobin/java/src/org/jrobin/core/RrdString.java new file mode 100644 index 000000000..14be148e6 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdString.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +class RrdString extends RrdPrimitive { + private String cache; + + RrdString(final RrdUpdater updater, final boolean isConstant) throws IOException { + super(updater, RrdPrimitive.RRD_STRING, isConstant); + } + + RrdString(final RrdUpdater updater) throws IOException { + this(updater, false); + } + + void set(final String value) throws IOException { + if (!isCachingAllowed()) { + writeString(value); + } + // caching allowed + else if (cache == null || !cache.equals(value)) { + // update cache + writeString(cache = value); + } + } + + String get() throws IOException { + return (cache != null) ? cache : readString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdToolReader.java b/apps/jrobin/java/src/org/jrobin/core/RrdToolReader.java new file mode 100644 index 000000000..97f9c121e --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdToolReader.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +import org.jrobin.core.jrrd.RRDatabase; + +class RrdToolReader extends DataImporter { + private RRDatabase rrd; + + RrdToolReader(String rrdPath) throws IOException,RrdException { + rrd = new RRDatabase(rrdPath); + } + + String getVersion() { + return rrd.getHeader().getVersion(); + } + + long getLastUpdateTime() { + return Util.getTimestamp(rrd.getLastUpdate()); + } + + long getStep() { + return rrd.getHeader().getPDPStep(); + } + + int getDsCount() { + return rrd.getHeader().getDSCount(); + } + + int getArcCount() throws RrdException, IOException { + return rrd.getNumArchives(); + } + + String getDsName(int dsIndex) { + return rrd.getDataSource(dsIndex).getName(); + } + + String getDsType(int dsIndex) { + return rrd.getDataSource(dsIndex).getType().toString(); + } + + long getHeartbeat(int dsIndex) { + return rrd.getDataSource(dsIndex).getMinimumHeartbeat(); + } + + double getMinValue(int dsIndex) { + return rrd.getDataSource(dsIndex).getMinimum(); + } + + double getMaxValue(int dsIndex) { + return rrd.getDataSource(dsIndex).getMaximum(); + } + + double getLastValue(int dsIndex) { + String valueStr = rrd.getDataSource(dsIndex).getPDPStatusBlock().getLastReading(); + return Util.parseDouble(valueStr); + } + + double getAccumValue(int dsIndex) { + return rrd.getDataSource(dsIndex).getPDPStatusBlock().getValue(); + } + + long getNanSeconds(int dsIndex) { + return rrd.getDataSource(dsIndex).getPDPStatusBlock().getUnknownSeconds(); + } + + String getConsolFun(int arcIndex) { + return rrd.getArchive(arcIndex).getType().toString(); + } + + double getXff(int arcIndex) { + return rrd.getArchive(arcIndex).getXff(); + } + + int getSteps(int arcIndex) { + return rrd.getArchive(arcIndex).getPdpCount(); + } + + int getRows(int arcIndex) throws RrdException, IOException { + return rrd.getArchive(arcIndex).getRowCount(); + } + + double getStateAccumValue(int arcIndex, int dsIndex) throws RrdException, IOException { + return rrd.getArchive(arcIndex).getCDPStatusBlock(dsIndex).getValue(); + } + + int getStateNanSteps(int arcIndex, int dsIndex) throws RrdException, IOException { + return rrd.getArchive(arcIndex).getCDPStatusBlock(dsIndex).getUnknownDatapoints(); + } + + double[] getValues(int arcIndex, int dsIndex) throws RrdException, IOException,RrdException { + return rrd.getArchive(arcIndex).getValues()[dsIndex]; + } + + void release() throws IOException { + if (rrd != null) { + rrd.close(); + rrd = null; + } + } + + protected void finalize() throws Throwable { + super.finalize(); + release(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdToolkit.java b/apps/jrobin/java/src/org/jrobin/core/RrdToolkit.java new file mode 100644 index 000000000..6e68348d6 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdToolkit.java @@ -0,0 +1,658 @@ +/******************************************************************************* + * 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.core; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.LinkedList; +import java.util.Arrays; + +/** + * Class used to perform various complex operations on RRD files. Use an instance of the + * RrdToolkit class to: + *

+ *

+ * All these operations can be performed on the copy of the original RRD file, or on the + * original file itself (with possible backup file creation). + *

+ * IMPORTANT: NEVER use methods found in this class on 'live' RRD files + * (files which are currently in use). + */ +public class RrdToolkit { + /** + * Creates a new RRD file with one more datasource in it. RRD file is created based on the + * existing one (the original RRD file is not modified at all). All data from + * the original RRD file is copied to the new one. + * + * @param sourcePath path to a RRD file to import data from (will not be modified) + * @param destPath path to a new RRD file (will be created) + * @param newDatasource Datasource definition to be added to the new RRD file + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void addDatasource(String sourcePath, String destPath, DsDef newDatasource) + throws IOException, RrdException { + if (Util.sameFilePath(sourcePath, destPath)) { + throw new RrdException("Source and destination paths are the same"); + } + RrdDb rrdSource = new RrdDb(sourcePath); + try { + RrdDef rrdDef = rrdSource.getRrdDef(); + rrdDef.setPath(destPath); + rrdDef.addDatasource(newDatasource); + RrdDb rrdDest = new RrdDb(rrdDef); + try { + rrdSource.copyStateTo(rrdDest); + } + finally { + rrdDest.close(); + } + } + finally { + rrdSource.close(); + } + } + + /** + *

Adds one more datasource to a RRD file.

+ *

WARNING: This method is potentialy dangerous! It will modify your RRD file. + * It is highly recommended to preserve the original RRD file (saveBackup + * should be set to true). The backup file will be created in the same + * directory as the original one with .bak extension added to the + * original name.

+ *

Before applying this method, be sure that the specified RRD file is not in use + * (not open)

+ * + * @param sourcePath path to a RRD file to add datasource to. + * @param newDatasource Datasource definition to be added to the RRD file + * @param saveBackup true, if backup of the original file should be created; + * false, otherwise + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void addDatasource(String sourcePath, DsDef newDatasource, boolean saveBackup) + throws IOException, RrdException { + String destPath = Util.getTmpFilename(); + addDatasource(sourcePath, destPath, newDatasource); + copyFile(destPath, sourcePath, saveBackup); + } + + /** + * Creates a new RRD file with one datasource removed. RRD file is created based on the + * existing one (the original RRD file is not modified at all). All remaining data from + * the original RRD file is copied to the new one. + * + * @param sourcePath path to a RRD file to import data from (will not be modified) + * @param destPath path to a new RRD file (will be created) + * @param dsName Name of the Datasource to be removed from the new RRD file + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void removeDatasource(String sourcePath, String destPath, String dsName) + throws IOException, RrdException { + if (Util.sameFilePath(sourcePath, destPath)) { + throw new RrdException("Source and destination paths are the same"); + } + RrdDb rrdSource = new RrdDb(sourcePath); + try { + RrdDef rrdDef = rrdSource.getRrdDef(); + rrdDef.setPath(destPath); + rrdDef.removeDatasource(dsName); + RrdDb rrdDest = new RrdDb(rrdDef); + try { + rrdSource.copyStateTo(rrdDest); + } + finally { + rrdDest.close(); + } + } + finally { + rrdSource.close(); + } + } + + /** + *

Removes single datasource from a RRD file.

+ *

WARNING: This method is potentialy dangerous! It will modify your RRD file. + * It is highly recommended to preserve the original RRD file (saveBackup + * should be set to true). The backup file will be created in the same + * directory as the original one with .bak extension added to the + * original name.

+ *

Before applying this method, be sure that the specified RRD file is not in use + * (not open)

+ * + * @param sourcePath path to a RRD file to remove datasource from. + * @param dsName Name of the Datasource to be removed from the RRD file + * @param saveBackup true, if backup of the original file should be created; + * false, otherwise + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void removeDatasource(String sourcePath, String dsName, boolean saveBackup) + throws IOException, RrdException { + String destPath = Util.getTmpFilename(); + removeDatasource(sourcePath, destPath, dsName); + copyFile(destPath, sourcePath, saveBackup); + } + + /** + * Renames single datasource in the given RRD file. + * + * @param sourcePath Path to a RRD file + * @param oldDsName Old datasource name + * @param newDsName New datasource name + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error (invalid path or datasource names, + * for example) + */ + public static void renameDatasource(String sourcePath, String oldDsName, String newDsName) + throws IOException, RrdException { + RrdDb rrd = new RrdDb(sourcePath); + try { + if (rrd.containsDs(oldDsName)) { + Datasource datasource = rrd.getDatasource(oldDsName); + datasource.setDsName(newDsName); + } + else { + throw new RrdException("Could not find datasource [" + oldDsName + "] in file " + sourcePath); + } + } + finally { + rrd.close(); + } + } + + /** + * Updates single or all datasource names in the specified RRD file + * by appending '!' (if not already present). Datasources with names ending with '!' + * will never store NaNs in RRA archives (zero value will be used instead). Might be useful + * from time to time + * + * @param sourcePath Path to a RRD file + * @param dsName Datasource name or null if you want to rename all datasources + * @return Number of datasources successfully renamed + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error (invalid path or datasource name, + * for example) + */ + public static int forceZerosForNans(String sourcePath, String dsName) throws IOException, RrdException { + RrdDb rrd = new RrdDb(sourcePath); + try { + Datasource[] datasources; + if (dsName == null) { + datasources = rrd.getDatasources(); + } + else { + if (rrd.containsDs(dsName)) { + datasources = new Datasource[] {rrd.getDatasource(dsName)}; + } + else { + throw new RrdException("Could not find datasource [" + dsName + "] in file " + sourcePath); + } + } + int count = 0; + for (Datasource datasource : datasources) { + String currentDsName = datasource.getDsName(); + if (!currentDsName.endsWith(DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX)) { + datasource.setDsName(currentDsName + DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX); + count++; + } + } + return count; + } + finally { + rrd.close(); + } + } + + /** + * Creates a new RRD file with one more archive in it. RRD file is created based on the + * existing one (the original RRD file is not modified at all). All data from + * the original RRD file is copied to the new one. + * + * @param sourcePath path to a RRD file to import data from (will not be modified) + * @param destPath path to a new RRD file (will be created) + * @param newArchive Archive definition to be added to the new RRD file + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void addArchive(String sourcePath, String destPath, ArcDef newArchive) + throws IOException, RrdException { + if (Util.sameFilePath(sourcePath, destPath)) { + throw new RrdException("Source and destination paths are the same"); + } + RrdDb rrdSource = new RrdDb(sourcePath); + try { + RrdDef rrdDef = rrdSource.getRrdDef(); + rrdDef.setPath(destPath); + rrdDef.addArchive(newArchive); + RrdDb rrdDest = new RrdDb(rrdDef); + try { + rrdSource.copyStateTo(rrdDest); + } + finally { + rrdDest.close(); + } + } + finally { + rrdSource.close(); + } + } + + /** + *

Adds one more archive to a RRD file.

+ *

WARNING: This method is potentialy dangerous! It will modify your RRD file. + * It is highly recommended to preserve the original RRD file (saveBackup + * should be set to true). The backup file will be created in the same + * directory as the original one with .bak extension added to the + * original name.

+ *

Before applying this method, be sure that the specified RRD file is not in use + * (not open)

+ * + * @param sourcePath path to a RRD file to add datasource to. + * @param newArchive Archive definition to be added to the RRD file + * @param saveBackup true, if backup of the original file should be created; + * false, otherwise + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void addArchive(String sourcePath, ArcDef newArchive, boolean saveBackup) + throws IOException, RrdException { + String destPath = Util.getTmpFilename(); + addArchive(sourcePath, destPath, newArchive); + copyFile(destPath, sourcePath, saveBackup); + } + + /** + * Creates a new RRD file with one archive removed. RRD file is created based on the + * existing one (the original RRD file is not modified at all). All relevant data from + * the original RRD file is copied to the new one. + * + * @param sourcePath path to a RRD file to import data from (will not be modified) + * @param destPath path to a new RRD file (will be created) + * @param consolFun Consolidation function of Archive which should be removed + * @param steps Number of steps for Archive which should be removed + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void removeArchive(String sourcePath, String destPath, String consolFun, int steps) + throws IOException, RrdException { + if (Util.sameFilePath(sourcePath, destPath)) { + throw new RrdException("Source and destination paths are the same"); + } + RrdDb rrdSource = new RrdDb(sourcePath); + try { + RrdDef rrdDef = rrdSource.getRrdDef(); + rrdDef.setPath(destPath); + rrdDef.removeArchive(consolFun, steps); + RrdDb rrdDest = new RrdDb(rrdDef); + try { + rrdSource.copyStateTo(rrdDest); + } + finally { + rrdDest.close(); + } + } + finally { + rrdSource.close(); + } + } + + /** + *

Removes one archive from a RRD file.

+ *

WARNING: This method is potentialy dangerous! It will modify your RRD file. + * It is highly recommended to preserve the original RRD file (saveBackup + * should be set to true). The backup file will be created in the same + * directory as the original one with .bak extension added to the + * original name.

+ *

Before applying this method, be sure that the specified RRD file is not in use + * (not open)

+ * + * @param sourcePath path to a RRD file to add datasource to. + * @param consolFun Consolidation function of Archive which should be removed + * @param steps Number of steps for Archive which should be removed + * @param saveBackup true, if backup of the original file should be created; + * false, otherwise + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void removeArchive(String sourcePath, String consolFun, int steps, + boolean saveBackup) throws IOException, RrdException { + String destPath = Util.getTmpFilename(); + removeArchive(sourcePath, destPath, consolFun, steps); + copyFile(destPath, sourcePath, saveBackup); + } + + private static void copyFile(String sourcePath, String destPath, boolean saveBackup) + throws IOException { + File source = new File(sourcePath); + File dest = new File(destPath); + if (saveBackup) { + String backupPath = getBackupPath(destPath); + File backup = new File(backupPath); + deleteFile(backup); + if (!dest.renameTo(backup)) { + throw new IOException("Could not create backup file " + backupPath); + } + } + deleteFile(dest); + if (!source.renameTo(dest)) { + throw new IOException("Could not create file " + destPath + " from " + sourcePath); + } + } + + private static String getBackupPath(String destPath) { + StringBuffer sb = new StringBuffer(destPath); + do { + sb.append(".bak"); + } while (Util.fileExists(sb.toString())); + return sb.toString(); + } + + /** + * Sets datasource heartbeat to a new value. + * + * @param sourcePath Path to exisiting RRD file (will be updated) + * @param datasourceName Name of the datasource in the specified RRD file + * @param newHeartbeat New datasource heartbeat + * @throws RrdException Thrown in case of JRobin specific error + * @throws IOException Thrown in case of I/O error + */ + public static void setDsHeartbeat(String sourcePath, String datasourceName, + long newHeartbeat) throws RrdException, IOException { + RrdDb rrd = new RrdDb(sourcePath); + try { + Datasource ds = rrd.getDatasource(datasourceName); + ds.setHeartbeat(newHeartbeat); + } + finally { + rrd.close(); + } + } + + /** + * Sets datasource heartbeat to a new value. + * + * @param sourcePath Path to exisiting RRD file (will be updated) + * @param dsIndex Index of the datasource in the specified RRD file + * @param newHeartbeat New datasource heartbeat + * @throws RrdException Thrown in case of JRobin specific error + * @throws IOException Thrown in case of I/O error + */ + public static void setDsHeartbeat(String sourcePath, int dsIndex, long newHeartbeat) + throws RrdException, IOException { + RrdDb rrd = new RrdDb(sourcePath); + try { + Datasource ds = rrd.getDatasource(dsIndex); + ds.setHeartbeat(newHeartbeat); + } + finally { + rrd.close(); + } + } + + /** + * Sets datasource min value to a new value + * + * @param sourcePath Path to exisiting RRD file (will be updated) + * @param datasourceName Name of the datasource in the specified RRD file + * @param newMinValue New min value for the datasource + * @param filterArchivedValues set to true if archived values less than + * newMinValue should be set to NaN; set to false, otherwise. + * @throws RrdException Thrown in case of JRobin specific error + * @throws IOException Thrown in case of I/O error + */ + public static void setDsMinValue(String sourcePath, String datasourceName, + double newMinValue, boolean filterArchivedValues) throws RrdException, IOException { + RrdDb rrd = new RrdDb(sourcePath); + try { + Datasource ds = rrd.getDatasource(datasourceName); + ds.setMinValue(newMinValue, filterArchivedValues); + } + finally { + rrd.close(); + } + } + + /** + * Sets datasource max value to a new value. + * + * @param sourcePath Path to exisiting RRD file (will be updated) + * @param datasourceName Name of the datasource in the specified RRD file + * @param newMaxValue New max value for the datasource + * @param filterArchivedValues set to true if archived values greater than + * newMaxValue should be set to NaN; set to false, otherwise. + * @throws RrdException Thrown in case of JRobin specific error + * @throws IOException Thrown in case of I/O error + */ + public static void setDsMaxValue(String sourcePath, String datasourceName, + double newMaxValue, boolean filterArchivedValues) throws RrdException, IOException { + RrdDb rrd = new RrdDb(sourcePath); + try { + Datasource ds = rrd.getDatasource(datasourceName); + ds.setMaxValue(newMaxValue, filterArchivedValues); + } + finally { + rrd.close(); + } + } + + /** + * Updates valid value range for the given datasource. + * + * @param sourcePath Path to exisiting RRD file (will be updated) + * @param datasourceName Name of the datasource in the specified RRD file + * @param newMinValue New min value for the datasource + * @param newMaxValue New max value for the datasource + * @param filterArchivedValues set to true if archived values outside + * of the specified min/max range should be replaced with NaNs. + * @throws RrdException Thrown in case of JRobin specific error + * @throws IOException Thrown in case of I/O error + */ + public static void setDsMinMaxValue(String sourcePath, String datasourceName, + double newMinValue, double newMaxValue, boolean filterArchivedValues) + throws RrdException, IOException { + RrdDb rrd = new RrdDb(sourcePath); + try { + Datasource ds = rrd.getDatasource(datasourceName); + ds.setMinMaxValue(newMinValue, newMaxValue, filterArchivedValues); + } + finally { + rrd.close(); + } + } + + /** + * Sets single archive's X-files factor to a new value. + * + * @param sourcePath Path to existing RRD file (will be updated) + * @param consolFun Consolidation function of the target archive + * @param steps Number of sptes of the target archive + * @param newXff New X-files factor for the target archive + * @throws RrdException Thrown in case of JRobin specific error + * @throws IOException Thrown in case of I/O error + */ + public static void setArcXff(String sourcePath, String consolFun, int steps, + double newXff) throws RrdException, IOException { + RrdDb rrd = new RrdDb(sourcePath); + try { + Archive arc = rrd.getArchive(consolFun, steps); + arc.setXff(newXff); + } + finally { + rrd.close(); + } + } + + /** + * Creates new RRD file based on the existing one, but with a different + * size (number of rows) for a single archive. The archive to be resized + * is identified by its consolidation function and the number of steps. + * + * @param sourcePath Path to the source RRD file (will not be modified) + * @param destPath Path to the new RRD file (will be created) + * @param consolFun Consolidation function of the archive to be resized + * @param numSteps Number of steps of the archive to be resized + * @param newRows New archive size (number of archive rows) + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void resizeArchive(String sourcePath, String destPath, String consolFun, + int numSteps, int newRows) + throws IOException, RrdException { + if (Util.sameFilePath(sourcePath, destPath)) { + throw new RrdException("Source and destination paths are the same"); + } + if (newRows < 2) { + throw new RrdException("New arcihve size must be at least 2"); + } + RrdDb rrdSource = new RrdDb(sourcePath); + try { + RrdDef rrdDef = rrdSource.getRrdDef(); + ArcDef arcDef = rrdDef.findArchive(consolFun, numSteps); + if (arcDef.getRows() != newRows) { + arcDef.setRows(newRows); + rrdDef.setPath(destPath); + RrdDb rrdDest = new RrdDb(rrdDef); + try { + rrdSource.copyStateTo(rrdDest); + } + finally { + rrdDest.close(); + } + } + } + finally { + rrdSource.close(); + } + } + + /** + * Modifies existing RRD file, by resizing its chosen archive. The archive to be resized + * is identified by its consolidation function and the number of steps. + * + * @param sourcePath Path to the RRD file (will be modified) + * @param consolFun Consolidation function of the archive to be resized + * @param numSteps Number of steps of the archive to be resized + * @param newRows New archive size (number of archive rows) + * @param saveBackup true, if backup of the original file should be created; + * false, otherwise + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void resizeArchive(String sourcePath, String consolFun, + int numSteps, int newRows, boolean saveBackup) + throws IOException, RrdException { + String destPath = Util.getTmpFilename(); + resizeArchive(sourcePath, destPath, consolFun, numSteps, newRows); + copyFile(destPath, sourcePath, saveBackup); + } + + private static void deleteFile(File file) throws IOException { + if (file.exists() && !file.delete()) { + throw new IOException("Could not delete file: " + file.getCanonicalPath()); + } + } + + /** + * Splits single RRD file with several datasources into a number of smaller RRD files + * with a single datasource in it. All archived values are preserved. If + * you have a RRD file named 'traffic.rrd' with two datasources, 'in' and 'out', this + * method will create two files (with a single datasource, in the same directory) + * named 'in-traffic.rrd' and 'out-traffic.rrd'. + * + * @param sourcePath Path to a RRD file with multiple datasources defined + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin specific error + */ + public static void split(String sourcePath) throws IOException, RrdException { + RrdDb rrdSource = new RrdDb(sourcePath); + try { + String[] dsNames = rrdSource.getDsNames(); + for (String dsName : dsNames) { + RrdDef rrdDef = rrdSource.getRrdDef(); + rrdDef.setPath(createSplitPath(dsName, sourcePath)); + rrdDef.saveSingleDatasource(dsName); + RrdDb rrdDest = new RrdDb(rrdDef); + try { + rrdSource.copyStateTo(rrdDest); + } + finally { + rrdDest.close(); + } + } + } + finally { + rrdSource.close(); + } + } + + /** + * Returns list of canonical file names with the specified extension in the given directory. This + * method is not RRD related, but might come handy to create a quick list of all RRD files + * in the given directory. + * + * @param directory Source directory + * @param extension File extension (like ".rrd", ".jrb", ".rrd.jrb") + * @param resursive true if all subdirectories should be traversed for the same extension, false otherwise + * @return Array of sorted canonical file names with the given extension + * @throws IOException Thrown in case of I/O error + */ + public static String[] getCanonicalPaths(String directory, final String extension, boolean resursive) + throws IOException { + File baseDir = new File(directory); + if (!baseDir.isDirectory()) { + throw new IOException("Not a directory: " + directory); + } + List fileList = new LinkedList(); + traverseDirectory(new File(directory), extension, resursive, fileList); + String[] result = fileList.toArray(new String[fileList.size()]); + Arrays.sort(result); + return result; + } + + private static void traverseDirectory(File directory, String extension, boolean recursive, List list) + throws IOException { + File[] files = directory.listFiles(); + for (File file : files) { + if (file.isDirectory() && recursive) { + // traverse subdirectories only if recursive flag is specified + traverseDirectory(file, extension, recursive, list); + } + else if (file.isFile() && file.getName().endsWith(extension)) { + list.add(file.getCanonicalPath()); + } + } + } + + private static String createSplitPath(String dsName, String sourcePath) { + File file = new File(sourcePath); + String newName = dsName + "-" + file.getName(); + String path = file.getAbsolutePath(); + String parentDir = path.substring(0, 1 + path.lastIndexOf(Util.getFileSeparator())); + return parentDir + newName; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/RrdUpdater.java b/apps/jrobin/java/src/org/jrobin/core/RrdUpdater.java new file mode 100644 index 000000000..065cec1b0 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/RrdUpdater.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; + +interface RrdUpdater { + public RrdBackend getRrdBackend(); + + public void copyStateTo(RrdUpdater updater) throws IOException, RrdException; + + public RrdAllocator getRrdAllocator(); +} diff --git a/apps/jrobin/java/src/org/jrobin/core/Sample.java b/apps/jrobin/java/src/org/jrobin/core/Sample.java new file mode 100644 index 000000000..43212d8f6 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/Sample.java @@ -0,0 +1,295 @@ +/******************************************************************************* + * 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.core; + +import java.io.IOException; +import java.util.Date; +import java.util.StringTokenizer; + +/** + * Class to represent data source values for the given timestamp. Objects of this + * class are never created directly (no public constructor is provided). To learn more how + * to update RRDs, see RRDTool's + * rrdupdate man page. + *

+ * To update a RRD with JRobin use the following procedure: + *

+ *

    + *
  1. Obtain empty Sample object by calling method {@link RrdDb#createSample(long) + * createSample()} on respective {@link RrdDb RrdDb} object. + *
  2. Adjust Sample timestamp if necessary (see {@link #setTime(long) setTime()} method). + *
  3. Supply data source values (see {@link #setValue(String, double) setValue()}). + *
  4. Call Sample's {@link #update() update()} method. + *
+ *

+ * Newly created Sample object contains all data source values set to 'unknown'. + * You should specifify only 'known' data source values. However, if you want to specify + * 'unknown' values too, use Double.NaN. + * + * @author Sasa Markovic + */ +public class Sample { + private RrdDb parentDb; + private long time; + private String[] dsNames; + private double[] values; + + Sample(final RrdDb parentDb, final long time) throws IOException { + this.parentDb = parentDb; + this.time = time; + this.dsNames = parentDb.getDsNames(); + values = new double[dsNames.length]; + clearCurrentValues(); + } + + private Sample clearCurrentValues() { + for (int i = 0; i < values.length; i++) { + values[i] = Double.NaN; + } + return this; + } + + /** + * Sets single data source value in the sample. + * + * @param dsName Data source name. + * @param value Data source value. + * @return This Sample object + * @throws RrdException Thrown if invalid data source name is supplied. + */ + public Sample setValue(final String dsName, final double value) throws RrdException { + for (int i = 0; i < values.length; i++) { + if (dsNames[i].equals(dsName)) { + values[i] = value; + return this; + } + } + throw new RrdException("Datasource " + dsName + " not found"); + } + + /** + * Sets single datasource value using data source index. Data sources are indexed by + * the order specified during RRD creation (zero-based). + * + * @param i Data source index + * @param value Data source values + * @return This Sample object + * @throws RrdException Thrown if data source index is invalid. + */ + public Sample setValue(final int i, final double value) throws RrdException { + if (i < values.length) { + values[i] = value; + return this; + } + else { + throw new RrdException("Sample datasource index " + i + " out of bounds"); + } + } + + /** + * Sets some (possibly all) data source values in bulk. Data source values are + * assigned in the order of their definition inside the RRD. + * + * @param values Data source values. + * @return This Sample object + * @throws RrdException Thrown if the number of supplied values is zero or greater + * than the number of data sources defined in the RRD. + */ + public Sample setValues(final double[] values) throws RrdException { + if (values.length <= this.values.length) { + System.arraycopy(values, 0, this.values, 0, values.length); + return this; + } + else { + throw new RrdException("Invalid number of values specified (found " + values.length + ", only " + dsNames.length + " allowed)"); + } + } + + /** + * Returns all current data source values in the sample. + * + * @return Data source values. + */ + public double[] getValues() { + return values; + } + + /** + * Returns sample timestamp (in seconds, without milliseconds). + * + * @return Sample timestamp. + */ + public long getTime() { + return time; + } + + /** + * Sets sample timestamp. Timestamp should be defined in seconds (without milliseconds). + * + * @param time New sample timestamp. + * @return This Sample object + */ + public Sample setTime(final long time) { + this.time = time; + return this; + } + + /** + * Returns an array of all data source names. If you try to set value for the data source + * name not in this array, an exception is thrown. + * + * @return Acceptable data source names. + */ + public String[] getDsNames() { + return dsNames; + } + + /** + * Sets sample timestamp and data source values in a fashion similar to RRDTool. + * Argument string should be composed in the following way: + * timestamp:value1:value2:...:valueN. + *

+ * You don't have to supply all datasource values. Unspecified values will be treated + * as unknowns. To specify unknown value in the argument string, use letter 'U'. + * + * @param timeAndValues String made by concatenating sample timestamp with corresponding + * data source values delmited with colons. For example:

+ *

+	 *                      1005234132:12.2:35.6:U:24.5
+	 *                      NOW:12.2:35.6:U:24.5
+	 *                      
+ * 'N' stands for the current timestamp (can be replaced with 'NOW')

+ * Method will throw an exception if timestamp is invalid (cannot be parsed as Long, and is not 'N' + * or 'NOW'). Datasource value which cannot be parsed as 'double' will be silently set to NaN.

+ * @return This Sample object + * @throws RrdException Thrown if too many datasource values are supplied + */ + public Sample set(final String timeAndValues) throws RrdException { + final StringTokenizer tokenizer = new StringTokenizer(timeAndValues, ":", false); + final int tokenCount = tokenizer.countTokens(); + if (tokenCount > values.length + 1) { + throw new RrdException("Invalid number of values specified (found " + values.length + ", " + dsNames.length + " allowed)"); + } + final String timeToken = tokenizer.nextToken(); + try { + time = Long.parseLong(timeToken); + } + catch (final NumberFormatException nfe) { + if (timeToken.equalsIgnoreCase("N") || timeToken.equalsIgnoreCase("NOW")) { + time = Util.getTime(); + } + else { + throw new RrdException("Invalid sample timestamp: " + timeToken); + } + } + for (int i = 0; tokenizer.hasMoreTokens(); i++) { + try { + values[i] = Double.parseDouble(tokenizer.nextToken()); + } + catch (final NumberFormatException nfe) { + // NOP, value is already set to NaN + } + } + return this; + } + + /** + * Stores sample in the corresponding RRD. If the update operation succeedes, + * all datasource values in the sample will be set to Double.NaN (unknown) values. + * + * @throws IOException Thrown in case of I/O error. + * @throws RrdException Thrown in case of JRobin related error. + */ + public void update() throws IOException, RrdException { + parentDb.store(this); + clearCurrentValues(); + } + + /** + *

Creates sample with the timestamp and data source values supplied + * in the argument string and stores sample in the corresponding RRD. + * This method is just a shortcut for:

+ *
+	 *     set(timeAndValues);
+	 *     update();
+	 * 
+ * + * @param timeAndValues String made by concatenating sample timestamp with corresponding + * data source values delmited with colons. For example:
+ * 1005234132:12.2:35.6:U:24.5
+ * NOW:12.2:35.6:U:24.5 + * @throws IOException Thrown in case of I/O error. + * @throws RrdException Thrown in case of JRobin related error. + */ + public void setAndUpdate(final String timeAndValues) throws IOException, RrdException { + set(timeAndValues); + update(); + } + + /** + * Dumps sample content using the syntax of RRDTool's update command. + * + * @return Sample dump. + */ + public String dump() { + final StringBuffer buffer = new StringBuffer("update \""); + buffer.append(parentDb.getRrdBackend().getPath()).append("\" ").append(time); + for (final double value : values) { + buffer.append(":"); + buffer.append(Util.formatDouble(value, "U", false)); + } + return buffer.toString(); + } + + String getRrdToolCommand() { + return dump(); + } + + public String toString() { + return getClass().getSimpleName() + "@" + "[parentDb=" + parentDb + ",time=" + new Date(time * 1000L) + ",dsNames=[" + printList(dsNames) + "],values=[" + printList(values) + "]]"; + } + + private String printList(final Object[] dsNames) { + if (dsNames == null) return "null"; + final StringBuffer sb = new StringBuffer(); + for (int i = 0; i < dsNames.length; i++) { + if (i == dsNames.length - 1) { + sb.append(dsNames[i]); + } else { + sb.append(dsNames[i]).append(", "); + } + } + return sb.toString(); + } + + private String printList(final double[] values) { + if (values == null) return "null"; + final StringBuffer sb = new StringBuffer(); + for (int i = 0; i < values.length; i++) { + if (i == values.length - 1) { + sb.append(values[i]); + } else { + sb.append(values[i]).append(", "); + } + } + return sb.toString(); + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/core/SyncManager.java b/apps/jrobin/java/src/org/jrobin/core/SyncManager.java new file mode 100644 index 000000000..71bba51cb --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/SyncManager.java @@ -0,0 +1,82 @@ +package org.jrobin.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + + +public final class SyncManager { + private int m_syncPeriod = RrdNioBackendFactory.DEFAULT_SYNC_PERIOD; + private Timer m_timer = null; + private Map m_tasks = new HashMap(); + + public SyncManager(final int syncPeriod) { + m_syncPeriod = syncPeriod; + } + + public int getSyncPeriod() { + return m_syncPeriod; + } + + public void setSyncPeriod(final int syncPeriod) { + m_syncPeriod = syncPeriod; + synchronized(m_tasks) { + final Timer oldTimer = m_timer; + m_timer = new SyncTimer(); + for (final RrdNioBackend backend : m_tasks.keySet()) { + m_tasks.get(backend).cancel(); + scheduleTask(backend); + } + cancelTimer(oldTimer); + } + } + + public void add(final RrdNioBackend rrdNioBackend) { + synchronized(m_tasks) { + if (m_tasks.size() == 0) { + m_timer = new SyncTimer(); + } + scheduleTask(rrdNioBackend); + } + } + + public void remove(final RrdNioBackend rrdNioBackend) { + synchronized (m_tasks) { + final TimerTask oldTask = m_tasks.remove(rrdNioBackend); + if (oldTask != null) oldTask.cancel(); + if (m_tasks.size() == 0) { + cancelTimer(m_timer); + m_timer = null; + } + } + } + + public void shutdown() { + synchronized(m_tasks) { + for (final Map.Entry entry : m_tasks.entrySet()) { + entry.getValue().cancel(); + } + cancelTimer(m_timer); + } + } + + private void cancelTimer(final Timer timer) { + if (timer == null) return; + timer.cancel(); + timer.purge(); + } + + private void scheduleTask(final RrdNioBackend rrdNioBackend) { + final TimerTask task = new SyncTimerTask(rrdNioBackend); + if (m_tasks.containsKey(rrdNioBackend)) { + m_tasks.get(rrdNioBackend).cancel(); + } + m_tasks.put(rrdNioBackend, task); + m_timer.schedule(task, getSyncPeriod() * 1000L, getSyncPeriod() * 1000L); + } + + Timer getTimer() { + return m_timer; + } +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/core/SyncTimer.java b/apps/jrobin/java/src/org/jrobin/core/SyncTimer.java new file mode 100644 index 000000000..a9fcc3e1e --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/SyncTimer.java @@ -0,0 +1,12 @@ +package org.jrobin.core; + +import java.util.Timer; +import java.util.concurrent.atomic.AtomicInteger; + +public class SyncTimer extends Timer { + private static AtomicInteger m_serialNumber = new AtomicInteger(); + + public SyncTimer() { + super("SyncManager-" + m_serialNumber.getAndIncrement(), true); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/SyncTimerTask.java b/apps/jrobin/java/src/org/jrobin/core/SyncTimerTask.java new file mode 100644 index 000000000..2d7bf676a --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/SyncTimerTask.java @@ -0,0 +1,15 @@ +package org.jrobin.core; + +import java.util.TimerTask; + +public final class SyncTimerTask extends TimerTask { + private final RrdNioBackend m_rrdNioBackend; + + SyncTimerTask(final RrdNioBackend rrdNioBackend) { + m_rrdNioBackend = rrdNioBackend; + } + + @Override public void run() { + m_rrdNioBackend.sync(); + } +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/core/Util.java b/apps/jrobin/java/src/org/jrobin/core/Util.java new file mode 100644 index 000000000..567a70179 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/Util.java @@ -0,0 +1,757 @@ +/******************************************************************************* + * 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.core; + +import org.jrobin.core.timespec.TimeParser; +import org.jrobin.core.timespec.TimeSpec; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.awt.*; +import java.io.*; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +/** + * Class defines various utility functions used in JRobin. + * + * @author Sasa Markovic + */ +public class Util { + + public static final long MAX_LONG = Long.MAX_VALUE; + public static final long MIN_LONG = -Long.MAX_VALUE; + + public static final double MAX_DOUBLE = Double.MAX_VALUE; + public static final double MIN_DOUBLE = -Double.MAX_VALUE; + + // pattern RRDTool uses to format doubles in XML files + static final String PATTERN = "0.0000000000E00"; + // directory under $USER_HOME used for demo graphs storing + static final String JROBIN_DIR = "jrobin-demo"; + + static final DecimalFormat df; + + static { + df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ENGLISH); + df.applyPattern(PATTERN); + df.setPositivePrefix("+"); + } + + /** + * Converts an array of long primitives to an array of doubles. + * + * @param array input array of long values. + * @return Same array but with all values as double. + */ + public static double[] toDoubleArray(final long[] array) { + double[] values = new double[ array.length ]; + for (int i = 0; i < array.length; i++) { + values[i] = array[i]; + } + return values; + } + + /** + * Returns current timestamp in seconds (without milliseconds). Returned timestamp + * is obtained with the following expression: + *

+ * (System.currentTimeMillis() + 500L) / 1000L + * + * @return Current timestamp + */ + public static long getTime() { + return (System.currentTimeMillis() + 500L) / 1000L; + } + + /** + * Just an alias for {@link #getTime()} method. + * + * @return Current timestamp (without milliseconds) + */ + public static long getTimestamp() { + return getTime(); + } + + /** + * Rounds the given timestamp to the nearest whole "step". Rounded value is obtained + * from the following expression: + *

+ * timestamp - timestamp % step; + * + * @param timestamp Timestamp in seconds + * @param step Step in seconds + * @return "Rounded" timestamp + */ + public static long normalize(long timestamp, long step) { + return timestamp - timestamp % step; + } + + /** + * Returns the greater of two double values, but treats NaN as the smallest possible + * value. Note that Math.max() behaves differently for NaN arguments. + * + * @param x an argument + * @param y another argument + * @return the lager of arguments + */ + public static double max(double x, double y) { + return Double.isNaN(x) ? y : Double.isNaN(y) ? x : Math.max(x, y); + } + + /** + * Returns the smaller of two double values, but treats NaN as the greatest possible + * value. Note that Math.min() behaves differently for NaN arguments. + * + * @param x an argument + * @param y another argument + * @return the smaller of arguments + */ + public static double min(double x, double y) { + return Double.isNaN(x) ? y : Double.isNaN(y) ? x : Math.min(x, y); + } + + /** + * Calculates sum of two doubles, but treats NaNs as zeros. + * + * @param x First double + * @param y Second double + * @return Sum(x,y) calculated as Double.isNaN(x)? y: Double.isNaN(y)? x: x + y; + */ + public static double sum(double x, double y) { + return Double.isNaN(x) ? y : Double.isNaN(y) ? x : x + y; + } + + static String formatDouble(double x, String nanString, boolean forceExponents) { + if (Double.isNaN(x)) { + return nanString; + } + if (forceExponents) { + return df.format(x); + } + return "" + x; + } + + static String formatDouble(double x, boolean forceExponents) { + return formatDouble(x, "" + Double.NaN, forceExponents); + } + + /** + * Formats double as a string using exponential notation (RRDTool like). Used for debugging + * throught the project. + * + * @param x value to be formatted + * @return string like "+1.234567E+02" + */ + public static String formatDouble(double x) { + return formatDouble(x, true); + } + + /** + * Returns Date object for the given timestamp (in seconds, without + * milliseconds) + * + * @param timestamp Timestamp in seconds. + * @return Corresponding Date object. + */ + public static Date getDate(long timestamp) { + return new Date(timestamp * 1000L); + } + + /** + * Returns Calendar object for the given timestamp + * (in seconds, without milliseconds) + * + * @param timestamp Timestamp in seconds. + * @return Corresponding Calendar object. + */ + public static Calendar getCalendar(long timestamp) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(timestamp * 1000L); + return calendar; + } + + /** + * Returns Calendar object for the given Date object + * + * @param date Date object + * @return Corresponding Calendar object. + */ + public static Calendar getCalendar(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar; + } + + /** + * Returns timestamp (unix epoch) for the given Date object + * + * @param date Date object + * @return Corresponding timestamp (without milliseconds) + */ + public static long getTimestamp(final Date date) { + // round to whole seconds, ignore milliseconds + return (date.getTime() + 499L) / 1000L; + } + + /** + * Returns timestamp (unix epoch) for the given Calendar object + * + * @param gc Calendar object + * @return Corresponding timestamp (without milliseconds) + */ + public static long getTimestamp(final Calendar gc) { + return getTimestamp(gc.getTime()); + } + + /** + * Returns timestamp (unix epoch) for the given year, month, day, hour and minute. + * + * @param year Year + * @param month Month (zero-based) + * @param day Day in month + * @param hour Hour + * @param min Minute + * @return Corresponding timestamp + */ + public static long getTimestamp(final int year, final int month, final int day, final int hour, final int min) { + final Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(year, month, day, hour, min); + return Util.getTimestamp(calendar); + } + + /** + * Returns timestamp (unix epoch) for the given year, month and day. + * + * @param year Year + * @param month Month (zero-based) + * @param day Day in month + * @return Corresponding timestamp + */ + public static long getTimestamp(int year, int month, int day) { + return Util.getTimestamp(year, month, day, 0, 0); + } + + /** + * Parses at-style time specification and returns the corresponding timestamp. For example:

+ *

+	 * long t = Util.getTimestamp("now-1d");
+	 * 
+ * + * @param atStyleTimeSpec at-style time specification. For the complete explanation of the syntax + * allowed see RRDTool's rrdfetch man page.

+ * @return timestamp in seconds since epoch. + * @throws RrdException Thrown if invalid time specification is supplied. + */ + public static long getTimestamp(final String atStyleTimeSpec) throws RrdException { + final TimeSpec timeSpec = new TimeParser(atStyleTimeSpec).parse(); + return timeSpec.getTimestamp(); + } + + /** + * Parses two related at-style time specifications and returns corresponding timestamps. For example:

+ *

+	 * long[] t = Util.getTimestamps("end-1d","now");
+	 * 
+ * + * @param atStyleTimeSpec1 Starting at-style time specification. For the complete explanation of the syntax + * allowed see RRDTool's rrdfetch man page.

+ * @param atStyleTimeSpec2 Ending at-style time specification. For the complete explanation of the syntax + * allowed see RRDTool's rrdfetch man page.

+ * @return An array of two longs representing starting and ending timestamp in seconds since epoch. + * @throws RrdException Thrown if any input time specification is invalid. + */ + public static long[] getTimestamps(final String atStyleTimeSpec1, final String atStyleTimeSpec2) throws RrdException { + final TimeSpec timeSpec1 = new TimeParser(atStyleTimeSpec1).parse(); + final TimeSpec timeSpec2 = new TimeParser(atStyleTimeSpec2).parse(); + return TimeSpec.getTimestamps(timeSpec1, timeSpec2); + } + + /** + * Parses input string as a double value. If the value cannot be parsed, Double.NaN + * is returned (NumberFormatException is never thrown). + * + * @param valueStr String representing double value + * @return a double corresponding to the input string + */ + public static double parseDouble(final String valueStr) { + double value; + try { + value = Double.parseDouble(valueStr); + } + catch (final NumberFormatException nfe) { + value = Double.NaN; + } + return value; + } + + /** + * Checks if a string can be parsed as double. + * + * @param s Input string + * @return true if the string can be parsed as double, false otherwise + */ + public static boolean isDouble(final String s) { + try { + Double.parseDouble(s); + return true; + } + catch (final NumberFormatException nfe) { + return false; + } + } + + /** + * Parses input string as a boolean value. The parser is case insensitive. + * + * @param valueStr String representing boolean value + * @return true, if valueStr equals to 'true', 'on', 'yes', 'y' or '1'; + * false in all other cases. + */ + public static boolean parseBoolean(final String valueStr) { + return valueStr.equalsIgnoreCase("true") || + valueStr.equalsIgnoreCase("on") || + valueStr.equalsIgnoreCase("yes") || + valueStr.equalsIgnoreCase("y") || + valueStr.equalsIgnoreCase("1"); + } + + /** + * Parses input string as color. The color string should be of the form #RRGGBB (no alpha specified, + * opaque color) or #RRGGBBAA (alpa specified, transparent colors). Leading character '#' is + * optional. + * + * @param valueStr Input string, for example #FFAA24, #AABBCC33, 010203 or ABC13E4F + * @return Paint object + * @throws RrdException If the input string is not 6 or 8 characters long (without optional '#') + */ + public static Paint parseColor(final String valueStr) throws RrdException { + final String c = valueStr.startsWith("#") ? valueStr.substring(1) : valueStr; + if (c.length() != 6 && c.length() != 8) { + throw new RrdException("Invalid color specification: " + valueStr); + } + final String r = c.substring(0, 2), g = c.substring(2, 4), b = c.substring(4, 6); + if (c.length() == 6) { + return new Color(Integer.parseInt(r, 16), Integer.parseInt(g, 16), Integer.parseInt(b, 16)); + } + else { + final String a = c.substring(6); + return new Color(Integer.parseInt(r, 16), Integer.parseInt(g, 16), + Integer.parseInt(b, 16), Integer.parseInt(a, 16)); + } + } + + /** + * Returns file system separator string. + * + * @return File system separator ("/" on Unix, "\" on Windows) + */ + public static String getFileSeparator() { + return System.getProperty("file.separator"); + } + + /** + * Returns path to user's home directory. + * + * @return Path to users home directory, with file separator appended. + */ + public static String getUserHomeDirectory() { + return System.getProperty("user.home") + getFileSeparator(); + } + + /** + * Returns path to directory used for placement of JRobin demo graphs and creates it + * if necessary. + * + * @return Path to demo directory (defaults to $HOME/jrobin/) if directory exists or + * was successfully created. Null if such directory could not be created. + */ + public static String getJRobinDemoDirectory() { + final String homeDirPath = getUserHomeDirectory() + JROBIN_DIR + getFileSeparator(); + final File homeDirFile = new File(homeDirPath); + return (homeDirFile.exists() || homeDirFile.mkdirs()) ? homeDirPath : null; + } + + /** + * Returns full path to the file stored in the demo directory of JRobin + * + * @param filename Partial path to the file stored in the demo directory of JRobin + * (just name and extension, without parent directories) + * @return Full path to the file + */ + public static String getJRobinDemoPath(final String filename) { + final String demoDir = getJRobinDemoDirectory(); + if (demoDir != null) { + return demoDir + filename; + } + else { + return null; + } + } + + static boolean sameFilePath(final String path1, final String path2) throws IOException { + final File file1 = new File(path1); + final File file2 = new File(path2); + return file1.getCanonicalPath().equals(file2.getCanonicalPath()); + } + + static int getMatchingDatasourceIndex(final RrdDb rrd1, final int dsIndex, final RrdDb rrd2) throws IOException { + final String dsName = rrd1.getDatasource(dsIndex).getDsName(); + try { + return rrd2.getDsIndex(dsName); + } + catch (final RrdException e) { + return -1; + } + } + + static int getMatchingArchiveIndex(final RrdDb rrd1, final int arcIndex, final RrdDb rrd2) throws IOException { + final Archive archive = rrd1.getArchive(arcIndex); + final String consolFun = archive.getConsolFun(); + final int steps = archive.getSteps(); + try { + return rrd2.getArcIndex(consolFun, steps); + } + catch (final RrdException e) { + return -1; + } + } + + static String getTmpFilename() throws IOException { + return File.createTempFile("JROBIN_", ".tmp").getCanonicalPath(); + } + + static final String ISO_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; // ISO + + /** + * Creates Calendar object from a string. The string should represent + * either a long integer (UNIX timestamp in seconds without milliseconds, + * like "1002354657") or a human readable date string in the format "yyyy-MM-dd HH:mm:ss" + * (like "2004-02-25 12:23:45"). + * + * @param timeStr Input string + * @return Calendar object + */ + public static Calendar getCalendar(final String timeStr) { + // try to parse it as long + try { + return Util.getCalendar(Long.parseLong(timeStr)); + } + catch (final NumberFormatException nfe) { + // not a long timestamp, try to parse it as data + final SimpleDateFormat df = new SimpleDateFormat(ISO_DATE_FORMAT); + df.setLenient(false); + try { + return Util.getCalendar(df.parse(timeStr)); + } + catch (final ParseException pe) { + throw new IllegalArgumentException("Time/date not in " + ISO_DATE_FORMAT + " format: " + timeStr); + } + } + } + + /** + * Various DOM utility functions + */ + public static class Xml { + public static Node[] getChildNodes(final Node parentNode) { + return getChildNodes(parentNode, null); + } + + public static Node[] getChildNodes(final Node parentNode, final String childName) { + final ArrayList nodes = new ArrayList(); + final NodeList nodeList = parentNode.getChildNodes(); + for (int i = 0; i < nodeList.getLength(); i++) { + final Node node = nodeList.item(i); + if (childName == null || node.getNodeName().equals(childName)) { + nodes.add(node); + } + } + return nodes.toArray(new Node[0]); + } + + public static Node getFirstChildNode(final Node parentNode, final String childName) throws RrdException { + final Node[] childs = getChildNodes(parentNode, childName); + if (childs.length > 0) { + return childs[0]; + } + throw new RrdException("XML Error, no such child: " + childName); + } + + public static boolean hasChildNode(final Node parentNode, final String childName) { + final Node[] childs = getChildNodes(parentNode, childName); + return childs.length > 0; + } + + // -- Wrapper around getChildValue with trim + public static String getChildValue(final Node parentNode, final String childName) throws RrdException { + return getChildValue(parentNode, childName, true); + } + + public static String getChildValue(final Node parentNode, final String childName, final boolean trim) throws RrdException { + final NodeList children = parentNode.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + final Node child = children.item(i); + if (child.getNodeName().equals(childName)) { + return getValue(child, trim); + } + } + throw new RrdException("XML Error, no such child: " + childName); + } + + // -- Wrapper around getValue with trim + public static String getValue(final Node node) { + return getValue(node, true); + } + + public static String getValue(final Node node, final boolean trimValue) { + String value = null; + final Node child = node.getFirstChild(); + if (child != null) { + value = child.getNodeValue(); + if (value != null && trimValue) { + value = value.trim(); + } + } + return value; + } + + public static int getChildValueAsInt(final Node parentNode, final String childName) throws RrdException { + final String valueStr = getChildValue(parentNode, childName); + return Integer.parseInt(valueStr); + } + + public static int getValueAsInt(final Node node) { + return Integer.parseInt(getValue(node)); + } + + public static long getChildValueAsLong(final Node parentNode, final String childName) throws RrdException { + final String valueStr = getChildValue(parentNode, childName); + return Long.parseLong(valueStr); + } + + public static long getValueAsLong(final Node node) { + return Long.parseLong(getValue(node)); + } + + public static double getChildValueAsDouble(final Node parentNode, final String childName) throws RrdException { + return Util.parseDouble(getChildValue(parentNode, childName)); + } + + public static double getValueAsDouble(final Node node) { + return Util.parseDouble(getValue(node)); + } + + public static boolean getChildValueAsBoolean(final Node parentNode, final String childName) throws RrdException { + return Util.parseBoolean(getChildValue(parentNode, childName)); + } + + public static boolean getValueAsBoolean(final Node node) { + return Util.parseBoolean(getValue(node)); + } + + public static Element getRootElement(final InputSource inputSource) throws RrdException, IOException { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setNamespaceAware(false); + try { + final DocumentBuilder builder = factory.newDocumentBuilder(); + final Document doc = builder.parse(inputSource); + return doc.getDocumentElement(); + } + catch (final ParserConfigurationException e) { + throw new RrdException(e); + } + catch (final SAXException e) { + throw new RrdException(e); + } + } + + public static Element getRootElement(final String xmlString) throws RrdException, IOException { + return getRootElement(new InputSource(new StringReader(xmlString))); + } + + public static Element getRootElement(final File xmlFile) throws RrdException, IOException { + Reader reader = null; + try { + reader = new FileReader(xmlFile); + return getRootElement(new InputSource(reader)); + } + finally { + if (reader != null) { + reader.close(); + } + } + } + } + + private static long lastLap = System.currentTimeMillis(); + + /** + * Function used for debugging purposes and performance bottlenecks detection. + * Probably of no use for end users of JRobin. + * + * @return String representing time in seconds since last + * getLapTime() method call. + */ + public static String getLapTime() { + final long newLap = System.currentTimeMillis(); + final double seconds = (newLap - lastLap) / 1000.0; + lastLap = newLap; + return "[" + seconds + " sec]"; + } + + /** + * Returns the root directory of the JRobin distribution. Useful in some demo applications, + * probably of no use anywhere else. + *

+ * The function assumes that all JRobin .class files are placed under + * the <root>/classes subdirectory and that all jars (libraries) are placed in the + * <root>/lib subdirectory (the original JRobin directory structure). + * + * @return absolute path to JRobin's home directory + */ + public static String getJRobinHomeDirectory() { + final String className = Util.class.getName().replace('.', '/'); + String uri = Util.class.getResource("/" + className + ".class").toString(); + //System.out.println(uri); + if (uri.startsWith("file:/")) { + uri = uri.substring(6); + File file = new File(uri); + // let's go 5 steps backwards + for (int i = 0; i < 5; i++) { + file = file.getParentFile(); + } + uri = file.getAbsolutePath(); + } + else if (uri.startsWith("jar:file:/")) { + uri = uri.substring(9, uri.lastIndexOf('!')); + File file = new File(uri); + // let's go 2 steps backwards + for (int i = 0; i < 2; i++) { + file = file.getParentFile(); + } + uri = file.getAbsolutePath(); + } + else { + uri = null; + } + return uri; + } + + /** + * Compares two doubles but treats all NaNs as equal. + * In Java (by default) Double.NaN == Double.NaN always returns false + * + * @param x the first value + * @param y the second value + * @return true if x and y are both equal to Double.NaN, or if x == y. false otherwise + */ + public static boolean equal(final double x, final double y) { + return (Double.isNaN(x) && Double.isNaN(y)) || (x == y); + } + + /** + * Returns canonical file path for the given file path + * + * @param path Absolute or relative file path + * @return Canonical file path + * @throws IOException Thrown if canonical file path could not be resolved + */ + public static String getCanonicalPath(final String path) throws IOException { + return new File(path).getCanonicalPath(); + } + + /** + * Returns last modification time for the given file. + * + * @param file File object representing file on the disk + * @return Last modification time in seconds (without milliseconds) + */ + public static long getLastModified(final String file) { + return (new File(file).lastModified() + 500L) / 1000L; + } + + /** + * Checks if the file with the given file name exists + * + * @param filename File name + * @return true if file exists, false otherwise + */ + public static boolean fileExists(final String filename) { + return new File(filename).exists(); + } + + /** + * Finds max value for an array of doubles (NaNs are ignored). If all values in the array + * are NaNs, NaN is returned. + * + * @param values Array of double values + * @return max value in the array (NaNs are ignored) + */ + public static double max(final double[] values) { + double max = Double.NaN; + for (final double value : values) { + max = Util.max(max, value); + } + return max; + } + + /** + * Finds min value for an array of doubles (NaNs are ignored). If all values in the array + * are NaNs, NaN is returned. + * + * @param values Array of double values + * @return min value in the array (NaNs are ignored) + */ + public static double min(final double[] values) { + double min = Double.NaN; + for (final double value : values) { + min = Util.min(min, value); + } + return min; + } + + /** + * Equivalent of the C-style sprintf function. Sorry, it works only in Java5. + * + * @param format Format string + * @param args Arbitrary list of arguments + * @return Formatted string + */ + public static String sprintf(final String format, final Object ... args) { + final String fmt = format.replaceAll("([^%]|^)%([^a-zA-Z%]*)l(f|g|e)", "$1%$2$3"); + return String.format(fmt, args); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/XmlReader.java b/apps/jrobin/java/src/org/jrobin/core/XmlReader.java new file mode 100644 index 000000000..c373287bb --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/XmlReader.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * 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.core; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.io.File; +import java.io.IOException; + +class XmlReader extends DataImporter { + + private Element root; + private Node[] dsNodes, arcNodes; + + XmlReader(String xmlFilePath) throws IOException, RrdException { + root = Util.Xml.getRootElement(new File(xmlFilePath)); + dsNodes = Util.Xml.getChildNodes(root, "ds"); + arcNodes = Util.Xml.getChildNodes(root, "rra"); + } + + String getVersion() throws RrdException { + return Util.Xml.getChildValue(root, "version"); + } + + long getLastUpdateTime() throws RrdException { + return Util.Xml.getChildValueAsLong(root, "lastupdate"); + } + + long getStep() throws RrdException { + return Util.Xml.getChildValueAsLong(root, "step"); + } + + int getDsCount() { + return dsNodes.length; + } + + int getArcCount() { + return arcNodes.length; + } + + String getDsName(int dsIndex) throws RrdException { + return Util.Xml.getChildValue(dsNodes[dsIndex], "name"); + } + + String getDsType(int dsIndex) throws RrdException { + return Util.Xml.getChildValue(dsNodes[dsIndex], "type"); + } + + long getHeartbeat(int dsIndex) throws RrdException { + return Util.Xml.getChildValueAsLong(dsNodes[dsIndex], "minimal_heartbeat"); + } + + double getMinValue(int dsIndex) throws RrdException { + return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "min"); + } + + double getMaxValue(int dsIndex) throws RrdException { + return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "max"); + } + + double getLastValue(int dsIndex) throws RrdException { + return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "last_ds"); + } + + double getAccumValue(int dsIndex) throws RrdException { + return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "value"); + } + + long getNanSeconds(int dsIndex) throws RrdException { + return Util.Xml.getChildValueAsLong(dsNodes[dsIndex], "unknown_sec"); + } + + String getConsolFun(int arcIndex) throws RrdException { + return Util.Xml.getChildValue(arcNodes[arcIndex], "cf"); + } + + double getXff(int arcIndex) throws RrdException { + return Util.Xml.getChildValueAsDouble(arcNodes[arcIndex], "xff"); + } + + int getSteps(int arcIndex) throws RrdException { + return Util.Xml.getChildValueAsInt(arcNodes[arcIndex], "pdp_per_row"); + } + + double getStateAccumValue(int arcIndex, int dsIndex) throws RrdException { + Node cdpNode = Util.Xml.getFirstChildNode(arcNodes[arcIndex], "cdp_prep"); + Node[] dsNodes = Util.Xml.getChildNodes(cdpNode, "ds"); + return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "value"); + } + + int getStateNanSteps(int arcIndex, int dsIndex) throws RrdException { + Node cdpNode = Util.Xml.getFirstChildNode(arcNodes[arcIndex], "cdp_prep"); + Node[] dsNodes = Util.Xml.getChildNodes(cdpNode, "ds"); + return Util.Xml.getChildValueAsInt(dsNodes[dsIndex], "unknown_datapoints"); + } + + int getRows(int arcIndex) throws RrdException { + Node dbNode = Util.Xml.getFirstChildNode(arcNodes[arcIndex], "database"); + Node[] rows = Util.Xml.getChildNodes(dbNode, "row"); + return rows.length; + } + + double[] getValues(int arcIndex, int dsIndex) throws RrdException { + Node dbNode = Util.Xml.getFirstChildNode(arcNodes[arcIndex], "database"); + Node[] rows = Util.Xml.getChildNodes(dbNode, "row"); + double[] values = new double[rows.length]; + for (int i = 0; i < rows.length; i++) { + Node[] vNodes = Util.Xml.getChildNodes(rows[i], "v"); + Node vNode = vNodes[dsIndex]; + values[i] = Util.parseDouble(vNode.getFirstChild().getNodeValue().trim()); + } + return values; + } +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/core/XmlTemplate.java b/apps/jrobin/java/src/org/jrobin/core/XmlTemplate.java new file mode 100644 index 000000000..595bf68a2 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/XmlTemplate.java @@ -0,0 +1,342 @@ +/******************************************************************************* + * 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.core; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class used as a base class for various XML template related classes. Class provides + * methods for XML source parsing and XML tree traversing. XML source may have unlimited + * number of placeholders (variables) in the format ${variable_name}. + * Methods are provided to specify variable values at runtime. + * Note that this class has limited functionality: XML source gets parsed, and variable + * values are collected. You have to extend this class to do something more useful.

+ */ +public abstract class XmlTemplate { + private static final String PATTERN_STRING = "\\$\\{(\\w+)\\}"; + private static final Pattern PATTERN = Pattern.compile(PATTERN_STRING); + + protected Element root; + private HashMap valueMap = new HashMap(); + private HashSet validatedNodes = new HashSet(); + + protected XmlTemplate(InputSource xmlSource) throws IOException, RrdException { + root = Util.Xml.getRootElement(xmlSource); + } + + protected XmlTemplate(String xmlString) throws IOException, RrdException { + root = Util.Xml.getRootElement(xmlString); + } + + protected XmlTemplate(File xmlFile) throws IOException, RrdException { + root = Util.Xml.getRootElement(xmlFile); + } + + /** + * Removes all placeholder-value mappings. + */ + public void clearValues() { + valueMap.clear(); + } + + /** + * Sets value for a single XML template variable. Variable name should be specified + * without leading '${' and ending '}' placeholder markers. For example, for a placeholder + * ${start}, specify start for the name parameter. + * + * @param name variable name + * @param value value to be set in the XML template + */ + public void setVariable(String name, String value) { + valueMap.put(name, value); + } + + /** + * Sets value for a single XML template variable. Variable name should be specified + * without leading '${' and ending '}' placeholder markers. For example, for a placeholder + * ${start}, specify start for the name parameter. + * + * @param name variable name + * @param value value to be set in the XML template + */ + public void setVariable(String name, int value) { + valueMap.put(name, value); + } + + /** + * Sets value for a single XML template variable. Variable name should be specified + * without leading '${' and ending '}' placeholder markers. For example, for a placeholder + * ${start}, specify start for the name parameter. + * + * @param name variable name + * @param value value to be set in the XML template + */ + public void setVariable(String name, long value) { + valueMap.put(name, value); + } + + /** + * Sets value for a single XML template variable. Variable name should be specified + * without leading '${' and ending '}' placeholder markers. For example, for a placeholder + * ${start}, specify start for the name parameter. + * + * @param name variable name + * @param value value to be set in the XML template + */ + public void setVariable(String name, double value) { + valueMap.put(name, value); + } + + /** + * Sets value for a single XML template variable. Variable name should be specified + * without leading '${' and ending '}' placeholder markers. For example, for a placeholder + * ${start}, specify start for the name parameter. + * + * @param name variable name + * @param value value to be set in the XML template + */ + public void setVariable(String name, Color value) { + String r = byteToHex(value.getRed()); + String g = byteToHex(value.getGreen()); + String b = byteToHex(value.getBlue()); + String a = byteToHex(value.getAlpha()); + valueMap.put(name, "#" + r + g + b + a); + } + + private String byteToHex(int i) { + String s = Integer.toHexString(i); + while (s.length() < 2) { + s = "0" + s; + } + return s; + } + + /** + * Sets value for a single XML template variable. Variable name should be specified + * without leading '${' and ending '}' placeholder markers. For example, for a placeholder + * ${start}, specify start for the name parameter. + * + * @param name variable name + * @param value value to be set in the XML template + */ + public void setVariable(String name, Date value) { + setVariable(name, Util.getTimestamp(value)); + } + + /** + * Sets value for a single XML template variable. Variable name should be specified + * without leading '${' and ending '}' placeholder markers. For example, for a placeholder + * ${start}, specify start for the name parameter. + * + * @param name variable name + * @param value value to be set in the XML template + */ + public void setVariable(String name, Calendar value) { + setVariable(name, Util.getTimestamp(value)); + } + + /** + * Sets value for a single XML template variable. Variable name should be specified + * without leading '${' and ending '}' placeholder markers. For example, for a placeholder + * ${start}, specify start for the name parameter. + * + * @param name variable name + * @param value value to be set in the XML template + */ + public void setVariable(String name, boolean value) { + valueMap.put(name, "" + value); + } + + /** + * Searches the XML template to see if there are variables in there that + * will need to be set. + * + * @return True if variables were detected, false if not. + */ + public boolean hasVariables() { + return PATTERN.matcher(root.toString()).find(); + } + + /** + * Returns the list of variables that should be set in this template. + * + * @return List of variable names as an array of strings. + */ + public String[] getVariables() { + ArrayList list = new ArrayList(); + Matcher m = PATTERN.matcher(root.toString()); + + while (m.find()) { + String var = m.group(1); + if (!list.contains(var)) { + list.add(var); + } + } + + return list.toArray(new String[list.size()]); + } + + protected static Node[] getChildNodes(Node parentNode, String childName) { + return Util.Xml.getChildNodes(parentNode, childName); + } + + protected static Node[] getChildNodes(Node parentNode) { + return Util.Xml.getChildNodes(parentNode, null); + } + + protected static Node getFirstChildNode(Node parentNode, String childName) throws RrdException { + return Util.Xml.getFirstChildNode(parentNode, childName); + } + + protected boolean hasChildNode(Node parentNode, String childName) { + return Util.Xml.hasChildNode(parentNode, childName); + } + + protected String getChildValue(Node parentNode, String childName) throws RrdException { + return getChildValue(parentNode, childName, true); + } + + protected String getChildValue(Node parentNode, String childName, boolean trim) throws RrdException { + String value = Util.Xml.getChildValue(parentNode, childName, trim); + return resolveMappings(value); + } + + protected String getValue(Node parentNode) { + return getValue(parentNode, true); + } + + protected String getValue(Node parentNode, boolean trim) { + String value = Util.Xml.getValue(parentNode, trim); + return resolveMappings(value); + } + + private String resolveMappings(String templateValue) { + if (templateValue == null) { + return null; + } + Matcher matcher = PATTERN.matcher(templateValue); + StringBuffer result = new StringBuffer(); + int lastMatchEnd = 0; + while (matcher.find()) { + String var = matcher.group(1); + if (valueMap.containsKey(var)) { + // mapping found + result.append(templateValue.substring(lastMatchEnd, matcher.start())); + result.append(valueMap.get(var).toString()); + lastMatchEnd = matcher.end(); + } + else { + // no mapping found - this is illegal + // throw runtime exception + throw new IllegalArgumentException("No mapping found for template variable ${" + var + "}"); + } + } + result.append(templateValue.substring(lastMatchEnd)); + return result.toString(); + } + + protected int getChildValueAsInt(Node parentNode, String childName) throws RrdException { + String valueStr = getChildValue(parentNode, childName); + return Integer.parseInt(valueStr); + } + + protected int getValueAsInt(Node parentNode) { + String valueStr = getValue(parentNode); + return Integer.parseInt(valueStr); + } + + protected long getChildValueAsLong(Node parentNode, String childName) throws RrdException { + String valueStr = getChildValue(parentNode, childName); + return Long.parseLong(valueStr); + } + + protected long getValueAsLong(Node parentNode) { + String valueStr = getValue(parentNode); + return Long.parseLong(valueStr); + } + + protected double getChildValueAsDouble(Node parentNode, String childName) throws RrdException { + String valueStr = getChildValue(parentNode, childName); + return Util.parseDouble(valueStr); + } + + protected double getValueAsDouble(Node parentNode) { + String valueStr = getValue(parentNode); + return Util.parseDouble(valueStr); + } + + protected boolean getChildValueAsBoolean(Node parentNode, String childName) throws RrdException { + String valueStr = getChildValue(parentNode, childName); + return Util.parseBoolean(valueStr); + } + + protected boolean getValueAsBoolean(Node parentNode) { + String valueStr = getValue(parentNode); + return Util.parseBoolean(valueStr); + } + + protected Paint getValueAsColor(Node parentNode) throws RrdException { + String rgbStr = getValue(parentNode); + return Util.parseColor(rgbStr); + } + + protected boolean isEmptyNode(Node node) { + // comment node or empty text node + return node.getNodeName().equals("#comment") || + (node.getNodeName().equals("#text") && node.getNodeValue().trim().length() == 0); + } + + protected void validateTagsOnlyOnce(Node parentNode, String[] allowedChildNames) throws RrdException { + // validate node only once + if (validatedNodes.contains(parentNode)) { + return; + } + Node[] childs = getChildNodes(parentNode); + main: + for (Node child : childs) { + String childName = child.getNodeName(); + for (int j = 0; j < allowedChildNames.length; j++) { + if (allowedChildNames[j].equals(childName)) { + // only one such tag is allowed + allowedChildNames[j] = "<--removed-->"; + continue main; + } + else if (allowedChildNames[j].equals(childName + "*")) { + // several tags allowed + continue main; + } + } + if (!isEmptyNode(child)) { + throw new RrdException("Unexpected tag encountered: <" + childName + ">"); + } + } + // everything is OK + validatedNodes.add(parentNode); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/XmlWriter.java b/apps/jrobin/java/src/org/jrobin/core/XmlWriter.java new file mode 100644 index 000000000..bda0ea080 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/XmlWriter.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * 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.core; + +import java.awt.*; +import java.io.File; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Stack; + +/** + * Extremely simple utility class used to create XML documents. + */ +public class XmlWriter { + static final String INDENT_STR = " "; + + private PrintWriter writer; + private StringBuffer indent = new StringBuffer(""); + private Stack openTags = new Stack(); + + /** + * Creates XmlWriter with the specified output stream to send XML code to. + * + * @param stream Output stream which receives XML code + */ + public XmlWriter(OutputStream stream) { + writer = new PrintWriter(stream, true); + } + + /** + * Opens XML tag + * + * @param tag XML tag name + */ + public void startTag(String tag) { + writer.println(indent + "<" + tag + ">"); + openTags.push(tag); + indent.append(INDENT_STR); + } + + /** + * Closes the corresponding XML tag + */ + public void closeTag() { + String tag = openTags.pop(); + indent.setLength(indent.length() - INDENT_STR.length()); + writer.println(indent + ""); + } + + /** + * Writes <tag>value</tag> to output stream + * + * @param tag XML tag name + * @param value value to be placed between <tag> and </tag> + */ + public void writeTag(String tag, Object value) { + if (value != null) { + writer.println(indent + "<" + tag + ">" + + escape(value.toString()) + ""); + } + else { + writer.println(indent + "<" + tag + ">"); + } + } + + /** + * Writes <tag>value</tag> to output stream + * + * @param tag XML tag name + * @param value value to be placed between <tag> and </tag> + */ + public void writeTag(String tag, int value) { + writeTag(tag, "" + value); + } + + /** + * Writes <tag>value</tag> to output stream + * + * @param tag XML tag name + * @param value value to be placed between <tag> and </tag> + */ + public void writeTag(String tag, long value) { + writeTag(tag, "" + value); + } + + /** + * Writes <tag>value</tag> to output stream + * + * @param tag XML tag name + * @param value value to be placed between <tag> and </tag> + * @param nanString string to display if the value is NaN. + */ + public void writeTag(String tag, double value, String nanString) { + writeTag(tag, Util.formatDouble(value, nanString, true)); + } + + /** + * Writes <tag>value</tag> to output stream + * + * @param tag XML tag name + * @param value value to be placed between <tag> and </tag> + */ + public void writeTag(String tag, double value) { + writeTag(tag, Util.formatDouble(value, true)); + } + + /** + * Writes <tag>value</tag> to output stream + * + * @param tag XML tag name + * @param value value to be placed between <tag> and </tag> + */ + public void writeTag(String tag, boolean value) { + writeTag(tag, "" + value); + } + + /** + * Writes <tag>value</tag> to output stream + * + * @param tag XML tag name + * @param value value to be placed between <tag> and </tag> + */ + public void writeTag(String tag, Color value) { + int rgb = value.getRGB() & 0xFFFFFF; + writeTag(tag, "#" + Integer.toHexString(rgb).toUpperCase()); + } + + /** + * Writes <tag>value</tag> to output stream + * + * @param tag XML tag name + * @param value value to be placed between <tag> and </tag> + */ + public void writeTag(String tag, Font value) { + startTag(tag); + writeTag("name", value.getName()); + int style = value.getStyle(); + if ((style & Font.BOLD) != 0 && (style & Font.ITALIC) != 0) { + writeTag("style", "BOLDITALIC"); + } + else if ((style & Font.BOLD) != 0) { + writeTag("style", "BOLD"); + } + else if ((style & Font.ITALIC) != 0) { + writeTag("style", "ITALIC"); + } + else { + writeTag("style", "PLAIN"); + } + writeTag("size", value.getSize()); + closeTag(); + } + + /** + * Writes <tag>value</tag> to output stream + * + * @param tag XML tag name + * @param value value to be placed between <tag> and </tag> + */ + public void writeTag(String tag, File value) { + writeTag(tag, value.getPath()); + } + + /** + * Flushes the output stream + */ + public void flush() { + writer.flush(); + } + + protected void finalize() throws Throwable { + super.finalize(); + writer.close(); + } + + /** + * Writes XML comment to output stream + * + * @param comment comment string + */ + public void writeComment(Object comment) { + writer.println(indent + ""); + } + + private static String escape(String s) { + return s.replaceAll("<", "<").replaceAll(">", ">"); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/Archive.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/Archive.java new file mode 100644 index 000000000..92da22709 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/Archive.java @@ -0,0 +1,426 @@ +/******************************************************************************* + * 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.core.jrrd; + +import java.io.IOException; +import java.io.PrintStream; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Iterator; + +import org.jrobin.core.RrdException; + +/** + * Instances of this class model an archive section of an RRD file. + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class Archive { + + RRDatabase db; + long offset; + long dataOffset; + long size; + ConsolidationFunctionType type; + int rowCount; + int pdpCount; + double xff; + ArrayList cdpStatusBlocks; + int currentRow; + + private double[][] values; + + Archive(RRDatabase db) throws IOException,RrdException { + + this.db = db; + + RRDFile file = db.rrdFile; + + offset = file.getFilePointer(); + type = + ConsolidationFunctionType.get(file.readString(Constants.CF_NAM_SIZE)); + rowCount = file.readInt(); + pdpCount = file.readInt(); + + file.align(); + + xff = file.readDouble(); + + // Skip rest of rra_def_t.par[] + file.align(); + file.skipBytes(72); + + size = file.getFilePointer() - offset; + } + + /** + * Returns the type of function used to calculate the consolidated data point. + * + * @return the type of function used to calculate the consolidated data point. + */ + public ConsolidationFunctionType getType() { + return type; + } + + void loadCDPStatusBlocks(RRDFile file, int numBlocks) throws IOException, RrdException { + + cdpStatusBlocks = new ArrayList(); + + for (int i = 0; i < numBlocks; i++) { + cdpStatusBlocks.add(new CDPStatusBlock(file)); + } + } + + /** + * Returns the CDPStatusBlock at the specified position in this archive. + * + * @param index index of CDPStatusBlock to return. + * @return the CDPStatusBlock at the specified position in this archive. + */ + public CDPStatusBlock getCDPStatusBlock(int index) { + return cdpStatusBlocks.get(index); + } + + /** + * Returns an iterator over the CDP status blocks in this archive in proper sequence. + * + * @return an iterator over the CDP status blocks in this archive in proper sequence. + * @see CDPStatusBlock + */ + public Iterator getCDPStatusBlocks() { + return cdpStatusBlocks.iterator(); + } + + void loadCurrentRow(RRDFile file) throws IOException,RrdException { + currentRow = file.readInt(); + } + + void loadData(RRDFile file, int dsCount) throws IOException { + + dataOffset = file.getFilePointer(); + + // Skip over the data to position ourselves at the start of the next archive + file.skipBytes(8 * rowCount * dsCount); + } + + DataChunk loadData(DataChunk chunk) throws IOException,RrdException { + + Calendar end = Calendar.getInstance(); + Calendar start = (Calendar) end.clone(); + + start.add(Calendar.DATE, -1); + + loadData(chunk, start.getTime().getTime() / 1000, + end.getTime().getTime() / 1000); + return chunk; + } + + void loadData(DataChunk chunk, long startTime, long endTime) + throws IOException,RrdException { + + long pointer; + + if (chunk.start < 0) { + pointer = currentRow + 1; + } + else { + pointer = currentRow + chunk.start + 1; + } + + db.rrdFile.ras.seek(dataOffset + (pointer * 8)); + //cat.debug("Archive Base: " + dataOffset + " Archive Pointer: " + pointer); + //cat.debug("Start Offset: " + chunk.start + " End Offset: " + // + (rowCount - chunk.end)); + + double[][] data = chunk.data; + + /* + * This is also terrible - cleanup - CT + */ + int row = 0; + for (int i = chunk.start; i < rowCount - chunk.end; i++, row++) { + if (i < 0) { // no valid data yet + for (int ii = 0; ii < chunk.dsCount; ii++) { + data[row][ii] = Double.NaN; + } + } + else if (i >= rowCount) { // past valid data area + for (int ii = 0; ii < chunk.dsCount; ii++) { + data[row][ii] = Double.NaN; + } + } + else { // inside the valid are but the pointer has to be wrapped + if (pointer >= rowCount) { + pointer -= rowCount; + + db.rrdFile.ras.seek(dataOffset + (pointer * 8)); + } + + for (int ii = 0; ii < chunk.dsCount; ii++) { + data[row][ii] = db.rrdFile.readDouble(); + } + + pointer++; + } + } + } + + void printInfo(PrintStream s, NumberFormat numberFormat, int index) { + + StringBuffer sb = new StringBuffer("rra["); + + sb.append(index); + s.print(sb); + s.print("].cf = \""); + s.print(type); + s.println("\""); + s.print(sb); + s.print("].rows = "); + s.println(rowCount); + s.print(sb); + s.print("].pdp_per_row = "); + s.println(pdpCount); + s.print(sb); + s.print("].xff = "); + s.println(xff); + sb.append("].cdp_prep["); + + int cdpIndex = 0; + + for (Iterator i = cdpStatusBlocks.iterator(); i.hasNext();) { + CDPStatusBlock cdp = i.next(); + + s.print(sb); + s.print(cdpIndex); + s.print("].value = "); + + double value = cdp.value; + + s.println(Double.isNaN(value) + ? "NaN" + : numberFormat.format(value)); + s.print(sb); + s.print(cdpIndex++); + s.print("].unknown_datapoints = "); + s.println(cdp.unknownDatapoints); + } + } + + void toXml(PrintStream s) throws RrdException { + + try { + s.println("\t"); + s.print("\t\t "); + s.print(type); + s.println(" "); + s.print("\t\t "); + s.print(pdpCount); + s.print(" "); + s.print("\t\t "); + s.print(xff); + s.println(" "); + s.println(); + s.println("\t\t"); + + for (int i = 0; i < cdpStatusBlocks.size(); i++) { + cdpStatusBlocks.get(i).toXml(s); + } + + s.println("\t\t"); + s.println("\t\t"); + + long timer = -(rowCount - 1); + int counter = 0; + int row = currentRow; + + db.rrdFile.ras.seek(dataOffset + (row + 1) * 16); + + long lastUpdate = db.lastUpdate.getTime() / 1000; + int pdpStep = db.header.pdpStep; + NumberFormat numberFormat = new DecimalFormat("0.0000000000E0"); + SimpleDateFormat dateFormat = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + + while (counter++ < rowCount) { + row++; + + if (row == rowCount) { + row = 0; + + db.rrdFile.ras.seek(dataOffset); + } + + long now = (lastUpdate - lastUpdate % (pdpCount * pdpStep)) + + (timer * pdpCount * pdpStep); + + timer++; + + s.print("\t\t\t "); + + for (int col = 0; col < db.header.dsCount; col++) { + s.print(" "); + + double value = db.rrdFile.readDouble(); + + // NumberFormat doesn't know how to handle NaN + if (Double.isNaN(value)) { + s.print("NaN"); + } + else { + s.print(numberFormat.format(value)); + } + + s.print(" "); + } + + s.println(""); + } + + s.println("\t\t"); + s.println("\t"); + } + catch (IOException e) { // Is the best thing to do here? + throw new RuntimeException(e.getMessage()); + } + } + + /* + // THIS IS THE ORIGINAL CODE: BUGGY! Replaced by Sasa Markovic with a new method + // Funny: the bug will appear only if dsCount != 2 :) + public double[][] getValuesOriginal() throws IOException { + if (values != null) { + return values; + } + values = new double[db.header.dsCount][rowCount]; + int row = currentRow; + db.rrdFile.ras.seek(dataOffset + (row + 1) * 16); // <----- BUG (resolved below) + for (int counter = 0; counter < rowCount; counter++) { + row++; + if (row == rowCount) { + row = 0; + db.rrdFile.ras.seek(dataOffset); + } + for (int col = 0; col < db.header.dsCount; col++) { + double value = db.rrdFile.readDouble(); + values[col][counter] = value; + } + } + return values; + } + */ + + // Resolved bug from the original method (see above) + public double[][] getValues() throws IOException,RrdException { + // OK PART + if (values != null) { + return values; + } + values = new double[db.header.dsCount][rowCount]; + int row = currentRow; + // HERE ARE THE DRAGONS! + db.rrdFile.ras.seek(dataOffset + (row + 1) * db.header.dsCount * 8); + // OK, TOO! + for (int counter = 0; counter < rowCount; counter++) { + row++; + if (row == rowCount) { + row = 0; + db.rrdFile.ras.seek(dataOffset); + } + for (int col = 0; col < db.header.dsCount; col++) { + double value = db.rrdFile.readDouble(); + values[col][counter] = value; + } + } + return values; + } + + /** + * Returns the number of primary data points required for a consolidated + * data point in this archive. + * + * @return the number of primary data points required for a consolidated + * data point in this archive. + */ + public int getPdpCount() { + return pdpCount; + } + + /** + * Returns the number of entries in this archive. + * + * @return the number of entries in this archive. + */ + public int getRowCount() { + return rowCount; + } + + /** + * Returns the X-Files Factor for this archive. + * + * @return the X-Files Factor for this archive. + */ + public double getXff() { + return xff; + } + + /** + * Returns a summary the contents of this archive. + * + * @return a summary of the information contained in this archive. + */ + public String toString() { + + StringBuffer sb = new StringBuffer("[Archive: OFFSET=0x"); + + sb.append(Long.toHexString(offset)); + sb.append(", SIZE=0x"); + sb.append(Long.toHexString(size)); + sb.append(", type="); + sb.append(type); + sb.append(", rowCount="); + sb.append(rowCount); + sb.append(", pdpCount="); + sb.append(pdpCount); + sb.append(", xff="); + sb.append(xff); + sb.append(", currentRow="); + sb.append(currentRow); + sb.append("]"); + + for (Iterator i = cdpStatusBlocks.iterator(); i.hasNext();) { + CDPStatusBlock cdp = i.next(); + + sb.append("\n\t\t"); + sb.append(cdp.toString()); + } + + return sb.toString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/CDPStatusBlock.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/CDPStatusBlock.java new file mode 100644 index 000000000..f46ab808a --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/CDPStatusBlock.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * 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.core.jrrd; + +import java.io.IOException; +import java.io.PrintStream; + +import org.jrobin.core.RrdException; + +/** + * Instances of this class model the consolidation data point status from an RRD file. + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class CDPStatusBlock { + + long offset; + long size; + int unknownDatapoints; + double value; + + CDPStatusBlock(RRDFile file) throws IOException, RrdException { + + offset = file.getFilePointer(); + value = file.readDouble(); + unknownDatapoints = file.readInt(); + file.align(8); + // Skip rest of cdp_prep_t.scratch + file.skipBytes(64); + + size = file.getFilePointer() - offset; + } + + /** + * Returns the number of unknown primary data points that were integrated. + * + * @return the number of unknown primary data points that were integrated. + */ + public int getUnknownDatapoints() { + return unknownDatapoints; + } + + /** + * Returns the value of this consolidated data point. + * + * @return the value of this consolidated data point. + */ + public double getValue() { + return value; + } + + void toXml(PrintStream s) { + + s.print("\t\t\t "); + s.print(value); + s.print(" "); + s.print(unknownDatapoints); + s.println(" "); + } + + /** + * Returns a summary the contents of this CDP status block. + * + * @return a summary of the information contained in the CDP status block. + */ + public String toString() { + + StringBuffer sb = new StringBuffer("[CDPStatusBlock: OFFSET=0x"); + + sb.append(Long.toHexString(offset)); + sb.append(", SIZE=0x"); + sb.append(Long.toHexString(size)); + sb.append(", unknownDatapoints="); + sb.append(unknownDatapoints); + sb.append(", value="); + sb.append(value); + sb.append("]"); + + return sb.toString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/ConsolidationFunctionType.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/ConsolidationFunctionType.java new file mode 100644 index 000000000..e5f98da73 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/ConsolidationFunctionType.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * 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.core.jrrd; + +/** + * Class ConsolidationFunctionType + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class ConsolidationFunctionType { + + private static final int _AVERAGE = 0; + private static final String STR_AVERAGE = "AVERAGE"; + + /** + * Field AVERAGE + */ + public static final ConsolidationFunctionType AVERAGE = new ConsolidationFunctionType(_AVERAGE); + private static final int _MIN = 1; + private static final String STR_MIN = "MIN"; + + /** + * Field MIN + */ + public static final ConsolidationFunctionType MIN = new ConsolidationFunctionType(_MIN); + private static final int _MAX = 2; + private static final String STR_MAX = "MAX"; + + /** + * Field MAX + */ + public static final ConsolidationFunctionType MAX = new ConsolidationFunctionType(_MAX); + private static final int _LAST = 3; + private static final String STR_LAST = "LAST"; + + /** + * Field LAST + */ + public static final ConsolidationFunctionType LAST = new ConsolidationFunctionType(_LAST); + private int type; + + private ConsolidationFunctionType(int type) { + this.type = type; + } + + /** + * Returns a ConsolidationFunctionType with the given name. + * + * @param s name of the ConsolidationFunctionType required. + * @return a ConsolidationFunctionType with the given name. + */ + public static ConsolidationFunctionType get(final String s) { + + if (STR_AVERAGE.equalsIgnoreCase(s)) { + return AVERAGE; + } + else if (STR_MIN.equalsIgnoreCase(s)) { + return MIN; + } + else if (STR_MAX.equalsIgnoreCase(s)) { + return MAX; + } + else if (STR_LAST.equalsIgnoreCase(s)) { + return LAST; + } + else { + throw new IllegalArgumentException("Invalid ConsolidationFunctionType: " + s); + } + } + + /** + * Compares this object against the specified object. + * + * @return true if the objects are the same, + * false otherwise. + */ + public boolean equals(final Object o) { + + if (!(o instanceof ConsolidationFunctionType)) { + throw new IllegalArgumentException("Not a ConsolidationFunctionType"); + } + + return (((ConsolidationFunctionType) o).type == type) + ? true + : false; + } + + public int hashCode() { + return type * 93; + } + + /** + * Returns a string representation of this object. + * + * @return a string representation of this object. + */ + public String toString() { + + String strType; + + switch (type) { + + case _AVERAGE: + strType = STR_AVERAGE; + break; + + case _MIN: + strType = STR_MIN; + break; + + case _MAX: + strType = STR_MAX; + break; + + case _LAST: + strType = STR_LAST; + break; + + default : + throw new RuntimeException("This should never happen"); + } + + return strType; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/Constants.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/Constants.java new file mode 100644 index 000000000..c7f6ca3a5 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/Constants.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.core.jrrd; + +interface Constants { + + int DS_NAM_SIZE = 20; + int DST_SIZE = 20; + int CF_NAM_SIZE = 20; + int LAST_DS_LEN = 30; + static String COOKIE = "RRD"; + static String VERSION = "0001"; + static String VERSION3 = "0003"; + double FLOAT_COOKIE = 8.642135E130; + static byte[] FLOAT_COOKIE_BIG_ENDIAN = {0x5B, 0x1F, 0x2B, 0x43, + (byte) 0xC7, (byte) 0xC0, 0x25, + 0x2F}; + static byte[] FLOAT_COOKIE_LITTLE_ENDIAN = {0x2F, 0x25, (byte) 0xC0, + (byte) 0xC7, 0x43, 0x2B, 0x1F, + 0x5B}; +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/DataChunk.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/DataChunk.java new file mode 100644 index 000000000..48b5a6aa6 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/DataChunk.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.core.jrrd; + +/** + * Models a chunk of result data from an RRDatabase. + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class DataChunk { + + private static final String NEWLINE = System.getProperty("line.separator"); + long startTime; + int start; + int end; + long step; + int dsCount; + double[][] data; + int rows; + + DataChunk(long startTime, int start, int end, long step, int dsCount, int rows) { + this.startTime = startTime; + this.start = start; + this.end = end; + this.step = step; + this.dsCount = dsCount; + this.rows = rows; + data = new double[rows][dsCount]; + } + + /** + * Returns a summary of the contents of this data chunk. The first column is + * the time (RRD format) and the following columns are the data source + * values. + * + * @return a summary of the contents of this data chunk. + */ + public String toString() { + + StringBuffer sb = new StringBuffer(); + long time = startTime; + + for (int row = 0; row < rows; row++, time += step) { + sb.append(time); + sb.append(": "); + + for (int ds = 0; ds < dsCount; ds++) { + sb.append(data[row][ds]); + sb.append(" "); + } + + sb.append(NEWLINE); + } + + return sb.toString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/DataSource.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/DataSource.java new file mode 100644 index 000000000..ac90eca33 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/DataSource.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * 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.core.jrrd; + +import java.io.IOException; +import java.io.PrintStream; +import java.text.NumberFormat; + +import org.jrobin.core.RrdException; + +/** + * Instances of this class model a data source in an RRD file. + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class DataSource { + + long offset; + long size; + String name; + DataSourceType type; + int minimumHeartbeat; + double minimum; + double maximum; + PDPStatusBlock pdpStatusBlock; + + DataSource(RRDFile file) throws IOException,RrdException { + + offset = file.getFilePointer(); + name = file.readString(Constants.DS_NAM_SIZE); + type = DataSourceType.get(file.readString(Constants.DST_SIZE)); + + file.align(8); + + minimumHeartbeat = file.readInt(true); + + file.align(8); + + minimum = file.readDouble(); + maximum = file.readDouble(); + + // Skip rest of ds_def_t.par[] + file.align(); + file.skipBytes(56); + + size = file.getFilePointer() - offset; + } + + void loadPDPStatusBlock(RRDFile file) throws IOException,RrdException { + pdpStatusBlock = new PDPStatusBlock(file); + } + + /** + * Returns the primary data point status block for this data source. + * + * @return the primary data point status block for this data source. + */ + public PDPStatusBlock getPDPStatusBlock() { + return pdpStatusBlock; + } + + /** + * Returns the minimum required heartbeat for this data source. + * + * @return the minimum required heartbeat for this data source. + */ + public int getMinimumHeartbeat() { + return minimumHeartbeat; + } + + /** + * Returns the minimum value input to this data source can have. + * + * @return the minimum value input to this data source can have. + */ + public double getMinimum() { + return minimum; + } + + /** + * Returns the type this data source is. + * + * @return the type this data source is. + * @see DataSourceType + */ + public DataSourceType getType() { + return type; + } + + /** + * Returns the maximum value input to this data source can have. + * + * @return the maximum value input to this data source can have. + */ + public double getMaximum() { + return maximum; + } + + /** + * Returns the name of this data source. + * + * @return the name of this data source. + */ + public String getName() { + return name; + } + + void printInfo(PrintStream s, NumberFormat numberFormat) { + + StringBuffer sb = new StringBuffer("ds["); + + sb.append(name); + s.print(sb); + s.print("].type = \""); + s.print(type); + s.println("\""); + s.print(sb); + s.print("].minimal_heartbeat = "); + s.println(minimumHeartbeat); + s.print(sb); + s.print("].min = "); + s.println(Double.isNaN(minimum) + ? "NaN" + : numberFormat.format(minimum)); + s.print(sb); + s.print("].max = "); + s.println(Double.isNaN(maximum) + ? "NaN" + : numberFormat.format(maximum)); + s.print(sb); + s.print("].last_ds = "); + s.println(pdpStatusBlock.lastReading); + s.print(sb); + s.print("].value = "); + + double value = pdpStatusBlock.value; + + s.println(Double.isNaN(value) + ? "NaN" + : numberFormat.format(value)); + s.print(sb); + s.print("].unknown_sec = "); + s.println(pdpStatusBlock.unknownSeconds); + } + + void toXml(PrintStream s) { + + s.println("\t"); + s.print("\t\t "); + s.print(name); + s.println(" "); + s.print("\t\t "); + s.print(type); + s.println(" "); + s.print("\t\t "); + s.print(minimumHeartbeat); + s.println(" "); + s.print("\t\t "); + s.print(minimum); + s.println(" "); + s.print("\t\t "); + s.print(maximum); + s.println(" "); + s.println(); + s.println("\t\t"); + s.print("\t\t "); + s.print(pdpStatusBlock.lastReading); + s.println(" "); + s.print("\t\t "); + s.print(pdpStatusBlock.value); + s.println(" "); + s.print("\t\t "); + s.print(pdpStatusBlock.unknownSeconds); + s.println(" "); + s.println("\t"); + s.println(); + } + + /** + * Returns a summary the contents of this data source. + * + * @return a summary of the information contained in this data source. + */ + public String toString() { + + StringBuffer sb = new StringBuffer("[DataSource: OFFSET=0x"); + + sb.append(Long.toHexString(offset)); + sb.append(", SIZE=0x"); + sb.append(Long.toHexString(size)); + sb.append(", name="); + sb.append(name); + sb.append(", type="); + sb.append(type.toString()); + sb.append(", minHeartbeat="); + sb.append(minimumHeartbeat); + sb.append(", min="); + sb.append(minimum); + sb.append(", max="); + sb.append(maximum); + sb.append("]"); + sb.append("\n\t\t"); + sb.append(pdpStatusBlock.toString()); + + return sb.toString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/DataSourceType.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/DataSourceType.java new file mode 100644 index 000000000..336e115b8 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/DataSourceType.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * 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.core.jrrd; + +/** + * Class DataSourceType + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class DataSourceType { + + private static final int _COUNTER = 0; + private static final String STR_COUNTER = "COUNTER"; + + /** + * Field COUNTER + */ + public static final DataSourceType COUNTER = new DataSourceType(_COUNTER); + private static final int _ABSOLUTE = 1; + private static final String STR_ABSOLUTE = "ABSOLUTE"; + + /** + * Field ABSOLUTE + */ + public static final DataSourceType ABSOLUTE = new DataSourceType(_ABSOLUTE); + private static final int _GAUGE = 2; + private static final String STR_GAUGE = "GAUGE"; + + /** + * Field GAUGE + */ + public static final DataSourceType GAUGE = new DataSourceType(_GAUGE); + private static final int _DERIVE = 3; + private static final String STR_DERIVE = "DERIVE"; + + /** + * Field DERIVE + */ + public static final DataSourceType DERIVE = new DataSourceType(_DERIVE); + private int type; + + private DataSourceType(final int type) { + this.type = type; + } + + /** + * Returns a DataSourceType with the given name. + * + * @param s name of the DataSourceType required. + * @return a DataSourceType with the given name. + */ + public static DataSourceType get(final String s) { + + if (STR_COUNTER.equalsIgnoreCase(s)) { + return COUNTER; + } + else if (STR_ABSOLUTE.equalsIgnoreCase(s)) { + return ABSOLUTE; + } + else if (STR_GAUGE.equalsIgnoreCase(s)) { + return GAUGE; + } + else if (STR_DERIVE.equalsIgnoreCase(s)) { + return DERIVE; + } + else { + throw new IllegalArgumentException("Invalid DataSourceType"); + } + } + + /** + * Compares this object against the specified object. + * + * @return true if the objects are the same, + * false otherwise. + */ + public boolean equals(final Object obj) { + + if (!(obj instanceof DataSourceType)) { + throw new IllegalArgumentException("Not a DataSourceType"); + } + + return (((DataSourceType) obj).type == type) + ? true + : false; + } + + public int hashCode() { + return type * 37; + } + + /** + * Returns a string representation of this object. + * + * @return a string representation of this object. + */ + public String toString() { + + String strType; + + switch (type) { + + case _COUNTER: + strType = STR_COUNTER; + break; + + case _ABSOLUTE: + strType = STR_ABSOLUTE; + break; + + case _GAUGE: + strType = STR_GAUGE; + break; + + case _DERIVE: + strType = STR_DERIVE; + break; + + default : + // Don't you just hate it when you see a line like this? + throw new RuntimeException("This should never happen"); + } + + return strType; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/Header.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/Header.java new file mode 100644 index 000000000..c301e3ace --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/Header.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * 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.core.jrrd; + +import java.io.IOException; + +import org.jrobin.core.RrdException; + +/** + * Instances of this class model the header section of an RRD file. + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class Header implements Constants { + + static final long offset = 0; + long size; + String version; + int intVersion; + int dsCount; + int rraCount; + int pdpStep; + + Header(RRDFile file) throws IOException,RrdException { + + if (!file.readString(4).equals(COOKIE)) { + throw new IOException("Invalid COOKIE"); + } + + version = file.readString(5); + intVersion = Integer.parseInt(version); + if( intVersion > 3 ) { + throw new IOException("Unsupported RRD version (" + version + ")"); + } + + file.align(); + + // Consume the FLOAT_COOKIE + file.readDouble(); + + dsCount = file.readInt(); + rraCount = file.readInt(); + pdpStep = file.readInt(); + + // Skip rest of stat_head_t.par + file.align(); + file.skipBytes(80); + + size = file.getFilePointer() - offset; + } + + /** + * Returns the version of the database. + * + * @return the version of the database. + */ + public String getVersion() { + return version; + } + + public int getIntVersion() { + return intVersion; + } + + /** + * Returns the number of DataSources in the database. + * + * @return the number of DataSources in the database. + */ + public int getDSCount() { + return dsCount; + } + + /** + * Returns the number of Archives in the database. + * + * @return the number of Archives in the database. + */ + public int getRRACount() { + return rraCount; + } + + /** + * Returns the primary data point interval in seconds. + * + * @return the primary data point interval in seconds. + */ + public int getPDPStep() { + return pdpStep; + } + + /** + * Returns a summary the contents of this header. + * + * @return a summary of the information contained in this header. + */ + public String toString() { + + StringBuffer sb = new StringBuffer("[Header: OFFSET=0x00, SIZE=0x"); + + sb.append(Long.toHexString(size)); + sb.append(", version="); + sb.append(version); + sb.append(", dsCount="); + sb.append(dsCount); + sb.append(", rraCount="); + sb.append(rraCount); + sb.append(", pdpStep="); + sb.append(pdpStep); + sb.append("]"); + + return sb.toString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/Main.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/Main.java new file mode 100644 index 000000000..c7f17b7a8 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/Main.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * 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.core.jrrd; + +import java.io.IOException; + +import org.jrobin.core.RrdException; + +/** + * Show some of the things jRRD can do. + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class Main { + + public Main(String rrdFile) { + + RRDatabase rrd = null; + DataChunk chunk = null; + + try { + rrd = new RRDatabase(rrdFile); + chunk = rrd.getData(ConsolidationFunctionType.AVERAGE); + } catch (Exception e) { + e.printStackTrace(); + + return; + } + + try { + rrd.toXml(System.out); + } catch (RrdException e) { + e.printStackTrace(); + return; + } + // Dump the database as XML. + rrd.printInfo(System.out); // Dump the database header information. + System.out.println(rrd); // Dump a summary of the contents of the database. + System.out.println(chunk); // Dump the chunk. + + try { + rrd.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + static void usage(int status) { + System.err.println("Usage: " + Main.class.getName() + " rrdfile"); + System.exit(status); + } + + public static void main(String[] args) { + if (args.length != 1) { + usage(1); + } + new Main(args[0]); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/PDPStatusBlock.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/PDPStatusBlock.java new file mode 100644 index 000000000..449897bf6 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/PDPStatusBlock.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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.core.jrrd; + +import java.io.IOException; + +import org.jrobin.core.RrdException; + +/** + * Instances of this class model the primary data point status from an RRD file. + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class PDPStatusBlock { + + long offset; + long size; + String lastReading; + int unknownSeconds; + double value; + + PDPStatusBlock(RRDFile file) throws IOException,RrdException { + + offset = file.getFilePointer(); + lastReading = file.readString(Constants.LAST_DS_LEN); + + file.align(4); + + unknownSeconds = file.readInt(); + + file.align(8); //8 bytes per scratch value in pdp_prep; align on that + + value = file.readDouble(); + + // Skip rest of pdp_prep_t.par[] + file.skipBytes(64); + + size = file.getFilePointer() - offset; + } + + /** + * Returns the last reading from the data source. + * + * @return the last reading from the data source. + */ + public String getLastReading() { + return lastReading; + } + + /** + * Returns the current value of the primary data point. + * + * @return the current value of the primary data point. + */ + public double getValue() { + return value; + } + + /** + * Returns the number of seconds of the current primary data point is + * unknown data. + * + * @return the number of seconds of the current primary data point is unknown data. + */ + public int getUnknownSeconds() { + return unknownSeconds; + } + + /** + * Returns a summary the contents of this PDP status block. + * + * @return a summary of the information contained in this PDP status block. + */ + public String toString() { + + StringBuffer sb = new StringBuffer("[PDPStatus: OFFSET=0x"); + + sb.append(Long.toHexString(offset)); + sb.append(", SIZE=0x"); + sb.append(Long.toHexString(size)); + sb.append(", lastReading="); + sb.append(lastReading); + sb.append(", unknownSeconds="); + sb.append(unknownSeconds); + sb.append(", value="); + sb.append(value); + sb.append("]"); + + return sb.toString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/RRDFile.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/RRDFile.java new file mode 100644 index 000000000..179cc473e --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/RRDFile.java @@ -0,0 +1,245 @@ +/******************************************************************************* + * 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.core.jrrd; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +import org.jrobin.core.RrdException; + +/** + * This class is a quick hack to read information from an RRD file. Writing + * to RRD files is not currently supported. As I said, this is a quick hack. + * Some thought should be put into the overall design of the file IO. + *

+ * Currently this can read RRD files that were generated on Solaris (Sparc) + * and Linux (x86). + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class RRDFile implements Constants { + + boolean bigEndian; + boolean debug; + int alignment; + RandomAccessFile ras; + byte[] buffer; + + RRDFile(String name) throws IOException, RrdException { + this(new File(name)); + } + + RRDFile(File file) throws IOException, RrdException { + + ras = new RandomAccessFile(file, "r"); + buffer = new byte[128]; + + this.debug = false; + initDataLayout(file); + } + + private void initDataLayout(File file) throws IOException, RrdException { + + if (file.exists()) { // Load the data formats from the file + int bytes = ras.read(buffer, 0, 24); + if (bytes < 24) { + throw new RrdException("Invalid RRD file"); + } + + int index; + + if ((index = indexOf(FLOAT_COOKIE_BIG_ENDIAN, buffer)) != -1) { + bigEndian = true; + } + else if ((index = indexOf(FLOAT_COOKIE_LITTLE_ENDIAN, buffer)) + != -1) { + bigEndian = false; + } + else { + throw new RrdException("Invalid RRD file"); + } + + switch (index) { + + case 12: + alignment = 4; + break; + + case 16: + alignment = 8; + break; + + default : + throw new RuntimeException("Unsupported architecture - neither 32-bit nor 64-bit, or maybe the file is corrupt"); + } + } + else { // Default to data formats for this hardware architecture + } + + ras.seek(0); // Reset file pointer to start of file + } + + private int indexOf(byte[] pattern, byte[] array) { + return (new String(array)).indexOf(new String(pattern)); + } + + boolean isBigEndian() { + return bigEndian; + } + + int getAlignment() { + return alignment; + } + + double readDouble() throws IOException, RrdException { + if(debug) { + System.out.print("Read 8 bytes (Double) from offset "+ras.getFilePointer()+":"); + } + + //double value; + byte[] tx = new byte[8]; + + if(ras.read(buffer, 0, 8) != 8) { + throw new RrdException("Invalid RRD file"); + } + + if (bigEndian) { + tx = buffer; + } + else { + for (int i = 0; i < 8; i++) { + tx[7 - i] = buffer[i]; + } + } + + DataInputStream reverseDis = + new DataInputStream(new ByteArrayInputStream(tx)); + + Double result = reverseDis.readDouble(); + if(this.debug) { + System.out.println(result); + } + return result; + } + + int readInt() throws IOException, RrdException { + return readInt(false); + } + + /** + * Reads the next integer (4 or 8 bytes depending on alignment), advancing the file pointer + * and returns it + * If the alignment is 8-bytes (64-bit), then 8 bytes are read, but only the lower 4-bytes (32-bits) are + * returned. The upper 4 bytes are ignored. + * + * @return the 32-bit integer read from the file + * @throws IOException - A file access error + * @throws RrdException - Not enough bytes were left in the file to read the integer. + */ + int readInt(boolean dump) throws IOException, RrdException { + //An integer is "alignment" bytes long - 4 bytes on 32-bit, 8 on 64-bit. + if(this.debug) { + System.out.print("Read "+alignment+" bytes (int) from offset "+ras.getFilePointer()+":"); + } + + if(ras.read(buffer, 0, alignment) != alignment) { + throw new RrdException("Invalid RRD file"); + } + + int value; + + if (bigEndian) { + if(alignment == 8) { + //For big-endian, the low 4-bytes of the 64-bit integer are the last 4 bytes + value = (0xFF & buffer[7]) | ((0xFF & buffer[6]) << 8) + | ((0xFF & buffer[5]) << 16) | ((0xFF & buffer[4]) << 24); + } else { + value = (0xFF & buffer[3]) | ((0xFF & buffer[2]) << 8) + | ((0xFF & buffer[1]) << 16) | ((0xFF & buffer[0]) << 24); + } + } + else { + //For little-endian, there's no difference between 4 and 8 byte alignment. + // The first 4 bytes are the low end of a 64-bit number + value = (0xFF & buffer[0]) | ((0xFF & buffer[1]) << 8) + | ((0xFF & buffer[2]) << 16) | ((0xFF & buffer[3]) << 24); + } + + if(this.debug) { + System.out.println(value); + } + return (int)value; + } + + String readString(int maxLength) throws IOException, RrdException { + if(this.debug) { + System.out.print("Read "+maxLength+" bytes (string) from offset "+ras.getFilePointer()+":"); + } + maxLength = ras.read(buffer, 0, maxLength); + if(maxLength == -1) { + throw new RrdException("Invalid RRD file"); + } + + String result = new String(buffer, 0, maxLength).trim(); + if(this.debug) { + System.out.println( result +":"); + } + return result; + } + + void skipBytes(final int n) throws IOException { + int bytesSkipped = ras.skipBytes(n); + if(this.debug) { + System.out.println("Skipping "+bytesSkipped+" bytes"); + } + } + + int align(int boundary) throws IOException { + + int skip = (int) (boundary - (ras.getFilePointer() % boundary)) % boundary; + + if (skip != 0) { + skip = ras.skipBytes(skip); + } + if(this.debug) { + System.out.println("Aligning to boundary "+ boundary +". Offset is now "+ras.getFilePointer()); + } + return skip; + } + + int align() throws IOException { + return align(alignment); + } + + long info() throws IOException { + return ras.getFilePointer(); + } + + long getFilePointer() throws IOException { + return ras.getFilePointer(); + } + + void close() throws IOException { + ras.close(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/jrrd/RRDatabase.java b/apps/jrobin/java/src/org/jrobin/core/jrrd/RRDatabase.java new file mode 100644 index 000000000..0a39f1f16 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/jrrd/RRDatabase.java @@ -0,0 +1,508 @@ +/******************************************************************************* + * 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.core.jrrd; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Iterator; + +import org.jrobin.core.RrdException; + +/** + * Instances of this class model + * Round Robin Database + * (RRD) files. + * + * @author Ciaran Treanor + * @version $Revision$ + */ +public class RRDatabase { + + RRDFile rrdFile; + + // RRD file name + private String name; + Header header; + ArrayList dataSources; + ArrayList archives; + Date lastUpdate; + + /** + * Creates a database to read from. + * + * @param name the filename of the file to read from. + * @throws IOException if an I/O error occurs. + */ + public RRDatabase(String name) throws IOException,RrdException { + this(new File(name)); + } + + /** + * Creates a database to read from. + * + * @param file the file to read from. + * @throws IOException if an I/O error occurs. + */ + public RRDatabase(File file) throws IOException,RrdException { + + name = file.getName(); + rrdFile = new RRDFile(file); + header = new Header(rrdFile); + + // Load the data sources + dataSources = new ArrayList(); + + for (int i = 0; i < header.dsCount; i++) { + DataSource ds = new DataSource(rrdFile); + + dataSources.add(ds); + } + + // Load the archives + archives = new ArrayList(); + + for (int i = 0; i < header.rraCount; i++) { + Archive archive = new Archive(this); + + archives.add(archive); + } + + rrdFile.align(); + + long timestamp = (long)(rrdFile.readInt()) * 1000; + if(header.getIntVersion() >= 3) { + //Version 3 has an additional microsecond field + int microSeconds = rrdFile.readInt(); + timestamp += (microSeconds/1000); //Date only does up to milliseconds + } + lastUpdate = new Date( timestamp ); + // Load PDPStatus(s) + for (int i = 0; i < header.dsCount; i++) { + DataSource ds = dataSources.get(i); + + ds.loadPDPStatusBlock(rrdFile); + } + + // Load CDPStatus(s) + for (int i = 0; i < header.rraCount; i++) { + Archive archive = archives.get(i); + + archive.loadCDPStatusBlocks(rrdFile, header.dsCount); + } + + // Load current row information for each archive + for (int i = 0; i < header.rraCount; i++) { + Archive archive = archives.get(i); + + archive.loadCurrentRow(rrdFile); + } + + // Now load the data + for (int i = 0; i < header.rraCount; i++) { + Archive archive = archives.get(i); + + archive.loadData(rrdFile, header.dsCount); + } + } + + /** + * Returns the Header for this database. + * + * @return the Header for this database. + */ + public Header getHeader() { + return header; + } + + /** + * Returns the date this database was last updated. To convert this date to + * the form returned by rrdtool last call Date.getTime() and + * divide the result by 1000. + * + * @return the date this database was last updated. + */ + public Date getLastUpdate() { + return lastUpdate; + } + + /** + * Returns the DataSource at the specified position in this database. + * + * @param index index of DataSource to return. + * @return the DataSource at the specified position in this database + */ + public DataSource getDataSource(int index) { + return dataSources.get(index); + } + + /** + * Returns an iterator over the data sources in this database in proper sequence. + * + * @return an iterator over the data sources in this database in proper sequence. + */ + public Iterator getDataSources() { + return dataSources.iterator(); + } + + /** + * Returns the Archive at the specified position in this database. + * + * @param index index of Archive to return. + * @return the Archive at the specified position in this database. + */ + public Archive getArchive(int index) { + return archives.get(index); + } + + /** + * Returns an iterator over the archives in this database in proper sequence. + * + * @return an iterator over the archives in this database in proper sequence. + */ + public Iterator getArchives() { + return archives.iterator(); + } + + /** + * Returns the number of archives in this database. + * + * @return the number of archives in this database. + */ + public int getNumArchives() { + return header.rraCount; + } + + /** + * Returns an iterator over the archives in this database of the given type + * in proper sequence. + * + * @param type the consolidation function that should have been applied to + * the data. + * @return an iterator over the archives in this database of the given type + * in proper sequence. + */ + public Iterator getArchives(ConsolidationFunctionType type) { + return getArchiveList(type).iterator(); + } + + ArrayList getArchiveList(ConsolidationFunctionType type) { + + ArrayList subset = new ArrayList(); + + for (int i = 0; i < archives.size(); i++) { + Archive archive = archives.get(i); + + if (archive.getType().equals(type)) { + subset.add(archive); + } + } + + return subset; + } + + /** + * Closes this database stream and releases any associated system resources. + * + * @throws IOException if an I/O error occurs. + */ + public void close() throws IOException { + rrdFile.close(); + } + + /** + * Outputs the header information of the database to the given print stream + * using the default number format. The default format for double + * is 0.0000000000E0. + * + * @param s the PrintStream to print the header information to. + */ + public void printInfo(PrintStream s) { + + NumberFormat numberFormat = new DecimalFormat("0.0000000000E0"); + + printInfo(s, numberFormat); + } + + /** + * Returns data from the database corresponding to the given consolidation + * function and a step size of 1. + * + * @param type the consolidation function that should have been applied to + * the data. + * @return the raw data. + * @throws RrdException if there was a problem locating a data archive with + * the requested consolidation function. + * @throws IOException if there was a problem reading data from the database. + */ + public DataChunk getData(ConsolidationFunctionType type) + throws RrdException, IOException { + return getData(type, 1L); + } + + /** + * Returns data from the database corresponding to the given consolidation + * function. + * + * @param type the consolidation function that should have been applied to + * the data. + * @param step the step size to use. + * @return the raw data. + * @throws RrdException if there was a problem locating a data archive with + * the requested consolidation function. + * @throws IOException if there was a problem reading data from the database. + */ + public DataChunk getData(ConsolidationFunctionType type, long step) + throws RrdException, IOException { + + ArrayList possibleArchives = getArchiveList(type); + + if (possibleArchives.size() == 0) { + throw new RrdException("Database does not contain an Archive of consolidation function type " + + type); + } + + Calendar endCal = Calendar.getInstance(); + + endCal.set(Calendar.MILLISECOND, 0); + + Calendar startCal = (Calendar) endCal.clone(); + + startCal.add(Calendar.DATE, -1); + + long end = endCal.getTime().getTime() / 1000; + long start = startCal.getTime().getTime() / 1000; + Archive archive = findBestArchive(start, end, step, possibleArchives); + + // Tune the parameters + step = header.pdpStep * archive.pdpCount; + start -= start % step; + + if (end % step != 0) { + end += step - end % step; + } + + int rows = (int) ((end - start) / step + 1); + + //cat.debug("start " + start + " end " + end + " step " + step + " rows " + // + rows); + + // Find start and end offsets + // This is terrible - some of this should be encapsulated in Archive - CT. + long lastUpdateLong = lastUpdate.getTime() / 1000; + long archiveEndTime = lastUpdateLong - (lastUpdateLong % step); + long archiveStartTime = archiveEndTime - (step * (archive.rowCount - 1)); + int startOffset = (int) ((start - archiveStartTime) / step); + int endOffset = (int) ((archiveEndTime - end) / step); + + //cat.debug("start " + archiveStartTime + " end " + archiveEndTime + // + " startOffset " + startOffset + " endOffset " + // + (archive.rowCount - endOffset)); + + DataChunk chunk = new DataChunk(start, startOffset, endOffset, step, + header.dsCount, rows); + + archive.loadData(chunk); + + return chunk; + } + + /* + * This is almost a verbatim copy of the original C code by Tobias Oetiker. + * I need to put more of a Java style on it - CT + */ + private Archive findBestArchive(long start, long end, long step, + ArrayList archives) { + + Archive archive = null; + Archive bestFullArchive = null; + Archive bestPartialArchive = null; + long lastUpdateLong = lastUpdate.getTime() / 1000; + int firstPart = 1; + int firstFull = 1; + long bestMatch = 0; + //long bestPartRRA = 0; + long bestStepDiff = 0; + long tmpStepDiff = 0; + + for (int i = 0; i < archives.size(); i++) { + archive = archives.get(i); + + long calEnd = lastUpdateLong + - (lastUpdateLong + % (archive.pdpCount * header.pdpStep)); + long calStart = calEnd + - (archive.pdpCount * archive.rowCount + * header.pdpStep); + long fullMatch = end - start; + + if ((calEnd >= end) && (calStart < start)) { // Best full match + tmpStepDiff = Math.abs(step - (header.pdpStep * archive.pdpCount)); + + if ((firstFull != 0) || (tmpStepDiff < bestStepDiff)) { + firstFull = 0; + bestStepDiff = tmpStepDiff; + bestFullArchive = archive; + } + } + else { // Best partial match + long tmpMatch = fullMatch; + + if (calStart > start) { + tmpMatch -= calStart - start; + } + + if (calEnd < end) { + tmpMatch -= end - calEnd; + } + + if ((firstPart != 0) || (bestMatch < tmpMatch)) { + firstPart = 0; + bestMatch = tmpMatch; + bestPartialArchive = archive; + } + } + } + + // See how the matching went + // optimise this + if (firstFull == 0) { + archive = bestFullArchive; + } + else if (firstPart == 0) { + archive = bestPartialArchive; + } + + return archive; + } + + /** + * Outputs the header information of the database to the given print stream + * using the given number format. The format is almost identical to that + * produced by + * rrdtool info + * + * @param s the PrintStream to print the header information to. + * @param numberFormat the format to print doubles as. + */ + public void printInfo(PrintStream s, NumberFormat numberFormat) { + + s.print("filename = \""); + s.print(name); + s.println("\""); + s.print("rrd_version = \""); + s.print(header.version); + s.println("\""); + s.print("step = "); + s.println(header.pdpStep); + s.print("last_update = "); + s.println(lastUpdate.getTime() / 1000); + + for (Iterator i = dataSources.iterator(); i.hasNext();) { + DataSource ds = i.next(); + + ds.printInfo(s, numberFormat); + } + + int index = 0; + + for (Iterator i = archives.iterator(); i.hasNext();) { + Archive archive = i.next(); + + archive.printInfo(s, numberFormat, index++); + } + } + + /** + * Outputs the content of the database to the given print stream + * as a stream of XML. The XML format is almost identical to that produced by + * rrdtool dump + * + * @param s the PrintStream to send the XML to. + */ + public void toXml(PrintStream s) throws RrdException { + + s.println(""); + s.println(""); + s.print("\t "); + s.print(header.version); + s.println(" "); + s.print("\t "); + s.print(header.pdpStep); + s.println(" "); + s.print("\t "); + s.print(lastUpdate.getTime() / 1000); + s.print(" "); + s.println(); + + for (int i = 0; i < header.dsCount; i++) { + DataSource ds = dataSources.get(i); + + ds.toXml(s); + } + + s.println(""); + + for (int i = 0; i < header.rraCount; i++) { + Archive archive = archives.get(i); + + archive.toXml(s); + } + + s.println(""); + } + + /** + * Returns a summary the contents of this database. + * + * @return a summary of the information contained in this database. + */ + public String toString() { + + StringBuffer sb = new StringBuffer("\n"); + + sb.append(header.toString()); + + for (Iterator i = dataSources.iterator(); i.hasNext();) { + DataSource ds = i.next(); + + sb.append("\n\t"); + sb.append(ds.toString()); + } + + for (Iterator i = archives.iterator(); i.hasNext();) { + Archive archive = i.next(); + + sb.append("\n\t"); + sb.append(archive.toString()); + } + + return sb.toString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/timespec/Epoch.java b/apps/jrobin/java/src/org/jrobin/core/timespec/Epoch.java new file mode 100644 index 000000000..7a5c70f34 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/timespec/Epoch.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * 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.core.timespec; + +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Small swing-based utility to convert timestamps (seconds since epoch) to readable dates and vice versa. + * Supports at-style time specification (like "now-2d", "noon yesterday") and other human-readable + * data formats:

+ *

    + *
  • MM/dd/yy HH:mm:ss + *
  • dd.MM.yy HH:mm:ss + *
  • dd.MM.yy HH:mm:ss + *
  • MM/dd/yy HH:mm + *
  • dd.MM.yy HH:mm + *
  • yy-MM-dd HH:mm + *
  • MM/dd/yy + *
  • dd.MM.yy + *
  • yy-MM-dd + *
  • HH:mm MM/dd/yy + *
  • HH:mm dd.MM.yy + *
  • HH:mm yy-MM-dd + *
  • HH:mm:ss MM/dd/yy + *
  • HH:mm:ss dd.MM.yy + *
  • HH:mm:ss yy-MM-dd + *
+ * The current timestamp is displayed in the title bar :)

+ */ +public class Epoch extends JFrame { + private static final long serialVersionUID = 1L; + private static final String[] supportedFormats = { + "MM/dd/yy HH:mm:ss", "dd.MM.yy HH:mm:ss", "yy-MM-dd HH:mm:ss", "MM/dd/yy HH:mm", + "dd.MM.yy HH:mm", "yy-MM-dd HH:mm", "MM/dd/yy", "dd.MM.yy", "yy-MM-dd", "HH:mm MM/dd/yy", + "HH:mm dd.MM.yy", "HH:mm yy-MM-dd", "HH:mm:ss MM/dd/yy", "HH:mm:ss dd.MM.yy", "HH:mm:ss yy-MM-dd" + }; + + private static final SimpleDateFormat[] parsers = new SimpleDateFormat[supportedFormats.length]; + private static final String helpText; + + private Timer timer = new Timer(1000, new ActionListener() { + public void actionPerformed(ActionEvent e) { + showTimestamp(); + } + }); + + static { + for (int i = 0; i < parsers.length; i++) { + parsers[i] = new SimpleDateFormat(supportedFormats[i]); + parsers[i].setLenient(true); + } + StringBuffer tooltipBuff = new StringBuffer("Supported input formats:
"); + for (String supportedFormat : supportedFormats) { + tooltipBuff.append(supportedFormat).append("
"); + } + tooltipBuff.append("AT-style time specification
"); + tooltipBuff.append("timestamp

"); + tooltipBuff.append("Copyright (C) 2003-2005 Sasa Markovic, All Rights Reserved"); + helpText = tooltipBuff.toString(); + } + + private JLabel topLabel = new JLabel("Enter timestamp or readable date:"); + private JTextField inputField = new JTextField(25); + private JButton convertButton = new JButton("Convert"); + private JButton helpButton = new JButton("Help"); + + private static final SimpleDateFormat OUTPUT_DATE_FORMAT = + new SimpleDateFormat("MM/dd/yy HH:mm:ss EEE"); + + Epoch() { + super("Epoch"); + constructUI(); + timer.start(); + } + + private void constructUI() { + JPanel c = (JPanel) getContentPane(); + c.setLayout(new BorderLayout(3, 3)); + c.add(topLabel, BorderLayout.NORTH); + c.add(inputField, BorderLayout.WEST); + c.add(convertButton, BorderLayout.CENTER); + convertButton.setToolTipText(helpText); + convertButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + convert(); + } + }); + c.add(helpButton, BorderLayout.EAST); + helpButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JOptionPane.showMessageDialog(helpButton, helpText, "Epoch Help", JOptionPane.INFORMATION_MESSAGE); + } + }); + inputField.requestFocus(); + getRootPane().setDefaultButton(convertButton); + setResizable(false); + setDefaultCloseOperation(EXIT_ON_CLOSE); + pack(); + centerOnScreen(); + setVisible(true); + } + + void centerOnScreen() { + Toolkit t = Toolkit.getDefaultToolkit(); + Dimension screenSize = t.getScreenSize(); + Dimension frameSize = getPreferredSize(); + double x = (screenSize.getWidth() - frameSize.getWidth()) / 2; + double y = (screenSize.getHeight() - frameSize.getHeight()) / 2; + setLocation((int) x, (int) y); + } + + private void convert() { + String time = inputField.getText().trim(); + if (time.length() > 0) { + // try simple timestamp + try { + long timestamp = Long.parseLong(time); + Date date = new Date(timestamp * 1000L); + formatDate(date); + } + catch (NumberFormatException nfe) { + // failed, try as a date + try { + inputField.setText("" + parseDate(time)); + } + catch (RrdException e) { + inputField.setText("Could not convert, sorry"); + } + } + } + } + + private void showTimestamp() { + long timestamp = Util.getTime(); + setTitle(timestamp + " seconds since epoch"); + } + + void formatDate(Date date) { + inputField.setText(OUTPUT_DATE_FORMAT.format(date)); + } + + private long parseDate(String time) throws RrdException { + for (SimpleDateFormat parser : parsers) { + try { + return Util.getTimestamp(parser.parse(time)); + } + catch (ParseException e) { + // NOP + } + } + return new TimeParser(time).parse().getTimestamp(); + } + + /** + * Main method which runs this utility. + * + * @param args Not used. + */ + public static void main(String[] args) { + new Epoch(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/timespec/TimeParser.java b/apps/jrobin/java/src/org/jrobin/core/timespec/TimeParser.java new file mode 100644 index 000000000..b957ecc14 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/timespec/TimeParser.java @@ -0,0 +1,443 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ + +/* + * Java port of Tobi's original parsetime.c routine + */ +package org.jrobin.core.timespec; + +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; + +/** + * Class which parses at-style time specification (described in detail on the rrdfetch man page), + * used in all RRDTool commands. This code is in most parts just a java port of Tobi's parsetime.c + * code. + */ +public class TimeParser { + private static final int PREVIOUS_OP = -1; + + TimeToken token; + TimeScanner scanner; + TimeSpec spec; + + int op = TimeToken.PLUS; + int prev_multiplier = -1; + + /** + * Constructs TimeParser instance from the given input string. + * + * @param dateString at-style time specification (read rrdfetch man page + * for the complete explanation) + */ + public TimeParser(String dateString) { + scanner = new TimeScanner(dateString); + spec = new TimeSpec(dateString); + } + + private void expectToken(int desired, String errorMessage) throws RrdException { + token = scanner.nextToken(); + if (token.id != desired) { + throw new RrdException(errorMessage); + } + } + + private void plusMinus(int doop) throws RrdException { + if (doop >= 0) { + op = doop; + expectToken(TimeToken.NUMBER, "There should be number after " + + (op == TimeToken.PLUS ? '+' : '-')); + prev_multiplier = -1; /* reset months-minutes guessing mechanics */ + } + int delta = Integer.parseInt(token.value); + token = scanner.nextToken(); + if (token.id == TimeToken.MONTHS_MINUTES) { + /* hard job to guess what does that -5m means: -5mon or -5min? */ + switch (prev_multiplier) { + case TimeToken.DAYS: + case TimeToken.WEEKS: + case TimeToken.MONTHS: + case TimeToken.YEARS: + token = scanner.resolveMonthsMinutes(TimeToken.MONTHS); + break; + case TimeToken.SECONDS: + case TimeToken.MINUTES: + case TimeToken.HOURS: + token = scanner.resolveMonthsMinutes(TimeToken.MINUTES); + break; + default: + if (delta < 6) { + token = scanner.resolveMonthsMinutes(TimeToken.MONTHS); + } + else { + token = scanner.resolveMonthsMinutes(TimeToken.MINUTES); + } + } + } + prev_multiplier = token.id; + delta *= (op == TimeToken.PLUS) ? +1 : -1; + switch (token.id) { + case TimeToken.YEARS: + spec.dyear += delta; + break; + case TimeToken.MONTHS: + spec.dmonth += delta; + break; + case TimeToken.WEEKS: + delta *= 7; + spec.dday += delta; + break; + case TimeToken.DAYS: + spec.dday += delta; + break; + case TimeToken.HOURS: + spec.dhour += delta; + break; + case TimeToken.MINUTES: + spec.dmin += delta; + break; + case TimeToken.SECONDS: + default: // default is 'seconds' + spec.dsec += delta; + break; + } + // unreachable statement + // throw new RrdException("Well-known time unit expected after " + delta); + } + + /** + * Try and read a "timeofday" specification. This method will be called + * when we see a plain number at the start of a time, which means we could be + * reading a time, or a day. If it turns out to be a date, then this method restores + * the scanner state to what it was at entry, and returns without setting anything. + * @throws RrdException + */ + private void timeOfDay() throws RrdException { + int hour, minute = 0; + /* save token status in case we must abort */ + scanner.saveState(); + /* first pick out the time of day - we assume a HH (COLON|DOT) MM time */ + if (token.value.length() > 2) { + //Definitely not an hour specification; probably a date or something. Give up now + return; + } + hour = Integer.parseInt(token.value); + token = scanner.nextToken(); + if (token.id == TimeToken.SLASH) { + /* guess we are looking at a date */ + token = scanner.restoreState(); + return; + } + if (token.id == TimeToken.COLON || token.id == TimeToken.DOT) { + expectToken(TimeToken.NUMBER, "Parsing HH:MM or HH.MM syntax, expecting MM as number, got none"); + minute = Integer.parseInt(token.value); + if (minute > 59) { + throw new RrdException("Parsing HH:MM or HH.MM syntax, got MM = " + + minute + " (>59!)"); + } + token = scanner.nextToken(); + if(token.id == TimeToken.DOT) { + //Oh look, another dot; must have actually been a date in DD.MM.YYYY format. Give up and return + token = scanner.restoreState(); + return; + } + + } + /* check if an AM or PM specifier was given */ + if (token.id == TimeToken.AM || token.id == TimeToken.PM) { + if (hour > 12) { + throw new RrdException("There cannot be more than 12 AM or PM hours"); + } + if (token.id == TimeToken.PM) { + if (hour != 12) { + /* 12:xx PM is 12:xx, not 24:xx */ + hour += 12; + } + } + else { + if (hour == 12) { + /* 12:xx AM is 00:xx, not 12:xx */ + hour = 0; + } + } + token = scanner.nextToken(); + } + else if (hour > 23) { + /* guess it was not a time then, probably a date ... */ + token = scanner.restoreState(); + return; + } + + spec.hour = hour; + spec.min = minute; + spec.sec = 0; + if (spec.hour == 24) { + spec.hour = 0; + spec.day++; + } + } + + private void assignDate(long mday, long mon, long year) throws RrdException { + if (year > 138) { + if (year > 1970) { + year -= 1900; + } + else { + throw new RrdException("Invalid year " + year + + " (should be either 00-99 or >1900)"); + } + } + else if (year >= 0 && year < 38) { + year += 100; /* Allow year 2000-2037 to be specified as */ + } /* 00-37 until the problem of 2038 year will */ + /* arise for unices with 32-bit time_t */ + if (year < 70) { + throw new RrdException("Won't handle dates before epoch (01/01/1970), sorry"); + } + spec.year = (int) year; + spec.month = (int) mon; + spec.day = (int) mday; + } + + private void day() throws RrdException { + long mday = 0, wday, mon, year = spec.year; + switch (token.id) { + case TimeToken.YESTERDAY: + spec.day--; + token = scanner.nextToken(); + break; + case TimeToken.TODAY: /* force ourselves to stay in today - no further processing */ + token = scanner.nextToken(); + break; + case TimeToken.TOMORROW: + spec.day++; + token = scanner.nextToken(); + break; + case TimeToken.JAN: + case TimeToken.FEB: + case TimeToken.MAR: + case TimeToken.APR: + case TimeToken.MAY: + case TimeToken.JUN: + case TimeToken.JUL: + case TimeToken.AUG: + case TimeToken.SEP: + case TimeToken.OCT: + case TimeToken.NOV: + case TimeToken.DEC: + /* do month mday [year] */ + mon = (token.id - TimeToken.JAN); + expectToken(TimeToken.NUMBER, "the day of the month should follow month name"); + mday = Long.parseLong(token.value); + token = scanner.nextToken(); + if (token.id == TimeToken.NUMBER) { + year = Long.parseLong(token.value); + token = scanner.nextToken(); + } + else { + year = spec.year; + } + assignDate(mday, mon, year); + break; + case TimeToken.SUN: + case TimeToken.MON: + case TimeToken.TUE: + case TimeToken.WED: + case TimeToken.THU: + case TimeToken.FRI: + case TimeToken.SAT: + /* do a particular day of the week */ + wday = (token.id - TimeToken.SUN); + spec.day += (wday - spec.wday); + token = scanner.nextToken(); + break; + case TimeToken.NUMBER: + /* get numeric , MM/DD/[YY]YY, or DD.MM.[YY]YY */ + // int tlen = token.value.length(); + mon = Long.parseLong(token.value); + if (mon > 10L * 365L * 24L * 60L * 60L) { + spec.localtime(mon); + token = scanner.nextToken(); + break; + } + if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */ + year = mon / 10000; + mday = mon % 100; + mon = (mon / 100) % 100; + token = scanner.nextToken(); + } + else { + token = scanner.nextToken(); + if (mon <= 31 && (token.id == TimeToken.SLASH || token.id == TimeToken.DOT)) { + int sep = token.id; + expectToken(TimeToken.NUMBER, "there should be " + + (sep == TimeToken.DOT ? "month" : "day") + + " number after " + + (sep == TimeToken.DOT ? '.' : '/')); + mday = Long.parseLong(token.value); + token = scanner.nextToken(); + if (token.id == sep) { + expectToken(TimeToken.NUMBER, "there should be year number after " + + (sep == TimeToken.DOT ? '.' : '/')); + year = Long.parseLong(token.value); + token = scanner.nextToken(); + } + /* flip months and days for European timing */ + if (sep == TimeToken.DOT) { + long x = mday; + mday = mon; + mon = x; + } + } + } + mon--; + if (mon < 0 || mon > 11) { + throw new RrdException("Did you really mean month " + (mon + 1)); + } + if (mday < 1 || mday > 31) { + throw new RrdException("I'm afraid that " + mday + + " is not a valid day of the month"); + } + assignDate(mday, mon, year); + break; + } + } + + /** + * Parses the input string specified in the constructor. + * + * @return Object representing parsed date/time. + * @throws RrdException Thrown if the date string cannot be parsed. + */ + public TimeSpec parse() throws RrdException { + long now = Util.getTime(); + int hr = 0; + /* this MUST be initialized to zero for midnight/noon/teatime */ + /* establish the default time reference */ + spec.localtime(now); + token = scanner.nextToken(); + switch (token.id) { + case TimeToken.PLUS: + case TimeToken.MINUS: + break; /* jump to OFFSET-SPEC part */ + case TimeToken.START: + spec.type = TimeSpec.TYPE_START; + /* FALLTHRU */ + case TimeToken.END: + if (spec.type != TimeSpec.TYPE_START) { + spec.type = TimeSpec.TYPE_END; + } + /* FALLTHRU */ + case TimeToken.EPOCH: + /* FALLTHRU */ + case TimeToken.NOW: + int time_reference = token.id; + if (token.id != TimeToken.NOW) { + spec.year = spec.month = spec.day = spec.hour = spec.min = spec.sec = 0; + } + token = scanner.nextToken(); + if (token.id == TimeToken.PLUS || token.id == TimeToken.MINUS) { + break; + } + if (time_reference == TimeToken.START || time_reference == TimeToken.END) { + throw new RrdException("Words 'start' or 'end' MUST be followed by +|- offset"); + } + else if (token.id != TimeToken.EOF) { + throw new RrdException("If 'now' or 'epoch' is followed by a token it must be +|- offset"); + } + break; + /* Only absolute time specifications below */ + case TimeToken.NUMBER: + timeOfDay(); + //Keep going; there might be a date after the time of day, which day() will pick up + /* fix month parsing */ + case TimeToken.JAN: + case TimeToken.FEB: + case TimeToken.MAR: + case TimeToken.APR: + case TimeToken.MAY: + case TimeToken.JUN: + case TimeToken.JUL: + case TimeToken.AUG: + case TimeToken.SEP: + case TimeToken.OCT: + case TimeToken.NOV: + case TimeToken.DEC: + case TimeToken.TODAY: + case TimeToken.YESTERDAY: + case TimeToken.TOMORROW: + day(); + if (token.id != TimeToken.NUMBER) { + break; + } + //Allows (but does not require) the time to be specified after the day. This extends the rrdfetch specifiation + timeOfDay(); + break; + + /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized + * hr to zero up above, then fall into this case in such a + * way so we add +12 +4 hours to it for teatime, +12 hours + * to it for noon, and nothing at all for midnight, then + * set our rettime to that hour before leaping into the + * month scanner + */ + case TimeToken.TEATIME: + hr += 4; + /* FALLTHRU */ + case TimeToken.NOON: + hr += 12; + /* FALLTHRU */ + case TimeToken.MIDNIGHT: + spec.hour = hr; + spec.min = 0; + spec.sec = 0; + token = scanner.nextToken(); + day(); + break; + default: + throw new RrdException("Unparsable time: " + token.value); + } + + /* + * the OFFSET-SPEC part + * + * (NOTE, the sc_tokid was prefetched for us by the previous code) + */ + if (token.id == TimeToken.PLUS || token.id == TimeToken.MINUS) { + scanner.setContext(false); + while (token.id == TimeToken.PLUS || token.id == TimeToken.MINUS || + token.id == TimeToken.NUMBER) { + if (token.id == TimeToken.NUMBER) { + plusMinus(PREVIOUS_OP); + } + else { + plusMinus(token.id); + } + token = scanner.nextToken(); + /* We will get EOF eventually but that's OK, since + token() will return us as many EOFs as needed */ + } + } + /* now we should be at EOF */ + if (token.id != TimeToken.EOF) { + throw new RrdException("Unparsable trailing text: " + token.value); + } + return spec; + } +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/core/timespec/TimeScanner.java b/apps/jrobin/java/src/org/jrobin/core/timespec/TimeScanner.java new file mode 100644 index 000000000..2895ae55d --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/timespec/TimeScanner.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * 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.core.timespec; + +class TimeScanner { + private String dateString; + + private int pos, pos_save; + private TimeToken token, token_save; + + static final TimeToken[] WORDS = { + new TimeToken("midnight", TimeToken.MIDNIGHT), /* 00:00:00 of today or tomorrow */ + new TimeToken("noon", TimeToken.NOON), /* 12:00:00 of today or tomorrow */ + new TimeToken("teatime", TimeToken.TEATIME), /* 16:00:00 of today or tomorrow */ + new TimeToken("am", TimeToken.AM), /* morning times for 0-12 clock */ + new TimeToken("pm", TimeToken.PM), /* evening times for 0-12 clock */ + new TimeToken("tomorrow", TimeToken.TOMORROW), + new TimeToken("yesterday", TimeToken.YESTERDAY), + new TimeToken("today", TimeToken.TODAY), + new TimeToken("now", TimeToken.NOW), + new TimeToken("n", TimeToken.NOW), + new TimeToken("start", TimeToken.START), + new TimeToken("s", TimeToken.START), + new TimeToken("end", TimeToken.END), + new TimeToken("e", TimeToken.END), + new TimeToken("jan", TimeToken.JAN), + new TimeToken("feb", TimeToken.FEB), + new TimeToken("mar", TimeToken.MAR), + new TimeToken("apr", TimeToken.APR), + new TimeToken("may", TimeToken.MAY), + new TimeToken("jun", TimeToken.JUN), + new TimeToken("jul", TimeToken.JUL), + new TimeToken("aug", TimeToken.AUG), + new TimeToken("sep", TimeToken.SEP), + new TimeToken("oct", TimeToken.OCT), + new TimeToken("nov", TimeToken.NOV), + new TimeToken("dec", TimeToken.DEC), + new TimeToken("january", TimeToken.JAN), + new TimeToken("february", TimeToken.FEB), + new TimeToken("march", TimeToken.MAR), + new TimeToken("april", TimeToken.APR), + new TimeToken("may", TimeToken.MAY), + new TimeToken("june", TimeToken.JUN), + new TimeToken("july", TimeToken.JUL), + new TimeToken("august", TimeToken.AUG), + new TimeToken("september", TimeToken.SEP), + new TimeToken("october", TimeToken.OCT), + new TimeToken("november", TimeToken.NOV), + new TimeToken("december", TimeToken.DEC), + new TimeToken("sunday", TimeToken.SUN), + new TimeToken("sun", TimeToken.SUN), + new TimeToken("monday", TimeToken.MON), + new TimeToken("mon", TimeToken.MON), + new TimeToken("tuesday", TimeToken.TUE), + new TimeToken("tue", TimeToken.TUE), + new TimeToken("wednesday", TimeToken.WED), + new TimeToken("wed", TimeToken.WED), + new TimeToken("thursday", TimeToken.THU), + new TimeToken("thu", TimeToken.THU), + new TimeToken("friday", TimeToken.FRI), + new TimeToken("fri", TimeToken.FRI), + new TimeToken("saturday", TimeToken.SAT), + new TimeToken("sat", TimeToken.SAT), + new TimeToken("epoch", TimeToken.EPOCH), + new TimeToken(null, 0) /*** SENTINEL ***/ + }; + + static TimeToken[] MULTIPLIERS = { + new TimeToken("second", TimeToken.SECONDS), /* seconds multiplier */ + new TimeToken("seconds", TimeToken.SECONDS), /* (pluralized) */ + new TimeToken("sec", TimeToken.SECONDS), /* (generic) */ + new TimeToken("s", TimeToken.SECONDS), /* (short generic) */ + new TimeToken("minute", TimeToken.MINUTES), /* minutes multiplier */ + new TimeToken("minutes", TimeToken.MINUTES), /* (pluralized) */ + new TimeToken("min", TimeToken.MINUTES), /* (generic) */ + new TimeToken("m", TimeToken.MONTHS_MINUTES), /* (short generic) */ + new TimeToken("hour", TimeToken.HOURS), /* hours ... */ + new TimeToken("hours", TimeToken.HOURS), /* (pluralized) */ + new TimeToken("hr", TimeToken.HOURS), /* (generic) */ + new TimeToken("h", TimeToken.HOURS), /* (short generic) */ + new TimeToken("day", TimeToken.DAYS), /* days ... */ + new TimeToken("days", TimeToken.DAYS), /* (pluralized) */ + new TimeToken("d", TimeToken.DAYS), /* (short generic) */ + new TimeToken("week", TimeToken.WEEKS), /* week ... */ + new TimeToken("weeks", TimeToken.WEEKS), /* (pluralized) */ + new TimeToken("wk", TimeToken.WEEKS), /* (generic) */ + new TimeToken("w", TimeToken.WEEKS), /* (short generic) */ + new TimeToken("month", TimeToken.MONTHS), /* week ... */ + new TimeToken("months", TimeToken.MONTHS), /* (pluralized) */ + new TimeToken("mon", TimeToken.MONTHS), /* (generic) */ + new TimeToken("year", TimeToken.YEARS), /* year ... */ + new TimeToken("years", TimeToken.YEARS), /* (pluralized) */ + new TimeToken("yr", TimeToken.YEARS), /* (generic) */ + new TimeToken("y", TimeToken.YEARS), /* (short generic) */ + new TimeToken(null, 0) /*** SENTINEL ***/ + }; + + TimeToken[] specials = WORDS; + + public TimeScanner(String dateString) { + this.dateString = dateString; + } + + void setContext(boolean parsingWords) { + specials = parsingWords ? WORDS : MULTIPLIERS; + } + + TimeToken nextToken() { + StringBuffer buffer = new StringBuffer(""); + while (pos < dateString.length()) { + char c = dateString.charAt(pos++); + if (Character.isWhitespace(c) || c == '_' || c == ',') { + continue; + } + buffer.append(c); + if (Character.isDigit(c)) { + // pick as many digits as possible + while (pos < dateString.length()) { + char next = dateString.charAt(pos); + if (Character.isDigit(next)) { + buffer.append(next); + pos++; + } + else { + break; + } + } + String value = buffer.toString(); + return token = new TimeToken(value, TimeToken.NUMBER); + } + if (Character.isLetter(c)) { + // pick as many letters as possible + while (pos < dateString.length()) { + char next = dateString.charAt(pos); + if (Character.isLetter(next)) { + buffer.append(next); + pos++; + } + else { + break; + } + } + String value = buffer.toString(); + return token = new TimeToken(value, parseToken(value)); + } + switch (c) { + case ':': + return token = new TimeToken(":", TimeToken.COLON); + case '.': + return token = new TimeToken(".", TimeToken.DOT); + case '+': + return token = new TimeToken("+", TimeToken.PLUS); + case '-': + return token = new TimeToken("-", TimeToken.MINUS); + case '/': + return token = new TimeToken("/", TimeToken.SLASH); + default: + pos--; + return token = new TimeToken(null, TimeToken.EOF); + } + } + return token = new TimeToken(null, TimeToken.EOF); + } + + TimeToken resolveMonthsMinutes(int newId) { + assert token.id == TimeToken.MONTHS_MINUTES; + assert newId == TimeToken.MONTHS || newId == TimeToken.MINUTES; + return token = new TimeToken(token.value, newId); + } + + void saveState() { + token_save = token; + pos_save = pos; + } + + TimeToken restoreState() { + pos = pos_save; + return token = token_save; + } + + private int parseToken(String arg) { + for (int i = 0; specials[i].value != null; i++) { + if (specials[i].value.equalsIgnoreCase(arg)) { + return specials[i].id; + } + } + return TimeToken.ID; + } +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/core/timespec/TimeSpec.java b/apps/jrobin/java/src/org/jrobin/core/timespec/TimeSpec.java new file mode 100644 index 000000000..79b18878d --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/timespec/TimeSpec.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * 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.core.timespec; + +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * Simple class to represent time obtained by parsing at-style date specification (described + * in detail on the rrdfetch man page. See javadoc for {@link org.jrobin.core.timespec.TimeParser} + * for more information. + */ +public class TimeSpec { + static final int TYPE_ABSOLUTE = 0; + static final int TYPE_START = 1; + static final int TYPE_END = 2; + + int type = TYPE_ABSOLUTE; + int year, month, day, hour, min, sec; + int wday; + int dyear, dmonth, dday, dhour, dmin, dsec; + + String dateString; + + TimeSpec context; + + TimeSpec(String dateString) { + this.dateString = dateString; + } + + void localtime(long timestamp) { + GregorianCalendar date = new GregorianCalendar(); + date.setTime(new Date(timestamp * 1000L)); + year = date.get(Calendar.YEAR) - 1900; + month = date.get(Calendar.MONTH); + day = date.get(Calendar.DAY_OF_MONTH); + hour = date.get(Calendar.HOUR_OF_DAY); + min = date.get(Calendar.MINUTE); + sec = date.get(Calendar.SECOND); + wday = date.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY; + } + + GregorianCalendar getTime() throws RrdException { + GregorianCalendar gc; + // absoulte time, this is easy + if (type == TYPE_ABSOLUTE) { + gc = new GregorianCalendar(year + 1900, month, day, hour, min, sec); + } + // relative time, we need a context to evaluate it + else if (context != null && context.type == TYPE_ABSOLUTE) { + gc = context.getTime(); + } + // how would I guess what time it was? + else { + throw new RrdException("Relative times like '" + + dateString + "' require proper absolute context to be evaluated"); + } + gc.add(Calendar.YEAR, dyear); + gc.add(Calendar.MONTH, dmonth); + gc.add(Calendar.DAY_OF_MONTH, dday); + gc.add(Calendar.HOUR_OF_DAY, dhour); + gc.add(Calendar.MINUTE, dmin); + gc.add(Calendar.SECOND, dsec); + return gc; + } + + /** + * Returns the corresponding timestamp (seconds since Epoch). Example:

+ *

+	 * TimeParser p = new TimeParser("now-1day");
+	 * TimeSpec ts = p.parse();
+	 * System.out.println("Timestamp was: " + ts.getTimestamp();
+	 * 
+ * + * @return Timestamp (in seconds, no milliseconds) + * @throws RrdException Thrown if this TimeSpec object does not represent absolute time. + */ + public long getTimestamp() throws RrdException { + return Util.getTimestamp(getTime()); + } + + String dump() { + return (type == TYPE_ABSOLUTE ? "ABSTIME" : type == TYPE_START ? "START" : "END") + + ": " + year + "/" + month + "/" + day + + "/" + hour + "/" + min + "/" + sec + " (" + + dyear + "/" + dmonth + "/" + dday + + "/" + dhour + "/" + dmin + "/" + dsec + ")"; + } + + /** + * Use this static method to resolve relative time references and obtain the corresponding + * Calendar objects. Example:

+ *

+	 * TimeParser pStart = new TimeParser("now-1month"); // starting time
+	 * TimeParser pEnd = new TimeParser("start+1week");  // ending time
+	 * TimeSpec specStart = pStart.parse();
+	 * TimeSpec specEnd = pEnd.parse();
+	 * GregorianCalendar[] gc = TimeSpec.getTimes(specStart, specEnd);
+	 * 
+ * + * @param spec1 Starting time specification + * @param spec2 Ending time specification + * @return Two element array containing Calendar objects + * @throws RrdException Thrown if relative time references cannot be resolved + */ + public static Calendar[] getTimes(TimeSpec spec1, TimeSpec spec2) throws RrdException { + if (spec1.type == TYPE_START || spec2.type == TYPE_END) { + throw new RrdException("Recursive time specifications not allowed"); + } + spec1.context = spec2; + spec2.context = spec1; + return new Calendar[] { + spec1.getTime(), + spec2.getTime() + }; + } + + /** + * Use this static method to resolve relative time references and obtain the corresponding + * timestamps (seconds since epoch). Example:

+ *

+	 * TimeParser pStart = new TimeParser("now-1month"); // starting time
+	 * TimeParser pEnd = new TimeParser("start+1week");  // ending time
+	 * TimeSpec specStart = pStart.parse();
+	 * TimeSpec specEnd = pEnd.parse();
+	 * long[] ts = TimeSpec.getTimestamps(specStart, specEnd);
+	 * 
+ * + * @param spec1 Starting time specification + * @param spec2 Ending time specification + * @return array containing two timestamps (in seconds since epoch) + * @throws RrdException Thrown if relative time references cannot be resolved + */ + public static long[] getTimestamps(TimeSpec spec1, TimeSpec spec2) throws RrdException { + Calendar[] gcs = getTimes(spec1, spec2); + return new long[] { + Util.getTimestamp(gcs[0]), Util.getTimestamp(gcs[1]) + }; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/core/timespec/TimeToken.java b/apps/jrobin/java/src/org/jrobin/core/timespec/TimeToken.java new file mode 100644 index 000000000..64b2057e7 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/core/timespec/TimeToken.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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.core.timespec; + +class TimeToken { + public static final int MIDNIGHT = 1; + public static final int NOON = 2; + public static final int TEATIME = 3; + public static final int PM = 4; + public static final int AM = 5; + public static final int YESTERDAY = 6; + public static final int TODAY = 7; + public static final int TOMORROW = 8; + public static final int NOW = 9; + public static final int START = 10; + public static final int END = 11; + public static final int SECONDS = 12; + public static final int MINUTES = 13; + public static final int HOURS = 14; + public static final int DAYS = 15; + public static final int WEEKS = 16; + public static final int MONTHS = 17; + public static final int YEARS = 18; + public static final int MONTHS_MINUTES = 19; + public static final int NUMBER = 20; + public static final int PLUS = 21; + public static final int MINUS = 22; + public static final int DOT = 23; + public static final int COLON = 24; + public static final int SLASH = 25; + public static final int ID = 26; + public static final int JUNK = 27; + public static final int JAN = 28; + public static final int FEB = 29; + public static final int MAR = 30; + public static final int APR = 31; + public static final int MAY = 32; + public static final int JUN = 33; + public static final int JUL = 34; + public static final int AUG = 35; + public static final int SEP = 36; + public static final int OCT = 37; + public static final int NOV = 38; + public static final int DEC = 39; + public static final int SUN = 40; + public static final int MON = 41; + public static final int TUE = 42; + public static final int WED = 43; + public static final int THU = 44; + public static final int FRI = 45; + public static final int SAT = 46; + public static final int EPOCH = 46; + public static final int EOF = -1; + + final String value; /* token name */ + final int id; /* token id */ + + public TimeToken(String value, int id) { + this.value = value; + this.id = id; + } + + public String toString() { + return value + " [" + id + "]"; + } +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/data/Aggregates.java b/apps/jrobin/java/src/org/jrobin/data/Aggregates.java new file mode 100644 index 000000000..f2194b593 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/Aggregates.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * 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 org.jrobin.core.ConsolFuns; +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; + +/** + * Simple class which holds aggregated values (MIN, MAX, FIRST, LAST, AVERAGE and TOTAL). You + * don't need to create objects of this class directly. Objects of this class are returned from + * getAggregates() method in + * {@link org.jrobin.core.FetchData#getAggregates(String) FetchData} and + * {@link DataProcessor#getAggregates(String)} DataProcessor} classes. + */ +public class Aggregates implements ConsolFuns { + double min = Double.NaN, max = Double.NaN; + double first = Double.NaN, last = Double.NaN; + double average = Double.NaN, total = Double.NaN; + double stdev = Double.NaN, lslslope = Double.NaN; + double lslint = Double.NaN, lslcorrel = Double.NaN; + + Aggregates() { + // NOP; + } + + /** + * Returns the minimal value + * + * @return Minimal value + */ + public double getMin() { + return min; + } + + /** + * Returns the maximum value + * + * @return Maximum value + */ + public double getMax() { + return max; + } + + /** + * Returns the first falue + * + * @return First value + */ + public double getFirst() { + return first; + } + + /** + * Returns the last value + * + * @return Last value + */ + public double getLast() { + return last; + } + + /** + * Returns average + * + * @return Average value + */ + public double getAverage() { + return average; + } + + /** + * Returns total value + * + * @return Total value + */ + public double getTotal() { + return total; + } + + /** + * Returns stdev value + * + * @return Stdev value + */ + public double getStdev() { + return stdev; + } + + /** + * Returns Least Squares Line Slope value + * + * @return lslslope value + */ + public double getLSLSlope() { + return stdev; + } + + /** + * Returns Least Squares Line y-intercept value + * + * @return lslint value + */ + public double getLSLInt() { + return lslint; + } + + /** + * Returns Least Squares Line Correlation Coefficient + * + * @return lslcorrel value + */ + public double getLSLCorrel() { + return lslcorrel; + } + + /** + * Returns single aggregated value for the give consolidation function + * + * @param consolFun Consolidation function: MIN, MAX, FIRST, LAST, AVERAGE, TOTAL. These constants + * are conveniently defined in the {@link org.jrobin.core.ConsolFuns ConsolFuns} interface. + * @return Aggregated value + * @throws RrdException Thrown if unsupported consolidation function is supplied + */ + public double getAggregate(String consolFun) throws RrdException { + if (consolFun.equals(CF_AVERAGE)) { + return average; + } + else if (consolFun.equals(CF_FIRST)) { + return first; + } + else if (consolFun.equals(CF_LAST)) { + return last; + } + else if (consolFun.equals(CF_MAX)) { + return max; + } + else if (consolFun.equals(CF_MIN)) { + return min; + } + else if (consolFun.equals(CF_TOTAL)) { + return total; + } + else if (consolFun.equals("STDEV")) { + return stdev; + } + else if (consolFun.equals("LSLSLOPE")) { + return lslslope; + } + else if (consolFun.equals("LSLINT")) { + return lslint; + } + else if (consolFun.equals("LSLCORREL")) { + return lslcorrel; + } + else { + throw new RrdException("Unknown consolidation function: " + consolFun); + } + } + + /** + * Returns String representing all aggregated values. Just for debugging purposes. + * + * @return String containing all aggregated values + */ + public String dump() { + return "MIN=" + Util.formatDouble(min) + ", MAX=" + Util.formatDouble(max) + "\n" + + "FIRST=" + Util.formatDouble(first) + ", LAST=" + Util.formatDouble(last) + "\n" + + "AVERAGE=" + Util.formatDouble(average) + ", TOTAL=" + Util.formatDouble(total); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/Aggregator.java b/apps/jrobin/java/src/org/jrobin/data/Aggregator.java new file mode 100644 index 000000000..05ceccbbc --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/Aggregator.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * 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 org.jrobin.core.ConsolFuns; +import org.jrobin.core.Util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +class Aggregator implements ConsolFuns { + private long timestamps[], step; + private double[] values; + + Aggregator(long[] timestamps, double[] values) { + assert timestamps.length == values.length: "Incompatible timestamps/values arrays (unequal lengths)"; + assert timestamps.length >= 2: "At least two timestamps must be supplied"; + this.timestamps = timestamps; + this.values = values; + this.step = timestamps[1] - timestamps[0]; + } + + Aggregates getAggregates(long tStart, long tEnd) { + Aggregates agg = new Aggregates(); + int cnt = 0; + int lslstep = 0; + boolean firstFound = false; + double SUMx, SUMy, SUMxy, SUMxx, SUMyy; + SUMx = 0.0; + SUMy = 0.0; + SUMxy = 0.0; + SUMxx = 0.0; + SUMyy = 0.0; + + for (int i = 0; i < timestamps.length; i++) { + long left = Math.max(timestamps[i] - step, tStart); + long right = Math.min(timestamps[i], tEnd); + long delta = right - left; + + // delta is only >= 0 when the timestamp for a given buck is within the range of tStart and tEnd + if (delta >= 0) { + double value = values[i]; + agg.min = Util.min(agg.min, value); + agg.max = Util.max(agg.max, value); + if (!firstFound) { + agg.first = value; + firstFound = true; + agg.last = value; + } else if (delta >= step) { // an entire bucket is included in this range + agg.last = value; + + /* + * Algorithmically, we're only updating last if it's either the first + * bucket encountered, or it's a "full" bucket. + + if ( !isInRange(tEnd, left, right) || + (isInRange(tEnd, left, right) && !Double.isNaN(value)) + ) { + agg.last = value; + } + */ + + } + if (!Double.isNaN(value)) { + cnt++; + SUMx += lslstep; + SUMxx += lslstep * lslstep; + SUMy = Util.sum(SUMy, value); + SUMxy = Util.sum(SUMxy, lslstep * value); + SUMyy = Util.sum(SUMyy, value * value); + } + lslstep ++; + } + } + agg.average = cnt > 0 ? (SUMy / cnt) : Double.NaN; + + // Work on STDEV + if (cnt > 0) { + double stdevSum = 0.0; + for (int i = 0; i < timestamps.length; i++) { + long left = Math.max(timestamps[i] - step, tStart); + long right = Math.min(timestamps[i], tEnd); + long delta = right - left; + + // delta is only >= 0 when the timestamp for a given buck is within the range of tStart and tEnd + if (delta >= 0) { + double value = values[i]; + if (!Double.isNaN(value)) { + stdevSum = Util.sum(stdevSum, Math.pow((value - agg.average), 2.0)); + } + } + } + agg.stdev = Math.pow(stdevSum / cnt, 0.5); + + /* Bestfit line by linear least squares method */ + agg.lslslope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx); + agg.lslint = (SUMy - agg.lslslope * SUMx) / cnt; + agg.lslcorrel = + (SUMxy - (SUMx * SUMy) / cnt) / + Math.sqrt((SUMxx - (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt)); + } + agg.total = SUMy * step; + + return agg; + } + + double getPercentile(long tStart, long tEnd, double percentile) { + return getPercentile(tStart, tEnd, percentile, false); + } + + double getPercentile(long tStart, long tEnd, double percentile, boolean includenan) { + List valueList = new ArrayList(); + // create a list of included datasource values (different from NaN) + for (int i = 0; i < timestamps.length; i++) { + long left = Math.max(timestamps[i] - step, tStart); + long right = Math.min(timestamps[i], tEnd); + if (right > left && (!Double.isNaN(values[i]) || includenan)) { + valueList.add(values[i]); + } + } + // create an array to work with + int count = valueList.size(); + if (count > 1) { + double[] valuesCopy = new double[count]; + for (int i = 0; i < count; i++) { + valuesCopy[i] = valueList.get(i); + } + // sort array + Arrays.sort(valuesCopy); + // skip top (100% - percentile) values + double topPercentile = (100.0 - percentile) / 100.0; + count -= (int) Math.ceil(count * topPercentile); + // if we have anything left... + if (count > 0) { + return valuesCopy[count - 1]; + } + } + // not enough data available + return Double.NaN; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/CDef.java b/apps/jrobin/java/src/org/jrobin/data/CDef.java new file mode 100644 index 000000000..639c36d0c --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/CDef.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * 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; + +class CDef extends Source { + private String rpnExpression; + + CDef(String name, String rpnExpression) { + super(name); + this.rpnExpression = rpnExpression; + } + + String getRpnExpression() { + return rpnExpression; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/CubicSplineInterpolator.java b/apps/jrobin/java/src/org/jrobin/data/CubicSplineInterpolator.java new file mode 100644 index 000000000..d86666805 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/CubicSplineInterpolator.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * 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 org.jrobin.core.RrdException; +import org.jrobin.core.Util; + +import java.util.Calendar; +import java.util.Date; + +/** + * Class used to interpolate datasource values from the collection of (timestamp, values) + * points using natural cubic spline interpolation. + *

+ * WARNING: So far, this class cannot handle NaN datasource values + * (an exception will be thrown by the constructor). Future releases might change this. + */ +public class CubicSplineInterpolator extends Plottable { + private double[] x; + private double[] y; + + // second derivates come here + private double[] y2; + + // internal spline variables + private int n, klo, khi; + + /** + * Creates cubic spline interpolator from arrays of timestamps and corresponding + * datasource values. + * + * @param timestamps timestamps in seconds + * @param values corresponding datasource values + * @throws RrdException Thrown if supplied arrays do not contain at least 3 values, or if + * timestamps are not ordered, or array lengths are not equal, or some datasource value is NaN. + */ + public CubicSplineInterpolator(long[] timestamps, double[] values) throws RrdException { + this.x = new double[timestamps.length]; + for (int i = 0; i < timestamps.length; i++) { + this.x[i] = timestamps[i]; + } + this.y = values; + validate(); + spline(); + } + + /** + * Creates cubic spline interpolator from arrays of Date objects and corresponding + * datasource values. + * + * @param dates Array of Date objects + * @param values corresponding datasource values + * @throws RrdException Thrown if supplied arrays do not contain at least 3 values, or if + * timestamps are not ordered, or array lengths are not equal, or some datasource value is NaN. + */ + public CubicSplineInterpolator(Date[] dates, double[] values) throws RrdException { + this.x = new double[dates.length]; + for (int i = 0; i < dates.length; i++) { + this.x[i] = Util.getTimestamp(dates[i]); + } + this.y = values; + validate(); + spline(); + } + + /** + * Creates cubic spline interpolator from arrays of GregorianCalendar objects and corresponding + * datasource values. + * + * @param dates Array of GregorianCalendar objects + * @param values corresponding datasource values + * @throws RrdException Thrown if supplied arrays do not contain at least 3 values, or if + * timestamps are not ordered, or array lengths are not equal, or some datasource value is NaN. + */ + public CubicSplineInterpolator(Calendar[] dates, double[] values) throws RrdException { + this.x = new double[dates.length]; + for (int i = 0; i < dates.length; i++) { + this.x[i] = Util.getTimestamp(dates[i]); + } + this.y = values; + validate(); + spline(); + } + + /** + * Creates cubic spline interpolator for an array of 2D-points. + * + * @param x x-axis point coordinates + * @param y y-axis point coordinates + * @throws RrdException Thrown if supplied arrays do not contain at least 3 values, or if + * timestamps are not ordered, or array lengths are not equal, or some datasource value is NaN. + */ + public CubicSplineInterpolator(double[] x, double[] y) throws RrdException { + this.x = x; + this.y = y; + validate(); + spline(); + } + + private void validate() throws RrdException { + boolean ok = true; + if (x.length != y.length || x.length < 3) { + ok = false; + } + for (int i = 0; i < x.length - 1 && ok; i++) { + if (x[i] >= x[i + 1] || Double.isNaN(y[i])) { + ok = false; + } + } + if (!ok) { + throw new RrdException("Invalid plottable data supplied"); + } + } + + private void spline() { + n = x.length; + y2 = new double[n]; + double[] u = new double[n - 1]; + y2[0] = y2[n - 1] = 0.0; + u[0] = 0.0; // natural spline + for (int i = 1; i <= n - 2; i++) { + double sig = (x[i] - x[i - 1]) / (x[i + 1] - x[i - 1]); + double p = sig * y2[i - 1] + 2.0; + y2[i] = (sig - 1.0) / p; + u[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]) - (y[i] - y[i - 1]) / (x[i] - x[i - 1]); + u[i] = (6.0 * u[i] / (x[i + 1] - x[i - 1]) - sig * u[i - 1]) / p; + } + for (int k = n - 2; k >= 0; k--) { + y2[k] = y2[k] * y2[k + 1] + u[k]; + } + // prepare everything for getValue() + klo = 0; + khi = n - 1; + } + + /** + * Calculates spline-interpolated y-value for the corresponding x-value. Call + * this if you need spline-interpolated values in your code. + * + * @param xval x-value + * @return inteprolated y-value + */ + public double getValue(double xval) { + if (xval < x[0] || xval > x[n - 1]) { + return Double.NaN; + } + if (xval < x[klo] || xval > x[khi]) { + // out of bounds + klo = 0; + khi = n - 1; + } + while (khi - klo > 1) { + // find bounding interval using bisection method + int k = (khi + klo) >>> 1; + if (x[k] > xval) { + khi = k; + } + else { + klo = k; + } + } + double h = x[khi] - x[klo]; + double a = (x[khi] - xval) / h; + double b = (xval - x[klo]) / h; + return a * y[klo] + b * y[khi] + + ((a * a * a - a) * y2[klo] + (b * b * b - b) * y2[khi]) * (h * h) / 6.0; + } + + /** + * Method overriden from the base class. This method will be called by the framework. Call + * this method only if you need spline-interpolated values in your code. + * + * @param timestamp timestamp in seconds + * @return inteprolated datasource value + */ + public double getValue(long timestamp) { + return getValue((double) timestamp); + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/data/DataProcessor.java b/apps/jrobin/java/src/org/jrobin/data/DataProcessor.java new file mode 100644 index 000000000..a25806834 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/DataProcessor.java @@ -0,0 +1,936 @@ +/******************************************************************************* + * 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 org.jrobin.core.*; + +import java.io.IOException; +import java.util.*; + +/** + * Class which should be used for all calculations based on the data fetched from RRD files. This class + * supports ordinary DEF datasources (defined in RRD files), CDEF datasources (RPN expressions evaluation), + * SDEF (static datasources - extension of JRobin) and PDEF (plottables, see + * {@link Plottable Plottable} for more information. + *

+ * Typical class usage: + *

+ *

+ * final long t1 = ...
+ * final long t2 = ...
+ * DataProcessor dp = new DataProcessor(t1, t2);
+ * // DEF datasource
+ * dp.addDatasource("x", "demo.rrd", "some_source", "AVERAGE");
+ * // DEF datasource
+ * dp.addDatasource("y", "demo.rrd", "some_other_source", "AVERAGE");
+ * // CDEF datasource, z = (x + y) / 2
+ * dp.addDatasource("z", "x,y,+,2,/");
+ * // ACTION!
+ * dp.processData();
+ * // Dump calculated values
+ * System.out.println(dp.dump());
+ * 
+ */ +public class DataProcessor implements ConsolFuns { + /** + * Constant representing the default number of pixels on a JRobin graph (will be used if + * no other value is specified with {@link #setStep(long) setStep()} method. + */ + public static final int DEFAULT_PIXEL_COUNT = 600; + private static final double DEFAULT_PERCENTILE = 95.0; // % + + private int pixelCount = DEFAULT_PIXEL_COUNT; + + /** + * Constant that defines the default {@link RrdDbPool} usage policy. Defaults to false + * (i.e. the pool will not be used to fetch data from RRD files) + */ + public static final boolean DEFAULT_POOL_USAGE_POLICY = false; + private boolean poolUsed = DEFAULT_POOL_USAGE_POLICY; + + private final long tStart; + private long tEnd, timestamps[]; + private long lastRrdArchiveUpdateTime = 0; + // this will be adjusted later + private long step = 0; + // resolution to be used for RRD fetch operation + private long fetchRequestResolution = 1; + + // the order is important, ordinary HashMap is unordered + private Map sources = new LinkedHashMap(); + + private Def[] defSources; + + /** + * Creates new DataProcessor object for the given time span. Ending timestamp may be set to zero. + * In that case, the class will try to find the optimal ending timestamp based on the last update time of + * RRD files processed with the {@link #processData()} method. + * + * @param t1 Starting timestamp in seconds without milliseconds + * @param t2 Ending timestamp in seconds without milliseconds + * @throws RrdException Thrown if invalid timestamps are supplied + */ + public DataProcessor(long t1, long t2) throws RrdException { + if ((t1 < t2 && t1 > 0 && t2 > 0) || (t1 > 0 && t2 == 0)) { + this.tStart = t1; + this.tEnd = t2; + } + else { + throw new RrdException("Invalid timestamps specified: " + t1 + ", " + t2); + } + } + + /** + * Creates new DataProcessor object for the given time span. Ending date may be set to null. + * In that case, the class will try to find optimal ending date based on the last update time of + * RRD files processed with the {@link #processData()} method. + * + * @param d1 Starting date + * @param d2 Ending date + * @throws RrdException Thrown if invalid timestamps are supplied + */ + public DataProcessor(Date d1, Date d2) throws RrdException { + this(Util.getTimestamp(d1), d2 != null ? Util.getTimestamp(d2) : 0); + } + + /** + * Creates new DataProcessor object for the given time span. Ending date may be set to null. + * In that case, the class will try to find optimal ending date based on the last update time of + * RRD files processed with the {@link #processData()} method. + * + * @param gc1 Starting Calendar date + * @param gc2 Ending Calendar date + * @throws RrdException Thrown if invalid timestamps are supplied + */ + public DataProcessor(Calendar gc1, Calendar gc2) throws RrdException { + this(Util.getTimestamp(gc1), gc2 != null ? Util.getTimestamp(gc2) : 0); + } + + /** + * Returns boolean value representing {@link org.jrobin.core.RrdDbPool RrdDbPool} usage policy. + * + * @return true, if the pool will be used internally to fetch data from RRD files, false otherwise. + */ + public boolean isPoolUsed() { + return poolUsed; + } + + /** + * Sets the {@link org.jrobin.core.RrdDbPool RrdDbPool} usage policy. + * + * @param poolUsed true, if the pool should be used to fetch data from RRD files, false otherwise. + */ + public void setPoolUsed(boolean poolUsed) { + this.poolUsed = poolUsed; + } + + /** + * Sets the number of pixels (target graph width). This number is used only to calculate pixel coordinates + * for JRobin graphs (methods {@link #getValuesPerPixel(String)} and {@link #getTimestampsPerPixel()}), + * but has influence neither on datasource values calculated with the + * {@link #processData()} method nor on aggregated values returned from {@link #getAggregates(String)} + * and similar methods. In other words, aggregated values will not change once you decide to change + * the dimension of your graph. + *

+ * The default number of pixels is defined by constant {@link #DEFAULT_PIXEL_COUNT} + * and can be changed with a {@link #setPixelCount(int)} method. + * + * @param pixelCount The number of pixels. If you process RRD data in order to display it on the graph, + * this should be the width of your graph. + */ + public void setPixelCount(int pixelCount) { + this.pixelCount = pixelCount; + } + + /** + * Returns the number of pixels (target graph width). See {@link #setPixelCount(int)} for more information. + * + * @return Target graph width + */ + public int getPixelCount() { + return pixelCount; + } + + /** + * Roughly corresponds to the --step option in RRDTool's graph/xport commands. Here is an explanation borrowed + * from RRDTool: + *

+ * "By default rrdgraph calculates the width of one pixel in the time + * domain and tries to get data at that resolution from the RRD. With + * this switch you can override this behavior. If you want rrdgraph to + * get data at 1 hour resolution from the RRD, then you can set the + * step to 3600 seconds. Note, that a step smaller than 1 pixel will + * be silently ignored." + *

+ * I think this option is not that useful, but it's here just for compatibility. + *

+ * @param step Time step at which data should be fetched from RRD files. If this method is not used, + * the step will be equal to the smallest RRD step of all processed RRD files. If no RRD file is processed, + * the step will be roughly equal to the with of one graph pixel (in seconds). + */ + public void setStep(long step) { + this.step = step; + } + + /** + * Returns the time step used for data processing. Initially, this method returns zero. + * Once {@link #processData()} is finished, the method will return the real value used for + * all internal computations. Roughly corresponds to the --step option in RRDTool's graph/xport commands. + * + * @return Step used for data processing. + */ + public long getStep() { + return step; + } + + /** + * Returns desired RRD archive step (reslution) in seconds to be used while fetching data + * from RRD files. In other words, this value will used as the last parameter of + * {@link RrdDb#createFetchRequest(String, long, long, long) RrdDb.createFetchRequest()} method + * when this method is called internally by this DataProcessor. + * + * @return Desired archive step (fetch resolution) in seconds. + */ + public long getFetchRequestResolution() { + return fetchRequestResolution; + } + + /** + * Sets desired RRD archive step in seconds to be used internally while fetching data + * from RRD files. In other words, this value will used as the last parameter of + * {@link RrdDb#createFetchRequest(String, long, long, long) RrdDb.createFetchRequest()} method + * when this method is called internally by this DataProcessor. If this method is never called, fetch + * request resolution defaults to 1 (smallest possible archive step will be chosen automatically). + * + * @param fetchRequestResolution Desired archive step (fetch resoltuion) in seconds. + */ + public void setFetchRequestResolution(long fetchRequestResolution) { + this.fetchRequestResolution = fetchRequestResolution; + } + + /** + * Returns ending timestamp. Basically, this value is equal to the ending timestamp + * specified in the constructor. However, if the ending timestamps was zero, it + * will be replaced with the real timestamp when the {@link #processData()} method returns. The real + * value will be calculated from the last update times of processed RRD files. + * + * @return Ending timestamp in seconds + */ + public long getEndingTimestamp() { + return tEnd; + } + + /** + * Returns consolidated timestamps created with the {@link #processData()} method. + * + * @return array of timestamps in seconds + * @throws RrdException thrown if timestamps are not calculated yet + */ + public long[] getTimestamps() throws RrdException { + if (timestamps == null) { + throw new RrdException("Timestamps not calculated yet"); + } + else { + return timestamps; + } + } + + /** + * Returns calculated values for a single datasource. Corresponding timestamps can be obtained from + * the {@link #getTimestamps()} method. + * + * @param sourceName Datasource name + * @return an array of datasource values + * @throws RrdException Thrown if invalid datasource name is specified, + * or if datasource values are not yet calculated (method {@link #processData()} + * was not called) + */ + public double[] getValues(String sourceName) throws RrdException { + Source source = getSource(sourceName); + double[] values = source.getValues(); + if (values == null) { + throw new RrdException("Values not available for source [" + sourceName + "]"); + } + return values; + } + + /** + * Returns single aggregated value for a single datasource. + * + * @param sourceName Datasource name + * @param consolFun Consolidation function to be applied to fetched datasource values. + * Valid consolidation functions are MIN, MAX, LAST, FIRST, AVERAGE and TOTAL + * (these string constants are conveniently defined in the {@link ConsolFuns} class) + * @return MIN, MAX, LAST, FIRST, AVERAGE or TOTAL value calculated from the data + * for the given datasource name + * @throws RrdException Thrown if invalid datasource name is specified, + * or if datasource values are not yet calculated (method {@link #processData()} + * was not called) + */ + public double getAggregate(String sourceName, String consolFun) throws RrdException { + Source source = getSource(sourceName); + return source.getAggregates(tStart, tEnd).getAggregate(consolFun); + } + + /** + * Returns all (MIN, MAX, LAST, FIRST, AVERAGE and TOTAL) aggregated values for a single datasource. + * + * @param sourceName Datasource name + * @return Object containing all aggregated values + * @throws RrdException Thrown if invalid datasource name is specified, + * or if datasource values are not yet calculated (method {@link #processData()} + * was not called) + */ + public Aggregates getAggregates(String sourceName) throws RrdException { + Source source = getSource(sourceName); + return source.getAggregates(tStart, tEnd); + } + + /** + * This method is just an alias for {@link #getPercentile(String)} method. + *

+ * Used by ISPs which charge for bandwidth utilization on a "95th percentile" basis. + *

+ * The 95th percentile is the highest source value left when the top 5% of a numerically sorted set + * of source data is discarded. It is used as a measure of the peak value used when one discounts + * a fair amount for transitory spikes. This makes it markedly different from the average. + *

+ * Read more about this topic at + * Rednet or + * Bytemark. + * + * @param sourceName Datasource name + * @return 95th percentile of fetched source values + * @throws RrdException Thrown if invalid source name is supplied + */ + public double get95Percentile(String sourceName) throws RrdException { + return getPercentile(sourceName); + } + + /** + * Used by ISPs which charge for bandwidth utilization on a "95th percentile" basis. + *

+ * The 95th percentile is the highest source value left when the top 5% of a numerically sorted set + * of source data is discarded. It is used as a measure of the peak value used when one discounts + * a fair amount for transitory spikes. This makes it markedly different from the average. + *

+ * Read more about this topic at + * Rednet or + * Bytemark. + * + * @param sourceName Datasource name + * @return 95th percentile of fetched source values + * @throws RrdException Thrown if invalid source name is supplied + */ + public double getPercentile(String sourceName) throws RrdException { + return getPercentile(sourceName, DEFAULT_PERCENTILE); + } + + /** + * The same as {@link #getPercentile(String)} but with a possibility to define custom percentile boundary + * (different from 95). + * + * @param sourceName Datasource name. + * @param percentile Boundary percentile. Value of 95 (%) is suitable in most cases, but you are free + * to provide your own percentile boundary between zero and 100. + * @return Requested percentile of fetched source values + * @throws RrdException Thrown if invalid sourcename is supplied, or if the percentile value makes no sense. + */ + public double getPercentile(String sourceName, double percentile) throws RrdException { + if (percentile <= 0.0 || percentile > 100.0) { + throw new RrdException("Invalid percentile [" + percentile + "], should be between 0 and 100"); + } + Source source = getSource(sourceName); + return source.getPercentile(tStart, tEnd, percentile); + } + + /** + * Returns array of datasource names defined in this DataProcessor. + * + * @return array of datasource names + */ + public String[] getSourceNames() { + return sources.keySet().toArray(new String[0]); + } + + /** + * Returns an array of all datasource values for all datasources. Each row in this two-dimensional + * array represents an array of calculated values for a single datasource. The order of rows is the same + * as the order in which datasources were added to this DataProcessor object. + * + * @return All datasource values for all datasources. The first index is the index of the datasource, + * the second index is the index of the datasource value. The number of datasource values is equal + * to the number of timestamps returned with {@link #getTimestamps()} method. + * @throws RrdException Thrown if invalid datasource name is specified, + * or if datasource values are not yet calculated (method {@link #processData()} + * was not called) + */ + public double[][] getValues() throws RrdException { + String[] names = getSourceNames(); + double[][] values = new double[names.length][]; + for (int i = 0; i < names.length; i++) { + values[i] = getValues(names[i]); + } + return values; + } + + private Source getSource(String sourceName) throws RrdException { + Source source = sources.get(sourceName); + if (source != null) { + return source; + } + throw new RrdException("Unknown source: " + sourceName); + } + + ///////////////////////////////////////////////////////////////// + // DATASOURCE DEFINITIONS + ///////////////////////////////////////////////////////////////// + + /** + * Adds a custom, {@link org.jrobin.data.Plottable plottable} datasource (PDEF). + * The datapoints should be made available by a class extending + * {@link org.jrobin.data.Plottable Plottable} class. + *

+ * + * @param name source name. + * @param plottable class that extends Plottable class and is suited for graphing. + */ + public void addDatasource(String name, Plottable plottable) { + PDef pDef = new PDef(name, plottable); + sources.put(name, pDef); + } + + /** + * Adds complex source (CDEF). + * Complex sources are evaluated using the supplied RPN expression. + *

+ * Complex source name can be used: + *

    + *
  • To specify sources for line, area and stack plots.
  • + *
  • To define other complex sources.
  • + *
+ *

+ * JRobin supports the following RPN functions, operators and constants: +, -, *, /, + * %, SIN, COS, LOG, EXP, FLOOR, CEIL, ROUND, POW, ABS, SQRT, RANDOM, LT, LE, GT, GE, EQ, + * IF, MIN, MAX, LIMIT, DUP, EXC, POP, UN, UNKN, NOW, TIME, PI, E, + * AND, OR, XOR, PREV, PREV(sourceName), INF, NEGINF, STEP, YEAR, MONTH, DATE, + * HOUR, MINUTE, SECOND, WEEK, SIGN and RND. + *

+ * JRobin does not force you to specify at least one simple source name as RRDTool. + *

+ * For more details on RPN see RRDTool's + * + * rrdgraph man page. + * + * @param name source name. + * @param rpnExpression RPN expression containig comma (or space) delimited simple and complex + * source names, RPN constants, functions and operators. + */ + public void addDatasource(String name, String rpnExpression) { + CDef cDef = new CDef(name, rpnExpression); + sources.put(name, cDef); + } + + /** + * Adds static source (SDEF). Static sources are the result of a consolidation function applied + * to *any* other source that has been defined previously. + * + * @param name source name. + * @param defName Name of the datasource to calculate the value from. + * @param consolFun Consolidation function to use for value calculation + */ + public void addDatasource(String name, String defName, String consolFun) { + SDef sDef = new SDef(name, defName, consolFun); + sources.put(name, sDef); + } + + /** + *

Adds simple datasource (DEF). Simple source name + * can be used:

+ *
    + *
  • To specify sources for line, area and stack plots.
  • + *
  • To define complex sources + *
+ * + * @param name source name. + * @param file Path to RRD file. + * @param dsName Datasource name defined in the RRD file. + * @param consolFunc Consolidation function that will be used to extract data from the RRD + * file ("AVERAGE", "MIN", "MAX" or "LAST" - these string constants are conveniently defined + * in the {@link org.jrobin.core.ConsolFuns ConsolFuns} class). + */ + public void addDatasource(String name, String file, String dsName, String consolFunc) { + Def def = new Def(name, file, dsName, consolFunc); + sources.put(name, def); + } + + /** + *

Adds simple source (DEF). Source name can be used:

+ *
    + *
  • To specify sources for line, area and stack plots.
  • + *
  • To define complex sources + *
+ * + * @param name Source name. + * @param file Path to RRD file. + * @param dsName Data source name defined in the RRD file. + * @param consolFunc Consolidation function that will be used to extract data from the RRD + * file ("AVERAGE", "MIN", "MAX" or "LAST" - these string constants are conveniently defined + * in the {@link org.jrobin.core.ConsolFuns ConsolFuns} class). + * @param backend Name of the RrdBackendFactory that should be used for this RrdDb. + */ + public void addDatasource(String name, String file, String dsName, String consolFunc, String backend) { + Def def = new Def(name, file, dsName, consolFunc, backend); + sources.put(name, def); + } + + /** + * Adds DEF datasource with datasource values already available in the FetchData object. This method is + * used internally by JRobin and probably has no purpose outside of it. + * + * @param name Source name. + * @param fetchData Fetched data containing values for the given source name. + */ + public void addDatasource(String name, FetchData fetchData) { + Def def = new Def(name, fetchData); + sources.put(name, def); + } + + /** + * Creates a new VDEF datasource that performs a percentile calculation on an + * another named datasource to yield a single value. + * + * Requires that the other datasource has already been defined; otherwise, it'll + * end up with no data + * + * @param name - the new virtual datasource name + * @param sourceName - the datasource from which to extract the percentile. Must be a previously + * defined virtual datasource + * @param percentile - the percentile to extract from the source datasource + */ + public void addDatasource(String name, String sourceName, double percentile) { + Source source = sources.get(sourceName); + sources.put(name, new PercentileDef(name, source, percentile)); + } + + /** + * Creates a new VDEF datasource that performs a percentile calculation on an + * another named datasource to yield a single value. + * + * Requires that the other datasource has already been defined; otherwise, it'll + * end up with no data + * + * @param name - the new virtual datasource name + * @param sourceName - the datasource from which to extract the percentile. Must be a previously + * defined virtual datasource + * @param percentile - the percentile to extract from the source datasource + * @param ignorenan - true if we include Double.NaN + */ + public void addDatasource(String name, String sourceName, double percentile, boolean ignorenan) { + Source source = sources.get(sourceName); + sources.put(name, new PercentileDef(name, source, percentile, ignorenan)); + } + + ///////////////////////////////////////////////////////////////// + // CALCULATIONS + ///////////////////////////////////////////////////////////////// + + /** + * Method that should be called once all datasources are defined. Data will be fetched from + * RRD files, RPN expressions will be calculated, etc. + * + * @throws IOException Thrown in case of I/O error (while fetching data from RRD files) + * @throws RrdException Thrown in case of JRobin specific error + */ + public void processData() throws IOException, RrdException { + extractDefs(); + fetchRrdData(); + fixZeroEndingTimestamp(); + chooseOptimalStep(); + createTimestamps(); + assignTimestampsToSources(); + normalizeRrdValues(); + calculateNonRrdSources(); + } + + /** + * Method used to calculate datasource values which should be presented on the graph + * based on the desired graph width. Each value returned represents a single pixel on the graph. + * Corresponding timestamp can be found in the array returned from {@link #getTimestampsPerPixel()} + * method. + * + * @param sourceName Datasource name + * @param pixelCount Graph width + * @return Per-pixel datasource values + * @throws RrdException Thrown if datasource values are not yet calculated (method {@link #processData()} + * was not called) + */ + public double[] getValuesPerPixel(String sourceName, int pixelCount) throws RrdException { + setPixelCount(pixelCount); + return getValuesPerPixel(sourceName); + } + + /** + * Method used to calculate datasource values which should be presented on the graph + * based on the graph width set with a {@link #setPixelCount(int)} method call. + * Each value returned represents a single pixel on the graph. Corresponding timestamp can be + * found in the array returned from {@link #getTimestampsPerPixel()} method. + * + * @param sourceName Datasource name + * @return Per-pixel datasource values + * @throws RrdException Thrown if datasource values are not yet calculated (method {@link #processData()} + * was not called) + */ + public double[] getValuesPerPixel(String sourceName) throws RrdException { + double[] values = getValues(sourceName); + double[] pixelValues = new double[pixelCount]; + Arrays.fill(pixelValues, Double.NaN); + long span = tEnd - tStart; + // this is the ugliest nested loop I have ever made + for (int pix = 0, ref = 0; pix < pixelCount; pix++) { + double t = tStart + (double) (span * pix) / (double) (pixelCount - 1); + while (ref < timestamps.length) { + if (t <= timestamps[ref] - step) { + // too left, nothing to do, already NaN + break; + } + else if (t <= timestamps[ref]) { + // in brackets, get this value + pixelValues[pix] = values[ref]; + break; + } + else { + // too right + ref++; + } + } + } + return pixelValues; + } + + /** + * Calculates timestamps which correspond to individual pixels on the graph. + * + * @param pixelCount Graph width + * @return Array of timestamps + */ + public long[] getTimestampsPerPixel(int pixelCount) { + setPixelCount(pixelCount); + return getTimestampsPerPixel(); + } + + /** + * Calculates timestamps which correspond to individual pixels on the graph + * based on the graph width set with a {@link #setPixelCount(int)} method call. + * + * @return Array of timestamps + */ + public long[] getTimestampsPerPixel() { + long[] times = new long[pixelCount]; + long span = tEnd - tStart; + for (int i = 0; i < pixelCount; i++) { + times[i] = Math.round(tStart + (double) (span * i) / (double) (pixelCount - 1)); + } + return times; + } + + /** + * Dumps timestamps and values of all datasources in a tabelar form. Very useful for debugging. + * + * @return Dumped object content. + * @throws RrdException Thrown if nothing is calculated so far (the method {@link #processData()} + * was not called). + */ + public String dump() throws RrdException { + String[] names = getSourceNames(); + double[][] values = getValues(); + StringBuilder buffer = new StringBuilder(); + buffer.append(format("timestamp", 12)); + for (String name : names) { + buffer.append(format(name, 20)); + } + buffer.append("\n"); + for (int i = 0; i < timestamps.length; i++) { + buffer.append(format("" + timestamps[i], 12)); + for (int j = 0; j < names.length; j++) { + buffer.append(format(Util.formatDouble(values[j][i]), 20)); + } + buffer.append("\n"); + } + return buffer.toString(); + } + + /** + * Returns time when last RRD archive was updated (all RRD files are considered). + * + * @return Last archive update time for all RRD files in this DataProcessor + */ + public long getLastRrdArchiveUpdateTime() { + return lastRrdArchiveUpdateTime; + } + + // PRIVATE METHODS + + private void extractDefs() { + List defList = new ArrayList(); + for (Source source : sources.values()) { + if (source instanceof Def) { + defList.add((Def) source); + } + } + defSources = defList.toArray(new Def[defList.size()]); + } + + private void fetchRrdData() throws IOException, RrdException { + long tEndFixed = (tEnd == 0) ? Util.getTime() : tEnd; + for (int i = 0; i < defSources.length; i++) { + if (!defSources[i].isLoaded()) { + // not fetched yet + Set dsNames = new HashSet(); + dsNames.add(defSources[i].getDsName()); + // look for all other datasources with the same path and the same consolidation function + for (int j = i + 1; j < defSources.length; j++) { + if (defSources[i].isCompatibleWith(defSources[j])) { + dsNames.add(defSources[j].getDsName()); + } + } + // now we have everything + RrdDb rrd = null; + try { + rrd = getRrd(defSources[i]); + lastRrdArchiveUpdateTime = Math.max(lastRrdArchiveUpdateTime, rrd.getLastArchiveUpdateTime()); + FetchRequest req = rrd.createFetchRequest(defSources[i].getConsolFun(), + tStart, tEndFixed, fetchRequestResolution); + req.setFilter(dsNames); + FetchData data = req.fetchData(); + defSources[i].setFetchData(data); + for (int j = i + 1; j < defSources.length; j++) { + if (defSources[i].isCompatibleWith(defSources[j])) { + defSources[j].setFetchData(data); + } + } + } + finally { + if (rrd != null) { + releaseRrd(rrd, defSources[i]); + } + } + } + } + } + + private void fixZeroEndingTimestamp() throws RrdException { + if (tEnd == 0) { + if (defSources.length == 0) { + throw new RrdException("Could not adjust zero ending timestamp, no DEF source provided"); + } + tEnd = defSources[0].getArchiveEndTime(); + for (int i = 1; i < defSources.length; i++) { + tEnd = Math.min(tEnd, defSources[i].getArchiveEndTime()); + } + if (tEnd <= tStart) { + throw new RrdException("Could not resolve zero ending timestamp."); + } + } + } + + // Tricky and ugly. Should be redesigned some time in the future + private void chooseOptimalStep() { + long newStep = Long.MAX_VALUE; + for (Def defSource : defSources) { + long fetchStep = defSource.getFetchStep(), tryStep = fetchStep; + if (step > 0) { + tryStep = Math.min(newStep, (((step - 1) / fetchStep) + 1) * fetchStep); + } + newStep = Math.min(newStep, tryStep); + } + if (newStep != Long.MAX_VALUE) { + // step resolved from a RRD file + step = newStep; + } + else { + // choose step based on the number of pixels (useful for plottable datasources) + step = Math.max((tEnd - tStart) / pixelCount, 1); + } + } + + private void createTimestamps() { + long t1 = Util.normalize(tStart, step); + long t2 = Util.normalize(tEnd, step); + if (t2 < tEnd) { + t2 += step; + } + int count = (int) (((t2 - t1) / step) + 1); + timestamps = new long[count]; + for (int i = 0; i < count; i++) { + timestamps[i] = t1; + t1 += step; + } + } + + private void assignTimestampsToSources() { + for (Source src : sources.values()) { + src.setTimestamps(timestamps); + } + } + + private void normalizeRrdValues() throws RrdException { + Normalizer normalizer = new Normalizer(timestamps); + for (Def def : defSources) { + long[] rrdTimestamps = def.getRrdTimestamps(); + double[] rrdValues = def.getRrdValues(); + double[] values = normalizer.normalize(rrdTimestamps, rrdValues); + def.setValues(values); + } + } + + private void calculateNonRrdSources() throws RrdException { + for (Source source : sources.values()) { + if (source instanceof SDef) { + calculateSDef((SDef) source); + } + else if (source instanceof CDef) { + calculateCDef((CDef) source); + } + else if (source instanceof PDef) { + calculatePDef((PDef) source); + } + else if (source instanceof PercentileDef) { + calculatePercentileDef((PercentileDef) source); + } + } + } + + private void calculatePDef(PDef pdef) { + pdef.calculateValues(); + } + + private void calculateCDef(CDef cDef) throws RrdException { + RpnCalculator calc = new RpnCalculator(cDef.getRpnExpression(), cDef.getName(), this); + cDef.setValues(calc.calculateValues()); + } + + private void calculateSDef(SDef sDef) throws RrdException { + String defName = sDef.getDefName(); + String consolFun = sDef.getConsolFun(); + Source source = getSource(defName); + if (consolFun.equals("MAXIMUM")) { consolFun = "MAX"; } + else if (consolFun.equals("MINIMUM")) { consolFun = "MIN"; } + double value = source.getAggregates(tStart, tEnd).getAggregate(consolFun); + sDef.setValue(value); + } + + //Yeah, this is different from the other calculation methods + // Frankly, this is how it *should* be done, and the other methods will + // be refactored to this design (and the instanceof's removed) at some point + private void calculatePercentileDef(PercentileDef def) throws RrdException { + def.calculate(tStart, tEnd); + } + + + private RrdDb getRrd(Def def) throws IOException, RrdException { + String path = def.getPath(), backend = def.getBackend(); + if (poolUsed && backend == null) { + return RrdDbPool.getInstance().requestRrdDb(path); + } + else if (backend != null) { + return new RrdDb(path, true, RrdBackendFactory.getFactory(backend)); + } + else { + return new RrdDb(path, true); + } + } + + private void releaseRrd(RrdDb rrd, Def def) throws IOException, RrdException { + String backend = def.getBackend(); + if (poolUsed && backend == null) { + RrdDbPool.getInstance().release(rrd); + } + else { + rrd.close(); + } + } + + private static String format(String s, int length) { + StringBuilder b = new StringBuilder(s); + for (int i = 0; i < length - s.length(); i++) { + b.append(' '); + } + return b.toString(); + } + + /** + * Cute little demo. Uses demo.rrd file previously created by basic JRobin demo. + * + * @param args Not used + * @throws IOException if an I/O error occurs. + * @throws RrdException Thrown if internal jrobin error occurs. + */ + public static void main(String[] args) throws IOException, RrdException { + // time span + long t1 = Util.getTimestamp(2003, 4, 1); + long t2 = Util.getTimestamp(2003, 5, 1); + System.out.println("t1 = " + t1); + System.out.println("t2 = " + t2); + + // RRD file to use + String rrdPath = Util.getJRobinDemoPath("demo.rrd"); + + // constructor + DataProcessor dp = new DataProcessor(t1, t2); + + // uncomment and run again + //dp.setFetchRequestResolution(86400); + + // uncomment and run again + //dp.setStep(86500); + + // datasource definitions + dp.addDatasource("X", rrdPath, "sun", "AVERAGE"); + dp.addDatasource("Y", rrdPath, "shade", "AVERAGE"); + dp.addDatasource("Z", "X,Y,+,2,/"); + dp.addDatasource("DERIVE[Z]", "Z,PREV(Z),-,STEP,/"); + dp.addDatasource("TREND[Z]", "DERIVE[Z],SIGN"); + dp.addDatasource("AVG[Z]", "Z", "AVERAGE"); + dp.addDatasource("DELTA", "Z,AVG[Z],-"); + + // action + long laptime = System.currentTimeMillis(); + //dp.setStep(86400); + dp.processData(); + System.out.println("Data processed in " + (System.currentTimeMillis() - laptime) + " milliseconds\n---"); + System.out.println(dp.dump()); + + // aggregates + System.out.println("\nAggregates for X"); + Aggregates agg = dp.getAggregates("X"); + System.out.println(agg.dump()); + System.out.println("\nAggregates for Y"); + agg = dp.getAggregates("Y"); + System.out.println(agg.dump()); + + // 95-percentile + System.out.println("\n95-percentile for X: " + Util.formatDouble(dp.get95Percentile("X"))); + System.out.println("95-percentile for Y: " + Util.formatDouble(dp.get95Percentile("Y"))); + + // lastArchiveUpdateTime + System.out.println("\nLast archive update time was: " + dp.getLastRrdArchiveUpdateTime()); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/Def.java b/apps/jrobin/java/src/org/jrobin/data/Def.java new file mode 100644 index 000000000..defd84fce --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/Def.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * 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 org.jrobin.core.FetchData; +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; + +import java.io.IOException; + +class Def extends Source { + private String path, dsName, consolFun, backend; + private FetchData fetchData; + + Def(String name, FetchData fetchData) { + this(name, null, name, null, null); + setFetchData(fetchData); + } + + Def(String name, String path, String dsName, String consolFunc) { + this(name, path, dsName, consolFunc, null); + } + + Def(String name, String path, String dsName, String consolFunc, String backend) { + super(name); + this.path = path; + this.dsName = dsName; + this.consolFun = consolFunc; + this.backend = backend; + } + + String getPath() { + return path; + } + + String getCanonicalPath() throws IOException { + return Util.getCanonicalPath(path); + } + + String getDsName() { + return dsName; + } + + String getConsolFun() { + return consolFun; + } + + String getBackend() { + return backend; + } + + boolean isCompatibleWith(Def def) throws IOException { + return getCanonicalPath().equals(def.getCanonicalPath()) && + getConsolFun().equals(def.consolFun) && + ((backend == null && def.backend == null) || + (backend != null && def.backend != null && backend.equals(def.backend))); + } + + void setFetchData(FetchData fetchData) { + this.fetchData = fetchData; + } + + long[] getRrdTimestamps() { + return fetchData.getTimestamps(); + } + + double[] getRrdValues() throws RrdException { + return fetchData.getValues(dsName); + } + + long getArchiveEndTime() { + return fetchData.getArcEndTime(); + } + + long getFetchStep() { + return fetchData.getStep(); + } + + Aggregates getAggregates(long tStart, long tEnd) throws RrdException { + long[] t = getRrdTimestamps(); + double[] v = getRrdValues(); + Aggregator agg = new Aggregator(t, v); + return agg.getAggregates(tStart, tEnd); + } + + double getPercentile(long tStart, long tEnd, double percentile) throws RrdException { + long[] t = getRrdTimestamps(); + double[] v = getRrdValues(); + Aggregator agg = new Aggregator(t, v); + return agg.getPercentile(tStart, tEnd, percentile); + } + + boolean isLoaded() { + return fetchData != null; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/LinearInterpolator.java b/apps/jrobin/java/src/org/jrobin/data/LinearInterpolator.java new file mode 100644 index 000000000..b64434517 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/LinearInterpolator.java @@ -0,0 +1,248 @@ +/******************************************************************************* + * 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 org.jrobin.core.RrdException; +import org.jrobin.core.Util; + +import java.util.Calendar; +import java.util.Date; + +/** + * Class used to interpolate datasource values from the collection of (timestamp, values) + * points. This class is suitable for linear interpolation only. + *

+ * Interpolation algorithm returns different values based on the value passed to + * {@link #setInterpolationMethod(int) setInterpolationMethod()}. If not set, interpolation + * method defaults to standard linear interpolation ({@link #INTERPOLATE_LINEAR}). + * Interpolation method handles NaN datasource + * values gracefully. + */ +public class LinearInterpolator extends Plottable { + /** + * constant used to specify LEFT interpolation. + * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation. + */ + public static final int INTERPOLATE_LEFT = 0; + /** + * constant used to specify RIGHT interpolation. + * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation. + */ + public static final int INTERPOLATE_RIGHT = 1; + /** + * constant used to specify LINEAR interpolation (default interpolation method). + * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation. + */ + public static final int INTERPOLATE_LINEAR = 2; + /** + * constant used to specify LINEAR REGRESSION as interpolation method. + * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation. + */ + public static final int INTERPOLATE_REGRESSION = 3; + + private int lastIndexUsed = 0; + private int interpolationMethod = INTERPOLATE_LINEAR; + + private long[] timestamps; + private double[] values; + + // used only if INTERPOLATE_BESTFIT is specified + double b0 = Double.NaN, b1 = Double.NaN; + + /** + * Creates LinearInterpolator from arrays of timestamps and corresponding datasource values. + * + * @param timestamps timestamps in seconds + * @param values corresponding datasource values + * @throws RrdException Thrown if supplied arrays do not contain at least two values, or if + * timestamps are not ordered, or array lengths are not equal. + */ + public LinearInterpolator(long[] timestamps, double[] values) throws RrdException { + this.timestamps = timestamps; + this.values = values; + validate(); + } + + /** + * Creates LinearInterpolator from arrays of timestamps and corresponding datasource values. + * + * @param dates Array of Date objects + * @param values corresponding datasource values + * @throws RrdException Thrown if supplied arrays do not contain at least two values, or if + * timestamps are not ordered, or array lengths are not equal. + */ + public LinearInterpolator(Date[] dates, double[] values) throws RrdException { + this.values = values; + timestamps = new long[dates.length]; + for (int i = 0; i < dates.length; i++) { + timestamps[i] = Util.getTimestamp(dates[i]); + } + validate(); + } + + /** + * Creates LinearInterpolator from arrays of timestamps and corresponding datasource values. + * + * @param dates array of GregorianCalendar objects + * @param values corresponding datasource values + * @throws RrdException Thrown if supplied arrays do not contain at least two values, or if + * timestamps are not ordered, or array lengths are not equal. + */ + public LinearInterpolator(Calendar[] dates, double[] values) throws RrdException { + this.values = values; + timestamps = new long[dates.length]; + for (int i = 0; i < dates.length; i++) { + timestamps[i] = Util.getTimestamp(dates[i]); + } + validate(); + } + + private void validate() throws RrdException { + boolean ok = true; + if (timestamps.length != values.length || timestamps.length < 2) { + ok = false; + } + for (int i = 0; i < timestamps.length - 1 && ok; i++) { + if (timestamps[i] >= timestamps[i + 1]) { + ok = false; + } + } + if (!ok) { + throw new RrdException("Invalid plottable data supplied"); + } + } + + /** + * Sets interpolation method to be used. Suppose that we have two timestamp/value pairs:
+ * (t, 100) and (t + 100, 300). Here are the results interpolator + * returns for t + 50 seconds, for various interpolationMethods: + *

+ *

    + *
  • INTERPOLATE_LEFT: 100 + *
  • INTERPOLATE_RIGHT: 300 + *
  • INTERPOLATE_LINEAR: 200 + *
+ * If not set, interpolation method defaults to INTERPOLATE_LINEAR. + *

+ * The fourth available interpolation method is INTERPOLATE_REGRESSION. This method uses + * simple linear regression to interpolate supplied data with a simple straight line which does not + * necessarily pass through all data points. The slope of the best-fit line will be chosen so that the + * total square distance of real data points from from the best-fit line is at minimum. + *

+ * The full explanation of this inteprolation method can be found + * here. + * + * @param interpolationMethod Should be INTERPOLATE_LEFT, + * INTERPOLATE_RIGHT, INTERPOLATE_LINEAR or + * INTERPOLATE_REGRESSION. Any other value will be interpreted as + * INTERPOLATE_LINEAR (default). + */ + public void setInterpolationMethod(int interpolationMethod) { + switch (interpolationMethod) { + case INTERPOLATE_REGRESSION: + calculateBestFitLine(); + this.interpolationMethod = interpolationMethod; + break; + case INTERPOLATE_LEFT: + case INTERPOLATE_RIGHT: + case INTERPOLATE_LINEAR: + this.interpolationMethod = interpolationMethod; + break; + default: + this.interpolationMethod = INTERPOLATE_LINEAR; + } + } + + private void calculateBestFitLine() { + int count = timestamps.length, validCount = 0; + double ts = 0.0, vs = 0.0; + for (int i = 0; i < count; i++) { + if (!Double.isNaN(values[i])) { + ts += timestamps[i]; + vs += values[i]; + validCount++; + } + } + if (validCount <= 1) { + // just one not-NaN point + b0 = b1 = Double.NaN; + return; + } + ts /= validCount; + vs /= validCount; + double s1 = 0, s2 = 0; + for (int i = 0; i < count; i++) { + if (!Double.isNaN(values[i])) { + double dt = timestamps[i] - ts; + double dv = values[i] - vs; + s1 += dt * dv; + s2 += dt * dt; + } + } + b1 = s1 / s2; + b0 = vs - b1 * ts; + } + + /** + * Method overriden from the base class. This method will be called by the framework. Call + * this method only if you need interpolated values in your code. + * + * @param timestamp timestamp in seconds + * @return inteprolated datasource value + */ + public double getValue(long timestamp) { + if (interpolationMethod == INTERPOLATE_REGRESSION) { + return b0 + b1 * timestamp; + } + int count = timestamps.length; + // check if out of range + if (timestamp < timestamps[0] || timestamp > timestamps[count - 1]) { + return Double.NaN; + } + // find matching segment + int startIndex = lastIndexUsed; + if (timestamp < timestamps[lastIndexUsed]) { + // backward reading, shift to the first timestamp + startIndex = 0; + } + for (int i = startIndex; i < count; i++) { + if (timestamps[i] == timestamp) { + return values[i]; + } + if (i < count - 1 && timestamps[i] < timestamp && timestamp < timestamps[i + 1]) { + // matching segment found + lastIndexUsed = i; + switch (interpolationMethod) { + case INTERPOLATE_LEFT: + return values[i]; + case INTERPOLATE_RIGHT: + return values[i + 1]; + case INTERPOLATE_LINEAR: + double slope = (values[i + 1] - values[i]) / + (timestamps[i + 1] - timestamps[i]); + return values[i] + slope * (timestamp - timestamps[i]); + default: + return Double.NaN; + } + } + } + // should not be here ever, but let's satisfy the compiler + return Double.NaN; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/Normalizer.java b/apps/jrobin/java/src/org/jrobin/data/Normalizer.java new file mode 100644 index 000000000..34c177e74 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/Normalizer.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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 org.jrobin.core.Util; + +import java.util.Arrays; + +class Normalizer { + final private long[] timestamps; + final int count; + final long step; + + Normalizer(long[] timestamps) { + this.timestamps = timestamps; + this.step = timestamps[1] - timestamps[0]; + this.count = timestamps.length; + } + + double[] normalize(long[] rawTimestamps, double[] rawValues) { + int rawCount = rawTimestamps.length; + long rawStep = rawTimestamps[1] - rawTimestamps[0]; + // check if we have a simple match + if (rawCount == count && rawStep == step && rawTimestamps[0] == timestamps[0]) { + return getCopyOf(rawValues); + } + // reset all normalized values to NaN + double[] values = new double[count]; + Arrays.fill(values, Double.NaN); + for (int rawSeg = 0, seg = 0; rawSeg < rawCount && seg < count; rawSeg++) { + double rawValue = rawValues[rawSeg]; + if (!Double.isNaN(rawValue)) { + long rawLeft = rawTimestamps[rawSeg] - rawStep; + while (seg < count && rawLeft >= timestamps[seg]) { + seg++; + } + boolean overlap = true; + for (int fillSeg = seg; overlap && fillSeg < count; fillSeg++) { + long left = timestamps[fillSeg] - step; + long t1 = Math.max(rawLeft, left); + long t2 = Math.min(rawTimestamps[rawSeg], timestamps[fillSeg]); + if (t1 < t2) { + values[fillSeg] = Util.sum(values[fillSeg], (t2 - t1) * rawValues[rawSeg]); + } + else { + overlap = false; + } + } + } + } + for (int seg = 0; seg < count; seg++) { + values[seg] /= step; + } + return values; + } + + private static double[] getCopyOf(double[] rawValues) { + int n = rawValues.length; + double[] values = new double[n]; + System.arraycopy(rawValues, 0, values, 0, n); + return values; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/PDef.java b/apps/jrobin/java/src/org/jrobin/data/PDef.java new file mode 100644 index 000000000..b6bb1f315 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/PDef.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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; + +class PDef extends Source { + private final Plottable plottable; + + PDef(String name, Plottable plottable) { + super(name); + this.plottable = plottable; + } + + void calculateValues() { + long[] times = getTimestamps(); + double[] vals = new double[times.length]; + for (int i = 0; i < times.length; i++) { + vals[i] = plottable.getValue(times[i]); + } + setValues(vals); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/PercentileDef.java b/apps/jrobin/java/src/org/jrobin/data/PercentileDef.java new file mode 100644 index 000000000..853f4d950 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/PercentileDef.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2011 Craig Miskell + * + * 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 org.jrobin.core.RrdException; + +public class PercentileDef extends Source { + + private Source m_source; + + private double m_value; + + private double m_percentile; + + @SuppressWarnings("unused") + private boolean m_ignorenan; + + PercentileDef(String name, Source source, double percentile) { + this(name, source, percentile, false); + } + + PercentileDef(String name, Source source, double percentile, boolean ignorenan) { + super(name); + + m_percentile = percentile; + m_ignorenan = ignorenan; + m_source = source; + + //The best we can do at this point; until this object has it's value realized over a + // particular time period (with calculate()), there's not much else to do + this.setValue(Double.NaN); + } + + /** + * Realize the calculation of this definition, over the given time period + * + * @param tStart the time period start + * @param tEnd the time period end + * @throws RrdException Thrown if we cannot get a percentile value for the time period. + */ + public void calculate(long tStart, long tEnd) throws RrdException { + if(m_source != null) { + this.setValue(m_source.getPercentile(tStart, tEnd, m_percentile)); + } + } + + /** + * Takes the given value and puts it in each position in the 'values' array. + * @param value + */ + private void setValue(double value) { + this.m_value = value; + long[] times = getTimestamps(); + if( times != null ) { + int count = times.length; + double[] values = new double[count]; + for (int i = 0; i < count; i++) { + values[i] = m_value; + } + setValues(values); + } + } + + @Override + void setTimestamps(long[] timestamps) { + super.setTimestamps(timestamps); + //And now also call setValue with the current value, to sort out "values" + setValue(m_value); + } + + /** + * Same as SDef; the aggregates of a static value are all just the + * same static value. + * + * Assumes this def has been realized by calling calculate(), otherwise + * the aggregated values will be NaN + */ + @Override + Aggregates getAggregates(long tStart, long tEnd) throws RrdException { + Aggregates agg = new Aggregates(); + agg.first = agg.last = agg.min = agg.max = agg.average = m_value; + agg.total = m_value * (tEnd - tStart); + return agg; + } + + /** + * Returns just the calculated percentile; the "Xth" percentile of a static value is + * the static value itself. + * + * Assumes this def has been realized by calling calculate(), otherwise + * the aggregated values will be NaN + */ + @Override + double getPercentile(long tStart, long tEnd, double percentile) + throws RrdException { + return m_value; + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/data/Plottable.java b/apps/jrobin/java/src/org/jrobin/data/Plottable.java new file mode 100644 index 000000000..b8c879608 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/Plottable.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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; + +/** + *

Interface to be used for custom datasources. + * If you wish to use a custom datasource in a graph, you should create a class implementing this interface + * that represents that datasource, and then pass this class on to the RrdGraphDef.

+ */ +public abstract class Plottable { + /** + * Retrieves datapoint value based on a given timestamp. + * Use this method if you only have one series of data in this class. + * + * @param timestamp Timestamp in seconds for the datapoint. + * @return Double value of the datapoint. + */ + public double getValue(long timestamp) { + return Double.NaN; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/RpnCalculator.java b/apps/jrobin/java/src/org/jrobin/data/RpnCalculator.java new file mode 100644 index 000000000..6e0dfbb93 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/RpnCalculator.java @@ -0,0 +1,832 @@ +/******************************************************************************* + * 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; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/SDef.java b/apps/jrobin/java/src/org/jrobin/data/SDef.java new file mode 100644 index 000000000..7d92be143 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/SDef.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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 org.jrobin.core.RrdException; + +class SDef extends Source { + private String defName; + private String consolFun; + private double value; + + SDef(String name, String defName, String consolFun) { + super(name); + this.defName = defName; + this.consolFun = consolFun; + } + + String getDefName() { + return defName; + } + + String getConsolFun() { + return consolFun; + } + + void setValue(double value) { + this.value = value; + int count = getTimestamps().length; + double[] values = new double[count]; + for (int i = 0; i < count; i++) { + values[i] = value; + } + setValues(values); + } + + Aggregates getAggregates(long tStart, long tEnd) throws RrdException { + Aggregates agg = new Aggregates(); + agg.first = agg.last = agg.min = agg.max = agg.average = value; + agg.total = value * (tEnd - tStart); + return agg; + } + + double getPercentile(long tStart, long tEnd, double percentile) throws RrdException { + return value; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/Source.java b/apps/jrobin/java/src/org/jrobin/data/Source.java new file mode 100644 index 000000000..302b70670 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/Source.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * 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 org.jrobin.core.ConsolFuns; +import org.jrobin.core.RrdException; + +abstract class Source implements ConsolFuns { + final private String name; + protected double[] values; + protected long[] timestamps; + + Source(String name) { + this.name = name; + } + + String getName() { + return name; + } + + void setValues(double[] values) { + this.values = values; + } + + void setTimestamps(long[] timestamps) { + this.timestamps = timestamps; + } + + double[] getValues() { + return values; + } + + long[] getTimestamps() { + return timestamps; + } + + Aggregates getAggregates(long tStart, long tEnd) throws RrdException { + Aggregator agg = new Aggregator(timestamps, values); + return agg.getAggregates(tStart, tEnd); + } + + double getPercentile(long tStart, long tEnd, double percentile) throws RrdException { + Aggregator agg = new Aggregator(timestamps, values); + return agg.getPercentile(tStart, tEnd, percentile, false); + } + + double getPercentile(long tStart, long tEnd, double percentile, boolean includenan) throws RrdException { + Aggregator agg = new Aggregator(timestamps, values); + return agg.getPercentile(tStart, tEnd, percentile, includenan); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/data/package.html b/apps/jrobin/java/src/org/jrobin/data/package.html new file mode 100644 index 000000000..809736855 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/data/package.html @@ -0,0 +1,5 @@ + + + JRobin data management. + + \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/graph/Area.java b/apps/jrobin/java/src/org/jrobin/graph/Area.java new file mode 100644 index 000000000..73845e3a0 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/Area.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; + +class Area extends SourcedPlotElement { + Area(String srcName, Paint color) { + super(srcName, color); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/CDef.java b/apps/jrobin/java/src/org/jrobin/graph/CDef.java new file mode 100644 index 000000000..39bb61481 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/CDef.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.data.DataProcessor; + +class CDef extends Source { + private final String rpnExpression; + + CDef(String name, String rpnExpression) { + super(name); + this.rpnExpression = rpnExpression; + } + + void requestData(DataProcessor dproc) { + dproc.addDatasource(name, rpnExpression); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/CommentText.java b/apps/jrobin/java/src/org/jrobin/graph/CommentText.java new file mode 100644 index 000000000..5031b88d9 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/CommentText.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.RrdException; +import org.jrobin.data.DataProcessor; + +class CommentText implements RrdGraphConstants { + private final String text; // original text + + String resolvedText; // resolved text + String marker; // end-of-text marker + boolean enabled; // hrule and vrule comments can be disabled at runtime + int x, y; // coordinates, evaluated later + + CommentText(String text) { + this.text = text; + } + + void resolveText(DataProcessor dproc, ValueScaler valueScaler) throws RrdException { + resolvedText = text; + marker = ""; + if (resolvedText != null) { + for (String mark : MARKERS) { + if (resolvedText.endsWith(mark)) { + marker = mark; + resolvedText = resolvedText.substring(0, resolvedText.length() - marker.length()); + trimIfGlue(); + break; + } + } + } + enabled = resolvedText != null; + } + + void trimIfGlue() { + if (marker.equals(GLUE_MARKER)) { + resolvedText = resolvedText.replaceFirst("\\s+$", ""); + } + } + + boolean isPrint() { + return false; + } + + boolean isValidGraphElement() { + return !isPrint() && enabled; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/Def.java b/apps/jrobin/java/src/org/jrobin/graph/Def.java new file mode 100644 index 000000000..06072fb5d --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/Def.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.data.DataProcessor; + +class Def extends Source { + private final String rrdPath, dsName, consolFun, backend; + + Def(String name, String rrdPath, String dsName, String consolFun) { + this(name, rrdPath, dsName, consolFun, null); + } + + Def(String name, String rrdPath, String dsName, String consolFun, String backend) { + super(name); + this.rrdPath = rrdPath; + this.dsName = dsName; + this.consolFun = consolFun; + this.backend = backend; + } + + void requestData(DataProcessor dproc) { + if (backend == null) { + dproc.addDatasource(name, rrdPath, dsName, consolFun); + } + else { + dproc.addDatasource(name, rrdPath, dsName, consolFun, backend); + } + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/GifEncoder.java b/apps/jrobin/java/src/org/jrobin/graph/GifEncoder.java new file mode 100644 index 000000000..a90d17192 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/GifEncoder.java @@ -0,0 +1,728 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ + +/////////////////////////////////////////////////////////////////// +// GifEncoder from J.M.G. Elliott +// http://jmge.net/java/gifenc/ +/////////////////////////////////////////////////////////////////// + +package org.jrobin.graph; + +import java.awt.*; +import java.awt.image.PixelGrabber; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; + +class GifEncoder { + private Dimension dispDim = new Dimension(0, 0); + private GifColorTable colorTable; + private int bgIndex = 0; + private int loopCount = 1; + private String theComments; + private Vector vFrames = new Vector(); + + GifEncoder() { + colorTable = new GifColorTable(); + } + + GifEncoder(Image static_image) throws IOException { + this(); + addFrame(static_image); + } + + GifEncoder(Color[] colors) { + colorTable = new GifColorTable(colors); + } + + GifEncoder(Color[] colors, int width, int height, byte ci_pixels[]) + throws IOException { + this(colors); + addFrame(width, height, ci_pixels); + } + + int getFrameCount() { + return vFrames.size(); + } + + Gif89Frame getFrameAt(int index) { + return isOk(index) ? vFrames.elementAt(index) : null; + } + + void addFrame(Gif89Frame gf) throws IOException { + accommodateFrame(gf); + vFrames.addElement(gf); + } + + void addFrame(Image image) throws IOException { + addFrame(new DirectGif89Frame(image)); + } + + void addFrame(int width, int height, byte ci_pixels[]) + throws IOException { + addFrame(new IndexGif89Frame(width, height, ci_pixels)); + } + + void insertFrame(int index, Gif89Frame gf) throws IOException { + accommodateFrame(gf); + vFrames.insertElementAt(gf, index); + } + + void setTransparentIndex(int index) { + colorTable.setTransparent(index); + } + + void setLogicalDisplay(Dimension dim, int background) { + dispDim = new Dimension(dim); + bgIndex = background; + } + + void setLoopCount(int count) { + loopCount = count; + } + + void setComments(String comments) { + theComments = comments; + } + + void setUniformDelay(int interval) { + for (int i = 0; i < vFrames.size(); ++i) { + vFrames.elementAt(i).setDelay(interval); + } + } + + void encode(OutputStream out) throws IOException { + int nframes = getFrameCount(); + boolean is_sequence = nframes > 1; + colorTable.closePixelProcessing(); + Put.ascii("GIF89a", out); + writeLogicalScreenDescriptor(out); + colorTable.encode(out); + if (is_sequence && loopCount != 1) { + writeNetscapeExtension(out); + } + if (theComments != null && theComments.length() > 0) { + writeCommentExtension(out); + } + for (int i = 0; i < nframes; ++i) { + vFrames.elementAt(i).encode( + out, is_sequence, colorTable.getDepth(), colorTable.getTransparent() + ); + } + out.write((int) ';'); + out.flush(); + } + + private void accommodateFrame(Gif89Frame gf) throws IOException { + dispDim.width = Math.max(dispDim.width, gf.getWidth()); + dispDim.height = Math.max(dispDim.height, gf.getHeight()); + colorTable.processPixels(gf); + } + + private void writeLogicalScreenDescriptor(OutputStream os) throws IOException { + Put.leShort(dispDim.width, os); + Put.leShort(dispDim.height, os); + os.write(0xf0 | colorTable.getDepth() - 1); + os.write(bgIndex); + os.write(0); + } + + + private void writeNetscapeExtension(OutputStream os) throws IOException { + os.write((int) '!'); + os.write(0xff); + os.write(11); + Put.ascii("NETSCAPE2.0", os); + os.write(3); + os.write(1); + Put.leShort(loopCount > 1 ? loopCount - 1 : 0, os); + os.write(0); + } + + + private void writeCommentExtension(OutputStream os) throws IOException { + os.write((int) '!'); + os.write(0xfe); + int remainder = theComments.length() % 255; + int nsubblocks_full = theComments.length() / 255; + int nsubblocks = nsubblocks_full + (remainder > 0 ? 1 : 0); + int ibyte = 0; + for (int isb = 0; isb < nsubblocks; ++isb) { + int size = isb < nsubblocks_full ? 255 : remainder; + os.write(size); + Put.ascii(theComments.substring(ibyte, ibyte + size), os); + ibyte += size; + } + os.write(0); + } + + + private boolean isOk(int frame_index) { + return frame_index >= 0 && frame_index < vFrames.size(); + } +} + +class DirectGif89Frame extends Gif89Frame { + private int[] argbPixels; + + DirectGif89Frame(Image img) throws IOException { + PixelGrabber pg = new PixelGrabber(img, 0, 0, -1, -1, true); + String errmsg = null; + try { + if (!pg.grabPixels()) { + errmsg = "can't grab pixels from image"; + } + } + catch (InterruptedException e) { + errmsg = "interrupted grabbing pixels from image"; + } + if (errmsg != null) { + throw new IOException(errmsg + " (" + getClass().getName() + ")"); + } + theWidth = pg.getWidth(); + theHeight = pg.getHeight(); + argbPixels = (int[]) pg.getPixels(); + ciPixels = new byte[argbPixels.length]; + } + + DirectGif89Frame(int width, int height, int argb_pixels[]) { + theWidth = width; + theHeight = height; + argbPixels = new int[theWidth * theHeight]; + System.arraycopy(argb_pixels, 0, argbPixels, 0, argbPixels.length); + ciPixels = new byte[argbPixels.length]; + } + + Object getPixelSource() { + return argbPixels; + } +} + + +class GifColorTable { + private int[] theColors = new int[256]; + private int colorDepth; + private int transparentIndex = -1; + private int ciCount = 0; + private ReverseColorMap ciLookup; + + GifColorTable() { + ciLookup = new ReverseColorMap(); + } + + GifColorTable(Color[] colors) { + int n2copy = Math.min(theColors.length, colors.length); + for (int i = 0; i < n2copy; ++i) { + theColors[i] = colors[i].getRGB(); + } + } + + int getDepth() { + return colorDepth; + } + + int getTransparent() { + return transparentIndex; + } + + void setTransparent(int color_index) { + transparentIndex = color_index; + } + + void processPixels(Gif89Frame gf) throws IOException { + if (gf instanceof DirectGif89Frame) { + filterPixels((DirectGif89Frame) gf); + } + else { + trackPixelUsage((IndexGif89Frame) gf); + } + } + + void closePixelProcessing() { + colorDepth = computeColorDepth(ciCount); + } + + void encode(OutputStream os) throws IOException { + int palette_size = 1 << colorDepth; + for (int i = 0; i < palette_size; ++i) { + os.write(theColors[i] >> 16 & 0xff); + os.write(theColors[i] >> 8 & 0xff); + os.write(theColors[i] & 0xff); + } + } + + private void filterPixels(DirectGif89Frame dgf) throws IOException { + if (ciLookup == null) { + throw new IOException("RGB frames require palette autodetection"); + } + int[] argb_pixels = (int[]) dgf.getPixelSource(); + byte[] ci_pixels = dgf.getPixelSink(); + int npixels = argb_pixels.length; + for (int i = 0; i < npixels; ++i) { + int argb = argb_pixels[i]; + if ((argb >>> 24) < 0x80) { + if (transparentIndex == -1) { + transparentIndex = ciCount; + } + else if (argb != theColors[transparentIndex]) { + ci_pixels[i] = (byte) transparentIndex; + continue; + } + } + int color_index = ciLookup.getPaletteIndex(argb & 0xffffff); + if (color_index == -1) { + if (ciCount == 256) { + throw new IOException("can't encode as GIF (> 256 colors)"); + } + theColors[ciCount] = argb; + ciLookup.put(argb & 0xffffff, ciCount); + ci_pixels[i] = (byte) ciCount; + ++ciCount; + } + else { + ci_pixels[i] = (byte) color_index; + } + } + } + + private void trackPixelUsage(IndexGif89Frame igf) { + byte[] ci_pixels = (byte[]) igf.getPixelSource(); + int npixels = ci_pixels.length; + for (int i = 0; i < npixels; ++i) { + if (ci_pixels[i] >= ciCount) { + ciCount = ci_pixels[i] + 1; + } + } + } + + private int computeColorDepth(int colorcount) { + if (colorcount <= 2) { + return 1; + } + if (colorcount <= 4) { + return 2; + } + if (colorcount <= 16) { + return 4; + } + return 8; + } +} + +class ReverseColorMap { + private static class ColorRecord { + int rgb; + int ipalette; + + ColorRecord(int rgb, int ipalette) { + this.rgb = rgb; + this.ipalette = ipalette; + } + } + + private static final int HCAPACITY = 2053; + private ColorRecord[] hTable = new ColorRecord[HCAPACITY]; + + int getPaletteIndex(int rgb) { + ColorRecord rec; + for (int itable = rgb % hTable.length; + (rec = hTable[itable]) != null && rec.rgb != rgb; + itable = ++itable % hTable.length + ) { + ; + } + if (rec != null) { + return rec.ipalette; + } + return -1; + } + + + void put(int rgb, int ipalette) { + int itable; + for (itable = rgb % hTable.length; + hTable[itable] != null; + itable = ++itable % hTable.length + ) { + ; + } + hTable[itable] = new ColorRecord(rgb, ipalette); + } +} + +abstract class Gif89Frame { + static final int DM_UNDEFINED = 0; + static final int DM_LEAVE = 1; + static final int DM_BGCOLOR = 2; + static final int DM_REVERT = 3; + int theWidth = -1; + int theHeight = -1; + byte[] ciPixels; + + private Point thePosition = new Point(0, 0); + private boolean isInterlaced; + private int csecsDelay; + private int disposalCode = DM_LEAVE; + + void setPosition(Point p) { + thePosition = new Point(p); + } + + void setInterlaced(boolean b) { + isInterlaced = b; + } + + void setDelay(int interval) { + csecsDelay = interval; + } + + void setDisposalMode(int code) { + disposalCode = code; + } + + Gif89Frame() { + } + + abstract Object getPixelSource(); + + int getWidth() { + return theWidth; + } + + int getHeight() { + return theHeight; + } + + byte[] getPixelSink() { + return ciPixels; + } + + void encode(OutputStream os, boolean epluribus, int color_depth, + int transparent_index) throws IOException { + writeGraphicControlExtension(os, epluribus, transparent_index); + writeImageDescriptor(os); + new GifPixelsEncoder( + theWidth, theHeight, ciPixels, isInterlaced, color_depth + ).encode(os); + } + + private void writeGraphicControlExtension(OutputStream os, boolean epluribus, + int itransparent) throws IOException { + int transflag = itransparent == -1 ? 0 : 1; + if (transflag == 1 || epluribus) { + os.write((int) '!'); + os.write(0xf9); + os.write(4); + os.write((disposalCode << 2) | transflag); + Put.leShort(csecsDelay, os); + os.write(itransparent); + os.write(0); + } + } + + private void writeImageDescriptor(OutputStream os) throws IOException { + os.write((int) ','); + Put.leShort(thePosition.x, os); + Put.leShort(thePosition.y, os); + Put.leShort(theWidth, os); + Put.leShort(theHeight, os); + os.write(isInterlaced ? 0x40 : 0); + } +} + +class GifPixelsEncoder { + private static final int EOF = -1; + private int imgW, imgH; + private byte[] pixAry; + private boolean wantInterlaced; + private int initCodeSize; + private int countDown; + private int xCur, yCur; + private int curPass; + + GifPixelsEncoder(int width, int height, byte[] pixels, boolean interlaced, + int color_depth) { + imgW = width; + imgH = height; + pixAry = pixels; + wantInterlaced = interlaced; + initCodeSize = Math.max(2, color_depth); + } + + void encode(OutputStream os) throws IOException { + os.write(initCodeSize); + + countDown = imgW * imgH; + xCur = yCur = curPass = 0; + + compress(initCodeSize + 1, os); + + os.write(0); + } + + private void bumpPosition() { + ++xCur; + if (xCur == imgW) { + xCur = 0; + if (!wantInterlaced) { + ++yCur; + } + else { + switch (curPass) { + case 0: + yCur += 8; + if (yCur >= imgH) { + ++curPass; + yCur = 4; + } + break; + case 1: + yCur += 8; + if (yCur >= imgH) { + ++curPass; + yCur = 2; + } + break; + case 2: + yCur += 4; + if (yCur >= imgH) { + ++curPass; + yCur = 1; + } + break; + case 3: + yCur += 2; + break; + } + } + } + } + + private int nextPixel() { + if (countDown == 0) { + return EOF; + } + --countDown; + byte pix = pixAry[yCur * imgW + xCur]; + bumpPosition(); + return pix & 0xff; + } + + static final int BITS = 12; + static final int HSIZE = 5003; + int n_bits; + int maxbits = BITS; + int maxcode; + int maxmaxcode = 1 << BITS; + + final int MAXCODE(int n_bits) { + return (1 << n_bits) - 1; + } + + int[] htab = new int[HSIZE]; + int[] codetab = new int[HSIZE]; + int hsize = HSIZE; + int free_ent = 0; + boolean clear_flg = false; + int g_init_bits; + int ClearCode; + int EOFCode; + + void compress(int init_bits, OutputStream outs) throws IOException { + int fcode; + int i /* = 0 */; + int c; + int ent; + int disp; + int hsize_reg; + int hshift; + g_init_bits = init_bits; + clear_flg = false; + n_bits = g_init_bits; + maxcode = MAXCODE(n_bits); + ClearCode = 1 << (init_bits - 1); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + char_init(); + ent = nextPixel(); + hshift = 0; + for (fcode = hsize; fcode < 65536; fcode *= 2) { + ++hshift; + } + hshift = 8 - hshift; + hsize_reg = hsize; + cl_hash(hsize_reg); + output(ClearCode, outs); + outer_loop: + while ((c = nextPixel()) != EOF) { + fcode = (c << maxbits) + ent; + i = (c << hshift) ^ ent; + if (htab[i] == fcode) { + ent = codetab[i]; + continue; + } + else if (htab[i] >= 0) { + disp = hsize_reg - i; + if (i == 0) { + disp = 1; + } + do { + if ((i -= disp) < 0) { + i += hsize_reg; + } + + if (htab[i] == fcode) { + ent = codetab[i]; + continue outer_loop; + } + } while (htab[i] >= 0); + } + output(ent, outs); + ent = c; + if (free_ent < maxmaxcode) { + codetab[i] = free_ent++; + htab[i] = fcode; + } + else { + cl_block(outs); + } + } + output(ent, outs); + output(EOFCode, outs); + } + + int cur_accum = 0; + int cur_bits = 0; + int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F, + 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, + 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF}; + + void output(int code, OutputStream outs) throws IOException { + cur_accum &= masks[cur_bits]; + if (cur_bits > 0) { + cur_accum |= (code << cur_bits); + } + else { + cur_accum = code; + } + + cur_bits += n_bits; + + while (cur_bits >= 8) { + char_out((byte) (cur_accum & 0xff), outs); + cur_accum >>= 8; + cur_bits -= 8; + } + if (free_ent > maxcode || clear_flg) { + if (clear_flg) { + maxcode = MAXCODE(n_bits = g_init_bits); + clear_flg = false; + } + else { + ++n_bits; + if (n_bits == maxbits) { + maxcode = maxmaxcode; + } + else { + maxcode = MAXCODE(n_bits); + } + } + } + if (code == EOFCode) { + + while (cur_bits > 0) { + char_out((byte) (cur_accum & 0xff), outs); + cur_accum >>= 8; + cur_bits -= 8; + } + flush_char(outs); + } + } + + + void cl_block(OutputStream outs) throws IOException { + cl_hash(hsize); + free_ent = ClearCode + 2; + clear_flg = true; + + output(ClearCode, outs); + } + + + void cl_hash(int hsize) { + for (int i = 0; i < hsize; ++i) { + htab[i] = -1; + } + } + + int a_count; + + void char_init() { + a_count = 0; + } + + byte[] accum = new byte[256]; + + void char_out(byte c, OutputStream outs) throws IOException { + accum[a_count++] = c; + if (a_count >= 254) { + flush_char(outs); + } + } + + void flush_char(OutputStream outs) throws IOException { + if (a_count > 0) { + outs.write(a_count); + outs.write(accum, 0, a_count); + a_count = 0; + } + } +} + +class IndexGif89Frame extends Gif89Frame { + + IndexGif89Frame(int width, int height, byte ci_pixels[]) { + theWidth = width; + theHeight = height; + ciPixels = new byte[theWidth * theHeight]; + System.arraycopy(ci_pixels, 0, ciPixels, 0, ciPixels.length); + } + + Object getPixelSource() { + return ciPixels; + } +} + + +final class Put { + static void ascii(String s, OutputStream os) throws IOException { + byte[] bytes = new byte[s.length()]; + for (int i = 0; i < bytes.length; ++i) { + bytes[i] = (byte) s.charAt(i); + } + os.write(bytes); + } + + static void leShort(int i16, OutputStream os) throws IOException { + os.write(i16 & 0xff); + os.write(i16 >> 8 & 0xff); + } +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/graph/HRule.java b/apps/jrobin/java/src/org/jrobin/graph/HRule.java new file mode 100644 index 000000000..163d59a21 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/HRule.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; + +class HRule extends Rule { + final double value; + + HRule(double value, Paint color, LegendText legend, float width) { + super(color, legend, width); + this.value = value; + } + + void setLegendVisibility(double minval, double maxval, boolean forceLegend) { + legend.enabled &= (forceLegend || (value >= minval && value <= maxval)); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/ImageParameters.java b/apps/jrobin/java/src/org/jrobin/graph/ImageParameters.java new file mode 100644 index 000000000..ddc55b181 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/ImageParameters.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * 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.graph; + +class ImageParameters { + long start, end; + double minval, maxval; + int unitsexponent; + double base; + double magfact; + char symbol; + double ygridstep; + int ylabfact; + double decimals; + int quadrant; + double scaledstep; + int xsize; + int ysize; + int xorigin; + int yorigin; + int unitslength; + int xgif, ygif; + String unit; +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/ImageWorker.java b/apps/jrobin/java/src/org/jrobin/graph/ImageWorker.java new file mode 100644 index 000000000..7a5fd53c3 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/ImageWorker.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; +import java.awt.font.LineMetrics; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.Iterator; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; + +class ImageWorker { + private static final String DUMMY_TEXT = "Dummy"; + + private BufferedImage img; + private Graphics2D gd; + private int imgWidth, imgHeight; + private AffineTransform aftInitial; + + ImageWorker(int width, int height) { + resize(width, height); + } + + void resize(int width, int height) { + if (gd != null) { + gd.dispose(); + } + this.imgWidth = width; + this.imgHeight = height; + this.img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + this.gd = img.createGraphics(); + this.aftInitial = gd.getTransform(); + this.setAntiAliasing(false); + } + + void clip(int x, int y, int width, int height) { + gd.setClip(x, y, width, height); + } + + void transform(int x, int y, double angle) { + gd.translate(x, y); + gd.rotate(angle); + } + + void reset() { + gd.setTransform(aftInitial); + gd.setClip(0, 0, imgWidth, imgHeight); + } + + void fillRect(int x, int y, int width, int height, Paint paint) { + gd.setPaint(paint); + gd.fillRect(x, y, width, height); + } + + void fillPolygon(int[] x, int[] y, Paint paint) { + gd.setPaint(paint); + gd.fillPolygon(x, y, x.length); + } + + void fillPolygon(double[] x, double yBottom, double[] yTop, Paint paint) { + gd.setPaint(paint); + PathIterator path = new PathIterator(yTop); + for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) { + int start = pos[0], end = pos[1], n = end - start; + int[] xDev = new int[n + 2], yDev = new int[n + 2]; + for (int i = start; i < end; i++) { + xDev[i - start] = (int) x[i]; + yDev[i - start] = (int) yTop[i]; + } + xDev[n] = xDev[n - 1]; + xDev[n + 1] = xDev[0]; + yDev[n] = yDev[n + 1] = (int) yBottom; + gd.fillPolygon(xDev, yDev, xDev.length); + gd.drawPolygon(xDev, yDev, xDev.length); + } + } + + void fillPolygon(double[] x, double[] yBottom, double[] yTop, Paint paint) { + gd.setPaint(paint); + PathIterator path = new PathIterator(yTop); + for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) { + int start = pos[0], end = pos[1], n = end - start; + int[] xDev = new int[n * 2], yDev = new int[n * 2]; + for (int i = start; i < end; i++) { + int ix1 = i - start, ix2 = n * 2 - 1 - i + start; + xDev[ix1] = xDev[ix2] = (int) x[i]; + yDev[ix1] = (int) yTop[i]; + yDev[ix2] = (int) yBottom[i]; + } + gd.fillPolygon(xDev, yDev, xDev.length); + gd.drawPolygon(xDev, yDev, xDev.length); + } + } + + void drawLine(int x1, int y1, int x2, int y2, Paint paint, Stroke stroke) { + gd.setStroke(stroke); + gd.setPaint(paint); + gd.drawLine(x1, y1, x2, y2); + } + + void drawPolyline(int[] x, int[] y, Paint paint, Stroke stroke) { + gd.setStroke(stroke); + gd.setPaint(paint); + gd.drawPolyline(x, y, x.length); + } + + void drawPolyline(double[] x, double[] y, Paint paint, Stroke stroke) { + gd.setPaint(paint); + gd.setStroke(stroke); + PathIterator path = new PathIterator(y); + for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) { + int start = pos[0], end = pos[1]; + int[] xDev = new int[end - start], yDev = new int[end - start]; + for (int i = start; i < end; i++) { + xDev[i - start] = (int) x[i]; + yDev[i - start] = (int) y[i]; + } + gd.drawPolyline(xDev, yDev, xDev.length); + } + } + + void drawString(String text, int x, int y, Font font, Paint paint) { + gd.setFont(font); + gd.setPaint(paint); + gd.drawString(text, x, y); + } + + double getFontAscent(Font font) { + LineMetrics lm = font.getLineMetrics(DUMMY_TEXT, gd.getFontRenderContext()); + return lm.getAscent(); + } + + double getFontHeight(Font font) { + LineMetrics lm = font.getLineMetrics(DUMMY_TEXT, gd.getFontRenderContext()); + return lm.getAscent() + lm.getDescent(); + } + + double getStringWidth(String text, Font font) { + return font.getStringBounds(text, 0, text.length(), gd.getFontRenderContext()).getBounds().getWidth(); + } + + void setAntiAliasing(boolean enable) { + gd.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + enable ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); + gd.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + gd.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + } + + void dispose() { + gd.dispose(); + } + + void saveImage(OutputStream stream, String type, float quality) throws IOException { + if (type.equalsIgnoreCase("png")) { + ImageIO.write(img, "png", stream); + } + else if (type.equalsIgnoreCase("gif")) { + GifEncoder gifEncoder = new GifEncoder(img); + gifEncoder.encode(stream); + } + else if (type.equalsIgnoreCase("jpg") || type.equalsIgnoreCase("jpeg")) { + Iterator iter = ImageIO.getImageWritersByFormatName("jpg"); + ImageWriter writer = iter.next(); + ImageWriteParam iwp = writer.getDefaultWriteParam(); + + iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + iwp.setCompressionQuality(quality); + writer.setOutput(stream); + writer.write(img); + writer.dispose(); + } + else { + throw new IOException("Unsupported image format: " + type); + } + stream.flush(); + } + + byte[] saveImage(String path, String type, float quality) throws IOException { + byte[] bytes = getImageBytes(type, quality); + RandomAccessFile f = new RandomAccessFile(path, "rw"); + try { + f.write(bytes); + return bytes; + } finally { + f.close(); + } + } + + byte[] getImageBytes(String type, float quality) throws IOException { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + saveImage(stream, type, quality); + return stream.toByteArray(); + } finally { + stream.close(); + } + } + + public void loadImage(String imageFile) throws IOException { + BufferedImage wpImage = ImageIO.read(new File(imageFile)); + TexturePaint paint = new TexturePaint(wpImage, new Rectangle(0, 0, wpImage.getWidth(), wpImage.getHeight())); + gd.setPaint(paint); + gd.fillRect(0, 0, wpImage.getWidth(), wpImage.getHeight()); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/LegendComposer.java b/apps/jrobin/java/src/org/jrobin/graph/LegendComposer.java new file mode 100644 index 000000000..85a9b0e18 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/LegendComposer.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * 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.graph; + +import java.util.ArrayList; +import java.util.List; + +class LegendComposer implements RrdGraphConstants { + private RrdGraphDef gdef; + private ImageWorker worker; + private int legX, legY, legWidth; + private double interlegendSpace; + private double leading; + private double smallLeading; + private double boxSpace; + + LegendComposer(RrdGraph rrdGraph, int legX, int legY, int legWidth) { + this.gdef = rrdGraph.gdef; + this.worker = rrdGraph.worker; + this.legX = legX; + this.legY = legY; + this.legWidth = legWidth; + interlegendSpace = rrdGraph.getInterlegendSpace(); + leading = rrdGraph.getLeading(); + smallLeading = rrdGraph.getSmallLeading(); + boxSpace = rrdGraph.getBoxSpace(); + } + + int placeComments() { + Line line = new Line(); + for (CommentText comment : gdef.comments) { + if (comment.isValidGraphElement()) { + if (!line.canAccomodate(comment)) { + line.layoutAndAdvance(false); + line.clear(); + } + line.add(comment); + } + } + line.layoutAndAdvance(true); + worker.dispose(); + return legY; + } + + class Line { + private String lastMarker; + private double width; + private int spaceCount; + private boolean noJustification; + private List comments = new ArrayList(); + + Line() { + clear(); + } + + void clear() { + lastMarker = ""; + width = 0; + spaceCount = 0; + noJustification = false; + comments.clear(); + } + + boolean canAccomodate(CommentText comment) { + // always accommodate if empty + if (comments.size() == 0) { + return true; + } + // cannot accommodate if the last marker was \j, \l, \r, \c, \s + if (lastMarker.equals(ALIGN_LEFT_MARKER) || lastMarker.equals(ALIGN_CENTER_MARKER) || + lastMarker.equals(ALIGN_RIGHT_MARKER) || lastMarker.equals(ALIGN_JUSTIFIED_MARKER) || + lastMarker.equals(VERTICAL_SPACING_MARKER)) { + return false; + } + // cannot accommodate if line would be too long + double commentWidth = getCommentWidth(comment); + if (!lastMarker.equals(GLUE_MARKER)) { + commentWidth += interlegendSpace; + } + return width + commentWidth <= legWidth; + } + + void add(CommentText comment) { + double commentWidth = getCommentWidth(comment); + if (comments.size() > 0 && !lastMarker.equals(GLUE_MARKER)) { + commentWidth += interlegendSpace; + spaceCount++; + } + width += commentWidth; + lastMarker = comment.marker; + noJustification |= lastMarker.equals(NO_JUSTIFICATION_MARKER); + comments.add(comment); + } + + void layoutAndAdvance(boolean isLastLine) { + if (comments.size() > 0) { + if (lastMarker.equals(ALIGN_LEFT_MARKER)) { + placeComments(legX, interlegendSpace); + } + else if (lastMarker.equals(ALIGN_RIGHT_MARKER)) { + placeComments(legX + legWidth - width, interlegendSpace); + } + else if (lastMarker.equals(ALIGN_CENTER_MARKER)) { + placeComments(legX + (legWidth - width) / 2.0, interlegendSpace); + } + else if (lastMarker.equals(ALIGN_JUSTIFIED_MARKER)) { + // anything to justify? + if (spaceCount > 0) { + placeComments(legX, (legWidth - width) / spaceCount + interlegendSpace); + } + else { + placeComments(legX, interlegendSpace); + } + } + else if (lastMarker.equals(VERTICAL_SPACING_MARKER)) { + placeComments(legX, interlegendSpace); + } + else { + // nothing specified, align with respect to '\J' + if (noJustification || isLastLine) { + placeComments(legX, interlegendSpace); + } + else { + placeComments(legX, (legWidth - width) / spaceCount + interlegendSpace); + } + } + if (lastMarker.equals(VERTICAL_SPACING_MARKER)) { + legY += smallLeading; + } + else { + legY += leading; + } + } + } + + private double getCommentWidth(CommentText comment) { + double commentWidth = worker.getStringWidth(comment.resolvedText, gdef.getFont(FONTTAG_LEGEND)); + if (comment instanceof LegendText) { + commentWidth += boxSpace; + } + return commentWidth; + } + + private void placeComments(double xStart, double space) { + double x = xStart; + for (CommentText comment : comments) { + comment.x = (int) x; + comment.y = legY; + x += getCommentWidth(comment); + if (!comment.marker.equals(GLUE_MARKER)) { + x += space; + } + } + } + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/LegendText.java b/apps/jrobin/java/src/org/jrobin/graph/LegendText.java new file mode 100644 index 000000000..da2efb79c --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/LegendText.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; + +class LegendText extends CommentText { + final Paint legendColor; + + LegendText(Paint legendColor, String text) { + super(text); + this.legendColor = legendColor; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/Line.java b/apps/jrobin/java/src/org/jrobin/graph/Line.java new file mode 100644 index 000000000..76ce08bc4 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/Line.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; + +class Line extends SourcedPlotElement { + final float width; + + Line(String srcName, Paint color, float width) { + super(srcName, color); + this.width = width; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/Mapper.java b/apps/jrobin/java/src/org/jrobin/graph/Mapper.java new file mode 100644 index 000000000..e0b2f8f9c --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/Mapper.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * 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.graph; + +class Mapper { + private RrdGraphDef gdef; + private ImageParameters im; + private double pixieX, pixieY; + + Mapper(RrdGraph rrdGraph) { + this(rrdGraph.gdef, rrdGraph.im); + } + + Mapper(RrdGraphDef gdef, ImageParameters im) { + this.gdef = gdef; + this.im = im; + pixieX = (double) im.xsize / (double) (im.end - im.start); + if (!gdef.logarithmic) { + pixieY = (double) im.ysize / (im.maxval - im.minval); + } + else { + pixieY = (double) im.ysize / (Math.log10(im.maxval) - Math.log10(im.minval)); + } + } + + int xtr(double mytime) { + return (int) ((double) im.xorigin + pixieX * (mytime - im.start)); + } + + int ytr(double value) { + double yval; + if (!gdef.logarithmic) { + yval = im.yorigin - pixieY * (value - im.minval) + 0.5; + } + else { + if (value < im.minval) { + yval = im.yorigin; + } + else { + yval = im.yorigin - pixieY * (Math.log10(value) - Math.log10(im.minval)) + 0.5; + } + } + if (!gdef.rigid) { + return (int) yval; + } + else if ((int) yval > im.yorigin) { + return im.yorigin + 2; + } + else if ((int) yval < im.yorigin - im.ysize) { + return im.yorigin - im.ysize - 2; + } + else { + return (int) yval; + } + } +} \ No newline at end of file diff --git a/apps/jrobin/java/src/org/jrobin/graph/Normalizer.java b/apps/jrobin/java/src/org/jrobin/graph/Normalizer.java new file mode 100644 index 000000000..6a3ce265d --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/Normalizer.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.Util; + +import java.util.Arrays; + +class Normalizer { + final private double[] timestamps; + final int count; + final double step; + + Normalizer(long tStart, long tEnd, int count) { + this.count = count; + this.step = (tEnd - tStart) / Double.valueOf(count - 1); + this.timestamps = new double[count]; + for (int i = 0; i < count; i++) { + this.timestamps[i] = tStart + ((double) i / (double) (count - 1)) * (tEnd - tStart); + } + } + + double[] getTimestamps() { + return timestamps; + } + + double[] normalize(long[] rawTimestamps, double[] rawValues) { + int rawCount = rawTimestamps.length; + long rawStep = rawTimestamps[1] - rawTimestamps[0]; + // check if we have a simple match + if (rawCount == count && rawStep == step && rawTimestamps[0] == timestamps[0]) { + return getCopyOf(rawValues); + } + // reset all normalized values to NaN + double[] values = new double[count]; + Arrays.fill(values, Double.NaN); + for (int rawSeg = 0, seg = 0; rawSeg < rawCount && seg < count; rawSeg++) { + double rawValue = rawValues[rawSeg]; + if (!Double.isNaN(rawValue)) { + long rawLeft = rawTimestamps[rawSeg] - rawStep; + while (seg < count && rawLeft >= timestamps[seg]) { + seg++; + } + boolean overlap = true; + for (int fillSeg = seg; overlap && fillSeg < count; fillSeg++) { + double left = timestamps[fillSeg] - step; + double t1 = Math.max(rawLeft, left); + double t2 = Math.min(rawTimestamps[rawSeg], timestamps[fillSeg]); + if (t1 < t2) { + values[fillSeg] = Util.sum(values[fillSeg], (t2 - t1) * rawValues[rawSeg]); + } + else { + overlap = false; + } + } + } + } + for (int seg = 0; seg < count; seg++) { + values[seg] /= step; + } + return values; + } + + private static double[] getCopyOf(double[] rawValues) { + int n = rawValues.length; + double[] values = new double[n]; + System.arraycopy(rawValues, 0, values, 0, n); + return values; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/PDef.java b/apps/jrobin/java/src/org/jrobin/graph/PDef.java new file mode 100644 index 000000000..875e88a47 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/PDef.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.data.DataProcessor; +import org.jrobin.data.Plottable; + +class PDef extends Source { + private Plottable plottable; + + PDef(String name, Plottable plottable) { + super(name); + this.plottable = plottable; + } + + void requestData(DataProcessor dproc) { + dproc.addDatasource(name, plottable); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/PathIterator.java b/apps/jrobin/java/src/org/jrobin/graph/PathIterator.java new file mode 100644 index 000000000..3e7055f16 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/PathIterator.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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.graph; + +class PathIterator { + private double[] y; + private int pos = 0; + + PathIterator(double[] y) { + this.y = y; + } + + int[] getNextPath() { + while (pos < y.length) { + if (Double.isNaN(y[pos])) { + pos++; + } + else { + int endPos = pos + 1; + while (endPos < y.length && !Double.isNaN(y[endPos])) { + endPos++; + } + int[] result = {pos, endPos}; + pos = endPos; + if (result[1] - result[0] >= 2) { + return result; + } + } + } + return null; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/PercentileDef.java b/apps/jrobin/java/src/org/jrobin/graph/PercentileDef.java new file mode 100644 index 000000000..43e38053f --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/PercentileDef.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2011 Craig Miskell + * + * 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.graph; + +import org.jrobin.data.DataProcessor; +import org.jrobin.graph.Source; + +public class PercentileDef extends Source { + private String m_sourceName; + + private double m_percentile; + + private boolean m_includenan; + + PercentileDef(String name, String sourceName, double percentile) { + this(name, sourceName, percentile, false); + } + + PercentileDef(String name, String sourceName, double percentile, boolean includenan) { + super(name); + m_sourceName = sourceName; + m_percentile = percentile; + m_includenan = includenan; + } + + @Override + void requestData(DataProcessor dproc) { + dproc.addDatasource(name, m_sourceName, m_percentile, m_includenan); + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/PlotElement.java b/apps/jrobin/java/src/org/jrobin/graph/PlotElement.java new file mode 100644 index 000000000..5217434e8 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/PlotElement.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; + +class PlotElement { + final Paint color; + + PlotElement(Paint color) { + this.color = color; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/PrintText.java b/apps/jrobin/java/src/org/jrobin/graph/PrintText.java new file mode 100644 index 000000000..ee6875a79 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/PrintText.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; +import org.jrobin.data.DataProcessor; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class PrintText extends CommentText { + static final String UNIT_MARKER = "([^%]?)%(s|S)"; + static final Pattern UNIT_PATTERN = Pattern.compile(UNIT_MARKER); + + private final String srcName, consolFun; + private final boolean includedInGraph; + + PrintText(String srcName, String consolFun, String text, boolean includedInGraph) { + super(text); + this.srcName = srcName; + this.consolFun = consolFun; + this.includedInGraph = includedInGraph; + } + + boolean isPrint() { + return !includedInGraph; + } + + void resolveText(DataProcessor dproc, ValueScaler valueScaler) throws RrdException { + super.resolveText(dproc, valueScaler); + if (resolvedText != null) { + double value = dproc.getAggregate(srcName, consolFun); + Matcher matcher = UNIT_PATTERN.matcher(resolvedText); + if (matcher.find()) { + // unit specified + ValueScaler.Scaled scaled = valueScaler.scale(value, matcher.group(2).equals("s")); + resolvedText = resolvedText.substring(0, matcher.start()) + + matcher.group(1) + scaled.unit + resolvedText.substring(matcher.end()); + value = scaled.value; + } + resolvedText = Util.sprintf(resolvedText, value); + trimIfGlue(); + } + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/RrdGraph.java b/apps/jrobin/java/src/org/jrobin/graph/RrdGraph.java new file mode 100644 index 000000000..6e1d2f886 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/RrdGraph.java @@ -0,0 +1,677 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; +import org.jrobin.data.DataProcessor; + +import javax.swing.*; +import java.awt.*; +import java.io.IOException; + +/** + * Class which actually creates JRobin graphs (does the hard work). + */ +public class RrdGraph implements RrdGraphConstants { + RrdGraphDef gdef; + ImageParameters im = new ImageParameters(); + DataProcessor dproc; + ImageWorker worker; + Mapper mapper; + RrdGraphInfo info = new RrdGraphInfo(); + private String signature; + + /** + * Creates graph from the corresponding {@link RrdGraphDef} object. + * + * @param gdef Graph definition + * @throws IOException Thrown in case of I/O error + * @throws RrdException Thrown in case of JRobin related error + */ + public RrdGraph(RrdGraphDef gdef) throws IOException, RrdException { + this.gdef = gdef; + signature = gdef.getSignature(); + worker = new ImageWorker(100, 100); // Dummy worker, just to start with something + try { + createGraph(); + } + finally { + worker.dispose(); + worker = null; + dproc = null; + } + } + + /** + * Returns complete graph information in a single object. + * + * @return Graph information (width, height, filename, image bytes, etc...) + */ + public RrdGraphInfo getRrdGraphInfo() { + return info; + } + + private void createGraph() throws RrdException, IOException { + boolean lazy = lazyCheck(); + if (!lazy || gdef.printStatementCount() != 0) { + fetchData(); + resolveTextElements(); + if (gdef.shouldPlot() && !lazy) { + calculatePlotValues(); + findMinMaxValues(); + identifySiUnit(); + expandValueRange(); + removeOutOfRangeRules(); + initializeLimits(); + placeLegends(); + createImageWorker(); + drawBackground(); + drawData(); + drawGrid(); + drawAxis(); + drawText(); + drawLegend(); + drawRules(); + gator(); + drawOverlay(); + saveImage(); + } + } + collectInfo(); + } + + private void collectInfo() { + info.filename = gdef.filename; + info.width = im.xgif; + info.height = im.ygif; + for (CommentText comment : gdef.comments) { + if (comment instanceof PrintText) { + PrintText pt = (PrintText) comment; + if (pt.isPrint()) { + info.addPrintLine(pt.resolvedText); + } + } + } + if (gdef.imageInfo != null) { + info.imgInfo = Util.sprintf(gdef.imageInfo, gdef.filename, im.xgif, im.ygif); + } + } + + private void saveImage() throws IOException { + if (!gdef.filename.equals("-")) { + info.bytes = worker.saveImage(gdef.filename, gdef.imageFormat, gdef.imageQuality); + } + else { + info.bytes = worker.getImageBytes(gdef.imageFormat, gdef.imageQuality); + } + } + + private void drawOverlay() throws IOException { + if (gdef.overlayImage != null) { + worker.loadImage(gdef.overlayImage); + } + } + + private void gator() { + if (!gdef.onlyGraph && gdef.showSignature) { + Font font = gdef.getFont(FONTTAG_WATERMARK); + int x = (int) (im.xgif - 2 - worker.getFontAscent(font)); + int y = 4; + worker.transform(x, y, Math.PI / 2); + worker.drawString(signature, 0, 0, font, Color.LIGHT_GRAY); + worker.reset(); + } + } + + private void drawRules() { + worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2); + for (PlotElement pe : gdef.plotElements) { + if (pe instanceof HRule) { + HRule hr = (HRule) pe; + if (hr.value >= im.minval && hr.value <= im.maxval) { + int y = mapper.ytr(hr.value); + worker.drawLine(im.xorigin, y, im.xorigin + im.xsize, y, hr.color, new BasicStroke(hr.width)); + } + } + else if (pe instanceof VRule) { + VRule vr = (VRule) pe; + if (vr.timestamp >= im.start && vr.timestamp <= im.end) { + int x = mapper.xtr(vr.timestamp); + worker.drawLine(x, im.yorigin, x, im.yorigin - im.ysize, vr.color, new BasicStroke(vr.width)); + } + } + } + worker.reset(); + } + + private void drawText() { + if (!gdef.onlyGraph) { + if (gdef.title != null) { + int x = im.xgif / 2 - (int) (worker.getStringWidth(gdef.title, gdef.getFont(FONTTAG_TITLE)) / 2); + int y = PADDING_TOP + (int) worker.getFontAscent(gdef.getFont(FONTTAG_TITLE)); + worker.drawString(gdef.title, x, y, gdef.getFont(FONTTAG_TITLE), gdef.colors[COLOR_FONT]); + } + if (gdef.verticalLabel != null) { + int x = PADDING_LEFT; + int y = im.yorigin - im.ysize / 2 + (int) worker.getStringWidth(gdef.verticalLabel, gdef.getFont(FONTTAG_UNIT)) / 2; + int ascent = (int) worker.getFontAscent(gdef.getFont(FONTTAG_UNIT)); + worker.transform(x, y, -Math.PI / 2); + worker.drawString(gdef.verticalLabel, 0, ascent, gdef.getFont(FONTTAG_UNIT), gdef.colors[COLOR_FONT]); + worker.reset(); + } + } + } + + private void drawGrid() { + if (!gdef.onlyGraph) { + Paint shade1 = gdef.colors[COLOR_SHADEA], shade2 = gdef.colors[COLOR_SHADEB]; + Stroke borderStroke = new BasicStroke(1); + worker.drawLine(0, 0, im.xgif - 1, 0, shade1, borderStroke); + worker.drawLine(1, 1, im.xgif - 2, 1, shade1, borderStroke); + worker.drawLine(0, 0, 0, im.ygif - 1, shade1, borderStroke); + worker.drawLine(1, 1, 1, im.ygif - 2, shade1, borderStroke); + worker.drawLine(im.xgif - 1, 0, im.xgif - 1, im.ygif - 1, shade2, borderStroke); + worker.drawLine(0, im.ygif - 1, im.xgif - 1, im.ygif - 1, shade2, borderStroke); + worker.drawLine(im.xgif - 2, 1, im.xgif - 2, im.ygif - 2, shade2, borderStroke); + worker.drawLine(1, im.ygif - 2, im.xgif - 2, im.ygif - 2, shade2, borderStroke); + if (gdef.drawXGrid) { + new TimeAxis(this).draw(); + } + if (gdef.drawYGrid) { + boolean ok; + if (gdef.altYMrtg) { + ok = new ValueAxisMrtg(this).draw(); + } + else if (gdef.logarithmic) { + ok = new ValueAxisLogarithmic(this).draw(); + } + else { + ok = new ValueAxis(this).draw(); + } + if (!ok) { + String msg = "No Data Found"; + worker.drawString(msg, + im.xgif / 2 - (int) worker.getStringWidth(msg, gdef.getFont(FONTTAG_TITLE)) / 2, + (2 * im.yorigin - im.ysize) / 2, + gdef.getFont(FONTTAG_TITLE), gdef.colors[COLOR_FONT]); + } + } + } + } + + private void drawData() throws RrdException { + worker.setAntiAliasing(gdef.antiAliasing); + worker.clip(im.xorigin + 1, im.yorigin - gdef.height - 1, gdef.width - 1, gdef.height + 2); + double areazero = mapper.ytr((im.minval > 0.0) ? im.minval : (im.maxval < 0.0) ? im.maxval : 0.0); + double[] x = xtr(dproc.getTimestamps()), lastY = null; + // draw line, area and stack + for (PlotElement plotElement : gdef.plotElements) { + if (plotElement instanceof SourcedPlotElement) { + SourcedPlotElement source = (SourcedPlotElement) plotElement; + double[] y = ytr(source.getValues()); + if (source instanceof Line) { + worker.drawPolyline(x, y, source.color, new BasicStroke(((Line) source).width)); + } + else if (source instanceof Area) { + worker.fillPolygon(x, areazero, y, source.color); + } + else if (source instanceof Stack) { + Stack stack = (Stack) source; + float width = stack.getParentLineWidth(); + if (width >= 0F) { + // line + worker.drawPolyline(x, y, stack.color, new BasicStroke(width)); + } + else { + // area + worker.fillPolygon(x, lastY, y, stack.color); + worker.drawPolyline(x, lastY, stack.getParentColor(), new BasicStroke(0)); + } + } + else { + // should not be here + throw new RrdException("Unknown plot source: " + source.getClass().getName()); + } + lastY = y; + } + } + worker.reset(); + worker.setAntiAliasing(false); + } + + private void drawAxis() { + if (!gdef.onlyGraph) { + Paint gridColor = gdef.colors[COLOR_GRID]; + Paint fontColor = gdef.colors[COLOR_FONT]; + Paint arrowColor = gdef.colors[COLOR_ARROW]; + Stroke stroke = new BasicStroke(1); + worker.drawLine(im.xorigin + im.xsize, im.yorigin, im.xorigin + im.xsize, im.yorigin - im.ysize, + gridColor, stroke); + worker.drawLine(im.xorigin, im.yorigin - im.ysize, im.xorigin + im.xsize, im.yorigin - im.ysize, + gridColor, stroke); + worker.drawLine(im.xorigin - 4, im.yorigin, im.xorigin + im.xsize + 4, im.yorigin, + fontColor, stroke); + worker.drawLine(im.xorigin, im.yorigin, im.xorigin, im.yorigin - im.ysize, + gridColor, stroke); + worker.drawLine(im.xorigin + im.xsize + 4, im.yorigin - 3, im.xorigin + im.xsize + 4, im.yorigin + 3, + arrowColor, stroke); + worker.drawLine(im.xorigin + im.xsize + 4, im.yorigin - 3, im.xorigin + im.xsize + 9, im.yorigin, + arrowColor, stroke); + worker.drawLine(im.xorigin + im.xsize + 4, im.yorigin + 3, im.xorigin + im.xsize + 9, im.yorigin, + arrowColor, stroke); + } + } + + private void drawBackground() throws IOException { + worker.fillRect(0, 0, im.xgif, im.ygif, gdef.colors[COLOR_BACK]); + if (gdef.backgroundImage != null) { + worker.loadImage(gdef.backgroundImage); + } + worker.fillRect(im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize, gdef.colors[COLOR_CANVAS]); + } + + private void createImageWorker() { + worker.resize(im.xgif, im.ygif); + } + + private void placeLegends() { + if (!gdef.noLegend && !gdef.onlyGraph) { + int border = (int) (getSmallFontCharWidth() * PADDING_LEGEND); + LegendComposer lc = new LegendComposer(this, border, im.ygif, im.xgif - 2 * border); + im.ygif = lc.placeComments() + PADDING_BOTTOM; + } + } + + private void initializeLimits() throws RrdException { + im.xsize = gdef.width; + im.ysize = gdef.height; + im.unitslength = gdef.unitsLength; + if (gdef.onlyGraph) { + if (im.ysize > 64) { + throw new RrdException("Cannot create graph only, height too big"); + } + im.xorigin = 0; + } + else { + im.xorigin = (int) (PADDING_LEFT + im.unitslength * getSmallFontCharWidth()); + } + if (gdef.verticalLabel != null) { + im.xorigin += getFontHeight(FONTTAG_UNIT); + } + if (gdef.onlyGraph) { + im.yorigin = im.ysize; + } + else { + im.yorigin = PADDING_TOP + im.ysize; + } + mapper = new Mapper(this); + if (gdef.title != null) { + im.yorigin += getFontHeight(FONTTAG_TITLE) + PADDING_TITLE; + } + if (gdef.onlyGraph) { + im.xgif = im.xsize; + im.ygif = im.yorigin; + } + else { + im.xgif = PADDING_RIGHT + im.xsize + im.xorigin; + im.ygif = im.yorigin + (int) (PADDING_PLOT * getFontHeight(FONTTAG_DEFAULT)); + } + } + + private void removeOutOfRangeRules() { + for (PlotElement plotElement : gdef.plotElements) { + if (plotElement instanceof HRule) { + ((HRule) plotElement).setLegendVisibility(im.minval, im.maxval, gdef.forceRulesLegend); + } + else if (plotElement instanceof VRule) { + ((VRule) plotElement).setLegendVisibility(im.start, im.end, gdef.forceRulesLegend); + } + } + } + + private void expandValueRange() { + im.ygridstep = (gdef.valueAxisSetting != null) ? gdef.valueAxisSetting.gridStep : Double.NaN; + im.ylabfact = (gdef.valueAxisSetting != null) ? gdef.valueAxisSetting.labelFactor : 0; + if (!gdef.rigid && !gdef.logarithmic) { + double sensiblevalues[] = { + 1000.0, 900.0, 800.0, 750.0, 700.0, 600.0, 500.0, 400.0, 300.0, 250.0, 200.0, 125.0, 100.0, + 90.0, 80.0, 75.0, 70.0, 60.0, 50.0, 40.0, 30.0, 25.0, 20.0, 10.0, + 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.5, 3.0, 2.5, 2.0, 1.8, 1.5, 1.2, 1.0, + 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1 + }; + double scaled_min, scaled_max, adj; + if (Double.isNaN(im.ygridstep)) { + if (gdef.altYMrtg) { /* mrtg */ + im.decimals = Math.ceil(Math.log10(Math.max(Math.abs(im.maxval), Math.abs(im.minval)))); + im.quadrant = 0; + if (im.minval < 0) { + im.quadrant = 2; + if (im.maxval <= 0) { + im.quadrant = 4; + } + } + switch (im.quadrant) { + case 2: + im.scaledstep = Math.ceil(50 * Math.pow(10, -(im.decimals)) * Math.max(Math.abs(im.maxval), + Math.abs(im.minval))) * Math.pow(10, im.decimals - 2); + scaled_min = -2 * im.scaledstep; + scaled_max = 2 * im.scaledstep; + break; + case 4: + im.scaledstep = Math.ceil(25 * Math.pow(10, + -(im.decimals)) * Math.abs(im.minval)) * Math.pow(10, im.decimals - 2); + scaled_min = -4 * im.scaledstep; + scaled_max = 0; + break; + default: /* quadrant 0 */ + im.scaledstep = Math.ceil(25 * Math.pow(10, -(im.decimals)) * im.maxval) * + Math.pow(10, im.decimals - 2); + scaled_min = 0; + scaled_max = 4 * im.scaledstep; + break; + } + im.minval = scaled_min; + im.maxval = scaled_max; + } + else if (gdef.altAutoscale) { + /* measure the amplitude of the function. Make sure that + graph boundaries are slightly higher then max/min vals + so we can see amplitude on the graph */ + double delt, fact; + + delt = im.maxval - im.minval; + adj = delt * 0.1; + fact = 2.0 * Math.pow(10.0, + Math.floor(Math.log10(Math.max(Math.abs(im.minval), Math.abs(im.maxval)))) - 2); + if (delt < fact) { + adj = (fact - delt) * 0.55; + } + im.minval -= adj; + im.maxval += adj; + } + else if (gdef.altAutoscaleMax) { + /* measure the amplitude of the function. Make sure that + graph boundaries are slightly higher than max vals + so we can see amplitude on the graph */ + adj = (im.maxval - im.minval) * 0.1; + im.maxval += adj; + } + else { + scaled_min = im.minval / im.magfact; + scaled_max = im.maxval / im.magfact; + for (int i = 1; sensiblevalues[i] > 0; i++) { + if (sensiblevalues[i - 1] >= scaled_min && sensiblevalues[i] <= scaled_min) { + im.minval = sensiblevalues[i] * im.magfact; + } + if (-sensiblevalues[i - 1] <= scaled_min && -sensiblevalues[i] >= scaled_min) { + im.minval = -sensiblevalues[i - 1] * im.magfact; + } + if (sensiblevalues[i - 1] >= scaled_max && sensiblevalues[i] <= scaled_max) { + im.maxval = sensiblevalues[i - 1] * im.magfact; + } + if (-sensiblevalues[i - 1] <= scaled_max && -sensiblevalues[i] >= scaled_max) { + im.maxval = -sensiblevalues[i] * im.magfact; + } + } + } + } + else { + im.minval = (double) im.ylabfact * im.ygridstep * + Math.floor(im.minval / ((double) im.ylabfact * im.ygridstep)); + im.maxval = (double) im.ylabfact * im.ygridstep * + Math.ceil(im.maxval / ((double) im.ylabfact * im.ygridstep)); + } + + } + } + + private void identifySiUnit() { + im.unitsexponent = gdef.unitsExponent; + im.base = gdef.base; + if (!gdef.logarithmic) { + final char symbol[] = {'a', 'f', 'p', 'n', 'u', 'm', ' ', 'k', 'M', 'G', 'T', 'P', 'E'}; + int symbcenter = 6; + double digits; + if (im.unitsexponent != Integer.MAX_VALUE) { + digits = Math.floor(im.unitsexponent / 3.0); + } + else { + digits = Math.floor(Math.log(Math.max(Math.abs(im.minval), Math.abs(im.maxval))) / Math.log(im.base)); + } + im.magfact = Math.pow(im.base, digits); + if (((digits + symbcenter) < symbol.length) && ((digits + symbcenter) >= 0)) { + im.symbol = symbol[(int) digits + symbcenter]; + } + else { + im.symbol = '?'; + } + } + } + + private void findMinMaxValues() { + double minval = Double.NaN, maxval = Double.NaN; + for (PlotElement pe : gdef.plotElements) { + if (pe instanceof SourcedPlotElement) { + minval = Util.min(((SourcedPlotElement) pe).getMinValue(), minval); + maxval = Util.max(((SourcedPlotElement) pe).getMaxValue(), maxval); + } + } + if (Double.isNaN(minval)) { + minval = 0D; + } + if (Double.isNaN(maxval)) { + maxval = 1D; + } + im.minval = gdef.minValue; + im.maxval = gdef.maxValue; + /* adjust min and max values */ + if (Double.isNaN(im.minval) || ((!gdef.logarithmic && !gdef.rigid) && im.minval > minval)) { + im.minval = minval; + } + if (Double.isNaN(im.maxval) || (!gdef.rigid && im.maxval < maxval)) { + if (gdef.logarithmic) { + im.maxval = maxval * 1.1; + } + else { + im.maxval = maxval; + } + } + /* make sure min is smaller than max */ + if (im.minval > im.maxval) { + im.minval = 0.99 * im.maxval; + } + /* make sure min and max are not equal */ + if (Math.abs(im.minval - im.maxval) < .0000001) { + im.maxval *= 1.01; + if (!gdef.logarithmic) { + im.minval *= 0.99; + } + /* make sure min and max are not both zero */ + if (im.maxval == 0.0) { + im.maxval = 1.0; + } + } + } + + private void calculatePlotValues() throws RrdException { + for (PlotElement pe : gdef.plotElements) { + if (pe instanceof SourcedPlotElement) { + ((SourcedPlotElement) pe).assignValues(dproc); + } + } + } + + private void resolveTextElements() throws RrdException { + ValueScaler valueScaler = new ValueScaler(gdef.base); + for (CommentText comment : gdef.comments) { + comment.resolveText(dproc, valueScaler); + } + } + + private void fetchData() throws RrdException, IOException { + dproc = new DataProcessor(gdef.startTime, gdef.endTime); + dproc.setPoolUsed(gdef.poolUsed); + if (gdef.step > 0) { + dproc.setStep(gdef.step); + } + for (Source src : gdef.sources) { + src.requestData(dproc); + } + dproc.processData(); + //long[] t = dproc.getTimestamps(); + //im.start = t[0]; + //im.end = t[t.length - 1]; + im.start = gdef.startTime; + im.end = gdef.endTime; + } + + private boolean lazyCheck() { + // redraw if lazy option is not set or file does not exist + if (!gdef.lazy || !Util.fileExists(gdef.filename)) { + return false; // 'false' means 'redraw' + } + // redraw if not enough time has passed + long secPerPixel = (gdef.endTime - gdef.startTime) / gdef.width; + long elapsed = Util.getTimestamp() - Util.getLastModified(gdef.filename); + return elapsed <= secPerPixel; + } + + private void drawLegend() { + if (!gdef.onlyGraph && !gdef.noLegend) { + int ascent = (int) worker.getFontAscent(gdef.getFont(FONTTAG_LEGEND)); + int box = (int) getBox(), boxSpace = (int) (getBoxSpace()); + for (CommentText c : gdef.comments) { + if (c.isValidGraphElement()) { + int x = c.x, y = c.y + ascent; + if (c instanceof LegendText) { + // draw with BOX + worker.fillRect(x, y - box, box, box, gdef.colors[COLOR_FRAME]); + worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, gdef.colors[COLOR_CANVAS]); + worker.fillRect(x + 1, y - box + 1, box - 2, box - 2, ((LegendText) c).legendColor); + worker.drawString(c.resolvedText, x + boxSpace, y, gdef.getFont(FONTTAG_LEGEND), gdef.colors[COLOR_FONT]); + } + else { + worker.drawString(c.resolvedText, x, y, gdef.getFont(FONTTAG_LEGEND), gdef.colors[COLOR_FONT]); + } + } + } + } + } + + // helper methods + + double getFontHeight(int fonttag) { + return worker.getFontHeight(gdef.getFont(fonttag)); + } + + double getFontCharWidth(int fonttag) { + return worker.getStringWidth("a", gdef.getFont(fonttag)); + } + + double getSmallFontHeight() { + return getFontHeight(FONTTAG_LEGEND); + } + + @SuppressWarnings("unused") + private double getTitleFontHeight() { + return getFontHeight(FONTTAG_TITLE); + } + + private double getSmallFontCharWidth() { + return getFontCharWidth(FONTTAG_LEGEND); + } + + double getInterlegendSpace() { + return getFontCharWidth(FONTTAG_LEGEND) * LEGEND_INTERSPACING; + } + + double getLeading() { + return getFontHeight(FONTTAG_LEGEND) * LEGEND_LEADING; + } + + double getSmallLeading() { + return getFontHeight(FONTTAG_LEGEND) * LEGEND_LEADING_SMALL; + } + + double getBoxSpace() { + return Math.ceil(getFontHeight(FONTTAG_LEGEND) * LEGEND_BOX_SPACE); + } + + private double getBox() { + return getFontHeight(FONTTAG_LEGEND) * LEGEND_BOX; + } + + double[] xtr(long[] timestamps) { + /* + double[] timestampsDev = new double[timestamps.length]; + for (int i = 0; i < timestamps.length; i++) { + timestampsDev[i] = mapper.xtr(timestamps[i]); + } + return timestampsDev; + */ + double[] timestampsDev = new double[2 * timestamps.length - 1]; + for (int i = 0, j = 0; i < timestamps.length; i += 1, j += 2) { + timestampsDev[j] = mapper.xtr(timestamps[i]); + if (i < timestamps.length - 1) { + timestampsDev[j + 1] = timestampsDev[j]; + } + } + return timestampsDev; + } + + double[] ytr(double[] values) { + /* + double[] valuesDev = new double[values.length]; + for (int i = 0; i < values.length; i++) { + if (Double.isNaN(values[i])) { + valuesDev[i] = Double.NaN; + } + else { + valuesDev[i] = mapper.ytr(values[i]); + } + } + return valuesDev; + */ + double[] valuesDev = new double[2 * values.length - 1]; + for (int i = 0, j = 0; i < values.length; i += 1, j += 2) { + if (Double.isNaN(values[i])) { + valuesDev[j] = Double.NaN; + } + else { + valuesDev[j] = mapper.ytr(values[i]); + } + if (j > 0) { + valuesDev[j - 1] = valuesDev[j]; + } + } + return valuesDev; + } + + /** + * Renders this graph onto graphing device + * + * @param g Graphics handle + */ + public void render(Graphics g) { + byte[] imageData = getRrdGraphInfo().getBytes(); + ImageIcon image = new ImageIcon(imageData); + image.paintIcon(null, g, 0, 0); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/RrdGraphConstants.java b/apps/jrobin/java/src/org/jrobin/graph/RrdGraphConstants.java new file mode 100644 index 000000000..1ed8d303c --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/RrdGraphConstants.java @@ -0,0 +1,368 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; +import java.util.Calendar; +import java.util.Locale; + +/** + * Class to represent various constants used for graphing. No methods are specified. + */ +public interface RrdGraphConstants { + /** + * Default graph starting time + */ + String DEFAULT_START = "end-1d"; + /** + * Default graph ending time + */ + String DEFAULT_END = "now"; + + /** + * Constant to represent second + */ + int SECOND = Calendar.SECOND; + /** + * Constant to represent minute + */ + int MINUTE = Calendar.MINUTE; + /** + * Constant to represent hour + */ + int HOUR = Calendar.HOUR_OF_DAY; + /** + * Constant to represent day + */ + int DAY = Calendar.DAY_OF_MONTH; + /** + * Constant to represent week + */ + int WEEK = Calendar.WEEK_OF_YEAR; + /** + * Constant to represent month + */ + int MONTH = Calendar.MONTH; + /** + * Constant to represent year + */ + int YEAR = Calendar.YEAR; + + /** + * Constant to represent Monday + */ + int MONDAY = Calendar.MONDAY; + /** + * Constant to represent Tuesday + */ + int TUESDAY = Calendar.TUESDAY; + /** + * Constant to represent Wednesday + */ + int WEDNESDAY = Calendar.WEDNESDAY; + /** + * Constant to represent Thursday + */ + int THURSDAY = Calendar.THURSDAY; + /** + * Constant to represent Friday + */ + int FRIDAY = Calendar.FRIDAY; + /** + * Constant to represent Saturday + */ + int SATURDAY = Calendar.SATURDAY; + /** + * Constant to represent Sunday + */ + int SUNDAY = Calendar.SUNDAY; + + /** + * Index of the canvas color. Used in {@link RrdGraphDef#setColor(int, java.awt.Paint)} + */ + int COLOR_CANVAS = 0; + /** + * Index of the background color. Used in {@link RrdGraphDef#setColor(int, java.awt.Paint)} + */ + int COLOR_BACK = 1; + /** + * Index of the top-left graph shade color. Used in {@link RrdGraphDef#setColor(int, java.awt.Paint)} + */ + int COLOR_SHADEA = 2; + /** + * Index of the bottom-right graph shade color. Used in {@link RrdGraphDef#setColor(int, java.awt.Paint)} + */ + int COLOR_SHADEB = 3; + /** + * Index of the minor grid color. Used in {@link RrdGraphDef#setColor(int, java.awt.Paint)} + */ + int COLOR_GRID = 4; + /** + * Index of the major grid color. Used in {@link RrdGraphDef#setColor(int, java.awt.Paint)} + */ + int COLOR_MGRID = 5; + /** + * Index of the font color. Used in {@link RrdGraphDef#setColor(int, java.awt.Paint)} + */ + int COLOR_FONT = 6; + /** + * Index of the frame color. Used in {@link RrdGraphDef#setColor(int, java.awt.Paint)} + */ + int COLOR_FRAME = 7; + /** + * Index of the arrow color. Used in {@link RrdGraphDef#setColor(int, java.awt.Paint)} + */ + int COLOR_ARROW = 8; + + /** + * Allowed color names which can be used in {@link RrdGraphDef#setColor(String, java.awt.Paint)} method + */ + String[] COLOR_NAMES = { + "canvas", "back", "shadea", "shadeb", "grid", "mgrid", "font", "frame", "arrow" + }; + + /** + * Default first day of the week (obtained from the default locale) + */ + int FIRST_DAY_OF_WEEK = Calendar.getInstance(Locale.getDefault()).getFirstDayOfWeek(); + + /** + * Default graph canvas color + */ + Color DEFAULT_CANVAS_COLOR = Color.WHITE; + /** + * Default graph background color + */ + Color DEFAULT_BACK_COLOR = new Color(245, 245, 245); + /** + * Default top-left graph shade color + */ + Color DEFAULT_SHADEA_COLOR = new Color(200, 200, 200); + /** + * Default bottom-right graph shade color + */ + Color DEFAULT_SHADEB_COLOR = new Color(150, 150, 150); + /** + * Default minor grid color + */ + Color DEFAULT_GRID_COLOR = new Color(171, 171, 171, 95); + // Color DEFAULT_GRID_COLOR = new Color(140, 140, 140); + /** + * Default major grid color + */ + Color DEFAULT_MGRID_COLOR = new Color(255, 91, 91, 95); + // Color DEFAULT_MGRID_COLOR = new Color(130, 30, 30); + /** + * Default font color + */ + Color DEFAULT_FONT_COLOR = Color.BLACK; + /** + * Default frame color + */ + Color DEFAULT_FRAME_COLOR = Color.BLACK; + /** + * Default arrow color + */ + Color DEFAULT_ARROW_COLOR = Color.RED; + + /** + * Constant to represent left alignment marker + */ + String ALIGN_LEFT_MARKER = "\\l"; + /** + * Constant to represent centered alignment marker + */ + String ALIGN_CENTER_MARKER = "\\c"; + /** + * Constant to represent right alignment marker + */ + String ALIGN_RIGHT_MARKER = "\\r"; + /** + * Constant to represent justified alignment marker + */ + String ALIGN_JUSTIFIED_MARKER = "\\j"; + /** + * Constant to represent "glue" marker + */ + String GLUE_MARKER = "\\g"; + /** + * Constant to represent vertical spacing marker + */ + String VERTICAL_SPACING_MARKER = "\\s"; + /** + * Constant to represent no justification markers + */ + String NO_JUSTIFICATION_MARKER = "\\J"; + /** + * Used internally + */ + String[] MARKERS = { + ALIGN_LEFT_MARKER, ALIGN_CENTER_MARKER, ALIGN_RIGHT_MARKER, + ALIGN_JUSTIFIED_MARKER, GLUE_MARKER, VERTICAL_SPACING_MARKER, NO_JUSTIFICATION_MARKER + }; + + /** + * Constant to represent in-memory image name + */ + String IN_MEMORY_IMAGE = "-"; + + /** + * Default units length + */ + int DEFAULT_UNITS_LENGTH = 9; + /** + * Default graph width + */ + int DEFAULT_WIDTH = 400; + /** + * Default graph height + */ + int DEFAULT_HEIGHT = 100; + /** + * Default image format + */ + String DEFAULT_IMAGE_FORMAT = "gif"; + /** + * Default image quality, used only for jpeg graphs + */ + float DEFAULT_IMAGE_QUALITY = 0.8F; // only for jpegs, not used for png/gif + /** + * Default value base + */ + double DEFAULT_BASE = 1000; + + /** + * Default font name, determined based on the current operating system + */ + String DEFAULT_FONT_NAME = System.getProperty("os.name").toLowerCase().contains("windows") ? + "Lucida Sans Typewriter" : "Monospaced"; + + /** + * Default graph small font + */ + String DEFAULT_MONOSPACE_FONT_FILE = "DejaVuSansMono.ttf"; + + /** + * Default graph large font + */ + String DEFAULT_PROPORTIONAL_FONT_FILE = "DejaVuSans-Bold.ttf"; + + /** + * Used internally + */ + double LEGEND_LEADING = 1.2; // chars + /** + * Used internally + */ + double LEGEND_LEADING_SMALL = 0.7; // chars + /** + * Used internally + */ + double LEGEND_BOX_SPACE = 1.2; // chars + /** + * Used internally + */ + double LEGEND_BOX = 0.9; // chars + /** + * Used internally + */ + int LEGEND_INTERSPACING = 2; // chars + /** + * Used internally + */ + int PADDING_LEFT = 10; // pix + /** + * Used internally + */ + int PADDING_TOP = 12; // pix + /** + * Used internally + */ + int PADDING_TITLE = 6; // pix + /** + * Used internally + */ + int PADDING_RIGHT = 16; // pix + /** + * Used internally + */ + int PADDING_PLOT = 2; //chars + /** + * Used internally + */ + int PADDING_LEGEND = 2; // chars + /** + * Used internally + */ + int PADDING_BOTTOM = 6; // pix + /** + * Used internally + */ + int PADDING_VLABEL = 7; // pix + + /** + * Stroke used to draw grid + */ + // solid line + Stroke GRID_STROKE = new BasicStroke(1); + + // dotted line + // Stroke GRID_STROKE = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1, new float[] {1, 1}, 0); + /** + * Stroke used to draw ticks + */ + Stroke TICK_STROKE = new BasicStroke(1); + + /** + * Index of the default font. Used in {@link RrdGraphDef#setFont(int, java.awt.Font)} + */ + int FONTTAG_DEFAULT = 0; + + /** + * Index of the title font. Used in {@link RrdGraphDef#setFont(int, java.awt.Font)} + */ + int FONTTAG_TITLE = 1; + + /** + * Index of the axis label font. Used in {@link RrdGraphDef#setFont(int, java.awt.Font)} + */ + int FONTTAG_AXIS = 2; + + /** + * Index of the vertical unit label font. Used in {@link RrdGraphDef#setFont(int, java.awt.Font)} + */ + int FONTTAG_UNIT = 3; + + /** + * Index of the graph legend font. Used in {@link RrdGraphDef#setFont(int, java.awt.Font)} + */ + int FONTTAG_LEGEND = 4; + + /** + * Index of the edge watermark font. Used in {@link RrdGraphDef#setFont(int, java.awt.Font)} + */ + int FONTTAG_WATERMARK = 5; + + /** + * Allowed font tag names which can be used in {@link RrdGraphDef#setFont(String, java.awt.Font)} method + */ + String[] FONTTAG_NAMES = { + "DEFAULT", "TITLE", "AXIS", "UNIT", "LEGEND", "WATERMARK" + }; +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/RrdGraphDef.java b/apps/jrobin/java/src/org/jrobin/graph/RrdGraphDef.java new file mode 100644 index 000000000..6f4e966a3 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/RrdGraphDef.java @@ -0,0 +1,1223 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; +import org.jrobin.data.Plottable; + +import java.awt.*; +import java.io.File; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * Class which should be used to define new JRobin graph. Once constructed and populated with data + * object of this class should be passed to the constructor of the {@link RrdGraph} class which + * will actually create the graph. + *

+ * The text printed below the actual graph can be formated by appending + * special escaped characters at the end of a text. When ever such a + * character occurs, all pending text is pushed onto the graph according to + * the character specified. + *

+ * Valid markers are: \j for justified, \l for left aligned, \r for right + * aligned and \c for centered. + *

+ * Normally there are two space characters inserted between every two + * items printed into the graph. The space following a string can be + * suppressed by putting a \g at the end of the string. The \g also squashes + * any space inside the string if it is at the very end of the string. + * This can be used in connection with %s to suppress empty unit strings. + *

+ * A special case is COMMENT:\s this inserts some additional vertical + * space before placing the next row of legends. + *

+ * When text has to be formated without special instructions from your + * side, RRDTool will automatically justify the text as soon as one string + * goes over the right edge. If you want to prevent the justification + * without forcing a newline, you can use the special tag \J at the end of + * the string to disable the auto justification. + */ +public class RrdGraphDef implements RrdGraphConstants { + boolean poolUsed = false; // ok + boolean antiAliasing = false; // ok + String filename = RrdGraphConstants.IN_MEMORY_IMAGE; // ok + long startTime, endTime; // ok + TimeAxisSetting timeAxisSetting = null; // ok + ValueAxisSetting valueAxisSetting = null; // ok + boolean altYGrid = false; // ok + boolean noMinorGrid = false; // ok + boolean altYMrtg = false; // ok + boolean altAutoscale = false; // ok + boolean altAutoscaleMax = false; // ok + int unitsExponent = Integer.MAX_VALUE; // ok + int unitsLength = DEFAULT_UNITS_LENGTH; // ok + String verticalLabel = null; // ok + int width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT; // ok + boolean interlaced = false; // ok + String imageInfo = null; // ok + String imageFormat = DEFAULT_IMAGE_FORMAT; // ok + float imageQuality = DEFAULT_IMAGE_QUALITY; // ok + String backgroundImage = null; // ok + String overlayImage = null; // ok + String unit = null; // ok + String signature = "Created with JRobin"; // ok + boolean lazy = false; // ok + double minValue = Double.NaN; // ok + double maxValue = Double.NaN; // ok + boolean rigid = false; // ok + double base = DEFAULT_BASE; // ok + boolean logarithmic = false; // ok + Paint[] colors = new Paint[]{ + // ok + DEFAULT_CANVAS_COLOR, + DEFAULT_BACK_COLOR, + DEFAULT_SHADEA_COLOR, + DEFAULT_SHADEB_COLOR, + DEFAULT_GRID_COLOR, + DEFAULT_MGRID_COLOR, + DEFAULT_FONT_COLOR, + DEFAULT_FRAME_COLOR, + DEFAULT_ARROW_COLOR + }; + boolean noLegend = false; // ok + boolean onlyGraph = false; // ok + boolean forceRulesLegend = false; // ok + String title = null; // ok + long step = 0; // ok + Font[] fonts = new Font[FONTTAG_NAMES.length]; + boolean drawXGrid = true; // ok + boolean drawYGrid = true; // ok + int firstDayOfWeek = FIRST_DAY_OF_WEEK; // ok + boolean showSignature = true; + File fontDir = null; + + List sources = new ArrayList(); + List comments = new ArrayList(); + List plotElements = new ArrayList(); + + /** + * Creates RrdGraphDef object and sets default time span (default ending time is 'now', + * default starting time is 'end-1day'. + */ + public RrdGraphDef() { + try { + setTimeSpan(Util.getTimestamps(DEFAULT_START, DEFAULT_END)); + } catch (RrdException e) { + throw new RuntimeException(e); + } + + String fontdirProperty = System.getProperty("jrobin.fontdir"); + if (fontdirProperty != null && fontdirProperty.length() != 0) { + fontDir = new File(fontdirProperty); + } + + fonts[FONTTAG_DEFAULT] = new Font(DEFAULT_FONT_NAME, Font.PLAIN, 8); + fonts[FONTTAG_TITLE] = new Font(DEFAULT_FONT_NAME, Font.PLAIN, 9); + fonts[FONTTAG_AXIS] = new Font(DEFAULT_FONT_NAME, Font.PLAIN, 7); + fonts[FONTTAG_UNIT] = new Font(DEFAULT_FONT_NAME, Font.PLAIN, 8); + fonts[FONTTAG_LEGEND] = new Font(DEFAULT_FONT_NAME, Font.PLAIN, 8); + fonts[FONTTAG_WATERMARK] = new Font(DEFAULT_FONT_NAME, Font.PLAIN, 1).deriveFont(5.5F); + } + + protected Font getFontFromResourceName(String name) { + Font font = null; + Exception exception = null; + URL file = null; + + if (fontDir != null) { + try { + file = new URL("file://" + new File(fontDir, name).getAbsolutePath()); + } catch (MalformedURLException e) { + // fall through to the jar + exception = e; + } + } + if (file == null) { + file = this.getClass().getResource(name); + } + + if (file != null) { + // System.err.println("Found a font URL: " + file.toExternalForm()); + try { + InputStream fontStream = file.openStream(); + font = Font.createFont(Font.TRUETYPE_FONT, fontStream); + fontStream.close(); + } catch (Exception e) { + exception = e; + } + } + else { + // we can't find our fonts, fall back to the system font + System.err.println("An error occurred loading the font '" + name + "'. Falling back to the default."); + if (exception != null) { + System.err.println(exception.getLocalizedMessage()); + } + font = new Font(DEFAULT_FONT_NAME, Font.PLAIN, 10); + } + + if (font == null) { + font = new Font(null, Font.PLAIN, 10); + } + return font; + } + + /** + * Sets the signature string that runs along the right-side of the graph. + * Defaults to "Created with JRobin". + * + * @param signature the string to print + */ + public void setSignature(String signature) { + this.signature = signature; + } + + /** + * Gets the signature string that runs along the right-side of the graph. + * + * @return the signature string + */ + public String getSignature() { + return this.signature; + } + + /** + * Sets the time when the graph should begin. Time in seconds since epoch + * (1970-01-01) is required. Negative numbers are relative to the current time. + * + * @param time Starting time for the graph in seconds since epoch + */ + public void setStartTime(long time) { + this.startTime = time; + if (time <= 0) { + this.startTime += Util.getTime(); + } + } + + /** + * Sets the time when the graph should end. Time in seconds since epoch + * (1970-01-01) is required. Negative numbers are relative to the current time. + * + * @param time Ending time for the graph in seconds since epoch + */ + public void setEndTime(long time) { + this.endTime = time; + if (time <= 0) { + this.endTime += Util.getTime(); + } + } + + /** + * Sets starting and ending time for the for the graph. Timestamps in seconds since epoch are + * required. Negative numbers are relative to the current time. + * + * @param startTime Starting time in seconds since epoch + * @param endTime Ending time in seconds since epoch + */ + public void setTimeSpan(long startTime, long endTime) { + setStartTime(startTime); + setEndTime(endTime); + } + + /** + * Sets starting and ending time for the for the graph. Timestamps in seconds since epoch are + * required. + * + * @param timestamps Array of timestamps. The first array item will be chosen for the starting + * timestamp. The last array item will be chosen for the ending timestamp. + */ + public void setTimeSpan(long[] timestamps) { + setTimeSpan(timestamps[0], timestamps[timestamps.length - 1]); + } + + /** + * Sets RrdDbPool usage policy (defaults to true). If set to true, + * {@link org.jrobin.core.RrdDbPool RrdDbPool} will be used to + * access individual RRD files. If set to false, RRD files will be accessed directly. + * + * @param poolUsed true, if RrdDbPool class should be used. False otherwise. + */ + public void setPoolUsed(boolean poolUsed) { + this.poolUsed = poolUsed; + } + + /** + * Sets the name of the graph to generate. Since JRobin outputs GIFs, PNGs, + * and JPEGs it's recommended that the filename end in either .gif, + * .png or .jpg. JRobin does not enforce this, however. If the filename is + * set to '-' the image will be created only in memory (no file will be created). + * PNG and GIF formats are recommended but JPEGs should be avoided. + * + * @param filename Path to the image file + */ + public void setFilename(String filename) { + this.filename = filename; + } + + /** + * Configures x-axis grid and labels. The x-axis label is quite complex to configure. + * So if you don't have very special needs, you can rely on the autoconfiguration to + * get this right. + *

+ * Otherwise, you have to configure three elements making up the x-axis labels + * and grid. The base grid, the major grid and the labels. + * The configuration is based on the idea that you first specify a well + * known amount of time and then say how many times + * it has to pass between each minor/major grid line or label. For the label + * you have to define two additional items: The precision of the label + * in seconds and the format used to generate the text + * of the label. + *

+ * For example, if you wanted a graph with a base grid every 10 minutes and a major + * one every hour, with labels every hour you would use the following + * x-axis definition. + *

+ *

+     * setTimeAxis(RrdGraphConstants.MINUTE, 10,
+     *             RrdGraphConstants.HOUR, 1,
+     *             RrdGraphConstants.HOUR, 1,
+     *             0, "%H:%M")
+     * 
+ *

+ * The precision in this example is 0 because the %X format is exact. + * If the label was the name of the day, we would have had a precision + * of 24 hours, because when you say something like 'Monday' you mean + * the whole day and not Monday morning 00:00. Thus the label should + * be positioned at noon. By defining a precision of 24 hours or + * rather 86400 seconds, you make sure that this happens. + * + * @param minorUnit Minor grid unit. Minor grid, major grid and label units + * can be one of the following constants defined in + * {@link RrdGraphConstants}: {@link RrdGraphConstants#SECOND SECOND}, + * {@link RrdGraphConstants#MINUTE MINUTE}, {@link RrdGraphConstants#HOUR HOUR}, + * {@link RrdGraphConstants#DAY DAY}, {@link RrdGraphConstants#WEEK WEEK}, + * {@link RrdGraphConstants#MONTH MONTH}, {@link RrdGraphConstants#YEAR YEAR}. + * @param minorUnitCount Number of minor grid units between minor grid lines. + * @param majorUnit Major grid unit. + * @param majorUnitCount Number of major grid units between major grid lines. + * @param labelUnit Label unit. + * @param labelUnitCount Number of label units between labels. + * @param labelSpan Label precision + * @param simpleDateFormat Date format (SimpleDateFormat pattern of strftime-like pattern) + */ + public void setTimeAxis(int minorUnit, int minorUnitCount, int majorUnit, int majorUnitCount, + int labelUnit, int labelUnitCount, int labelSpan, String simpleDateFormat) { + timeAxisSetting = new TimeAxisSetting(minorUnit, minorUnitCount, majorUnit, majorUnitCount, + labelUnit, labelUnitCount, labelSpan, simpleDateFormat); + } + + /** + * Sets vertical axis grid and labels. Makes vertical grid lines appear + * at gridStep interval. Every labelFactor*gridStep, a major grid line is printed, + * along with label showing the value of the grid line. + * + * @param gridStep Minor grid step + * @param labelFactor Specifies how many minor minor grid steps will appear between labels + * (major grid lines) + */ + public void setValueAxis(double gridStep, int labelFactor) { + valueAxisSetting = new ValueAxisSetting(gridStep, labelFactor); + } + + /** + * Places Y grid dynamically based on graph Y range. Algorithm ensures + * that you always have grid, that there are enough but not too many + * grid lines and the grid is metric. That is grid lines are placed + * every 1, 2, 5 or 10 units. + * + * @param altYGrid true, if Y grid should be calculated dynamically (defaults to false) + */ + public void setAltYGrid(boolean altYGrid) { + this.altYGrid = altYGrid; + } + + /** + * Use this method to turn off minor grid lines (printed by default) + * + * @param noMinorGrid true, to turn off, false to turn on (default) + */ + public void setNoMinorGrid(boolean noMinorGrid) { + this.noMinorGrid = noMinorGrid; + } + + /** + * Use this method to request MRTG-like graph (false by default) + * + * @param altYMrtg true, to create MRTG-like graph, false otherwise (default) + */ + public void setAltYMrtg(boolean altYMrtg) { + this.altYMrtg = altYMrtg; + } + + /** + * Computes Y range based on function absolute minimum and maximum + * values. Default algorithm uses predefined set of ranges. This is + * good in many cases but it fails miserably when you need to graph + * something like 260 + 0.001 * sin(x). Default algorithm will use Y + * range from 250 to 300 and on the graph you will see almost straight + * line. With --alt-autoscale Y range will be from slightly less the + * 260 - 0.001 to slightly more then 260 + 0.001 and periodic behavior + * will be seen. + * + * @param altAutoscale true to request alternative autoscaling, false otherwise + * (default). + */ + public void setAltAutoscale(boolean altAutoscale) { + this.altAutoscale = altAutoscale; + } + + /** + * Computes Y range based on function absolute minimum and maximum + * values. Where setAltAutoscale(true) will modify both the absolute maximum AND + * minimum values, this option will only affect the maximum value. The + * minimum value, if not defined elsewhere, will be 0. This + * option can be useful when graphing router traffic when the WAN line + * uses compression, and thus the throughput may be higher than the + * WAN line speed. + * + * @param altAutoscaleMax true to request alternative autoscaling, false + * otherwise (default) + */ + public void setAltAutoscaleMax(boolean altAutoscaleMax) { + this.altAutoscaleMax = altAutoscaleMax; + } + + /** + * Sets the 10**unitsExponent scaling of the y-axis values. Normally + * values will be scaled to the appropriate units (k, M, etc.). However + * you may wish to display units always in k (Kilo, 10e3) even if + * the data is in the M (Mega, 10e6) range for instance. Value should + * be an integer which is a multiple of 3 between -18 and 18, inclu- + * sive. It is the exponent on the units you which to use. For example, + * use 3 to display the y-axis values in k (Kilo, 10e3, thou- + * sands), use -6 to display the y-axis values in u (Micro, 10e-6, + * millionths). Use a value of 0 to prevent any scaling of the y-axis + * values. + * + * @param unitsExponent the 10**unitsExponent value for scaling y-axis values. + */ + public void setUnitsExponent(int unitsExponent) { + this.unitsExponent = unitsExponent; + } + + /** + * Sets the character width on the left side of the graph for + * y-axis values. + * + * @param unitsLength Number of characters on the left side of the graphs + * reserved for vertical axis labels. + */ + public void setUnitsLength(int unitsLength) { + this.unitsLength = unitsLength; + } + + /** + * Sets vertical label on the left side of the graph. This is normally used + * to specify the units used. + * + * @param verticalLabel Vertical axis label + */ + public void setVerticalLabel(String verticalLabel) { + this.verticalLabel = verticalLabel; + } + + /** + * Sets width of the drawing area within the graph. This affects the total + * size of the image. + * + * @param width Width of the drawing area. + */ + public void setWidth(int width) { + this.width = width; + } + + /** + * Sets height of the drawing area within the graph. This affects the total + * size of the image. + * + * @param height Height of the drawing area. + */ + public void setHeight(int height) { + this.height = height; + } + + /** + * Creates interlaced GIF image (currently not supported, + * method is present only for RRDTool comaptibility). + * + * @param interlaced true, if GIF image should be interlaced. + */ + public void setInterlaced(boolean interlaced) { + this.interlaced = interlaced; + } + + /** + * Creates additional image information. + * After the image has been created, the graph function uses imageInfo + * format string (printf-like) to create output similar to + * the {@link #print(String, String, String)} function. + * The format string is supplied with the following parameters: + * filename, xsize and ysize (in that particular order). + *

+ * For example, in order to generate an IMG tag + * suitable for including the graph into a web page, the command + * would look like this: + *

+     * setImageInfo("<IMG SRC='/img/%s' WIDTH='%d' HEIGHT='%d' ALT='Demo'>");
+     * 
+ * + * @param imageInfo Image info format. Use %s placeholder for filename, %d placeholder for + * image width and height. + */ + public void setImageInfo(String imageInfo) { + this.imageInfo = imageInfo; + } + + /** + * Sets image format. + * + * @param imageFormat "PNG", "GIF" or "JPG". + */ + public void setImageFormat(String imageFormat) { + this.imageFormat = imageFormat; + } + + /** + * Sets background image - currently, only PNG images can be used as background. + * + * @param backgroundImage Path to background image + */ + public void setBackgroundImage(String backgroundImage) { + this.backgroundImage = backgroundImage; + } + + /** + * Sets overlay image - currently, only PNG images can be used as overlay. Overlay image is + * printed on the top of the image, once it is completely created. + * + * @param overlayImage Path to overlay image + */ + public void setOverlayImage(String overlayImage) { + this.overlayImage = overlayImage; + } + + /** + * Sets unit to be displayed on y axis. It is wise to use only short units on graph, however. + * + * @param unit Unit description + */ + public void setUnit(String unit) { + this.unit = unit; + } + + /** + * Creates graph only if the current graph is out of date or not existent. + * + * @param lazy true, if graph should be 'lazy', false otherwise (defualt) + */ + public void setLazy(boolean lazy) { + this.lazy = lazy; + } + + /** + * Sets the lower limit of a graph. But rather, this is the + * maximum lower bound of a graph. For example, the value -100 will + * result in a graph that has a lower limit of -100 or less. Use this + * method to expand graphs down. + * + * @param minValue Minimal value displayed on the graph + */ + public void setMinValue(double minValue) { + this.minValue = minValue; + } + + /** + * Defines the value normally located at the upper border of the + * graph. If the graph contains higher values, the upper border will + * move upwards to accommodate these values as well. + *

+ * If you want to define an upper-limit which will not move in any + * event you have to use {@link #setRigid(boolean)} method as well. + * + * @param maxValue Maximal value displayed on the graph. + */ + public void setMaxValue(double maxValue) { + this.maxValue = maxValue; + } + + /** + * Sets rigid boundaries mode. Normally JRObin will automatically expand + * the lower and upper limit if the graph contains a value outside the + * valid range. With the true argument you can disable this behavior. + * + * @param rigid true if uper and lower limits should not be expanded to accomodate + * values outside of the specified range. False otherwise (default). + */ + public void setRigid(boolean rigid) { + this.rigid = rigid; + } + + /** + * Sets default base for magnitude scaling. If you are graphing memory + * (and NOT network traffic) this switch should be set to 1024 so that 1Kb is 1024 byte. + * For traffic measurement, 1 kb/s is 1000 b/s. + * + * @param base Base value (defaults to 1000.0) + */ + public void setBase(double base) { + this.base = base; + } + + /** + * Sets logarithmic y-axis scaling. + * + * @param logarithmic true, for logarithmic scaling, false otherwise (default). + */ + public void setLogarithmic(boolean logarithmic) { + this.logarithmic = logarithmic; + } + + /** + * Overrides the colors for the standard elements of the graph. The colorTag + * must be one of the following constants defined in the + * {@link RrdGraphConstants}: + * {@link RrdGraphConstants#COLOR_BACK COLOR_BACK} background, + * {@link RrdGraphConstants#COLOR_CANVAS COLOR_CANVAS} canvas, + * {@link RrdGraphConstants#COLOR_SHADEA COLOR_SHADEA} left/top border, + * {@link RrdGraphConstants#COLOR_SHADEB COLOR_SHADEB} right/bottom border, + * {@link RrdGraphConstants#COLOR_GRID COLOR_GRID} major grid, + * {@link RrdGraphConstants#COLOR_MGRID COLOR_MGRID} minor grid, + * {@link RrdGraphConstants#COLOR_FONT COLOR_FONT} font, + * {@link RrdGraphConstants#COLOR_FRAME COLOR_FRAME} axis of the graph, + * {@link RrdGraphConstants#COLOR_ARROW COLOR_ARROW} arrow. This method can + * be called multiple times to set several colors. + * + * @param colorTag Color tag, as explained above. + * @param color Any color (paint) you like + * @throws RrdException Thrown if invalid colorTag is supplied. + */ + public void setColor(int colorTag, Paint color) throws RrdException { + if (colorTag >= 0 && colorTag < colors.length) { + colors[colorTag] = color; + } + else { + throw new RrdException("Invalid color index specified: " + colorTag); + } + } + + /** + * Overrides the colors for the standard elements of the graph by element name. + * See {@link #setColor(int, java.awt.Paint)} for full explanation. + * + * @param colorName One of the following strings: "BACK", "CANVAS", "SHADEA", "SHADEB", + * "GRID", "MGRID", "FONT", "FRAME", "ARROW" + * @param color Any color (paint) you like + * @throws RrdException Thrown if invalid element name is supplied. + */ + public void setColor(String colorName, Paint color) throws RrdException { + setColor(getColorTagByName(colorName), color); + } + + private static int getColorTagByName(String colorName) throws RrdException { + for (int i = 0; i < COLOR_NAMES.length; i++) { + if (COLOR_NAMES[i].equalsIgnoreCase(colorName)) { + return i; + } + } + throw new RrdException("Unknown color name specified: " + colorName); + } + + /** + * Suppress generation of legend, only render the graph. + * + * @param noLegend true if graph legend should be omitted. False otherwise (default). + */ + public void setNoLegend(boolean noLegend) { + this.noLegend = noLegend; + } + + /** + * Suppresses anything but the graph, works only for height < 64. + * + * @param onlyGraph true if only graph should be created, false otherwise (default). + */ + public void setOnlyGraph(boolean onlyGraph) { + this.onlyGraph = onlyGraph; + } + + /** + * Force the generation of HRULE and VRULE legend even if those HRULE + * or VRULE will not be drawn because out of graph boundaries. + * + * @param forceRulesLegend true if rule legend should be always printed, + * false otherwise (default). + */ + public void setForceRulesLegend(boolean forceRulesLegend) { + this.forceRulesLegend = forceRulesLegend; + } + + /** + * Defines a title to be written into the graph. + * + * @param title Graph title. + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Suggests which time step should be used by JRobin while processing data from RRD files. + * + * @param step Desired time step (don't use this method if you don't know what you're doing). + */ + public void setStep(long step) { + this.step = step; + } + + /** + * Get the default small font for graphing. + * + * @return the font + */ + public Font getSmallFont() { + return this.fonts[FONTTAG_DEFAULT]; + } + + /** + * Get the default large font for graphing. + * + * @return the font + */ + public Font getLargeFont() { + return this.fonts[FONTTAG_TITLE]; + } + + /** + * Sets default font for graphing. Note that JRobin will behave unpredictably if proportional + * font is selected. + * + * @param smallFont Default font for graphing. Use only monospaced fonts. + * @throws RrdException Thrown if invalid fontTag is supplied. + */ + public void setSmallFont(final Font smallFont) throws RrdException{ + this.setFont(FONTTAG_DEFAULT, smallFont); + } + + /** + * Sets title font. + * + * @param largeFont Font to be used for graph title. + * @throws RrdException Thrown if invalid fontTag is supplied. + */ + public void setLargeFont(final Font largeFont) throws RrdException { + this.setFont(FONTTAG_TITLE, largeFont); + } + + /** + * Sets font to be used for a specific font tag. The fontTag + * must be one of the following constants defined in the + * {@link RrdGraphConstants}: + * {@link RrdGraphConstants#FONTTAG_DEFAULT FONTTAG_DEFAULT} default font,, + * {@link RrdGraphConstants#FONTTAG_TITLE FONTTAG_TITLE} title, + * {@link RrdGraphConstants#FONTTAG_AXIS FONTTAG_AXIS} grid axis,, + * {@link RrdGraphConstants#FONTTAG_UNIT FONTTAG_UNIT} vertical unit label,, + * {@link RrdGraphConstants#FONTTAG_LEGEND FONTTAG_LEGEND} legend, + * {@link RrdGraphConstants#FONTTAG_WATERMARK FONTTAG_WATERMARK} watermark. + * This method can be called multiple times to set several fonts. + * + * @param fontTag Font tag, as explained above. + * @param font Font to be used for tag + * @throws RrdException Thrown if invalid fontTag is supplied. + */ + public void setFont(final int fontTag, final Font font) throws RrdException { + this.setFont(fontTag, font, false); + } + + /** + * Sets font. + * + * @param fontTag Font tag, as explained above. + * @param font Font to be used for tag + * @param setAll Boolean to flag whether to set all fonts if fontTag == FONTTAG_DEFAULT + * @throws RrdException Thrown if invalid fontTag is supplied. + */ + public void setFont(final int fontTag, final Font font, final boolean setAll) throws RrdException { + this.setFont(fontTag, font, setAll, false); + } + + /** + * Sets font. + * + * @param fontTag Font tag, as explained above. + * @param font Font to be used for tag + * @param setAll Boolean to flag whether to set all fonts if fontTag == FONTTAG_DEFAULT + * @param keepSizes Boolean to flag whether to keep original font sizes if setting all fonts. + */ + public void setFont(final int fontTag, final Font font, final boolean setAll, final boolean keepSizes) { + if (fontTag == FONTTAG_DEFAULT && setAll) { + if (keepSizes) { + this.fonts[FONTTAG_DEFAULT] = font.deriveFont(this.fonts[FONTTAG_DEFAULT].getSize()); + this.fonts[FONTTAG_TITLE] = font.deriveFont(this.fonts[FONTTAG_TITLE].getSize()); + this.fonts[FONTTAG_AXIS] = font.deriveFont(this.fonts[FONTTAG_AXIS].getSize()); + this.fonts[FONTTAG_UNIT] = font.deriveFont(this.fonts[FONTTAG_UNIT].getSize()); + this.fonts[FONTTAG_LEGEND] = font.deriveFont(this.fonts[FONTTAG_LEGEND].getSize()); + this.fonts[FONTTAG_WATERMARK] = font.deriveFont(this.fonts[FONTTAG_WATERMARK].getSize()); + } else { + this.fonts[FONTTAG_DEFAULT] = font; + this.fonts[FONTTAG_TITLE] = null; + this.fonts[FONTTAG_AXIS] = null; + this.fonts[FONTTAG_UNIT] = null; + this.fonts[FONTTAG_LEGEND] = null; + this.fonts[FONTTAG_WATERMARK] = null; + } + } else { + this.fonts[fontTag] = font; + } + } + + /** + * Sets font. + * + * @param fontTag Font tag as String, as explained in {@link RrdGraphDef#setFont setFont(int, java.awt.Font)}. + * @param font Font to be used for tag + * @throws RrdException Thrown if invalid fontTag is supplied. + */ + public void setFont(final String fontTag, final Font font) throws RrdException { + this.setFont(getFontTagByName(fontTag), font); + } + + /** + * Sets font. + * + * @param fontTag Font tag as String, as explained in {@link RrdGraphDef#setFont setFont(int, java.awt.Font)}. + * @param font Font to be used for tag + * @param setAll Boolean to flag whether to set all fonts if fontTag == FONTTAG_DEFAULT + * @throws RrdException Thrown if invalid fontTag is supplied. + */ + public void setFont(final String fontTag, final Font font, final boolean setAll) throws RrdException { + this.setFont(getFontTagByName(fontTag), font, setAll); + } + + /** + * Sets font. + * + * @param fontTag Font tag as String, as explained in {@link RrdGraphDef#setFont setFont(int, java.awt.Font)}. + * @param font Font to be used for tag + * @param setAll Boolean to flag whether to set all fonts if fontTag == FONTTAG_DEFAULT + * @param keepSizes Boolean to flag whether to keep original font sizes if setting all fonts. + * @throws RrdException Thrown if invalid fontTag is supplied. + */ + public void setFont(final String fontTag, final Font font, final boolean setAll, final boolean keepSizes) throws RrdException { + this.setFont(getFontTagByName(fontTag), font, setAll, keepSizes); + } + + private static int getFontTagByName(String tagName) throws RrdException { + for (int i = 0; i < FONTTAG_NAMES.length; i++) { + if (FONTTAG_NAMES[i].equalsIgnoreCase(tagName)) { + return i; + } + } + throw new RrdException("Unknown tag name specified: " + tagName); + } + + public Font getFont(int tag) { + return this.fonts[tag] == null ? this.fonts[FONTTAG_DEFAULT] : this.fonts[tag]; + } + + /** + * Defines virtual datasource. This datasource can then be used + * in other methods like {@link #datasource(String, String)} or + * {@link #gprint(String, String, String)}. + * + * @param name Source name + * @param rrdPath Path to RRD file + * @param dsName Datasource name in the specified RRD file + * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) + */ + public void datasource(String name, String rrdPath, String dsName, String consolFun) { + sources.add(new Def(name, rrdPath, dsName, consolFun)); + } + + /** + * Defines virtual datasource. This datasource can then be used + * in other methods like {@link #datasource(String, String)} or + * {@link #gprint(String, String, String)}. + * + * @param name Source name + * @param rrdPath Path to RRD file + * @param dsName Datasource name in the specified RRD file + * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) + * @param backend Backend to be used while fetching data from a RRD file. + */ + public void datasource(String name, String rrdPath, String dsName, String consolFun, String backend) { + sources.add(new Def(name, rrdPath, dsName, consolFun, backend)); + } + + /** + * Create a new virtual datasource by evaluating a mathematical + * expression, specified in Reverse Polish Notation (RPN). + * + * @param name Source name + * @param rpnExpression RPN expression. + */ + public void datasource(String name, String rpnExpression) { + sources.add(new CDef(name, rpnExpression)); + } + + /** + * Creates a new (static) virtual datasource. The value of the datasource is constant. This value is + * evaluated by applying the given consolidation function to another virtual datasource. + * + * @param name Source name + * @param defName Other source name + * @param consolFun Consolidation function to be applied to other datasource. + */ + public void datasource(String name, String defName, String consolFun) { + sources.add(new SDef(name, defName, consolFun)); + } + + /** + * Creates a new (plottable) datasource. Datasource values are obtained from the given plottable + * object. + * + * @param name Source name. + * @param plottable Plottable object. + */ + public void datasource(String name, Plottable plottable) { + sources.add(new PDef(name, plottable)); + } + + /** + * Creates a new static virtual datasource that performs a percentile calculation on an + * another named datasource to yield a single value. + *

+ * Requires that the other datasource has already been defined otherwise it throws an exception + * (we need to look at the existing data source to extract the required data) + * + * @param name - the new virtual datasource name + * @param sourceName - the datasource from which to extract the percentile. Must be a previously + * defined virtula datasource + * @param percentile - the percentile to extract from the source datasource + */ + public void datasource(String name, String sourceName, double percentile) { + sources.add(new PercentileDef(name, sourceName, percentile)); + } + + /** + * Creates a new static virtual datasource that performs a percentile calculation on an + * another named datasource to yield a single value. + *

+ * Requires that the other datasource has already been defined otherwise it throws an exception + * (we need to look at the existing data source to extract the required data) + * + * @param name - the new virtual datasource name + * @param sourceName - the datasource from which to extract the percentile. Must be a previously + * defined virtula datasource + * @param percentile - the percentile to extract from the source datasource + * @param includenan - whether to include NaNs in the percentile calculations. + */ + public void datasource(String name, String sourceName, double percentile, boolean includenan) { + sources.add(new PercentileDef(name, sourceName, percentile, includenan)); + } + + /** + * Calculates the chosen consolidation function CF over the given datasource + * and creates the result by using the given format string. In + * the format string there should be a '%[l]f', '%[l]g' or '%[l]e' marker in + * the place where the number should be printed. + *

+ * If an additional '%s' is found AFTER the marker, the value will be + * scaled and an appropriate SI magnitude unit will be printed in + * place of the '%s' marker. The scaling will take the '--base' argument + * into consideration! + *

+ * If a '%S' is used instead of a '%s', then instead of calculating + * the appropriate SI magnitude unit for this value, the previously + * calculated SI magnitude unit will be used. This is useful if you + * want all the values in a print statement to have the same SI magnitude + * unit. If there was no previous SI magnitude calculation made, + * then '%S' behaves like a '%s', unless the value is 0, in which case + * it does not remember a SI magnitude unit and a SI magnitude unit + * will only be calculated when the next '%s' is seen or the next '%S' + * for a non-zero value. + *

+ * Print results are collected in the {@link RrdGraphInfo} object which is retrieved + * from the {@link RrdGraph object} once the graph is created. + * + * @param srcName Virtual source name + * @param consolFun Consolidation function to be applied to the source + * @param format Format string (like "average = %10.3f %s") + */ + public void print(String srcName, String consolFun, String format) { + comments.add(new PrintText(srcName, consolFun, format, false)); + } + + /** + * This method does basically the same thing as {@link #print(String, String, String)}, + * but the result is printed on the graph itself, below the chart area. + * + * @param srcName Virtual source name + * @param consolFun Consolidation function to be applied to the source + * @param format Format string (like "average = %10.3f %s") + */ + public void gprint(String srcName, String consolFun, String format) { + comments.add(new PrintText(srcName, consolFun, format, true)); + } + + /** + * Comment to be printed on the graph. + * + * @param text Comment text + */ + public void comment(String text) { + comments.add(new CommentText(text)); + } + + /** + * Draws a horizontal rule into the graph and optionally adds a legend + * + * @param value Position of the rule + * @param color Rule color + * @param legend Legend text. If null, legend text will be omitted. + */ + public void hrule(double value, Paint color, String legend) { + hrule(value, color, legend, 1.0F); + } + + /** + * Draws a horizontal rule into the graph and optionally adds a legend + * + * @param value Position of the rule + * @param color Rule color + * @param legend Legend text. If null, legend text will be omitted. + * @param width Rule width + */ + public void hrule(double value, Paint color, String legend, float width) { + LegendText legendText = new LegendText(color, legend); + comments.add(legendText); + plotElements.add(new HRule(value, color, legendText, width)); + } + + /** + * Draws a vertical rule into the graph and optionally adds a legend + * + * @param timestamp Position of the rule (seconds since epoch) + * @param color Rule color + * @param legend Legend text. Use null to omit the text. + */ + public void vrule(long timestamp, Paint color, String legend) { + vrule(timestamp, color, legend, 1.0F); + } + + /** + * Draws a vertical rule into the graph and optionally adds a legend + * + * @param timestamp Position of the rule (seconds since epoch) + * @param color Rule color + * @param legend Legend text. Use null to omit the text. + * @param width Rule width + */ + public void vrule(long timestamp, Paint color, String legend, float width) { + LegendText legendText = new LegendText(color, legend); + comments.add(legendText); + plotElements.add(new VRule(timestamp, color, legendText, width)); + } + + /** + * Plots requested data as a line, using the color and the line width specified. + * + * @param srcName Virtual source name + * @param color Line color + * @param legend Legend text + * @param width Line width (default: 1.0F) + */ + public void line(String srcName, Paint color, String legend, float width) { + if (legend != null) { + comments.add(new LegendText(color, legend)); + } + plotElements.add(new Line(srcName, color, width)); + } + + /** + * Plots requested data as a line, using the color specified. Line width is assumed to be + * 1.0F. + * + * @param srcName Virtual source name + * @param color Line color + * @param legend Legend text + */ + public void line(String srcName, Paint color, String legend) { + line(srcName, color, legend, 1F); + } + + /** + * Plots requested data in the form of the filled area starting from zero, + * using the color specified. + * + * @param srcName Virtual source name. + * @param color Color of the filled area. + * @param legend Legend text. + */ + public void area(String srcName, Paint color, String legend) { + area(srcName, color); + if ((legend != null) && (legend.length() > 0)) { + LegendText legendText = new LegendText(color, legend); + comments.add(legendText); + } + } + + /** + * Plots requested data in the form of the filled area starting from zero, + * using the color specified. + * + * @param srcName Virtual source name. + * @param color Color of the filled area. + */ + public void area(String srcName, Paint color) { + plotElements.add(new Area(srcName, color)); + } + + /** + * Does the same as {@link #line(String, java.awt.Paint, String)}, + * but the graph gets stacked on top of the + * previous LINE, AREA or STACK graph. Depending on the type of the + * previous graph, the STACK will be either a LINE or an AREA. This + * obviously implies that the first STACK must be preceded by an AREA + * or LINE. + *

+ * Note, that when you STACK onto *UNKNOWN* data, JRobin will not + * draw any graphics ... *UNKNOWN* is not zero. + * + * @param srcName Virtual source name + * @param color Stacked graph color + * @param legend Legend text + * @throws RrdException Thrown if this STACK has no previously defined AREA, STACK or LINE + * graph bellow it. + */ + public void stack(String srcName, Paint color, String legend) throws RrdException { + // find parent AREA or LINE + SourcedPlotElement parent = null; + for (int i = plotElements.size() - 1; i >= 0; i--) { + PlotElement plotElement = plotElements.get(i); + if (plotElement instanceof SourcedPlotElement) { + parent = (SourcedPlotElement) plotElement; + break; + } + } + if (parent == null) { + throw new RrdException("You have to stack graph onto something (line or area)"); + } + else { + LegendText legendText = new LegendText(color, legend); + comments.add(legendText); + plotElements.add(new Stack(parent, srcName, color)); + } + } + + /** + * Sets visibility of the X-axis grid. + * + * @param drawXGrid True if X-axis grid should be created (default), false otherwise. + */ + public void setDrawXGrid(boolean drawXGrid) { + this.drawXGrid = drawXGrid; + } + + /** + * Sets visibility of the Y-axis grid. + * + * @param drawYGrid True if Y-axis grid should be created (default), false otherwise. + */ + public void setDrawYGrid(boolean drawYGrid) { + this.drawYGrid = drawYGrid; + } + + /** + * Sets image quality. Relevant only for JPEG images. + * + * @param imageQuality (0F=worst, 1F=best). + */ + public void setImageQuality(float imageQuality) { + this.imageQuality = imageQuality; + } + + /** + * Controls if the chart area of the image should be antialiased or not. + * + * @param antiAliasing use true to turn antialiasing on, false to turn it off (default) + */ + public void setAntiAliasing(boolean antiAliasing) { + this.antiAliasing = antiAliasing; + } + + /** + * Shows or hides graph signature (gator) in the top right corner of the graph + * + * @param showSignature true, if signature should be seen (default), false otherwise + */ + public void setShowSignature(boolean showSignature) { + this.showSignature = showSignature; + } + + /** + * Sets first day of the week. + * + * @param firstDayOfWeek One of the following constants: + * {@link RrdGraphConstants#MONDAY MONDAY}, + * {@link RrdGraphConstants#TUESDAY TUESDAY}, + * {@link RrdGraphConstants#WEDNESDAY WEDNESDAY}, + * {@link RrdGraphConstants#THURSDAY THURSDAY}, + * {@link RrdGraphConstants#FRIDAY FRIDAY}, + * {@link RrdGraphConstants#SATURDAY SATURDAY}, + * {@link RrdGraphConstants#SUNDAY SUNDAY} + */ + public void setFirstDayOfWeek(int firstDayOfWeek) { + this.firstDayOfWeek = firstDayOfWeek; + } + + // helper methods + + int printStatementCount() { + int count = 0; + for (CommentText comment : comments) { + if (comment instanceof PrintText) { + if (comment.isPrint()) { + count++; + } + } + } + return count; + } + + boolean shouldPlot() { + if (plotElements.size() > 0) { + return true; + } + for (CommentText comment : comments) { + if (comment.isValidGraphElement()) { + return true; + } + } + return false; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/RrdGraphDefTemplate.java b/apps/jrobin/java/src/org/jrobin/graph/RrdGraphDefTemplate.java new file mode 100644 index 000000000..7b0c7f1c5 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/RrdGraphDefTemplate.java @@ -0,0 +1,982 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; +import org.jrobin.core.XmlTemplate; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +import java.awt.*; +import java.io.File; +import java.io.IOException; + +/** + * Class used to create an arbitrary number of RrdGraphDef (graph definition) objects + * from a single XML template. XML template can be supplied as an XML InputSource, + * XML file or XML formatted string. + *

+ * Here is an example of a properly formatted XML template with all available options in it + * (unwanted options can be removed/ignored): + *

+ *

+ * <rrd_graph_def>
+ *     <!-- use '-' to represent in-memory graph -->
+ *     <filename>test.png</filename>
+ *     <!--
+ *         starting and ending timestamps can be specified by
+ *         using at-style time specification, or by specifying
+ *         exact timestamps since epoch (without milliseconds)
+ *     -->
+ *     <span>
+ *         <start>now - 1d</start>
+ *         <end>now</end>
+ *     </span>
+ *     <options>
+ *         <!--
+ *             specify 'true' if you want to use RrdDbPool while
+ *             creating graph
+ *         -->
+ *         <use_pool>false</use_pool>
+ *         <anti_aliasing>true</anti_aliasing>
+ *         <time_grid>
+ *             <show_grid>true</show_grid>
+ *             <!-- allowed units: second, minute, hour, day, week, month, year -->
+ *             <minor_grid_unit>minute</minor_grid_unit>
+ *             <minor_grid_unit_count>60</minor_grid_unit_count>
+ *             <major_grid_unit>hour</major_grid_unit>
+ *             <major_grid_unit_count>2</major_grid_unit_count>
+ *             <label_unit>hour</label_unit>
+ *             <label_unit_count>2</label_unit_count>
+ *             <label_span>1200</label_span>
+ *             <!-- use SimpleDateFormat or strftime-like format to format labels -->
+ *             <label_format>dd-MMM-yy</label_format>
+ *         </time_grid>
+ *         <value_grid>
+ *             <show_grid>true</show_grid>
+ *             <grid_step>100.0</grid_step>
+ *             <label_factor>5</label_factor>
+ *         </value_grid>
+ *         <no_minor_grid>true</no_minor_grid>
+ *         <alt_y_grid>true</alt_y_grid>
+ *         <alt_y_mrtg>true</alt_y_mrtg>
+ *         <alt_autoscale>true</alt_autoscale>
+ *         <alt_autoscale_max>true</alt_autoscale_max>
+ *         <units_exponent>3</units_exponent>
+ *         <units_length>13</units_length>
+ *         <vertical_label>Speed (kbits/sec)</vertical_label>
+ *         <width>444</width>
+ *         <height>222</height>
+ *         <interlaced>true</interlaced>
+ *         <image_info>filename = %s, width=%d, height=%d</image_info>
+ *         <image_format>png</image_format>
+ *         <image_quality>0.8</image_quality>
+ *         <background_image>luka.png</background_image>
+ *         <overlay_image>luka.png</overlay_image>
+ *         <unit>kilos</unit>
+ *         <lazy>false</lazy>
+ *         <min_value>0</min_value>
+ *         <max_value>5000</max_value>
+ *         <rigid>true</rigid>
+ *         <base>1000</base>
+ *         <logarithmic>false</logarithmic>
+ *         <colors>
+ *             <canvas>#FFFFFF</canvas>
+ *             <back>#FFFFFF</back>
+ *             <shadea>#AABBCC</shadea>
+ *             <shadeb>#DDDDDD</shadeb>
+ *             <grid>#FF0000</grid>
+ *             <mgrid>#00FF00</mgrid>
+ *             <font>#FFFFFF</font>
+ *             <frame>#EE00FF</frame>
+ *             <arrow>#FF0000</arrow>
+ *         </colors>
+ *         <no_legend>false</no_legend>
+ *         <only_graph>false</only_graph>
+ *         <force_rules_legend>false</force_rules_legend>
+ *         <title>This is a title</title>
+ *         <step>300</step>
+ *         <fonts>
+ *             <small_font>
+ *                 <name>Courier</name>
+ *                 <style>bold italic</style>
+ *                 <size>12</size>
+ *             </small_font>
+ *             <large_font>
+ *                 <name>Courier</name>
+ *                 <style>plain</style>
+ *                 <size>11</size>
+ *             </large_font>
+ *         </fonts>
+ *         <first_day_of_week>SUNDAY</first_day_of_week>
+ *     </options>
+ *     <datasources>
+ *         <def>
+ *             <name>x</name>
+ *             <rrd>test.rrd</rrd>
+ *             <source>sun</source>
+ *             <cf>AVERAGE</cf>
+ *             <backend>FILE</backend>
+ *         </def>
+ *         <def>
+ *             <name>y</name>
+ *             <rrd>test.rrd</rrd>
+ *             <source>shade</source>
+ *             <cf>AVERAGE</cf>
+ *         </def>
+ *         <cdef>
+ *             <name>x_plus_y</name>
+ *             <rpn>x,y,+</rpn>
+ *         </cdef>
+ *         <cdef>
+ *             <name>x_minus_y</name>
+ *             <rpn>x,y,-</rpn>
+ *         </cdef>
+ *         <sdef>
+ *             <name>x_avg</name>
+ *             <source>x</source>
+ *             <cf>AVERAGE</cf>
+ *         </sdef>
+ *         <sdef>
+ *             <name>y_max</name>
+ *             <source>y</source>
+ *             <cf>MAX</cf>
+ *         </sdef>
+ *     </datasources>
+ *     <graph>
+ *         <area>
+ *             <datasource>x</datasource>
+ *             <color>#FF0000</color>
+ *             <legend>X value\r</legend>
+ *         </area>
+ *         <stack>
+ *             <datasource>y</datasource>
+ *             <color>#00FF00</color>
+ *             <legend>Y value\r</legend>
+ *         </stack>
+ *         <line>
+ *             <datasource>x</datasource>
+ *             <color>#FF0000</color>
+ *             <legend>X value\r</legend>
+ *             <width>2</width>
+ *         </line>
+ *         <print>
+ *             <datasource>x</datasource>
+ *             <cf>AVERAGE</cf>
+ *             <format>Average is %7.3f\c</format>
+ *         </print>
+ *         <gprint>
+ *             <datasource>y</datasource>
+ *             <cf>MAX</cf>
+ *             <format>Max is %7.3f\c</format>
+ *         </gprint>
+ *         <hrule>
+ *             <value>1250</value>
+ *             <color>#0000FF</color>
+ *             <legend>This is a horizontal rule</legend>
+ *         </hrule>
+ *         <vrule>
+ *             <time>now-6h</time>
+ *             <color>#0000FF</color>
+ *             <legend>This is a vertical rule</legend>
+ *         </vrule>
+ *         <comment>Simple comment</comment>
+ *         <comment>One more comment\c</comment>
+ *     </graph>
+ * </rrd_graph_def>
+ * 
+ * Notes on the template syntax: + *

+ *

    + *
  • There is a strong relation between the XML template syntax and the syntax of + * {@link RrdGraphDef} class methods. If you are not sure what some XML tag means, check javadoc + * for the corresponding class method. + *
  • hard-coded timestamps in templates should be long integeres + * (like: 1000243567) or at-style formatted strings + *
  • whitespaces are not harmful + *
  • use true, on, yes, y, + * or 1 to specify boolean true value (anything else will + * be treated as false). + *
  • floating point values: anything that cannot be parsed will be treated as Double.NaN + * (like: U, unknown, 12r.23) + *
  • use #RRGGBB or #RRGGBBAA format to specify colors. + *
  • valid font styles are: PLAIN, ITALIC, BOLD, BOLDITALIC + *
  • comments are allowed. + *
+ * Any template value (text between <some_tag> and + * </some_tag>) can be replaced with + * a variable of the following form: ${variable_name}. Use + * {@link XmlTemplate#setVariable(String, String) setVariable()} + * methods from the base class to replace + * template variables with real values at runtime. + *

+ * Typical usage scenario: + *

+ *

    + *
  • Create your XML template and save it to a file (template.xml, for example) + *
  • Replace template values with variables if you want to change them during runtime. + * For example, time span should not be hard-coded in the template - you probably want to create + * many different graphs with different time spans from the same XML template. + * For example, your XML template could start with: + *
    + * <rrd_graph_def>
    + *     ...
    + *     <span>
    + *         <start>${start}</start>
    + *         <end>${end}</end>
    + *     </span>
    + *     ...
    + * 
    + *
  • In your Java code, create RrdGraphDefTemplate object using your XML template file: + *
    + * RrdGraphDefTemplate t = new RrdGraphDefTemplate(new File(template.xml));
    + * 
    + *
  • Then, specify real values for template variables: + *
    + * t.setVariable("start", new GregorianCalendar(2004, 2, 25));
    + * t.setVariable("end", new GregorianCalendar(2004, 2, 26));
    + * 
    + *
  • Once all template variables are set, just use the template object to create RrdGraphDef + * object. This object is actually used to create JRobin grahps: + *
    + * RrdGraphDef gdef = t.getRrdGraphDef();
    + * RrdGraph g = new RrdGraph(gdef);
    + * 
    + *
+ * You should create new RrdGraphDefTemplate object only once for each XML template. Single template + * object can be reused to create as many RrdGraphDef objects as needed, with different values + * specified for template variables. XML synatax check is performed only once - the first graph + * definition object gets created relatively slowly, but it will be created much faster next time. + */ +public class RrdGraphDefTemplate extends XmlTemplate implements RrdGraphConstants { + static final Color BLIND_COLOR = new Color(0, 0, 0, 0); + + private RrdGraphDef rrdGraphDef; + + /** + * Creates template object from any parsable XML source + * + * @param inputSource XML source + * @throws IOException thrown in case of I/O error + * @throws RrdException usually thrown in case of XML related error + */ + public RrdGraphDefTemplate(InputSource inputSource) throws IOException, RrdException { + super(inputSource); + } + + /** + * Creates template object from the file containing XML template code + * + * @param xmlFile file containing XML template + * @throws IOException thrown in case of I/O error + * @throws RrdException usually thrown in case of XML related error + */ + public RrdGraphDefTemplate(File xmlFile) throws IOException, RrdException { + super(xmlFile); + } + + /** + * Creates template object from the string containing XML template code + * + * @param xmlString string containing XML template + * @throws IOException thrown in case of I/O error + * @throws RrdException usually thrown in case of XML related error + */ + public RrdGraphDefTemplate(String xmlString) throws IOException, RrdException { + super(xmlString); + } + + /** + * Creates RrdGraphDef object which can be used to create RrdGraph + * object (actual JRobin graphs). Before this method is called, all template variables (if any) + * must be resolved (replaced with real values). + * See {@link XmlTemplate#setVariable(String, String) setVariable()} method information to + * understand how to supply values for template variables. + * + * @return Graph definition which can be used to create RrdGraph object (actual JRobin graphs) + * @throws RrdException Thrown if parsed XML template contains invalid (unrecognized) tags + */ + public RrdGraphDef getRrdGraphDef() throws RrdException { + // basic check + if (!root.getTagName().equals("rrd_graph_def")) { + throw new RrdException("XML definition must start with "); + } + validateTagsOnlyOnce(root, new String[] {"filename", "span", "options", "datasources", "graph"}); + rrdGraphDef = new RrdGraphDef(); + // traverse all nodes + Node[] childNodes = getChildNodes(root); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("filename")) { + resolveFilename(childNode); + } + // SPAN + else if (nodeName.equals("span")) { + resolveSpan(childNode); + } + // OPTIONS + else if (nodeName.equals("options")) { + resolveOptions(childNode); + } + // DATASOURCES + else if (nodeName.equals("datasources")) { + resolveDatasources(childNode); + } + // GRAPH ELEMENTS + else if (nodeName.equals("graph")) { + resolveGraphElements(childNode); + } + } + return rrdGraphDef; + } + + private void resolveGraphElements(Node graphNode) throws RrdException { + validateTagsOnlyOnce(graphNode, new String[] {"area*", "line*", "stack*", + "print*", "gprint*", "hrule*", "vrule*", "comment*"}); + Node[] childNodes = getChildNodes(graphNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("area")) { + resolveArea(childNode); + } + else if (nodeName.equals("line")) { + resolveLine(childNode); + } + else if (nodeName.equals("stack")) { + resolveStack(childNode); + } + else if (nodeName.equals("print")) { + resolvePrint(childNode, false); + } + else if (nodeName.equals("gprint")) { + resolvePrint(childNode, true); + } + else if (nodeName.equals("hrule")) { + resolveHRule(childNode); + } + else if (nodeName.equals("vrule")) { + resolveVRule(childNode); + } + else if (nodeName.equals("comment")) { + rrdGraphDef.comment(getValue(childNode)); + } + } + } + + private void resolveVRule(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"time", "color", "legend"}); + long timestamp = Long.MIN_VALUE; + Paint color = null; + String legend = null; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("time")) { + timestamp = Util.getTimestamp(getValue(childNode)); + } + else if (nodeName.equals("color")) { + color = getValueAsColor(childNode); + } + else if (nodeName.equals("legend")) { + legend = getValue(childNode); + } + } + if (timestamp != Long.MIN_VALUE && color != null) { + rrdGraphDef.vrule(timestamp, color, legend); + } + else { + throw new RrdException("Incomplete VRULE settings"); + } + } + + private void resolveHRule(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"value", "color", "legend"}); + double value = Double.NaN; + Paint color = null; + String legend = null; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("value")) { + value = getValueAsDouble(childNode); + } + else if (nodeName.equals("color")) { + color = getValueAsColor(childNode); + } + else if (nodeName.equals("legend")) { + legend = getValue(childNode); + } + } + if (!Double.isNaN(value) && color != null) { + rrdGraphDef.hrule(value, color, legend); + } + else { + throw new RrdException("Incomplete HRULE settings"); + } + } + + private void resolvePrint(Node parentNode, boolean isInGraph) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"datasource", "cf", "format"}); + String datasource = null, cf = null, format = null; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("datasource")) { + datasource = getValue(childNode); + } + else if (nodeName.equals("cf")) { + cf = getValue(childNode); + } + else if (nodeName.equals("format")) { + format = getValue(childNode); + } + } + if (datasource != null && cf != null && format != null) { + if (isInGraph) { + rrdGraphDef.gprint(datasource, cf, format); + } + else { + rrdGraphDef.print(datasource, cf, format); + } + } + else { + throw new RrdException("Incomplete " + (isInGraph ? "GRPINT" : "PRINT") + " settings"); + } + } + + private void resolveStack(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"datasource", "color", "legend"}); + String datasource = null, legend = null; + Paint color = null; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("datasource")) { + datasource = getValue(childNode); + } + else if (nodeName.equals("color")) { + color = getValueAsColor(childNode); + } + else if (nodeName.equals("legend")) { + legend = getValue(childNode); + } + } + if (datasource != null) { + if (color != null) { + rrdGraphDef.stack(datasource, color, legend); + } + else { + rrdGraphDef.stack(datasource, BLIND_COLOR, legend); + } + } + else { + throw new RrdException("Incomplete STACK settings"); + } + } + + private void resolveLine(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"datasource", "color", "legend", "width"}); + String datasource = null, legend = null; + Paint color = null; + float width = 1.0F; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("datasource")) { + datasource = getValue(childNode); + } + else if (nodeName.equals("color")) { + color = getValueAsColor(childNode); + } + else if (nodeName.equals("legend")) { + legend = getValue(childNode); + } + else if (nodeName.equals("width")) { + width = (float) getValueAsDouble(childNode); + } + } + if (datasource != null) { + if (color != null) { + rrdGraphDef.line(datasource, color, legend, width); + } + else { + rrdGraphDef.line(datasource, BLIND_COLOR, legend, width); + } + } + else { + throw new RrdException("Incomplete LINE settings"); + } + } + + private void resolveArea(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"datasource", "color", "legend"}); + String datasource = null, legend = null; + Paint color = null; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("datasource")) { + datasource = getValue(childNode); + } + else if (nodeName.equals("color")) { + color = getValueAsColor(childNode); + } + else if (nodeName.equals("legend")) { + legend = getValue(childNode); + } + } + if (datasource != null) { + if (color != null) { + rrdGraphDef.area(datasource, color, legend); + } + else { + rrdGraphDef.area(datasource, BLIND_COLOR, legend); + } + } + else { + throw new RrdException("Incomplete AREA settings"); + } + } + + private void resolveDatasources(Node datasourcesNode) throws RrdException { + validateTagsOnlyOnce(datasourcesNode, new String[] {"def*", "cdef*", "sdef*"}); + Node[] childNodes = getChildNodes(datasourcesNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("def")) { + resolveDef(childNode); + } + else if (nodeName.equals("cdef")) { + resolveCDef(childNode); + } + else if (nodeName.equals("sdef")) { + resolveSDef(childNode); + } + } + } + + private void resolveSDef(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"name", "source", "cf"}); + String name = null, source = null, cf = null; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("name")) { + name = getValue(childNode); + } + else if (nodeName.equals("source")) { + source = getValue(childNode); + } + else if (nodeName.equals("cf")) { + cf = getValue(childNode); + } + } + if (name != null && source != null && cf != null) { + rrdGraphDef.datasource(name, source, cf); + } + else { + throw new RrdException("Incomplete SDEF settings"); + } + } + + private void resolveCDef(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"name", "rpn"}); + String name = null, rpn = null; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("name")) { + name = getValue(childNode); + } + else if (nodeName.equals("rpn")) { + rpn = getValue(childNode); + } + } + if (name != null && rpn != null) { + rrdGraphDef.datasource(name, rpn); + } + else { + throw new RrdException("Incomplete CDEF settings"); + } + } + + private void resolveDef(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"name", "rrd", "source", "cf", "backend"}); + String name = null, rrd = null, source = null, cf = null, backend = null; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("name")) { + name = getValue(childNode); + } + else if (nodeName.equals("rrd")) { + rrd = getValue(childNode); + } + else if (nodeName.equals("source")) { + source = getValue(childNode); + } + else if (nodeName.equals("cf")) { + cf = getValue(childNode); + } + else if (nodeName.equals("backend")) { + backend = getValue(childNode); + } + } + if (name != null && rrd != null && source != null && cf != null) { + rrdGraphDef.datasource(name, rrd, source, cf, backend); + } + else { + throw new RrdException("Incomplete DEF settings"); + } + } + + private void resolveFilename(Node filenameNode) { + String filename = getValue(filenameNode); + rrdGraphDef.setFilename(filename); + } + + private void resolveSpan(Node spanNode) throws RrdException { + validateTagsOnlyOnce(spanNode, new String[] {"start", "end"}); + String startStr = getChildValue(spanNode, "start"); + String endStr = getChildValue(spanNode, "end"); + long[] span = Util.getTimestamps(startStr, endStr); + rrdGraphDef.setStartTime(span[0]); + rrdGraphDef.setEndTime(span[1]); + } + + private void resolveOptions(Node rootOptionNode) throws RrdException { + validateTagsOnlyOnce(rootOptionNode, new String[] { + "anti_aliasing", "use_pool", "time_grid", "value_grid", "alt_y_grid", "alt_y_mrtg", + "no_minor_grid", "alt_autoscale", "alt_autoscale_max", "units_exponent", "units_length", + "vertical_label", "width", "height", "interlaced", "image_info", "image_format", + "image_quality", "background_image", "overlay_image", "unit", "lazy", + "min_value", "max_value", "rigid", "base", "logarithmic", "colors", + "no_legend", "only_graph", "force_rules_legend", "title", "step", "fonts", + "first_day_of_week", "signature" + }); + Node[] optionNodes = getChildNodes(rootOptionNode); + for (Node optionNode : optionNodes) { + String option = optionNode.getNodeName(); + if (option.equals("use_pool")) { + rrdGraphDef.setPoolUsed(getValueAsBoolean(optionNode)); + } + else if (option.equals("anti_aliasing")) { + rrdGraphDef.setAntiAliasing(getValueAsBoolean(optionNode)); + } + else if (option.equals("time_grid")) { + resolveTimeGrid(optionNode); + } + else if (option.equals("value_grid")) { + resolveValueGrid(optionNode); + } + else if (option.equals("no_minor_grid")) { + rrdGraphDef.setNoMinorGrid(getValueAsBoolean(optionNode)); + } + else if (option.equals("alt_y_grid")) { + rrdGraphDef.setAltYGrid(getValueAsBoolean(optionNode)); + } + else if (option.equals("alt_y_mrtg")) { + rrdGraphDef.setAltYMrtg(getValueAsBoolean(optionNode)); + } + else if (option.equals("alt_autoscale")) { + rrdGraphDef.setAltAutoscale(getValueAsBoolean(optionNode)); + } + else if (option.equals("alt_autoscale_max")) { + rrdGraphDef.setAltAutoscaleMax(getValueAsBoolean(optionNode)); + } + else if (option.equals("units_exponent")) { + rrdGraphDef.setUnitsExponent(getValueAsInt(optionNode)); + } + else if (option.equals("units_length")) { + rrdGraphDef.setUnitsLength(getValueAsInt(optionNode)); + } + else if (option.equals("vertical_label")) { + rrdGraphDef.setVerticalLabel(getValue(optionNode)); + } + else if (option.equals("width")) { + rrdGraphDef.setWidth(getValueAsInt(optionNode)); + } + else if (option.equals("height")) { + rrdGraphDef.setHeight(getValueAsInt(optionNode)); + } + else if (option.equals("interlaced")) { + rrdGraphDef.setInterlaced(getValueAsBoolean(optionNode)); + } + else if (option.equals("image_info")) { + rrdGraphDef.setImageInfo(getValue(optionNode)); + } + else if (option.equals("image_format")) { + rrdGraphDef.setImageFormat(getValue(optionNode)); + } + else if (option.equals("image_quality")) { + rrdGraphDef.setImageQuality((float) getValueAsDouble(optionNode)); + } + else if (option.equals("background_image")) { + rrdGraphDef.setBackgroundImage(getValue(optionNode)); + } + else if (option.equals("overlay_image")) { + rrdGraphDef.setOverlayImage(getValue(optionNode)); + } + else if (option.equals("unit")) { + rrdGraphDef.setUnit(getValue(optionNode)); + } + else if (option.equals("lazy")) { + rrdGraphDef.setLazy(getValueAsBoolean(optionNode)); + } + else if (option.equals("min_value")) { + rrdGraphDef.setMinValue(getValueAsDouble(optionNode)); + } + else if (option.equals("max_value")) { + rrdGraphDef.setMaxValue(getValueAsDouble(optionNode)); + } + else if (option.equals("rigid")) { + rrdGraphDef.setRigid(getValueAsBoolean(optionNode)); + } + else if (option.equals("base")) { + rrdGraphDef.setBase(getValueAsDouble(optionNode)); + } + else if (option.equals("logarithmic")) { + rrdGraphDef.setLogarithmic(getValueAsBoolean(optionNode)); + } + else if (option.equals("colors")) { + resolveColors(optionNode); + } + else if (option.equals("no_legend")) { + rrdGraphDef.setNoLegend(getValueAsBoolean(optionNode)); + } + else if (option.equals("only_graph")) { + rrdGraphDef.setOnlyGraph(getValueAsBoolean(optionNode)); + } + else if (option.equals("force_rules_legend")) { + rrdGraphDef.setForceRulesLegend(getValueAsBoolean(optionNode)); + } + else if (option.equals("title")) { + rrdGraphDef.setTitle(getValue(optionNode)); + } + else if (option.equals("step")) { + rrdGraphDef.setStep(getValueAsLong(optionNode)); + } + else if (option.equals("fonts")) { + resolveFonts(optionNode); + } + else if (option.equals("first_day_of_week")) { + int dayIndex = resolveFirstDayOfWeek(getValue(optionNode)); + rrdGraphDef.setFirstDayOfWeek(dayIndex); + } + else if (option.equals("signature")) { + rrdGraphDef.setShowSignature(getValueAsBoolean(optionNode)); + } + } + } + + private int resolveFirstDayOfWeek(String firstDayOfWeek) throws RrdException { + if (firstDayOfWeek.equalsIgnoreCase("sunday")) { + return SUNDAY; + } + else if (firstDayOfWeek.equalsIgnoreCase("monday")) { + return MONDAY; + } + else if (firstDayOfWeek.equalsIgnoreCase("tuesday")) { + return TUESDAY; + } + else if (firstDayOfWeek.equalsIgnoreCase("wednesday")) { + return WEDNESDAY; + } + else if (firstDayOfWeek.equalsIgnoreCase("thursday")) { + return THURSDAY; + } + else if (firstDayOfWeek.equalsIgnoreCase("friday")) { + return FRIDAY; + } + else if (firstDayOfWeek.equalsIgnoreCase("saturday")) { + return SATURDAY; + } + throw new RrdException("Never heard for this day of week: " + firstDayOfWeek); + } + + private void resolveFonts(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"small_font", "large_font"}); + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("small_font")) { + rrdGraphDef.setSmallFont(resolveFont(childNode)); + } + else if (nodeName.equals("large_font")) { + rrdGraphDef.setLargeFont(resolveFont(childNode)); + } + } + } + + private Font resolveFont(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"name", "style", "size"}); + String name = null, style = null; + int size = 0; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("name")) { + name = getValue(childNode); + } + else if (nodeName.equals("style")) { + style = getValue(childNode).toLowerCase(); + } + else if (nodeName.equals("size")) { + size = getValueAsInt(childNode); + } + } + if (name != null && style != null && size > 0) { + boolean isItalic = style.contains("italic"), isBold = style.contains("bold"); + int fstyle = Font.PLAIN; + if (isItalic && isBold) { + fstyle = Font.BOLD + Font.ITALIC; + } + else if (isItalic) { + fstyle = Font.ITALIC; + } + else if (isBold) { + fstyle = Font.BOLD; + } + return new Font(name, fstyle, size); + } + else { + throw new RrdException("Incomplete font specification"); + } + } + + private void resolveColors(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, COLOR_NAMES); + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String colorName = childNode.getNodeName(); + rrdGraphDef.setColor(colorName, getValueAsColor(childNode)); + } + } + + private void resolveValueGrid(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] {"show_grid", "grid_step", "label_factor"}); + boolean showGrid = true; + double gridStep = Double.NaN; + int NOT_SET = Integer.MIN_VALUE, labelFactor = NOT_SET; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("show_grid")) { + showGrid = getValueAsBoolean(childNode); + } + else if (nodeName.equals("grid_step")) { + gridStep = getValueAsDouble(childNode); + } + else if (nodeName.equals("label_factor")) { + labelFactor = getValueAsInt(childNode); + } + } + rrdGraphDef.setDrawYGrid(showGrid); + if (!Double.isNaN(gridStep) && labelFactor != NOT_SET) { + rrdGraphDef.setValueAxis(gridStep, labelFactor); + } + else if (!Double.isNaN(gridStep) || labelFactor != NOT_SET) { + throw new RrdException("Incomplete value axis settings"); + } + } + + private void resolveTimeGrid(Node parentNode) throws RrdException { + validateTagsOnlyOnce(parentNode, new String[] { + "show_grid", "minor_grid_unit", + "minor_grid_unit_count", "major_grid_unit", + "major_grid_unit_count", "label_unit", "label_unit_count", + "label_span", "label_format" + }); + boolean showGrid = true; + final int NOT_SET = Integer.MIN_VALUE; + int minorGridUnit = NOT_SET, minorGridUnitCount = NOT_SET, + majorGridUnit = NOT_SET, majorGridUnitCount = NOT_SET, + labelUnit = NOT_SET, labelUnitCount = NOT_SET, labelSpan = NOT_SET; + String labelFormat = null; + Node[] childNodes = getChildNodes(parentNode); + for (Node childNode : childNodes) { + String nodeName = childNode.getNodeName(); + if (nodeName.equals("show_grid")) { + showGrid = getValueAsBoolean(childNode); + } + else if (nodeName.equals("minor_grid_unit")) { + minorGridUnit = resolveTimeUnit(getValue(childNode)); + } + else if (nodeName.equals("minor_grid_unit_count")) { + minorGridUnitCount = getValueAsInt(childNode); + } + else if (nodeName.equals("major_grid_unit")) { + majorGridUnit = resolveTimeUnit(getValue(childNode)); + } + else if (nodeName.equals("major_grid_unit_count")) { + majorGridUnitCount = getValueAsInt(childNode); + } + else if (nodeName.equals("label_unit")) { + labelUnit = resolveTimeUnit(getValue(childNode)); + } + else if (nodeName.equals("label_unit_count")) { + labelUnitCount = getValueAsInt(childNode); + } + else if (nodeName.equals("label_span")) { + labelSpan = getValueAsInt(childNode); + } + else if (nodeName.equals("label_format")) { + labelFormat = getValue(childNode); + } + } + rrdGraphDef.setDrawXGrid(showGrid); + if (minorGridUnit != NOT_SET && minorGridUnitCount != NOT_SET && + majorGridUnit != NOT_SET && majorGridUnitCount != NOT_SET && + labelUnit != NOT_SET && labelUnitCount != NOT_SET && labelSpan != NOT_SET && labelFormat != null) { + rrdGraphDef.setTimeAxis(minorGridUnit, minorGridUnitCount, majorGridUnit, majorGridUnitCount, + labelUnit, labelUnitCount, labelSpan, labelFormat); + } + else if (minorGridUnit != NOT_SET || minorGridUnitCount != NOT_SET || + majorGridUnit != NOT_SET || majorGridUnitCount != NOT_SET || + labelUnit != NOT_SET || labelUnitCount != NOT_SET || labelSpan != NOT_SET || labelFormat != null) { + throw new RrdException("Incomplete time axis settings"); + } + } + + private int resolveTimeUnit(String unit) throws RrdException { + if (unit.equalsIgnoreCase("second")) { + return RrdGraphConstants.SECOND; + } + else if (unit.equalsIgnoreCase("minute")) { + return RrdGraphConstants.MINUTE; + } + else if (unit.equalsIgnoreCase("hour")) { + return RrdGraphConstants.HOUR; + } + else if (unit.equalsIgnoreCase("day")) { + return RrdGraphConstants.DAY; + } + else if (unit.equalsIgnoreCase("week")) { + return RrdGraphConstants.WEEK; + } + else if (unit.equalsIgnoreCase("month")) { + return RrdGraphConstants.MONTH; + } + else if (unit.equalsIgnoreCase("year")) { + return RrdGraphConstants.YEAR; + } + throw new RrdException("Unknown time unit specified: " + unit); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/RrdGraphInfo.java b/apps/jrobin/java/src/org/jrobin/graph/RrdGraphInfo.java new file mode 100644 index 000000000..469eb2efe --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/RrdGraphInfo.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * 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.graph; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class to represent successfully created JRobin graph. Objects of this class are created by method + * {@link RrdGraph#getRrdGraphInfo()}. + */ +public class RrdGraphInfo { + String filename; + int width, height; + byte[] bytes; + String imgInfo; + private List printLines = new ArrayList(); + + RrdGraphInfo() { + // cannot instantiate this class + } + + void addPrintLine(String printLine) { + printLines.add(printLine); + } + + /** + * Returns filename of the graph + * + * @return filename of the graph. '-' denotes in-memory graph (no file created) + */ + public String getFilename() { + return filename; + } + + /** + * Returns total graph width + * + * @return total graph width + */ + public int getWidth() { + return width; + } + + /** + * Returns total graph height + * + * @return total graph height + */ + public int getHeight() { + return height; + } + + /** + * Returns graph bytes + * + * @return Graph bytes + */ + public byte[] getBytes() { + return bytes; + } + + /** + * Returns PRINT lines requested by {@link RrdGraphDef#print(String, String, String)} method. + * + * @return An array of formatted PRINT lines + */ + public String[] getPrintLines() { + return printLines.toArray(new String[printLines.size()]); + } + + /** + * Returns image information requested by {@link RrdGraphDef#setImageInfo(String)} method + * + * @return Image information + */ + public String getImgInfo() { + return imgInfo; + } + + /** + * Returns the number of bytes in the graph file + * + * @return Length of the graph file + */ + public int getByteCount() { + return bytes != null ? bytes.length : 0; + } + + /** + * Dumps complete graph information. Useful for debugging purposes. + * + * @return String containing complete graph information + */ + public String dump() { + StringBuffer b = new StringBuffer(); + b.append("filename = \"").append(getFilename()).append("\"\n"); + b.append("width = ").append(getWidth()).append(", height = ").append(getHeight()).append("\n"); + b.append("byteCount = ").append(getByteCount()).append("\n"); + b.append("imginfo = \"").append(getImgInfo()).append("\"\n"); + String[] plines = getPrintLines(); + if (plines.length == 0) { + b.append("No print lines found\n"); + } + else { + for (int i = 0; i < plines.length; i++) { + b.append("print[").append(i).append("] = \"").append(plines[i]).append("\"\n"); + } + } + return b.toString(); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/Rule.java b/apps/jrobin/java/src/org/jrobin/graph/Rule.java new file mode 100644 index 000000000..3dccdad8c --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/Rule.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; + +class Rule extends PlotElement { + final LegendText legend; + final float width; + + Rule(Paint color, LegendText legend, float width) { + super(color); + this.legend = legend; + this.width = width; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/SDef.java b/apps/jrobin/java/src/org/jrobin/graph/SDef.java new file mode 100644 index 000000000..09f857b76 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/SDef.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.data.DataProcessor; + +class SDef extends Source { + private String defName, consolFun; + + SDef(String name, String defName, String consolFun) { + super(name); + this.defName = defName; + this.consolFun = consolFun; + } + + void requestData(DataProcessor dproc) { + dproc.addDatasource(name, defName, consolFun); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/Source.java b/apps/jrobin/java/src/org/jrobin/graph/Source.java new file mode 100644 index 000000000..733c9c464 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/Source.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.data.DataProcessor; + +abstract class Source { + final String name; + + Source(String name) { + this.name = name; + } + + abstract void requestData(DataProcessor dproc); +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/SourcedPlotElement.java b/apps/jrobin/java/src/org/jrobin/graph/SourcedPlotElement.java new file mode 100644 index 000000000..fdcd81e24 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/SourcedPlotElement.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.RrdException; +import org.jrobin.core.Util; +import org.jrobin.data.DataProcessor; + +import java.awt.*; + +class SourcedPlotElement extends PlotElement { + final String srcName; + double[] values; + + SourcedPlotElement(String srcName, Paint color) { + super(color); + this.srcName = srcName; + } + + void assignValues(DataProcessor dproc) throws RrdException { + values = dproc.getValues(srcName); + } + + double[] getValues() { + return values; + } + + double getMinValue() { + return Util.min(values); + } + + double getMaxValue() { + return Util.max(values); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/Stack.java b/apps/jrobin/java/src/org/jrobin/graph/Stack.java new file mode 100644 index 000000000..a4eaff55b --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/Stack.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.RrdException; +import org.jrobin.data.DataProcessor; + +import java.awt.*; + +class Stack extends SourcedPlotElement { + private final SourcedPlotElement parent; + + Stack(SourcedPlotElement parent, String srcName, Paint color) { + super(srcName, color); + this.parent = parent; + } + + void assignValues(DataProcessor dproc) throws RrdException { + double[] parentValues = parent.getValues(); + double[] procValues = dproc.getValues(srcName); + values = new double[procValues.length]; + for (int i = 0; i < values.length; i++) { + values[i] = parentValues[i] + procValues[i]; + } + } + + float getParentLineWidth() { + if (parent instanceof Line) { + return ((Line) parent).width; + } + else if (parent instanceof Area) { + return -1F; + } + else /* if(parent instanceof Stack) */ { + return ((Stack) parent).getParentLineWidth(); + } + } + + Paint getParentColor() { + return parent.color; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/TimeAxis.java b/apps/jrobin/java/src/org/jrobin/graph/TimeAxis.java new file mode 100644 index 000000000..5a19f851d --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/TimeAxis.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +class TimeAxis implements RrdGraphConstants { + private static final TimeAxisSetting[] tickSettings = { + new TimeAxisSetting(0, SECOND, 30, MINUTE, 5, MINUTE, 5, 0, "HH:mm"), + new TimeAxisSetting(2, MINUTE, 1, MINUTE, 5, MINUTE, 5, 0, "HH:mm"), + new TimeAxisSetting(5, MINUTE, 2, MINUTE, 10, MINUTE, 10, 0, "HH:mm"), + new TimeAxisSetting(10, MINUTE, 5, MINUTE, 20, MINUTE, 20, 0, "HH:mm"), + new TimeAxisSetting(30, MINUTE, 10, HOUR, 1, HOUR, 1, 0, "HH:mm"), + new TimeAxisSetting(60, MINUTE, 30, HOUR, 2, HOUR, 2, 0, "HH:mm"), + new TimeAxisSetting(180, HOUR, 1, HOUR, 6, HOUR, 6, 0, "HH:mm"), + new TimeAxisSetting(600, HOUR, 6, DAY, 1, DAY, 1, 24 * 3600, "EEE"), + new TimeAxisSetting(1800, HOUR, 12, DAY, 1, DAY, 2, 24 * 3600, "EEE"), + new TimeAxisSetting(3600, DAY, 1, WEEK, 1, WEEK, 1, 7 * 24 * 3600, "'Week 'w"), + new TimeAxisSetting(3 * 3600, WEEK, 1, MONTH, 1, WEEK, 2, 7 * 24 * 3600, "'Week 'w"), + new TimeAxisSetting(6 * 3600, MONTH, 1, MONTH, 1, MONTH, 1, 30 * 24 * 3600, "MMM"), + new TimeAxisSetting(48 * 3600, MONTH, 1, MONTH, 3, MONTH, 3, 30 * 24 * 3600, "MMM"), + new TimeAxisSetting(10 * 24 * 3600, YEAR, 1, YEAR, 1, YEAR, 1, 365 * 24 * 3600, "yy"), + new TimeAxisSetting(-1, MONTH, 0, MONTH, 0, MONTH, 0, 0, "") + }; + + private TimeAxisSetting tickSetting; + private RrdGraph rrdGraph; + private double secPerPix; + private Calendar calendar; + + TimeAxis(RrdGraph rrdGraph) { + this.rrdGraph = rrdGraph; + if (rrdGraph.im.xsize > 0) { + this.secPerPix = (rrdGraph.im.end - rrdGraph.im.start) / Double.valueOf(rrdGraph.im.xsize); + } + this.calendar = Calendar.getInstance(Locale.getDefault()); + this.calendar.setFirstDayOfWeek(rrdGraph.gdef.firstDayOfWeek); + } + + void draw() { + chooseTickSettings(); + if (tickSetting == null) { + return; + } + drawMinor(); + drawMajor(); + drawLabels(); + } + + private void drawMinor() { + if (!rrdGraph.gdef.noMinorGrid) { + adjustStartingTime(tickSetting.minorUnit, tickSetting.minorUnitCount); + Paint color = rrdGraph.gdef.colors[COLOR_GRID]; + int y0 = rrdGraph.im.yorigin, y1 = y0 - rrdGraph.im.ysize; + for (int status = getTimeShift(); status <= 0; status = getTimeShift()) { + if (status == 0) { + long time = calendar.getTime().getTime() / 1000L; + int x = rrdGraph.mapper.xtr(time); + rrdGraph.worker.drawLine(x, y0 - 1, x, y0 + 1, color, TICK_STROKE); + rrdGraph.worker.drawLine(x, y0, x, y1, color, GRID_STROKE); + } + findNextTime(tickSetting.minorUnit, tickSetting.minorUnitCount); + } + } + } + + private void drawMajor() { + adjustStartingTime(tickSetting.majorUnit, tickSetting.majorUnitCount); + Paint color = rrdGraph.gdef.colors[COLOR_MGRID]; + int y0 = rrdGraph.im.yorigin, y1 = y0 - rrdGraph.im.ysize; + for (int status = getTimeShift(); status <= 0; status = getTimeShift()) { + if (status == 0) { + long time = calendar.getTime().getTime() / 1000L; + int x = rrdGraph.mapper.xtr(time); + rrdGraph.worker.drawLine(x, y0 - 2, x, y0 + 2, color, TICK_STROKE); + rrdGraph.worker.drawLine(x, y0, x, y1, color, GRID_STROKE); + } + findNextTime(tickSetting.majorUnit, tickSetting.majorUnitCount); + } + } + + private void drawLabels() { + // escape strftime like format string + String labelFormat = tickSetting.format.replaceAll("([^%]|^)%([^%t])", "$1%t$2"); + Font font = rrdGraph.gdef.getFont(FONTTAG_AXIS); + Paint color = rrdGraph.gdef.colors[COLOR_FONT]; + adjustStartingTime(tickSetting.labelUnit, tickSetting.labelUnitCount); + int y = rrdGraph.im.yorigin + (int) rrdGraph.worker.getFontHeight(font) + 2; + for (int status = getTimeShift(); status <= 0; status = getTimeShift()) { + String label = formatLabel(labelFormat, calendar.getTime()); + long time = calendar.getTime().getTime() / 1000L; + int x1 = rrdGraph.mapper.xtr(time); + int x2 = rrdGraph.mapper.xtr(time + tickSetting.labelSpan); + int labelWidth = (int) rrdGraph.worker.getStringWidth(label, font); + int x = x1 + (x2 - x1 - labelWidth) / 2; + if (x >= rrdGraph.im.xorigin && x + labelWidth <= rrdGraph.im.xorigin + rrdGraph.im.xsize) { + rrdGraph.worker.drawString(label, x, y, font, color); + } + findNextTime(tickSetting.labelUnit, tickSetting.labelUnitCount); + } + } + + private static String formatLabel(String format, Date date) { + if (format.contains("%")) { + // strftime like format string + return String.format(format, date); + } + else { + // simple date format + return new SimpleDateFormat(format).format(date); + } + } + + private void findNextTime(int timeUnit, int timeUnitCount) { + switch (timeUnit) { + case SECOND: + calendar.add(Calendar.SECOND, timeUnitCount); + break; + case MINUTE: + calendar.add(Calendar.MINUTE, timeUnitCount); + break; + case HOUR: + calendar.add(Calendar.HOUR_OF_DAY, timeUnitCount); + break; + case DAY: + calendar.add(Calendar.DAY_OF_MONTH, timeUnitCount); + break; + case WEEK: + calendar.add(Calendar.DAY_OF_MONTH, 7 * timeUnitCount); + break; + case MONTH: + calendar.add(Calendar.MONTH, timeUnitCount); + break; + case YEAR: + calendar.add(Calendar.YEAR, timeUnitCount); + break; + } + } + + private int getTimeShift() { + long time = calendar.getTime().getTime() / 1000L; + return (time < rrdGraph.im.start) ? -1 : (time > rrdGraph.im.end) ? +1 : 0; + } + + private void adjustStartingTime(int timeUnit, int timeUnitCount) { + calendar.setTime(new Date(rrdGraph.im.start * 1000L)); + switch (timeUnit) { + case SECOND: + calendar.add(Calendar.SECOND, -(calendar.get(Calendar.SECOND) % timeUnitCount)); + break; + case MINUTE: + calendar.set(Calendar.SECOND, 0); + calendar.add(Calendar.MINUTE, -(calendar.get(Calendar.MINUTE) % timeUnitCount)); + break; + case HOUR: + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.add(Calendar.HOUR_OF_DAY, -(calendar.get(Calendar.HOUR_OF_DAY) % timeUnitCount)); + break; + case DAY: + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + break; + case WEEK: + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + int diffDays = calendar.get(Calendar.DAY_OF_WEEK) - calendar.getFirstDayOfWeek(); + if (diffDays < 0) { + diffDays += 7; + } + calendar.add(Calendar.DAY_OF_MONTH, -diffDays); + break; + case MONTH: + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.DAY_OF_MONTH, 1); + calendar.add(Calendar.MONTH, -(calendar.get(Calendar.MONTH) % timeUnitCount)); + break; + case YEAR: + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.DAY_OF_MONTH, 1); + calendar.set(Calendar.MONTH, 0); + calendar.add(Calendar.YEAR, -(calendar.get(Calendar.YEAR) % timeUnitCount)); + break; + } + } + + + private void chooseTickSettings() { + if (rrdGraph.gdef.timeAxisSetting != null) { + tickSetting = new TimeAxisSetting(rrdGraph.gdef.timeAxisSetting); + } + else { + for (int i = 0; tickSettings[i].secPerPix >= 0 && secPerPix > tickSettings[i].secPerPix; i++) { + tickSetting = tickSettings[i]; + } + } + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/TimeAxisSetting.java b/apps/jrobin/java/src/org/jrobin/graph/TimeAxisSetting.java new file mode 100644 index 000000000..ab955e773 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/TimeAxisSetting.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.graph; + +class TimeAxisSetting { + final long secPerPix; + final int minorUnit, minorUnitCount, majorUnit, majorUnitCount; + final int labelUnit, labelUnitCount, labelSpan; + final String format; + + TimeAxisSetting(long secPerPix, int minorUnit, int minorUnitCount, int majorUnit, int majorUnitCount, + int labelUnit, int labelUnitCount, int labelSpan, String format) { + this.secPerPix = secPerPix; + this.minorUnit = minorUnit; + this.minorUnitCount = minorUnitCount; + this.majorUnit = majorUnit; + this.majorUnitCount = majorUnitCount; + this.labelUnit = labelUnit; + this.labelUnitCount = labelUnitCount; + this.labelSpan = labelSpan; + this.format = format; + } + + TimeAxisSetting(TimeAxisSetting s) { + this.secPerPix = s.secPerPix; + this.minorUnit = s.minorUnit; + this.minorUnitCount = s.minorUnitCount; + this.majorUnit = s.majorUnit; + this.majorUnitCount = s.majorUnitCount; + this.labelUnit = s.labelUnit; + this.labelUnitCount = s.labelUnitCount; + this.labelSpan = s.labelSpan; + this.format = s.format; + } + + TimeAxisSetting(int minorUnit, int minorUnitCount, int majorUnit, int majorUnitCount, + int labelUnit, int labelUnitCount, int labelSpan, String format) { + this(0, minorUnit, minorUnitCount, majorUnit, majorUnitCount, + labelUnit, labelUnitCount, labelSpan, format); + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/VRule.java b/apps/jrobin/java/src/org/jrobin/graph/VRule.java new file mode 100644 index 000000000..a3178b180 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/VRule.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.*; + +class VRule extends Rule { + final long timestamp; + + VRule(long timestamp, Paint color, LegendText legend, float width) { + super(color, legend, width); + this.timestamp = timestamp; + } + + void setLegendVisibility(long minval, long maxval, boolean forceLegend) { + legend.enabled &= (forceLegend || (timestamp >= minval && timestamp <= maxval)); + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/ValueAxis.java b/apps/jrobin/java/src/org/jrobin/graph/ValueAxis.java new file mode 100644 index 000000000..29e4e0ded --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/ValueAxis.java @@ -0,0 +1,269 @@ +/******************************************************************************* + * 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.graph; + +import java.awt.Font; +import java.awt.Paint; + +import org.jrobin.core.Util; + +class ValueAxis implements RrdGraphConstants { + private static final YLab[] ylab = { + new YLab(0.1, 1, 2, 5, 10), + new YLab(0.2, 1, 5, 10, 20), + new YLab(0.5, 1, 2, 4, 10), + new YLab(1.0, 1, 2, 5, 10), + new YLab(2.0, 1, 5, 10, 20), + new YLab(5.0, 1, 2, 4, 10), + new YLab(10.0, 1, 2, 5, 10), + new YLab(20.0, 1, 5, 10, 20), + new YLab(50.0, 1, 2, 4, 10), + new YLab(100.0, 1, 2, 5, 10), + new YLab(200.0, 1, 5, 10, 20), + new YLab(500.0, 1, 2, 4, 10), + new YLab(1000.0, 1, 2, 5, 10), + new YLab(2000.0, 1, 5, 10, 20), + new YLab(5000.0, 1, 2, 4, 10), + new YLab(10000.0, 1, 2, 5, 10), + new YLab(20000.0, 1, 5, 10, 20), + new YLab(50000.0, 1, 2, 4, 10), + new YLab(100000.0, 1, 2, 5, 10), + new YLab(0.0, 0, 0, 0, 0) + }; + + //private RrdGraph rrdGraph; + private ImageParameters im; + private ImageWorker worker; + private RrdGraphDef gdef; + private Mapper mapper; + + ValueAxis(RrdGraph rrdGraph) { + this(rrdGraph.im, rrdGraph.worker, rrdGraph.gdef, rrdGraph.mapper); + } + + ValueAxis(ImageParameters im, ImageWorker worker, RrdGraphDef gdef, Mapper mapper) { + this.im = im; + this.gdef = gdef; + this.worker = worker; + this.mapper = mapper; + } + + boolean draw() { + Font font = gdef.getFont(FONTTAG_AXIS); + Paint gridColor = gdef.colors[COLOR_GRID]; + Paint mGridColor = gdef.colors[COLOR_MGRID]; + Paint fontColor = gdef.colors[COLOR_FONT]; + int labelOffset = (int) (worker.getFontAscent(font) / 2); + int labfact = 2; + double range = im.maxval - im.minval; + double scaledrange = range / im.magfact; + double gridstep; + if (Double.isNaN(scaledrange)) { + return false; + } + String labfmt = null; + if (Double.isNaN(im.ygridstep)) { + if (gdef.altYGrid) { + /* find the value with max number of digits. Get number of digits */ + int decimals = (int) Math.ceil(Math.log10(Math.max(Math.abs(im.maxval), + Math.abs(im.minval)))); + if (decimals <= 0) /* everything is small. make place for zero */ { + decimals = 1; + } + int fractionals = (int) Math.floor(Math.log10(range)); + if (fractionals < 0) /* small amplitude. */ { + labfmt = Util.sprintf("%%%d.%df", decimals - fractionals + 1, -fractionals + 1); + } + else { + labfmt = Util.sprintf("%%%d.1f", decimals + 1); + } + gridstep = Math.pow(10, fractionals); + if (gridstep == 0) /* range is one -> 0.1 is reasonable scale */ { + gridstep = 0.1; + } + /* should have at least 5 lines but no more then 15 */ + if (range / gridstep < 5) { + gridstep /= 10; + } + if (range / gridstep > 15) { + gridstep *= 10; + } + if (range / gridstep > 5) { + labfact = 1; + if (range / gridstep > 8) { + labfact = 2; + } + } + else { + gridstep /= 5; + labfact = 5; + } + } + else { + //Start looking for a minimum of 3 labels, but settle for 2 or 1 if need be + int minimumLabelCount = 3; + YLab selectedYLab = null; + while(selectedYLab == null) { + selectedYLab = findYLab(minimumLabelCount); + minimumLabelCount--; + } + gridstep = selectedYLab.grid * im.magfact; + labfact = findLabelFactor(selectedYLab); + } + } + else { + gridstep = im.ygridstep; + labfact = im.ylabfact; + } + int x0 = im.xorigin, x1 = x0 + im.xsize; + int sgrid = (int) (im.minval / gridstep - 1); + int egrid = (int) (im.maxval / gridstep + 1); + double scaledstep = gridstep / im.magfact; + for (int i = sgrid; i <= egrid; i++) { + int y = this.mapper.ytr(gridstep * i); + if (y >= im.yorigin - im.ysize && y <= im.yorigin) { + if (i % labfact == 0) { + String graph_label; + if (i == 0 || im.symbol == ' ') { + if (scaledstep < 1) { + if (i != 0 && gdef.altYGrid) { + graph_label = Util.sprintf(labfmt, scaledstep * i); + } + else { + graph_label = Util.sprintf("%4.1f", scaledstep * i); + } + } + else { + graph_label = Util.sprintf("%4.0f", scaledstep * i); + } + } + else { + if (scaledstep < 1) { + graph_label = Util.sprintf("%4.1f %c", scaledstep * i, im.symbol); + } + else { + graph_label = Util.sprintf("%4.0f %c", scaledstep * i, im.symbol); + } + } + int length = (int) (worker.getStringWidth(graph_label, font)); + worker.drawString(graph_label, x0 - length - PADDING_VLABEL, y + labelOffset, font, fontColor); + worker.drawLine(x0 - 2, y, x0 + 2, y, mGridColor, TICK_STROKE); + worker.drawLine(x1 - 2, y, x1 + 2, y, mGridColor, TICK_STROKE); + worker.drawLine(x0, y, x1, y, mGridColor, GRID_STROKE); + } + else if (!(gdef.noMinorGrid)) { + worker.drawLine(x0 - 1, y, x0 + 1, y, gridColor, TICK_STROKE); + worker.drawLine(x1 - 1, y, x1 + 1, y, gridColor, TICK_STROKE); + worker.drawLine(x0, y, x1, y, gridColor, GRID_STROKE); + } + } + } + return true; + } + +/** +* Finds an acceptable YLab object for the current graph +* If the graph covers positive and negative on the y-axis, then +* desiredMinimumLabelCount is checked as well, to ensure the chosen YLab definition + * will result in the required number of labels +* + * Returns null if none are acceptable (none the right size or with +* enough labels) +*/ +private YLab findYLab(int desiredMinimumLabelCount) { + double scaledrange = this.getScaledRange(); + int labelFactor; + //Check each YLab definition to see if it's acceptable + for (int i = 0; ylab[i].grid > 0; i++) { + YLab thisYLab = ylab[i]; + //First cut is whether this gridstep would give enough space per gridline + if (this.getPixelsPerGridline(thisYLab) > 5 ) { + //Yep; now we might have to check the number of labels + if(im.minval < 0.0 && im.maxval > 0.0) { + //The graph covers positive and negative values, so we need the + // desiredMinimumLabelCount number of labels, which is going to + // usually be 3, then maybe 2, then only as a last resort, 1. + // So, we need to find out what the label factor would be + // if we chose this ylab definition + labelFactor = findLabelFactor(thisYLab); + if(labelFactor == -1) { + //Default to too many to satisfy the label count test, unless we're looking for just 1 + // in which case be sure to satisfy the label count test + labelFactor = desiredMinimumLabelCount==1?1:desiredMinimumLabelCount+1; + } + //Adding one? Think fenceposts (need one more than just dividing length by space between) + int labelCount = ((int)(scaledrange/thisYLab.grid)/labelFactor)+1; + if(labelCount > desiredMinimumLabelCount) { + return thisYLab; //Enough pixels, *and* enough labels + } + + } else { + //Only positive or negative on the graph y-axis. No need to + // care about the label count. + return thisYLab; + } + } + } + + double val = 1; + while(val < scaledrange) { + val = val * 10; + } + return new YLab(val/10, 1, 2, 5, 10); +} + +/** + * Find the smallest labelFactor acceptable (can fit labels) for the given YLab definition + * Returns the label factor if one is ok, otherwise returns -1 if none are acceptable + */ +private int findLabelFactor(YLab thisYLab) { + int pixel = this.getPixelsPerGridline(thisYLab); + int fontHeight = (int) Math.ceil(worker.getFontHeight(gdef.getFont(FONTTAG_AXIS))); + for (int j = 0; j < 4; j++) { + if (pixel * thisYLab.lfac[j] >= 2 * fontHeight) { + return thisYLab.lfac[j]; + } + } + return -1; +} + +/** + * Finds the number of pixels per gridline that the given YLab definition will result in + */ +private int getPixelsPerGridline(YLab thisYLab) { + double scaledrange = this.getScaledRange(); + return (int) (im.ysize / (scaledrange / thisYLab.grid)); +} + +private double getScaledRange() { + double range = im.maxval - im.minval; + return range / im.magfact; +} + + + static class YLab { + double grid; + int[] lfac; + + YLab(double grid, int lfac1, int lfac2, int lfac3, int lfac4) { + this.grid = grid; + lfac = new int[] {lfac1, lfac2, lfac3, lfac4}; + } + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/ValueAxisLogarithmic.java b/apps/jrobin/java/src/org/jrobin/graph/ValueAxisLogarithmic.java new file mode 100644 index 000000000..68ffc0f45 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/ValueAxisLogarithmic.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.Util; + +import java.awt.*; + +class ValueAxisLogarithmic implements RrdGraphConstants { + private static final double[][] yloglab = { + {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} + }; + + private RrdGraph rrdGraph; + private ImageParameters im; + private ImageWorker worker; + private RrdGraphDef gdef; + + ValueAxisLogarithmic(RrdGraph rrdGraph) { + this.rrdGraph = rrdGraph; + this.im = rrdGraph.im; + this.gdef = rrdGraph.gdef; + this.worker = rrdGraph.worker; + } + + boolean draw() { + Font font = gdef.getFont(FONTTAG_AXIS); + Paint gridColor = gdef.colors[COLOR_GRID]; + Paint mGridColor = gdef.colors[COLOR_MGRID]; + Paint fontColor = gdef.colors[COLOR_FONT]; + int fontHeight = (int) Math.ceil(worker.getFontHeight(font)); + int labelOffset = (int) (worker.getFontAscent(font) / 2); + + double pixpex = (double) im.ysize / (Math.log10(im.maxval) - Math.log10(im.minval)); + if (Double.isNaN(pixpex)) { + return false; + } + double minstep, pixperstep; + int minoridx = 0, majoridx = 0; + for (int i = 0; yloglab[i][0] > 0; i++) { + minstep = Math.log10(yloglab[i][0]); + for (int ii = 1; yloglab[i][ii + 1] > 0; ii++) { + if (yloglab[i][ii + 2] == 0) { + minstep = Math.log10(yloglab[i][ii + 1]) - Math.log10(yloglab[i][ii]); + break; + } + } + pixperstep = pixpex * minstep; + if (pixperstep > 5) { + minoridx = i; + } + if (pixperstep > 2 * fontHeight) { + majoridx = i; + } + } + int x0 = im.xorigin, x1 = x0 + im.xsize; + for (double value = Math.pow(10, Math.log10(im.minval) + - Math.log10(im.minval) % Math.log10(yloglab[minoridx][0])); + value <= im.maxval; + value *= yloglab[minoridx][0]) { + if (value < im.minval) { + continue; + } + int i = 0; + while (yloglab[minoridx][++i] > 0) { + int y = rrdGraph.mapper.ytr(value * yloglab[minoridx][i]); + if (y <= im.yorigin - im.ysize) { + break; + } + worker.drawLine(x0 - 1, y, x0 + 1, y, gridColor, TICK_STROKE); + worker.drawLine(x1 - 1, y, x1 + 1, y, gridColor, TICK_STROKE); + worker.drawLine(x0, y, x1, y, gridColor, GRID_STROKE); + } + } + for (double value = Math.pow(10, Math.log10(im.minval) + - (Math.log10(im.minval) % Math.log10(yloglab[majoridx][0]))); + value <= im.maxval; + value *= yloglab[majoridx][0]) { + if (value < im.minval) { + continue; + } + int i = 0; + while (yloglab[majoridx][++i] > 0) { + int y = rrdGraph.mapper.ytr(value * yloglab[majoridx][i]); + if (y <= im.yorigin - im.ysize) { + break; + } + worker.drawLine(x0 - 2, y, x0 + 2, y, mGridColor, TICK_STROKE); + worker.drawLine(x1 - 2, y, x1 + 2, y, mGridColor, TICK_STROKE); + worker.drawLine(x0, y, x1, y, mGridColor, GRID_STROKE); + String graph_label = Util.sprintf("%3.0e", value * yloglab[majoridx][i]); + int length = (int) (worker.getStringWidth(graph_label, font)); + worker.drawString(graph_label, x0 - length - PADDING_VLABEL, y + labelOffset, font, fontColor); + } + } + return true; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/ValueAxisMrtg.java b/apps/jrobin/java/src/org/jrobin/graph/ValueAxisMrtg.java new file mode 100644 index 000000000..9788519cc --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/ValueAxisMrtg.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * 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.graph; + +import org.jrobin.core.Util; + +import java.awt.*; + +class ValueAxisMrtg implements RrdGraphConstants { + private ImageParameters im; + private ImageWorker worker; + private RrdGraphDef gdef; + + ValueAxisMrtg(RrdGraph rrdGraph) { + this.im = rrdGraph.im; + this.gdef = rrdGraph.gdef; + this.worker = rrdGraph.worker; + im.unit = gdef.unit; + } + + boolean draw() { + Font font = gdef.getFont(FONTTAG_AXIS); + Paint mGridColor = gdef.colors[COLOR_MGRID]; + Paint fontColor = gdef.colors[COLOR_FONT]; + int labelOffset = (int) (worker.getFontAscent(font) / 2); + + if (Double.isNaN((im.maxval - im.minval) / im.magfact)) { + return false; + } + + int xLeft = im.xorigin; + int xRight = im.xorigin + im.xsize; + String labfmt; + if (im.scaledstep / im.magfact * Math.max(Math.abs(im.quadrant), Math.abs(4 - im.quadrant)) <= 1.0) { + labfmt = "%5.2f"; + } + else { + labfmt = Util.sprintf("%%4.%df", 1 - ((im.scaledstep / im.magfact > 10.0 || Math.ceil(im.scaledstep / im.magfact) == im.scaledstep / im.magfact) ? 1 : 0)); + } + if (im.symbol != ' ' || im.unit != null) { + labfmt += " "; + } + if (im.symbol != ' ') { + labfmt += im.symbol; + } + if (im.unit != null) { + labfmt += im.unit; + } + for (int i = 0; i <= 4; i++) { + int y = im.yorigin - im.ysize * i / 4; + if (y >= im.yorigin - im.ysize && y <= im.yorigin) { + String graph_label = Util.sprintf(labfmt, im.scaledstep / im.magfact * (i - im.quadrant)); + int length = (int) (worker.getStringWidth(graph_label, font)); + worker.drawString(graph_label, xLeft - length - PADDING_VLABEL, y + labelOffset, font, fontColor); + worker.drawLine(xLeft - 2, y, xLeft + 2, y, mGridColor, TICK_STROKE); + worker.drawLine(xRight - 2, y, xRight + 2, y, mGridColor, TICK_STROKE); + worker.drawLine(xLeft, y, xRight, y, mGridColor, GRID_STROKE); + } + } + return true; + } + +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/ValueAxisSetting.java b/apps/jrobin/java/src/org/jrobin/graph/ValueAxisSetting.java new file mode 100644 index 000000000..272763438 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/ValueAxisSetting.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.graph; + +class ValueAxisSetting { + final double gridStep; + final int labelFactor; + + ValueAxisSetting(double gridStep, int labelFactor) { + this.gridStep = gridStep; + this.labelFactor = labelFactor; + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/ValueScaler.java b/apps/jrobin/java/src/org/jrobin/graph/ValueScaler.java new file mode 100644 index 000000000..a63a76e6c --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/ValueScaler.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * 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.graph; + +class ValueScaler { + static final String UNIT_UNKNOWN = "?"; + static final String UNIT_SYMBOLS[] = { + "a", "f", "p", "n", "u", "m", " ", "k", "M", "G", "T", "P", "E" + }; + static final int SYMB_CENTER = 6; + + private final double base; + private double magfact = -1; // nothing scaled before, rescale + private String unit; + + ValueScaler(double base) { + this.base = base; + } + + Scaled scale(double value, boolean mustRescale) { + Scaled scaled; + if (mustRescale) { + scaled = rescale(value); + } + else if (magfact >= 0) { + // already scaled, need not rescale + scaled = new Scaled(value / magfact, unit); + } + else { + // scaling not requested, but never scaled before - must rescale anyway + scaled = rescale(value); + // if zero, scale again on the next try + if (scaled.value == 0.0 || Double.isNaN(scaled.value)) { + magfact = -1.0; + } + } + return scaled; + } + + private Scaled rescale(double value) { + int sindex; + if (value == 0.0 || Double.isNaN(value)) { + sindex = 0; + magfact = 1.0; + } + else { + sindex = (int) (Math.floor(Math.log(Math.abs(value)) / Math.log(base))); + magfact = Math.pow(base, sindex); + } + if (sindex <= SYMB_CENTER && sindex >= -SYMB_CENTER) { + unit = UNIT_SYMBOLS[sindex + SYMB_CENTER]; + } + else { + unit = UNIT_UNKNOWN; + } + return new Scaled(value / magfact, unit); + } + + static class Scaled { + double value; + String unit; + + public Scaled(double value, String unit) { + this.value = value; + this.unit = unit; + } + + void dump() { + System.out.println("[" + value + unit + "]"); + } + } +} diff --git a/apps/jrobin/java/src/org/jrobin/graph/package.html b/apps/jrobin/java/src/org/jrobin/graph/package.html new file mode 100644 index 000000000..5b9630221 --- /dev/null +++ b/apps/jrobin/java/src/org/jrobin/graph/package.html @@ -0,0 +1,5 @@ + + + JRobin graph capabilities. + + \ No newline at end of file diff --git a/apps/jrobin/jrobin-1.5.9.1.jar b/apps/jrobin/jrobin-1.5.9.1.jar deleted file mode 100644 index b4ffea6ee..000000000 Binary files a/apps/jrobin/jrobin-1.5.9.1.jar and /dev/null differ diff --git a/apps/routerconsole/java/build.xml b/apps/routerconsole/java/build.xml index 6248346ac..76162ae04 100644 --- a/apps/routerconsole/java/build.xml +++ b/apps/routerconsole/java/build.xml @@ -30,7 +30,7 @@ - + @@ -74,7 +74,7 @@ - + @@ -121,7 +121,6 @@ - diff --git a/build.xml b/build.xml index 17806f4f9..cc1612800 100644 --- a/build.xml +++ b/build.xml @@ -358,7 +358,7 @@ - + @@ -401,16 +401,8 @@ - - - - - - - - - + + @@ -541,7 +533,7 @@ in the build* targets --> - + @@ -739,6 +731,7 @@ + @@ -766,6 +759,7 @@ + @@ -989,6 +983,7 @@ + @@ -1201,7 +1196,7 @@ - + @@ -1538,7 +1533,7 @@ - +