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 @@
+
+
+ *
+ * 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. + * @returntrue 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
+ * 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
+ *
+ *
+ *
+ * To create your own backend in order to provide some custom type of RRD storage,
+ * you should do the following:
+ *
+ *
+ *
+ *
+ * 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
+ *
+ *
+ * @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:
+ *
+ *
+ * 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:
+ *
+ * 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 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.
+ *
+ * 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):
+ *
+ *
+ * Than, use the file
+ *
+ * or:
+ *
+ *
+ * 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
+ *
+ * Note that the prefix
+ *
+ * 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):
+ *
+ *
+ * Than, use the file
+ *
+ * or:
+ *
+ *
+ * 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
+ *
+ * Note that the prefix
+ * 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
+ * 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
+ * Prepares fetch request to be executed on this RRD. Use returned
+ * Returns string representing complete internal RRD state. The returned
+ * string can be printed to 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.
+ * Suppose that you have a JRobin RRD file
+ *
+ * Use
+ *
+ * Example:
+ *
+ *
+ *
+ * @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
+ *
+ *
+ *
+// *
+// * 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:
+// *
+// *
+// *
+// * 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
+// *
+// * By default, the pool behaviour is 'flexible' (
+// *
+// * @param limitedCapacity
+ * RRD definition (RrdDef object) consists of the following elements:
+ *
+ *
+ *
+ * @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 Creates new RRD definition object with the given path.
+ * When this object is passed to
+ * Creates new RRD definition object with the given path and step. Creates new RRD definition object with the given path, starting timestamp
+ * and step.
+ * 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
+ *
+ *
+ *
+ * @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:
+ *
+ *
+ *
+ *
+ * Here is an example of a properly formatted XML template with all available
+ * options in it (unwanted options can be removed):
+ *
+ *
+ * Typical usage scenario:
+ *
+ *
+ * @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
+ * 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
+ */
+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
+ * Calling {@link RrdDb#close() close()} on RrdDb objects does not release any memory at all
+ * (RRD data must be available for the next
+ *
+ * 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 Before applying this method, be sure that the specified RRD file is not in use
+ * (not open) 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 Before applying this method, be sure that the specified RRD file is not in use
+ * (not open) 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 Before applying this method, be sure that the specified RRD file is not in use
+ * (not open) 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 Before applying this method, be sure that the specified RRD file is not in use
+ * (not open)
+ * To update a RRD with JRobin use the following procedure:
+ *
+ *
+ * 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
+ * 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:
+ *
+ * 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 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:
+ *
+ *
+ *
+ * @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:
+ *
+ * @param atStyleTimeSpec2 Ending at-style time specification. For the complete explanation of the syntax
+ * allowed see RRDTool's
+ * @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
+ * 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
+ */
+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
+ *
+ */
+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:
+ *
+ *
+ *
+ * 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:
+ *
+ *
+ * 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
+ * Complex source
+ * 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 Adds simple source (DEF). Source
+ * 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:
+ *
+ * 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 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.
+ * 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
+ * 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.
+ *
+ *
+ * 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:
+ *
+ * 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
+ * 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):
+ *
+ *
+ *
+ * Typical usage scenario:
+ *
+ * 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[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:
+ *
+ *
+ *
+ *
+ */
+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.
+ *
+ *
+ *
+ *
+ * @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.
+ * // 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.
+ *
+ *
+ *
+ * RrdBackendFactory factory = RrdBackendFactory.getFactory("MEMORY");
+ * RrdDb rrdDb = new RrdDb(rrdDef, factory);
+ * rrdDb.close();
+ *
+ * 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;
+ }
+ }
+
+ /**
+ *
+ *
+ * rrdtool dump command).
+ *
+ * rrdtool dump original.rrd > original.xml
+ *
+ * original.xml to create JRobin RRD file named
+ * copy.rrd:
+ *
+ * RrdDb rrd = new RrdDb("copy.rrd", "original.xml");
+ *
+ *
+ * RrdDb rrd = new RrdDb("copy.rrd", "xml:/original.xml");
+ *
+ * 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");
+ *
+ * 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:
+ *
+ *
+ * rrdtool dump command).
+ *
+ * rrdtool dump original.rrd > original.xml
+ *
+ * original.xml to create JRobin RRD file named
+ * copy.rrd:
+ *
+ * RrdDb rrd = new RrdDb("copy.rrd", "original.xml");
+ *
+ *
+ * RrdDb rrd = new RrdDb("copy.rrd", "xml:/original.xml");
+ *
+ * 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");
+ *
+ * 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.
+ * 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.
+ * FetchRequest object and its {@link org.jrobin.core.FetchRequest#fetchData() fetchData()}
+ * method to actually fetch data from the RRD file.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).stdout and/or used for debugging purposes.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.
+ * 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");
+ * 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.
+ *
+ * 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 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.
+// * // 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.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 HashMapTrue, 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 {
+// Iteratortrue, 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");
+// Iteratortrue 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.limitedCapacity property defaults
+// * to falsetrue 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.
+ *
+ *
+ * RrdDef provides API to set all these elements. For the complete explanation of all
+ * RRD definition parameters, see RRDTool's
+ * rrdcreate man page.
+ * RrdDb constructor, new RRD will be created using the
+ * specified path. 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.
+ * 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.
+ *
+ * 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
+ *
+ *
+ * @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.
+ *
+ * <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.
+ *
+ *
+ * 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.
+ * <rrd_def>
+ * <path>${path}</path>
+ * <step>300</step>
+ * ...
+ *
+ *
+ * RrdDefTemplate t = new RrdDefTemplate(new File(template.xml));
+ *
+ *
+ * t.setVariable("path", "demo/test.rrd");
+ *
+ *
+ * RrdDef def = t.getRrdDef();
+ * RrdDb rrd = new RrdDb(def);
+ * rrd.close();
+ *
+ * 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.
+ * 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.
+ * 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 HashMapfalse
+ */
+ 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).
+ * true). The backup file will be created in the same
+ * directory as the original one with .bak extension added to the
+ * original name.true). The backup file will be created in the same
+ * directory as the original one with .bak extension added to the
+ * original name.true). The backup file will be created in the same
+ * directory as the original one with .bak extension added to the
+ * original name.true). The backup file will be created in the same
+ * directory as the original one with .bak extension added to the
+ * original name.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
+ *
+ * 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.
+ *
+ * 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')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();
+ }
+
+ /**
+ *
+ * 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(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.
+ * 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.rrdfetch man page.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 ArrayListgetLapTime() 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.
+ * 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.${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<tag> and </tag>
+ */
+ public void writeTag(String tag, Object value) {
+ if (value != null) {
+ writer.println(indent + "<" + tag + ">" +
+ escape(value.toString()) + "" + tag + ">");
+ }
+ else {
+ writer.println(indent + "<" + tag + ">" + 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;
+ ArrayListCDPStatusBlock 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 IteratorConsolidationFunctionType 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("\tDataSourceType 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;
+ ArrayListHeader 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 IteratorArchive 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 Iteratordouble
+ * 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 {
+
+ ArrayListdoubles 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
+ *
+ * The current timestamp is displayed in the title bar :)
");
+ 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
+ * 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
+ * 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 MapRPN expression.
+ * name can be used:
+ *
+ *
+ * name
+ * can be used:
+ *
+ *
+ * @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);
+ }
+
+ /**
+ * name can be used:
+ *
+ *
+ * @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
+ * (t, 100) and (t + 100, 300). Here are the results interpolator
+ * returns for t + 50 seconds, for various interpolationMethods:
+ *
+ *
+ * If not set, interpolation method defaults to INTERPOLATE_LEFT: 100
+ * INTERPOLATE_RIGHT: 300
+ * INTERPOLATE_LINEAR: 200
+ * INTERPOLATE_LINEAR.
+ * 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;
+
+/**
+ *
+ * setTimeAxis(RrdGraphConstants.MINUTE, 10,
+ * RrdGraphConstants.HOUR, 1,
+ * RrdGraphConstants.HOUR, 1,
+ * 0, "%H:%M")
+ *
+ *
+ * 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.
+ * 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.
+ *
+ * <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:
+ *
+ *
+ * Any template value (text between true, on, yes, y,
+ * or 1 to specify boolean true value (anything else will
+ * be treated as false).
+ * <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.
+ *
+ *
+ * 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
+ * <rrd_graph_def>
+ * ...
+ * <span>
+ * <start>${start}</start>
+ * <end>${end}</end>
+ * </span>
+ * ...
+ *
+ *
+ * RrdGraphDefTemplate t = new RrdGraphDefTemplate(new File(template.xml));
+ *
+ *
+ * t.setVariable("start", new GregorianCalendar(2004, 2, 25));
+ * t.setVariable("end", new GregorianCalendar(2004, 2, 26));
+ *
+ *
+ * RrdGraphDef gdef = t.getRrdGraphDef();
+ * RrdGraph g = new RrdGraph(gdef);
+ *
+ *