diff --git a/apps/jrobin/java/src/com/tomgibara/crinch/hashing/Hash.java b/apps/jrobin/java/src/com/tomgibara/crinch/hashing/Hash.java new file mode 100644 index 0000000000..f389ae83df --- /dev/null +++ b/apps/jrobin/java/src/com/tomgibara/crinch/hashing/Hash.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010 Tom Gibara + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.tomgibara.crinch.hashing; + +import java.math.BigInteger; + +/** + *
+ * Implementations of this interface can generate one hash value for a given + * object. Depending upon the implementation, null values may be supported. + *
+ * + * @author tomgibara + * + * @param+ * A "minimal perfect hash" for Strings. After construction with an array of + * n unique non-null strings, an instance of this class will return a + * unique hash value h (0 <= h < n) for any string s in the + * array. A negative has value will typically be returned for a string that is + * not in the array. + *
+ * + *+ * However, the supplied array is not retained. This means that the + * implementation cannot necessarily confirm that a string is not in the + * supplied array. Where this implementation cannot distinguish that a string is + * not in the array, a 'valid' hash value may be returned. Under no + * circumstances will a hash value be returned that is greater than or equal to + * n. + *
+ * + *
+ * IMPORTANT NOTE: The array of strings supplied to the
+ * constructor will be mutated: it is re-ordered so that
+ * hash(a[i]) == i
. Application code must generally use this
+ * information to map hash values back onto the appropriate string value.
+ *
+ * NOTE: Good performance of this algorithm is predicated on
+ * string hash values being cached by the String
class. Experience
+ * indicates that is is a good assumption.
+ *
hash(values[i]) == i
.
+ */
+
+ public PerfectStringHash(final String values[]) {
+ final int length = values.length;
+ if (length == 0) throw new IllegalArgumentException("No values supplied");
+
+ final int[] hashes = new int[length];
+ final int[] offsets = new int[2 * length];
+ final int[] runLengths = new int[length];
+
+ //sort values so that we can assume ordering by hashcode, length and char[]
+ Arrays.sort(values, comparator);
+
+ //pull the hashcodes into an array for analysis
+ for (int i = 0; i < length; i++) hashes[i] = values[i].hashCode();
+
+ //test for unique hashes
+ int offset = 0;
+ if (length > 1) {
+ int previousHash = hashes[0];
+ int runLength = 1;
+ for (int i = 1; i <= length; i++) {
+ int currentHash = i == length ? ~previousHash : hashes[i];
+ if (currentHash == previousHash) {
+ runLength++;
+ } else {
+ if (runLength > 1) {
+ final int firstIndex = i - runLength;
+ for (int j = i - 1; j >= firstIndex; j--) {
+ runLengths[j] = runLength;
+ //offset points to the first node in decision tree
+ offsets[ j<<1 ] = offset;
+ //adjustment is number of indices to first duplicate
+ offsets[(j<<1) + 1] = j - firstIndex;
+ }
+ //extra one for recording depth
+ offset += (Integer.highestOneBit(runLength - 1) << 1);
+ runLength = 1;
+ } else {
+ runLengths[i-1] = 1;
+ offsets[(i-1)<<1] = -1;
+ }
+ }
+ previousHash = currentHash;
+ }
+ }
+
+ //shortcut for when all hashes are unique
+ if (offset == 0) {
+ this.hashes = hashes;
+ this.offsets = null;
+ this.pivots = null;
+ this.range = new HashRange(0, length - 1);
+ return;
+ }
+
+ //build the decision trees
+ final int[] pivots = new int[offset * 2];
+ for (int i = 0; i < length;) {
+ final int runLength = runLengths[i];
+ if (runLength > 1) generatePivots(values, i, runLength, pivots, (int) offsets[i << 1]);
+ i += runLength;
+ }
+
+ //setup our state
+ this.pivots = pivots;
+ this.offsets = offsets;
+ this.hashes = hashes;
+ this.range = new HashRange(0, length - 1);
+ }
+
+ // hash generator methods
+
+ @Override
+ public HashRange getRange() {
+ return range;
+ }
+
+ @Override
+ public BigInteger hashAsBigInt(String value) {
+ return BigInteger.valueOf(hash(value));
+ }
+
+ //TODO decide whether to throw an IAE if -1 is returned from hash
+ @Override
+ public int hashAsInt(String value) {
+ return hash(value);
+ }
+
+ @Override
+ public long hashAsLong(String value) {
+ return hash(value);
+ }
+
+ /**
+ * Generates a hashcode for the supplied string.
+ *
+ * @param value
+ * any string, not null
+ * @return a minimal hashcode for the supplied string, or -1
+ */
+
+ private int hash(String value) {
+ final int h = value.hashCode();
+ final int index = Arrays.binarySearch(hashes, h);
+ final int[] pivots = this.pivots;
+ if (pivots == null || index < 0) return index;
+
+ final int offset = offsets[index << 1];
+ if (offset == -1) return index;
+
+ final int depth = pivots[(offset << 1) ];
+ final int count = pivots[(offset << 1) + 1];
+ int i = 0;
+ for (int d = 0; d < depth; d++) {
+ final int t = (offset + (1 << d) + i) << 1;
+ final int part = pivots[t ];
+ final int comp = pivots[t + 1];
+ final boolean right;
+ if (part == Integer.MIN_VALUE) { //easy case - no right value
+ right = false;
+ } else if (part == -1) { //compare length
+ right = value.length() > comp;
+ } else { //lengths are equal, compare character
+ right = value.charAt(part) > (char) comp;
+ }
+ i <<= 1;
+ if (right) i++;
+ }
+ return i >= count ? -1 : index + i - offsets[(index << 1) + 1];
+ }
+
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/ArcDef.java b/apps/jrobin/java/src/org/rrd4j/core/ArcDef.java
new file mode 100644
index 0000000000..1be7c07612
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/ArcDef.java
@@ -0,0 +1,133 @@
+package org.rrd4j.core;
+
+import org.rrd4j.ConsolFun;
+
+/**
+ * Class to represent single archive definition within the RRD.
+ * Archive definition consists of the following four elements:
+ *
+ * For the complete explanation of all archive definition parameters, see RRDTool's + * rrdcreate man page
+ * + * @param consolFun Consolidation function. Allowed values are "AVERAGE", "MIN", + * "MAX", "LAST" and "TOTAL" (these string constants are conveniently defined in the + * {@link org.rrd4j.ConsolFun} class). + * @param xff X-files factor, between 0 and 1. + * @param steps Number of archive steps. + * @param rows Number of archive rows. + */ + public ArcDef(ConsolFun consolFun, double xff, int steps, int rows) { + if (consolFun == null) { + throw new IllegalArgumentException("Null consolidation function specified"); + } + if (Double.isNaN(xff) || xff < 0.0 || xff >= 1.0) { + throw new IllegalArgumentException("Invalid xff, must be >= 0 and < 1: " + xff); + } + if (steps < 1 || rows < 2) { + throw new IllegalArgumentException("Invalid steps/rows settings: " + steps + "/" + rows + + ". Minimal values allowed are steps=1, rows=2"); + } + + this.consolFun = consolFun; + this.xff = xff; + this.steps = steps; + this.rows = rows; + } + + /** + * Returns consolidation function. + * + * @return Consolidation function. + */ + public ConsolFun 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; + } + + /** + * Returns string representing archive definition (RRDTool format). + * + * @return String containing all archive definition parameters. + */ + public String dump() { + return "RRA:" + consolFun + ":" + xff + ":" + steps + ":" + rows; + } + + /** + * {@inheritDoc} + * + * 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. + */ + public boolean equals(Object obj) { + if (obj instanceof ArcDef) { + ArcDef arcObj = (ArcDef) obj; + return consolFun == arcObj.consolFun && steps == arcObj.steps; + } + return false; + } + + @Override + public int hashCode() { + return consolFun.hashCode() + steps * 19; + } + + void setRows(int rows) { + this.rows = rows; + } + + boolean exactlyEqual(ArcDef def) { + return consolFun == def.consolFun && xff == def.xff && steps == def.steps && rows == def.rows; + } + +} diff --git a/apps/jrobin/java/src/org/rrd4j/core/ArcState.java b/apps/jrobin/java/src/org/rrd4j/core/ArcState.java new file mode 100644 index 0000000000..7a184fc8ec --- /dev/null +++ b/apps/jrobin/java/src/org/rrd4j/core/ArcState.java @@ -0,0 +1,109 @@ +package org.rrd4j.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 Rrd4j to manage internal archive states. + * + * @author Sasa Markovic + */ +public class ArcState implements RrdUpdater
+ * 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 these parts.
+ *
+ * @author Sasa Markovic
+ */
+public class Archive implements RrdUpdater An abstract class to import data from external source. 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 manipulate Datasource objects directly, it's up to
+ * Rrd4j framework to do it for you. Setter for the field 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.
+ * 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 org.rrd4j.DsType} class).
+ * @param heartbeat Hearbeat
+ * @param minValue Minimal value. Use
+ *
+ * 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.
+ *
+ *
+ * @author Sasa Markovic
+ */
+@SuppressWarnings("deprecation")
+public class FetchData {
+ // anything funny 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.
+ */
+ public double[] getValues(String dsName) {
+ for (int dsIndex = 0; dsIndex < getColumnCount(); dsIndex++) {
+ if (dsName.equals(dsNames[dsIndex])) {
+ return getValues(dsIndex);
+ }
+ }
+ throw new IllegalArgumentException("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
+ *
+ *
+ *
+ *
+ * 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
+ * You cannot create
+ *
+ * Normally, you don't need to manipulate the Header object directly - Rrd4j framework
+ * does it for you.
+ *
+ *
+ * @author Sasa Markovic*
+ */
+public class Header implements RrdUpdater getInfo. setInfo.
+ * 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 interface Robin extends RrdUpdater update. dump. store. bulkStore. getValues.
+ * Getter for the field update. dump. getParent. getSize. getRrdBackend. getRrdAllocator.
+ * update. dump. Base implementation class for all backend classes. Each Round Robin Database object
+ * ({@link org.rrd4j.core.RrdDb} object) is backed with a single RrdBackend object which performs
+ * actual I/O operations on the underlying storage. Rrd4j supports
+ * multiple backends out of the box. E.g.: 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 org.rrd4j.core.RrdBackend} implementations.
+ * Each factory creates unlimited number of specific backend objects.
+ *
+ * Rrd4j supports six different backend types (backend factories) out of the box:
+ *
+ * Each backend factory used to be identified by its {@link #getName() name}. Constructors
+ * are provided in the {@link org.rrd4j.core.RrdDb} class to create RrdDb objects (RRD databases)
+ * backed with a specific backend.
+ *
+ * A more generic management was added in version 3.2 that allows multiple instances of a backend to be used. Each backend can
+ * manage custom URL. They are tried in the declared order by the {@link #setActiveFactories(RrdBackendFactory...)} or
+ * {@link #addFactories(RrdBackendFactory...)} and the method {@link #canStore(URI)} return true when it can manage the given
+ * URI. Using {@link #setActiveFactories(RrdBackendFactory...)} with new created instance is the preferred way to manage factories, as
+ * it provides a much precise control of creation and end of life of factories.
+ *
+ * Since 3.4, using only {@link #setActiveFactories(RrdBackendFactory...)} and {@link #addActiveFactories(RrdBackendFactory...)} will not register any
+ * named backend at all. {@link #getDefaultFactory()} will return the first active factory. All methods using named backend and the registry of factory were deprecated.
+ *
+ * For default implementation, the path is separated in a root URI prefix and the path components. The root URI can be
+ * used to identify different name spaces or just be `/`.
+ *
+ * See javadoc for {@link org.rrd4j.core.RrdBackend} to find out how to create your custom backends.
+ *
+ */
+public abstract class RrdBackendFactory implements Closeable {
+
+ private static final class Registry {
+ private static final Map
+ * It also clear the list of actives factories and set it to the default factory.
+ *
+ *
+ * @deprecated Uses active factory instead
+ * @param factoryName Name of the default factory..
+ */
+ @Deprecated
+ public static synchronized void setDefaultFactory(String factoryName) {
+ // We will allow this only if no RRDs are created
+ if (!RrdBackend.isInstanceCreated()) {
+ activeFactories.clear();
+ activeFactories.add(getFactory(factoryName));
+ } else {
+ throw new IllegalStateException(
+ "Could not change the default backend factory. "
+ + "This method must be called before the first RRD gets created");
+ }
+ }
+
+ /**
+ * Set the list of active factories, i.e. the factory used to resolve URI.
+ *
+ * @param newFactories the new active factories.
+ */
+ public static synchronized void setActiveFactories(RrdBackendFactory... newFactories) {
+ activeFactories.clear();
+ activeFactories.addAll(Arrays.asList(newFactories));
+ }
+
+ /**
+ * Add factories to the list of active factories, i.e. the factory used to resolve URI.
+ *
+ * @deprecated Uses {@link #addActiveFactories(RrdBackendFactory...)} instead.
+ * @param newFactories active factories to add.
+ */
+ @Deprecated
+ public static synchronized void addFactories(RrdBackendFactory... newFactories) {
+ addActiveFactories(newFactories);
+ }
+
+ /**
+ * Add factories to the list of active factories, i.e. the factory used to resolve URI.
+ *
+ * @param newFactories active factories to add.
+ */
+ public static synchronized void addActiveFactories(RrdBackendFactory... newFactories) {
+ activeFactories.addAll(Arrays.asList(newFactories));
+ }
+
+ /**
+ * For a given URI, try to find a factory that can manage it in the list of active factories.
+ *
+ * @param uri URI to try.
+ * @return a {@link RrdBackendFactory} that can manage that URI.
+ * @throws IllegalArgumentException when no matching factory is found.
+ */
+ public static synchronized RrdBackendFactory findFactory(URI uri) {
+ // If no active factory defined, will try the default factory
+ if (activeFactories.isEmpty() && Registry.defaultFactory.canStore(uri)) {
+ return Registry.defaultFactory;
+ } else {
+ for (RrdBackendFactory tryfactory: activeFactories) {
+ if (tryfactory.canStore(uri)) {
+ return tryfactory;
+ }
+ }
+ throw new IllegalArgumentException(
+ "no matching backend factory for " + uri);
+ }
+ }
+
+ private static final Pattern URIPATTERN = Pattern.compile("^(?:(? Constructor for RrdByteArrayBackend. read. It will reserves a memory section as a RRD storage. Main class used to create and manipulate round robin databases (RRDs). Use this class to perform
+ * update and fetch operations on existing RRDs, to create new RRD from
+ * the definition (object of class {@link org.rrd4j.core.RrdDef RrdDef}) or
+ * from XML file (dumped content of RRDTool's or Rrd4j'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, Rrd4j gives you freedom to use other storage (backend) types
+ * even to create your own backend types for some special purposes. Rrd4j by default stores
+ * RRD data in files (as RRDTool), but you might choose to store RRD data in memory (this is
+ * supported in Rrd4j), 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 Rrd4j 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 Rrd4j and RRDTool. You will not be able to use Rrd4j 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 Rrd4j,
+ * there is an excellent
+ * CDEF tutorial. Constructor used to create new RRD object from the definition. If the rrdDef was constructed
+ * giving an {@link java.net.URI}, {@link org.rrd4j.core.RrdBackendFactory#findFactory(URI)} will be used to resolve the needed factory. If not, or a relative
+ * URI was given, 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 org.rrd4j.core.RrdBackendFactory#setDefaultFactory(String)} method call. New RRD file structure is specified with an object of class
+ * {@link RrdDef RrdDef}. The underlying RRD storage is created as soon
+ * as the constructor returns. Typical scenario: Returns a new RRD object from the definition. If the rrdDef was constructed
+ * giving an {@link java.net.URI}, {@link org.rrd4j.core.RrdBackendFactory#findFactory(URI)} will be used to resolve
+ * the needed factory. If not, or a relative URI was given, 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 org.rrd4j.core.RrdBackendFactory#setDefaultFactory(String)} method call. New RRD file structure is specified with an object of class
+ * {@link RrdDef RrdDef}. The underlying RRD storage is created as soon
+ * as the method returns. Typical scenario: Constructor used to create new RRD object from the definition object but with a storage
+ * (backend) different from default. Rrd4j uses factories to create RRD backend objects. There are three different
+ * backend factories supplied with Rrd4j, 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 RrdDef RrdDef}. The underlying RRD storage is created as soon
+ * as the constructor returns. Constructor used to open already existing RRD. The path will be parsed as an URI and checked against the active factories. If
+ * it's a relative URI (no scheme given, or just a plain path), the default factory will be used. Constructor obtains read or read/write access to this RRD. Constructor used to open already existing RRD. The URI will checked against the active factories. If
+ * it's a relative URI (no scheme given, or just a plain path), the default factory will be used. Constructor obtains read or read/write access to this RRD. 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. Constructor used to open already existing RRD. The path will be parsed as an URI and checked against the active factories. If
+ * it's a relative URI (no scheme given, or just a plain path), the default factory will be used. Constructor obtains read/write access to this RRD. Opens an existing RRD with read/write access.
+ * The path will be parsed as an URI and checked against the active factories.
+ * If it's a relative URI (no scheme given, or just a plain path), the default factory will be used. Constructor used to open already existing RRD. The URI will checked against the active factories. If
+ * it's a relative URI (no scheme given, or just a plain path), the default factory will be used. Constructor obtains read/write access to this RRD. Opens an existing RRD with read/write access.
+ * The URI will checked against the active factories.
+ * If it's a relative URI (no scheme given, or just a plain path), the default factory will be used. Constructor used to create RRD files from external file sources.
+ * Supported external file sources are: The path for the new rrd will be parsed as an URI and checked against the active factories. If
+ * it's a relative URI (no scheme given, or just a plain path), the default factory will be used. Rrd4j and RRDTool use the same format for XML dump and this constructor should be used to
+ * (re)create Rrd4j 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(String) dumpXml()} method
+ * to see how to convert Rrd4j files to RRDTool's format. To read RRDTool files directly, specify Note that the prefix Constructor used to create RRD files from external file sources.
+ * Supported external file sources are: The path for the new rrd will be parsed as an URI and checked against the active factories. If
+ * it's a relative URI (no scheme given, or just a plain path), the default factory will be used. Rrd4j and RRDTool use the same format for XML dump and this constructor should be used to
+ * (re)create Rrd4j 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(String) dumpXml()} method
+ * to see how to convert Rrd4j files to RRDTool's format. To read RRDTool files directly, specify Note that the prefix Constructor used to create RRD files from external file sources with a backend type
+ * different from default. Supported external file sources are: Rrd4j and RRDTool use the same format for XML dump and this constructor should be used to
+ * (re)create Rrd4j 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(String) dumpXml()} method
+ * to see how to convert Rrd4j files to RRDTool's format. To read RRDTool files directly, specify Note that the prefix Creates new sample with the given timestamp and all datasource values set to
+ * 'unknown'. Use returned Once populated with data source values, call Sample's
+ * {@link org.rrd4j.core.Sample#update() update()} method to actually
+ * store sample in the RRD associated with it. 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.rrd4j.core.Sample#update() update()} method to actually
+ * store sample in the RRD associated with it. Suppose that you have a Rrd4j RRD file
+ * Use Returns RRD definition object which can be used to create new RRD
+ * with the same creation parameters but with no data in it. Example:
+ * Copies object's internal state to another RrdDb object.
+ */
+ public synchronized void copyStateTo(RrdDb otherRrd) throws IOException {
+ 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 java.io.IOException Thrown in case of I/O error
+ */
+ public Datasource getDatasource(String dsName) throws IOException {
+ try {
+ return getDatasource(getDsIndex(dsName));
+ } catch (IllegalArgumentException 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 java.io.IOException Thrown in case of I/O error
+ */
+ public int getArcIndex(ConsolFun consolFun, int steps) throws IOException {
+ for (int i = 0; i < archives.length; i++) {
+ if (archives[i].getConsolFun() == consolFun && archives[i].getSteps() == steps) {
+ return i;
+ }
+ }
+ throw new IllegalArgumentException("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 java.io.IOException Thrown in case of I/O error
+ */
+ public Archive getArchive(ConsolFun consolFun, int steps) throws IOException {
+ try {
+ return getArchive(getArcIndex(consolFun, steps));
+ } catch (IllegalArgumentException 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 java.io.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 RrdBackendException("The underlying backend has no canonical path");
+ }
+ }
+
+ /**
+ * Returns the path to this RRD.
+ *
+ * @return Path to this RRD.
+ */
+ public String getPath() {
+ return backend.getPath();
+ }
+
+ /**
+ * Returns the URI to this RRD, as seen by the backend.
+ *
+ * @return URI to this RRD.
+ */
+ public URI getUri() {
+ return backend.getUri();
+ }
+
+ /**
+ * 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 java.io.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 org.rrd4j.core.RrdBackendFactory#setDefaultFactory(String)}.
+ *
+ * @param factoryName Name of the backend factory to be set as default.
+ * @throws java.lang.IllegalArgumentException Thrown if invalid factory name is supplied, or not called
+ * before the first backend object (before the first RrdDb object) is created.
+ * @deprecated uses {@link RrdBackendFactory#setActiveFactories(RrdBackendFactory...)} instead.
+ */
+ @Deprecated
+ public static void setDefaultFactory(String factoryName) {
+ 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 java.io.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 java.io.IOException Thrown in case of I/O error
+ * @throws java.lang.IllegalArgumentException Thrown if no datasource in this RrdDb matches the given datasource name
+ */
+ public synchronized double getLastDatasourceValue(String dsName) throws IOException {
+ 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 archives defined in the file
+ *
+ * @return The number of RRA archives 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 java.io.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;
+ }
+
+ /**
+ * getInfo.
+ *
+ * @return a {@link java.lang.String} object.
+ * @throws java.io.IOException if any.
+ */
+ public synchronized String getInfo() throws IOException {
+ return header.getInfo();
+ }
+
+ /**
+ * setInfo.
+ *
+ * @param info a {@link java.lang.String} object.
+ * @throws java.io.IOException if any.
+ */
+ public synchronized void setInfo(String info) throws IOException {
+ header.setInfo(info);
+ }
+
+ /**
+ * main.
+ *
+ * @param args an array of {@link java.lang.String} objects.
+ */
+ public static void main(String[] args) {
+ System.out.println("RRD4J :: RRDTool choice for the Java world");
+ System.out.println("===============================================================================");
+ System.out.println("RRD4J base directory: " + Util.getRrd4jHomeDirectory());
+ long time = Util.getTime();
+ System.out.println("Current time: " + time + ": " + new Date(time * 1000L));
+ System.out.println("-------------------------------------------------------------------------------");
+ System.out.println("See https://github.com/rrd4j/rrd4j for more information and the latest version.");
+ System.out.println("Copyright 2017 The RRD4J Authors. Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. Copyright (c) 2013 The OpenNMS Group, Inc.. Licensed under the Apache License, Version 2.0.");
+ }
+
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java b/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java
new file mode 100644
index 0000000000..292338162b
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java
@@ -0,0 +1,563 @@
+package org.rrd4j.core;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * This class should be used to synchronize access to RRD files
+ * in a multithreaded environment. This class should be also used to prevent opening of
+ * too many RRD files at the same time (thus avoiding operating system limits).
+ * It should not be called directly. Use {@link RrdDb.Builder#usePool()} instead. Requests a RrdDb reference for the given RRD file path. The path is transformed internally to URI using the default factory, that is the reference that will
+ * be used elsewhere. Requests a RrdDb reference for the given RRD file path. Requests a RrdDb reference for the given RRD file definition object. Requests a RrdDb reference for the given path. The file will be created from
+ * external data (from XML dump or RRDTool's binary RRD file). The path is transformed internally to URI using the default factory, that is the reference that will
+ * be used elsewhere. Requests a RrdDb reference for the given path. The file will be created from
+ * external data (from XML dump or RRDTool's binary RRD file). The path is transformed internally to URI using the default factory, that is the reference that will
+ * be used elsewhere. 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 RRD definition (RrdDef object) consists of the following elements: RrdDef provides API to set all these elements. For the complete explanation of all
+ * RRD definition parameters, see RRDTool's
+ * rrdcreate man page. Creates new RRD definition object with the given path.
+ * When this object is passed to
+ * The will be transformed internally to an URI using the default backend factory. Creates new RRD definition object with the given path and step. The will be transformed internally to an URI using the default backend factory. Creates new RRD definition object with the given path, starting timestamp
+ * and step. The will be transformed internally to an URI using the default backend factory. Creates new RRD definition object with the given path, starting timestamp,
+ * step and version. The will be transformed internally to an URI using the default backend factory. Sets path to RRD. The will be transformed internally to an URI using the default backend factory. Adds single datasource to RRD definition by specifying its data source name, source type,
+ * heartbeat, minimal and maximal value. For the complete explanation of all data
+ * source definition parameters see RRDTool's
+ * rrdcreate man page. IMPORTANT NOTE: If datasource name ends with '!', corresponding archives will never
+ * store NaNs as datasource values. In that case, NaN datasource values will be silently
+ * replaced with zeros by the framework. 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: For example: For more information on datasource definition parameters see 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: For example: For more information on archive definition parameters see Exports RrdDef object to output stream in XML format. Generated XML code can be parsed
+ * with {@link org.rrd4j.core.RrdDefTemplate} class. It use a format compatible with previous RRD4J's version, using
+ * a path, instead of an URI. If Exports RrdDef object to string in XML format. Generated XML string can be parsed
+ * with {@link org.rrd4j.core.RrdDefTemplate} class. If Exports RrdDef object to string in XML format. Generated XML string can be parsed
+ * with {@link org.rrd4j.core.RrdDefTemplate} class. It use a format compatible with previous RRD4J's version, using
+ * a path, instead of an URI. Exports RrdDef object to a file in XML format. Generated XML code can be parsed
+ * with {@link org.rrd4j.core.RrdDefTemplate} class. It use a format compatible with previous RRD4J's version, using
+ * a path, instead of an URI. Exports RrdDef object to a file in XML format. Generated XML code can be parsed
+ * with {@link org.rrd4j.core.RrdDefTemplate} class. If Compares the current RrdDef with another. RrdDefs are considered equal if: hasDatasources. hasArchives. Class used to create an arbitrary number of {@link org.rrd4j.core.RrdDef} (RRD definition) objects
+ * from a single XML template. XML template can be supplied as an XML InputSource,
+ * XML file or XML formatted string. Here is an example of a properly formatted XML template with all available
+ * options in it (unwanted options can be removed): Notes on the template syntax: Any template value (text between 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 java.lang.IllegalArgumentException Thrown (in most cases) if the value for some placeholder
+ * was not supplied through {@link org.rrd4j.core.XmlTemplate#setVariable(String, String) setVariable()}
+ * method call
+ */
+ public RrdDef getRrdDef() {
+ if (!"rrd_def".equals(root.getTagName())) {
+ throw new IllegalArgumentException("XML definition must start with set. Getter for the field Getter for the field
+ * Every backend storing RRD data as ordinary files should inherit from it, some check are done
+ * in the code for instanceof.
+ *
+ */
+public interface RrdFileBackend {
+
+ /**
+ * Returns canonical path to the file on the disk.
+ *
+ * @return Canonical file path
+ * @throws java.io.IOException Thrown in case of I/O error
+ */
+ String getCanonicalPath() throws IOException;
+
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdFileBackendFactory.java b/apps/jrobin/java/src/org/rrd4j/core/RrdFileBackendFactory.java
new file mode 100644
index 0000000000..0664a46a21
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdFileBackendFactory.java
@@ -0,0 +1,73 @@
+package org.rrd4j.core;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * An abstract backend factory which is used to store RRD data to ordinary files on the disk.
+ *
+ * Every backend factory storing RRD data as ordinary files should inherit from it, some check are done
+ * in the code for instanceof.
+ *
+ */
+public abstract class RrdFileBackendFactory extends RrdBackendFactory {
+
+ /**
+ * {@inheritDoc}
+ *
+ * Method to determine if a file with the given path already exists.
+ */
+ @Override
+ protected boolean exists(String path) {
+ return Util.fileExists(path);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean canStore(URI uri) {
+ if ((uri.isOpaque() || uri.isAbsolute()) && ! "file".equals(uri.getScheme())) {
+ return false;
+ } else if (uri.getAuthority() != null || uri.getFragment() != null || uri.getQuery() != null) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public URI getCanonicalUri(URI uri) {
+ try {
+ if (uri.isOpaque()) {
+ return new File(uri.getSchemeSpecificPart()).getCanonicalFile().toURI();
+ } else if (uri.isAbsolute()) {
+ return new File(uri).getCanonicalFile().toURI();
+ } else {
+ return new File(uri.getPath()).getCanonicalFile().toURI();
+ }
+ } catch (IOException e) {
+ throw new IllegalArgumentException("can't get canonical URI from " + uri + ": " + e);
+ }
+ }
+
+ @Override
+ public URI getUri(String path) {
+ try {
+ return new File(path).getCanonicalFile().toURI();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("can't get canonical URI from path " + path + ": " + e);
+ }
+ }
+
+ @Override
+ public String getPath(URI uri) {
+ if (uri.isOpaque()) {
+ return uri.getSchemeSpecificPart();
+ } else if (uri.isAbsolute()) {
+ return new File(uri).getPath();
+ } else {
+ return uri.getPath();
+ }
+ }
+
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdInt.java b/apps/jrobin/java/src/org/rrd4j/core/RrdInt.java
new file mode 100644
index 0000000000..6a081c2145
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdInt.java
@@ -0,0 +1,41 @@
+package org.rrd4j.core;
+
+import java.io.IOException;
+
+class RrdInt> extends RrdPrimitive {
+ private int cache;
+ private boolean cached = false;
+
+ RrdInt(RrdUpdater updater, boolean isConstant) throws IOException {
+ super(updater, RrdPrimitive.RRD_INT, isConstant);
+ }
+
+ RrdInt(RrdUpdater updater) throws IOException {
+ this(updater, false);
+ }
+
+ void set(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 {
+ if (!isCachingAllowed()) {
+ return readInt();
+ }
+ else {
+ if (!cached) {
+ cache = readInt();
+ cached = true;
+ }
+ return cache;
+ }
+ }
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdLong.java b/apps/jrobin/java/src/org/rrd4j/core/RrdLong.java
new file mode 100644
index 0000000000..3f81e991a1
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdLong.java
@@ -0,0 +1,41 @@
+package org.rrd4j.core;
+
+import java.io.IOException;
+
+class RrdLong> extends RrdPrimitive {
+ private long cache;
+ private boolean cached = false;
+
+ RrdLong(RrdUpdater updater, boolean isConstant) throws IOException {
+ super(updater, RrdPrimitive.RRD_LONG, isConstant);
+ }
+
+ RrdLong(RrdUpdater updater) throws IOException {
+ this(updater, false);
+ }
+
+ void set(long value) throws IOException {
+ if (!isCachingAllowed()) {
+ writeLong(value);
+ }
+ // caching allowed
+ else if (!cached || cache != value) {
+ // update cache
+ writeLong(cache = value);
+ cached = true;
+ }
+ }
+
+ long get() throws IOException {
+ if (!isCachingAllowed()) {
+ return readLong();
+ }
+ else {
+ if (!cached) {
+ cache = readLong();
+ cached = true;
+ }
+ return cache;
+ }
+ }
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackend.java b/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackend.java
new file mode 100644
index 0000000000..bf07edc93e
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackend.java
@@ -0,0 +1,47 @@
+package org.rrd4j.core;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Backend to be used to store all RRD bytes in memory.
+ *
+ */
+public class RrdMemoryBackend extends ByteBufferBackend {
+
+ private ByteBuffer dbb = null;
+ /**
+ * Constructor for RrdMemoryBackend.
+ * Calling {@link org.rrd4j.core.RrdDb#close() close()} on RrdDb objects does not release any memory at all
+ * (RRD data must be available for the next Each RrdNioBackendFactory is optionally backed by a {@link org.rrd4j.core.RrdSyncThreadPool}, which it uses to sync the memory-mapped files to
+ * disk. In order to avoid having these threads live longer than they should, it is recommended that clients create and
+ * destroy thread pools at the appropriate time in their application's life time. Failure to manage thread pools
+ * appropriately may lead to the thread pool hanging around longer than necessary, which in turn may cause memory leaks. if sync period is negative, no sync thread will be launched Setter for the field Setter for the field close. getLockInfo.
+ * For ease of use in standalone applications, clients may choose to register a shutdown hook by calling
+ * {@link #registerShutdownHook()}. However, in web applications it is best to explicitly {@code shutdown()} the pool
+ * when the application is un-deployed, usually within a {@code javax.servlet.ServletContextListener}.
+ *
+ * @since 2.2
+ */
+public class RrdSyncThreadPool
+{
+ /**
+ * The reference to the shutdown hook, or null.
+ */
+ private final AtomicReference
+ * This factory creates all new threads used by an Executor in the same ThreadGroup.
+ * If there is a SecurityManager, it uses the group of System.getSecurityManager(), else the group
+ * of the thread instantiating this DaemonThreadFactory. Each new thread is created as a daemon thread
+ * with priority Thread.NORM_PRIORITY. New threads have names accessible via Thread.getName()
+ * of "
+ * IMPORTANT: NEVER use methods found in this class on 'live' RRD files
+ * (files which are currently in use).
+ *
+ */
+@SuppressWarnings("deprecation")
+public class RrdToolkit {
+
+ private static final String SOURCE_AND_DESTINATION_PATHS_ARE_THE_SAME = "Source and destination paths are the same";
+
+ private 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 java.io.IOException Thrown in case of I/O error
+ */
+ public static void addDatasource(String sourcePath, String destPath, DsDef newDatasource)
+ throws IOException {
+ addDatasources(sourcePath, destPath, Collections.singleton(newDatasource));
+ }
+
+ /**
+ * 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 newDatasources Datasource definitions to be added to the new RRD file
+ * @throws java.io.IOException Thrown in case of I/O error
+ */
+ public static void addDatasources(String sourcePath, String destPath, Iterable Adds one more datasource to a RRD file. WARNING: This method is potentially 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 datasources to a RRD file. WARNING: This method is potentially 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 potentially 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 potentially 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 potentially 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) getRrdBackend. copyStateTo. getRrdAllocator. Class to represent data source values for the given timestamp. Objects of this
+ * class are never created directly (no public constructor is provided). To learn more how
+ * to update RRDs, see RRDTool's
+ * rrdupdate man page. To update a RRD with Rrd4j use the following procedure: Newly created Sample object contains all data source values set to 'unknown'.
+ * You should specify only 'known' data source values. However, if you want to specify
+ * 'unknown' values too, use Sets sample timestamp and data source values in a fashion similar to RRDTool.
+ * Argument string should be composed in the following way:
+ * 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'. String made by concatenating sample timestamp with corresponding
+ * data source values delmited with colons. For example: 'N' stands for the current timestamp (can be replaced with 'NOW')
+ * Method will throw an exception if timestamp is invalid (cannot be parsed as Long, and is not 'N'
+ * or 'NOW'). Datasource value which cannot be parsed as 'double' will be silently set to NaN.
+ *
+ * Parses at-style time specification and returns the corresponding timestamp. For example:
+ * @return timestamp in seconds since epoch.
+ */
+ public static long getTimestamp(String atStyleTimeSpec) {
+ 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.
+ */
+ public static long[] getTimestamps(String atStyleTimeSpec1, String atStyleTimeSpec2) {
+ TimeSpec timeSpec1 = new TimeParser(atStyleTimeSpec1).parse();
+ 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(String valueStr) {
+ double value;
+ try {
+ value = Double.parseDouble(valueStr);
+ }
+ catch (NumberFormatException nfe) {
+ value = Double.NaN;
+ }
+ return value;
+ }
+
+ /**
+ * Checks if a string can be parsed as double.
+ *
+ * @param s Input string
+ * @return Returns the root directory of the Rrd4j distribution. Useful in some demo applications,
+ * probably of no use anywhere else. The function assumes that all Rrd4j .class files are placed under
+ * the <root>/classes subdirectory and that all jars (libraries) are placed in the
+ * <root>/lib subdirectory (the original Rrd4j directory structure). Constructor for XmlTemplate. Constructor for XmlTemplate. Constructor for XmlTemplate. getChildNodes. getChildNodes. getFirstChildNode. hasChildNode. getChildValue. getChildValue. getValue. getValue. Getter for the field Getter for the field Getter for the time stamps values. getDataSourcesName. getArchive. getData. getLong. getDouble. This package provides read-only access to natives RRD file. But it should work on other environments too. Typical usage: Small swing-based utility to convert timestamps (seconds since epoch) to readable dates and vice versa.
+ * Supports at-style time specification (like "now-2d", "noon yesterday") and other human-readable
+ * data formats: Constructor for TimeScanner. Returns the corresponding timestamp (seconds since Epoch). Example: Use this static method to resolve relative time references and obtain the corresponding
+ * Calendar objects. Example: Use this static method to resolve relative time references and obtain the corresponding
+ * timestamps (seconds since epoch). Example: Constructor for TimeToken. toString.dsType
.filterArchivedValues
+ * argument 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 java.io.IOException Thrown in case of I/O error
+ * @throws java.lang.IllegalArgumentException Thrown if invalid minValue was supplied (not less then maxValue)
+ */
+ public void setMinValue(double minValue, boolean filterArchivedValues) throws IOException {
+ double maxValue = this.maxValue.get();
+ if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) {
+ throw new IllegalArgumentException(INVALID_MIN_MAX_VALUES + minValue + "/" + maxValue);
+ }
+
+ this.minValue.set(minValue);
+ if (!Double.isNaN(minValue) && filterArchivedValues) {
+ int dsIndex = getDsIndex();
+ Archive[] archives = parentDb.getArchives();
+ for (Archive archive : archives) {
+ archive.getRobin(dsIndex).filterValues(minValue, Double.NaN);
+ }
+ }
+ }
+
+ /**
+ * Sets maximum allowed value for this datasource. If filterArchivedValues
+ * argument 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 java.io.IOException Thrown in case of I/O error
+ * @throws java.lang.IllegalArgumentException Thrown if invalid maxValue was supplied (not greater then minValue)
+ */
+ public void setMaxValue(double maxValue, boolean filterArchivedValues) throws IOException {
+ double minValue = this.minValue.get();
+ if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) {
+ throw new IllegalArgumentException(INVALID_MIN_MAX_VALUES + minValue + "/" + maxValue);
+ }
+
+ this.maxValue.set(maxValue);
+ if (!Double.isNaN(maxValue) && filterArchivedValues) {
+ int dsIndex = getDsIndex();
+ Archive[] archives = parentDb.getArchives();
+ for (Archive archive : archives) {
+ archive.getRobin(dsIndex).filterValues(Double.NaN, maxValue);
+ }
+ }
+ }
+
+ /**
+ * Sets min/max values allowed for this datasource. If filterArchivedValues
+ * argument 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 java.io.IOException Thrown in case of I/O error
+ * @throws java.lang.IllegalArgumentException Thrown if invalid min/max values were supplied
+ */
+ public void setMinMaxValue(double minValue, double maxValue, boolean filterArchivedValues) throws IOException {
+ if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) {
+ throw new IllegalArgumentException(INVALID_MIN_MAX_VALUES + minValue + "/" + maxValue);
+ }
+ this.minValue.set(minValue);
+ this.maxValue.set(maxValue);
+ if (!(Double.isNaN(minValue) && Double.isNaN(maxValue)) && filterArchivedValues) {
+ int dsIndex = getDsIndex();
+ Archive[] archives = parentDb.getArchives();
+ for (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();
+ }
+}
+
diff --git a/apps/jrobin/java/src/org/rrd4j/core/DsDef.java b/apps/jrobin/java/src/org/rrd4j/core/DsDef.java
new file mode 100644
index 0000000000..41c148f732
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/DsDef.java
@@ -0,0 +1,154 @@
+package org.rrd4j.core;
+
+import org.rrd4j.DsType;
+
+/**
+ *
+ *
+ * Double.NaN
if unknown.
+ * @param maxValue Maximal value. Use Double.NaN
if unknown.
+ */
+ public DsDef(String dsName, DsType dsType, long heartbeat, double minValue, double maxValue) {
+ if (dsName == null) {
+ throw new IllegalArgumentException("Null datasource name specified");
+ }
+ if (dsName.length() == 0) {
+ throw new IllegalArgumentException("Datasource name length equal to zero");
+ }
+ if (dsType == null) {
+ throw new IllegalArgumentException("Null datasource type specified");
+ }
+ if (heartbeat <= 0) {
+ throw new IllegalArgumentException("Invalid heartbeat, must be positive: " + heartbeat);
+ }
+ if (!Double.isNaN(minValue) && !Double.isNaN(maxValue) && minValue >= maxValue) {
+ throw new IllegalArgumentException("Invalid min/max values specified: " +
+ minValue + "/" + maxValue);
+ }
+
+ this.dsName = dsName;
+ this.dsType = dsType;
+ this.heartbeat = heartbeat;
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ }
+
+ /**
+ * 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 DsType 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * 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.
+ */
+ public boolean equals(Object obj) {
+ if (obj instanceof DsDef) {
+ DsDef dsObj = (DsDef) obj;
+ return dsName.equals(dsObj.dsName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return dsName.hashCode();
+ }
+
+ boolean exactlyEqual(DsDef def) {
+ return dsName.equals(def.dsName) && dsType == def.dsType &&
+ heartbeat == def.heartbeat && Util.equal(minValue, def.minValue) &&
+ Util.equal(maxValue, def.maxValue);
+ }
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/FetchData.java b/apps/jrobin/java/src/org/rrd4j/core/FetchData.java
new file mode 100644
index 0000000000..142c2ecd55
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/FetchData.java
@@ -0,0 +1,500 @@
+package org.rrd4j.core;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.rrd4j.ConsolFun;
+import org.rrd4j.data.Aggregates;
+import org.rrd4j.data.DataProcessor;
+
+/**
+ * Class used to represent data fetched from the RRD.
+ * Object of this class is created when the method
+ * {@link org.rrd4j.core.FetchRequest#fetchData() fetchData()} is
+ * called on a {@link org.rrd4j.core.FetchRequest FetchRequest} object.
+ * 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 java.lang.IllegalArgumentException Thrown if invalid RPN expression is supplied
+ */
+ public double[] getRpnValues(String rpnExpression) {
+ 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 a {@link java.lang.String} containing the contents of this object, for debugging.
+ */
+ public String dump() {
+ StringBuilder buffer = new StringBuilder();
+ 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
+ StringBuilder buff = new StringBuilder();
+ buff.append(padWithBlanks("", 10))
+ .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(Long.toString(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) {
+ StringBuilder buff = new StringBuilder("");
+ 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 org.rrd4j.ConsolFun} class)
+ * @throws java.lang.IllegalArgumentException Thrown if the given datasource name cannot be found in fetched data.
+ * @return a double.
+ * @deprecated This method is deprecated. Uses instance of {@link org.rrd4j.data.Variable}, used with {@link org.rrd4j.data.DataProcessor#addDatasource(String, String, Variable)}
+ */
+ @Deprecated
+ public double getAggregate(String dsName, ConsolFun consolFun) {
+ DataProcessor dp = createDataProcessor(null);
+ return dp.getAggregate(dsName, 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 java.lang.IllegalArgumentException Thrown if invalid RPN expression is supplied
+ * @deprecated This method is deprecated. Uses instance of {@link org.rrd4j.data.Variable}, used with {@link org.rrd4j.data.DataProcessor#addDatasource(String, String, Variable)}
+ */
+ @Deprecated
+ public double getRpnAggregate(String rpnExpression, ConsolFun consolFun) {
+ 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 java.lang.IllegalArgumentException Thrown if the given datasource name cannot be found in the fetched data.
+ * @deprecated This method is deprecated. Uses instance of {@link org.rrd4j.data.Variable}, used with {@link org.rrd4j.data.DataProcessor#addDatasource(String, String, Variable)}
+ */
+ @Deprecated
+ public Aggregates getAggregates(String dsName) {
+ 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 java.lang.IllegalArgumentException Thrown if invalid RPN expression is supplied
+ * @throws java.io.IOException if any.
+ * @deprecated This method is deprecated. Uses instance of {@link org.rrd4j.data.Variable}, used with {@link org.rrd4j.data.DataProcessor#addDatasource(String, String, Variable)}
+ */
+ @Deprecated
+ public Aggregates getRpnAggregates(String rpnExpression) throws IOException {
+ DataProcessor dataProcessor = createDataProcessor(rpnExpression);
+ return dataProcessor.getAggregates(RPN_SOURCE_NAME);
+ }
+
+ /**
+ * Used by ISPs which charge for bandwidth utilization on a "95th percentile" basis.
+ *
+ * Bytemark.
+ *
+ * @param dsName Datasource name
+ * @return 95th percentile of fetched source values
+ * @throws java.lang.IllegalArgumentException Thrown if invalid source name is supplied
+ * @deprecated This method is deprecated. Uses instance of {@link org.rrd4j.data.Variable.PERCENTILE}, used with {@link org.rrd4j.data.DataProcessor#addDatasource(String, String, Variable)}
+ */
+ @Deprecated
+ public double get95Percentile(String dsName) {
+ 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 java.lang.IllegalArgumentException Thrown if invalid RPN expression is supplied
+ * @deprecated This method is deprecated. Uses instance of {@link org.rrd4j.data.Variable.PERCENTILE}, used with {@link org.rrd4j.data.DataProcessor#addDatasource(String, String, Variable)}
+ */
+ @Deprecated
+ public double getRpn95Percentile(String rpnExpression) {
+ DataProcessor dataProcessor = createDataProcessor(rpnExpression);
+ return dataProcessor.get95Percentile(RPN_SOURCE_NAME);
+ }
+
+ /**
+ * Dumps fetch data to output stream in XML format. A flush is issued at the end of the xml generation.
+ *
+ * @param outputStream Output stream to dump fetch data to
+ * @throws java.io.IOException Thrown in case of I/O error
+ */
+ public void exportXml(OutputStream outputStream) throws IOException {
+ //No auto flush for XmlWriter, it will be flushed once, when export is finished
+ XmlWriter writer = new XmlWriter(outputStream, false);
+ 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 java.io.IOException Thrown in case of I/O error
+ */
+ public void exportXml(String filepath) throws IOException {
+ try(OutputStream outputStream = new FileOutputStream(filepath)) {
+ exportXml(outputStream);
+ }
+ }
+
+ /**
+ * Dumps fetch data in XML format.
+ *
+ * @return String containing XML formatted fetch data
+ * @throws java.io.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) {
+ 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/rrd4j/core/FetchRequest.java b/apps/jrobin/java/src/org/rrd4j/core/FetchRequest.java
new file mode 100644
index 0000000000..9ffe43d128
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/FetchRequest.java
@@ -0,0 +1,175 @@
+package org.rrd4j.core;
+
+import org.rrd4j.ConsolFun;
+
+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.
+ * FetchRequest
directly (no public constructor
+ * is provided). Use {@link org.rrd4j.core.RrdDb#createFetchRequest(ConsolFun, long, long, long)
+ * createFetchRequest()} method of your {@link org.rrd4j.core.RrdDb RrdDb} object.
+ *
+ * @author Sasa Markovic
+ */
+public class FetchRequest {
+ private RrdDb parentDb;
+ private ConsolFun consolFun;
+ private long fetchStart;
+ private long fetchEnd;
+ private long resolution;
+ private String[] filter;
+
+ FetchRequest(RrdDb parentDb, ConsolFun consolFun, long fetchStart, long fetchEnd, long resolution) {
+ if (consolFun == null) {
+ throw new IllegalArgumentException("Null consolidation function in fetch request");
+ }
+ if (fetchStart < 0) {
+ throw new IllegalArgumentException("Invalid start time in fetch request: " + fetchStart);
+ }
+ if (fetchEnd < 0) {
+ throw new IllegalArgumentException("Invalid end time in fetch request: " + fetchEnd);
+ }
+ if (fetchStart > fetchEnd) {
+ throw new IllegalArgumentException("Invalid start/end time in fetch request: " + fetchStart +
+ " > " + fetchEnd);
+ }
+ if (resolution <= 0) {
+ throw new IllegalArgumentException("Invalid resolution in fetch request: " + resolution);
+ }
+
+ this.parentDb = parentDb;
+ this.consolFun = consolFun;
+ this.fetchStart = fetchStart;
+ this.fetchEnd = fetchEnd;
+ this.resolution = resolution;
+ }
+
+ /**
+ * 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
+ * contain 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 datasources (datasource 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
+ * contain 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 datasource 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 java.io.IOException Thrown in case of I/O error
+ */
+ void filterValues(double minValue, double maxValue) throws IOException;
+
+ /**
+ * Returns the underlying storage (backend) object which actually performs all
+ * I/O operations.
+ *
+ * @return I/O backend object
+ */
+ RrdBackend getRrdBackend();
+
+ /**
+ * Required to implement RrdUpdater interface. You should never call this method directly.
+ *
+ * @return Allocator object
+ */
+ RrdAllocator getRrdAllocator();
+
+ /**
+ * values
.[minValue, maxValue]
interval (inclusive)
+ * will be silently replaced with NaN
.
+ */
+ public void filterValues(double minValue, double maxValue) throws IOException {
+ for (int i = 0; i < rows; i++) {
+ double value = values.get(column, i);
+ if (!Double.isNaN(minValue) && !Double.isNaN(value) && minValue > value) {
+ values.set(column, i, Double.NaN);
+ }
+ if (!Double.isNaN(maxValue) && !Double.isNaN(value) && maxValue < value) {
+ values.set(column, 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/rrd4j/core/RrdAllocator.java b/apps/jrobin/java/src/org/rrd4j/core/RrdAllocator.java
new file mode 100644
index 0000000000..f0ff7d7267
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdAllocator.java
@@ -0,0 +1,22 @@
+package org.rrd4j.core;
+
+import java.io.IOException;
+
+/**
+ * An internal usage class.
+ *
+ * @author Sasa Markovic
+ */
+public class RrdAllocator {
+ private long allocationPointer = 0L;
+
+ RrdAllocator() {
+ super();
+ }
+
+ long allocate(long byteCount) throws IOException {
+ long pointer = allocationPointer;
+ allocationPointer += byteCount;
+ return pointer;
+ }
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdBackend.java b/apps/jrobin/java/src/org/rrd4j/core/RrdBackend.java
new file mode 100644
index 0000000000..3216ca6aff
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdBackend.java
@@ -0,0 +1,431 @@
+package org.rrd4j.core;
+
+import java.io.IOException;
+import java.lang.ref.PhantomReference;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+
+/**
+ *
+ *
+ *
+ *
+ *
+ *
+ * @author Sasa Markovic
+ */
+public abstract class RrdBackend {
+
+ /**
+ * All {@link java.nio.ByteBuffer} usage should use this standard order.
+ */
+ protected static final ByteOrder BYTEORDER = ByteOrder.BIG_ENDIAN;
+
+ private static final char STARTPRIVATEAREA = '\ue000';
+ private static final char ENDPRIVATEAREA = '\uf8ff';
+ private static final int STARTPRIVATEAREACODEPOINT = Character.codePointAt(new char[]{STARTPRIVATEAREA}, 0);
+ private static final int ENDPRIVATEAREACODEPOINT = Character.codePointAt(new char[]{ENDPRIVATEAREA}, 0);
+ private static final int PRIVATEAREASIZE = ENDPRIVATEAREACODEPOINT - STARTPRIVATEAREACODEPOINT + 1;
+ private static final int MAXUNSIGNEDSHORT = Short.MAX_VALUE - Short.MIN_VALUE;
+
+ private static volatile boolean instanceCreated = false;
+ private final String path;
+ private RrdBackendFactory factory;
+ private long nextBigStringOffset = -1;
+ private PhantomReferencetrue
+ * is returned, frontend 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 overridden in subclasses.
+ */
+ protected boolean isCachingAllowed() {
+ return factory.cachingAllowed;
+ }
+
+ /**
+ * Reads all RRD bytes from the underlying storage.
+ *
+ * @return RRD bytes
+ * @throws java.io.IOException Thrown in case of I/O error
+ */
+ public final byte[] readAll() throws IOException {
+ byte[] b = new byte[(int) getLength()];
+ read(0, b);
+ return b;
+ }
+
+ protected void writeShort(long offset, short value) throws IOException {
+ byte[] b = new byte[2];
+ b[0] = (byte) ((value >>> 8) & 0xFF);
+ b[1] = (byte) ((value >>> 0) & 0xFF);
+ write(offset, b);
+ }
+
+ protected void writeInt(long offset, int value) throws IOException {
+ write(offset, getIntBytes(value));
+ }
+
+ protected void writeLong(long offset, long value) throws IOException {
+ write(offset, getLongBytes(value));
+ }
+
+ protected void writeDouble(long offset, double value) throws IOException {
+ write(offset, getDoubleBytes(value));
+ }
+
+ protected void writeDouble(long offset, double value, int count) throws IOException {
+ byte[] b = getDoubleBytes(value);
+ byte[] image = new byte[8 * count];
+ int k = 0;
+ for (int i = 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);
+ }
+
+ protected void writeDouble(long offset, double[] values) throws IOException {
+ int count = values.length;
+ byte[] image = new byte[8 * count];
+ int k = 0;
+ for (int i = 0; i < count; i++) {
+ 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);
+ }
+
+ protected final void writeString(long offset, String value) throws IOException {
+ if (nextBigStringOffset < 0) {
+ nextBigStringOffset = getLength() - (Short.SIZE / 8);
+ }
+ value = value.trim();
+ // Over-sized string are appended at the end of the RRD
+ // The real position is encoded in the "short" ds name, using the private use area from Unicode
+ // This area span the range E000-F8FF, that' a 6400 char area,
+ if (value.length() > RrdPrimitive.STRING_LENGTH) {
+ String bigString = value;
+ int byteStringLength = Math.min(MAXUNSIGNEDSHORT, bigString.length());
+ long bigStringOffset = nextBigStringOffset;
+ nextBigStringOffset -= byteStringLength * 2 + (Short.SIZE / 8);
+ writeShort(bigStringOffset, (short)byteStringLength);
+ writeString(bigStringOffset - bigString.length() * 2, bigString, byteStringLength);
+ // Now we generate the new string that encode the position
+ long reminder = bigStringOffset;
+ StringBuilder newValue = new StringBuilder(value.substring(0, RrdPrimitive.STRING_LENGTH));
+ int i = RrdPrimitive.STRING_LENGTH;
+ // Read in inverse order, so write in inverse order
+ while (reminder > 0) {
+ // Only the first char is kept, as it will never byte a multi-char code point
+ newValue.setCharAt(--i, Character.toChars((int)(reminder % PRIVATEAREASIZE + STARTPRIVATEAREACODEPOINT))[0]);
+ reminder = (long) Math.floor( ((float)reminder) / (float)PRIVATEAREASIZE);
+ }
+ value = newValue.toString();
+ }
+ writeString(offset, value, RrdPrimitive.STRING_LENGTH);
+ }
+
+ protected void writeString(long offset, String value, int length) throws IOException {
+ ByteBuffer bbuf = ByteBuffer.allocate(length * 2);
+ bbuf.order(BYTEORDER);
+ bbuf.position(0);
+ bbuf.limit(length * 2);
+ CharBuffer cbuf = bbuf.asCharBuffer();
+ cbuf.put(value);
+ while (cbuf.position() < cbuf.limit()) {
+ cbuf.put(' ');
+ }
+ write(offset, bbuf.array());
+ }
+
+ protected short readShort(long offset) throws IOException {
+ byte[] b = new byte[2];
+ read(offset, b);
+ return (short) (((b[0] << 8) & 0x0000FF00) + (b[1] & 0x000000FF));
+ }
+
+ protected int readInt(long offset) throws IOException {
+ byte[] b = new byte[4];
+ read(offset, b);
+ return getInt(b);
+ }
+
+ protected long readLong(long offset) throws IOException {
+ byte[] b = new byte[8];
+ read(offset, b);
+ return getLong(b);
+ }
+
+ protected double readDouble(long offset) throws IOException {
+ byte[] b = new byte[8];
+ read(offset, b);
+ return getDouble(b);
+ }
+
+ protected double[] readDouble(long offset, int count) throws IOException {
+ int byteCount = 8 * count;
+ byte[] image = new byte[byteCount];
+ read(offset, image);
+ double[] values = new double[count];
+ int k = -1;
+ for (int i = 0; i < count; i++) {
+ 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;
+ }
+
+ /**
+ * Extract a CharBuffer from the backend, used by readString
+ *
+ * @param offset
+ * @param size
+ * @return
+ * @throws IOException
+ */
+ protected CharBuffer getCharBuffer(long offset, int size) throws IOException {
+ ByteBuffer bbuf = ByteBuffer.allocate(size * 2);
+ bbuf.order(BYTEORDER);
+ read(offset, bbuf.array());
+ bbuf.position(0);
+ bbuf.limit(size * 2);
+ return bbuf.asCharBuffer();
+ }
+
+ protected final String readString(long offset) throws IOException {
+ CharBuffer cbuf = getCharBuffer(offset, RrdPrimitive.STRING_LENGTH);
+ long realStringOffset = 0;
+ int i = -1;
+ while (++i < RrdPrimitive.STRING_LENGTH) {
+ char c = cbuf.charAt(RrdPrimitive.STRING_LENGTH - i - 1);
+ if (c >= STARTPRIVATEAREA && c <= ENDPRIVATEAREA) {
+ realStringOffset += ((long) c - STARTPRIVATEAREACODEPOINT) * Math.pow(PRIVATEAREASIZE, i);
+ cbuf.limit(RrdPrimitive.STRING_LENGTH - i - 1);
+ } else {
+ break;
+ }
+ }
+ if (realStringOffset > 0) {
+ int bigStringSize = readShort(realStringOffset);
+ // Signed to unsigned arithmetic
+ if (bigStringSize < 0) {
+ bigStringSize += MAXUNSIGNEDSHORT + 1;
+ }
+ return getCharBuffer(realStringOffset - bigStringSize * 2, bigStringSize).toString();
+ } else {
+ return cbuf.slice().toString().trim();
+ }
+ }
+
+ // static helper methods
+
+ private static byte[] getIntBytes(int value) {
+ 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 >>> 0) & 0xFF);
+ return b;
+ }
+
+ private static byte[] getLongBytes(long value) {
+ 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 >>> 0) & 0xFF);
+ return b;
+ }
+
+ private static byte[] getDoubleBytes(double value) {
+ return getLongBytes(Double.doubleToLongBits(value));
+ }
+
+ private static int getInt(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(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 double getDouble(byte[] b) {
+ assert b.length == 8 : "Invalid number of bytes for double conversion";
+ return Double.longBitsToDouble(getLong(b));
+ }
+
+ static boolean isInstanceCreated() {
+ return instanceCreated;
+ }
+
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdBackendAnnotation.java b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendAnnotation.java
new file mode 100644
index 0000000000..83243ac01e
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendAnnotation.java
@@ -0,0 +1,26 @@
+package org.rrd4j.core;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Description of a {@link RrdBackendFactory}
+ *
+ * @author Fabrice Bacchella
+ * @since 3.4
+ *
+ */
+@Documented
+@Retention(RUNTIME)
+@Target(TYPE)
+public @interface RrdBackendAnnotation {
+ public static boolean DEFAULT_CACHING_ALLOWED = true;
+ String name();
+ boolean cachingAllowed() default DEFAULT_CACHING_ALLOWED;
+ String scheme() default "";
+ boolean shouldValidateHeader();
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdBackendException.java b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendException.java
new file mode 100644
index 0000000000..7228f3ab75
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendException.java
@@ -0,0 +1,20 @@
+package org.rrd4j.core;
+
+/**
+ * Wrap a exception generated by the backend store
+ *
+ * @author Fabrice Bacchella
+ * @since 3.4
+ *
+ */
+public class RrdBackendException extends RrdException {
+
+ public RrdBackendException(String message) {
+ super(message);
+ }
+
+ public RrdBackendException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java
new file mode 100644
index 0000000000..e5ac6acdf8
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java
@@ -0,0 +1,576 @@
+package org.rrd4j.core;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 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.
+ *
+ *
+ *
+ *
+ *
+ * @deprecated Uses active factory instead
+ * @return Backend factory for the given factory name
+ */
+ @Deprecated
+ public static synchronized RrdBackendFactory getFactory(String name) {
+ RrdBackendFactory factory = Registry.factories.get(name);
+ if (factory != null) {
+ return factory;
+ } else {
+ throw new IllegalArgumentException(
+ "No backend factory found with the name specified ["
+ + name + "]");
+ }
+ }
+
+ /**
+ * Registers new (custom) backend factory within the Rrd4j framework.
+ *
+ * @deprecated Uses active factory instead
+ * @param factory Factory to be registered
+ */
+ @Deprecated
+ public static synchronized void registerFactory(RrdBackendFactory factory) {
+ String name = factory.getName();
+ if (!Registry.factories.containsKey(name)) {
+ Registry.factories.put(name, factory);
+ }
+ else {
+ throw new IllegalArgumentException("Backend factory '" + name + "' cannot be registered twice");
+ }
+ }
+
+ /**
+ * Registers new (custom) backend factory within the Rrd4j framework and sets this
+ * factory as the default.
+ *
+ * @deprecated Uses {@link #setActiveFactories(RrdBackendFactory...)} instead.
+ * @param factory Factory to be registered and set as default
+ */
+ @Deprecated
+ public static synchronized void registerAndSetAsDefaultFactory(RrdBackendFactory factory) {
+ registerFactory(factory);
+ setDefaultFactory(factory.getName());
+ }
+
+ /**
+ * Returns the default backend factory. This factory is used to construct
+ * {@link org.rrd4j.core.RrdDb} objects if no factory is specified in the RrdDb constructor.
+ *
+ * @return Default backend factory.
+ */
+ public static synchronized RrdBackendFactory getDefaultFactory() {
+ if (!activeFactories.isEmpty()) {
+ return activeFactories.get(0);
+ } else {
+ return Registry.defaultFactory;
+ }
+ }
+
+ /**
+ * Replaces the default backend factory with a new one. This method must be called before
+ * the first RRD gets created.
+ *
+ *
+ *
+ * @param rootUri
+ * @param uri
+ * @param relative
+ * @return a calculate normalized absolute URI or null if the tried URL don't match against the root.
+ */
+ protected URI resolve(URI rootUri, URI uri, boolean relative) {
+ String scheme = uri.getScheme();
+ if (scheme != null && ! scheme.equals(rootUri.getScheme())) {
+ throw new IllegalArgumentException(String.format("scheme %s not compatible with %s", scheme, rootUri.getScheme()));
+ } else if (scheme == null) {
+ scheme = rootUri.getScheme();
+ }
+ String authority = uri.getAuthority();
+ if (authority != null && ! authority.equals(rootUri.getAuthority())) {
+ throw new IllegalArgumentException("URI credential not compatible");
+ } else if (authority == null) {
+ authority = rootUri.getAuthority();
+ }
+ String path;
+ if (uri.isOpaque()) {
+ // try to resolve an opaque uri as scheme:relativepath
+ path = uri.getSchemeSpecificPart();
+ } else if (! uri.isAbsolute()) {
+ // A relative URI, resolve it against the root
+ path = rootUri.resolve(uri).normalize().getPath();
+ } else {
+ path = uri.normalize().getPath();
+ }
+ if (! path.startsWith(rootUri.getPath())) {
+ throw new IllegalArgumentException(String.format("URI destination path %s not root with %s", path, rootUri.getPath()));
+ }
+ String query = uri.getQuery();
+ String fragment = uri.getFragment();
+ try {
+ authority = authority != null ? authority : "";
+ query = query != null ? "?" + URLEncoder.encode(query, "UTF-8") : "";
+ fragment = fragment != null ? "#" + URLEncoder.encode(fragment, "UTF-8") : "";
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("UTF-8 is missing");
+ }
+ String newUriString = String.format("%s://%s%s%s%s", scheme, authority, path , query, fragment);
+ URI newURI = URI.create(newUriString);
+ if (relative) {
+ return rootUri.relativize(newURI);
+ } else {
+ return newURI;
+ }
+ }
+
+ /**
+ * Ensure that an URI is returned in a non-ambiguous way.
+ *
+ * @param uri a valid URI for this backend.
+ * @return the canonized URI.
+ */
+ public URI getCanonicalUri(URI uri) {
+ return resolve(getRootUri(), uri, false);
+ }
+
+ /**
+ * Transform an path in a valid URI for this backend.
+ *
+ * @param path
+ * @return
+ */
+ public URI getUri(String path) {
+ URI rootUri = getRootUri();
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ try {
+ return new URI(getScheme(), rootUri.getAuthority(), rootUri.getPath() + path, null, null);
+ } catch (URISyntaxException ex) {
+ throw new IllegalArgumentException(ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * Extract the local path from an URI.
+ *
+ * @param uri The URI to parse.
+ * @return the local path from the URI.
+ */
+ public String getPath(URI uri) {
+ URI rootUri = getRootUri();
+ uri = resolve(rootUri, uri, true);
+ if (uri == null) {
+ return null;
+ }
+ return "/" + uri.getPath();
+ }
+
+ protected abstract RrdBackend open(String path, boolean readOnly) throws IOException;
+
+ /**
+ * 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 java.io.IOException Thrown in case of I/O error.
+ */
+ RrdBackend getBackend(RrdDb rrdDb, String path, boolean readOnly) throws IOException {
+ checkClosing();
+ RrdBackend backend = open(path, readOnly);
+ backend.done(this, new ClosingReference(rrdDb, backend, refQueue));
+ return backend;
+ }
+
+ /**
+ * Creates RrdBackend object for the given storage path.
+ * @param rrdDb
+ *
+ * @param uri Storage uri
+ * @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 java.io.IOException Thrown in case of I/O error.
+ */
+ RrdBackend getBackend(RrdDb rrdDb, URI uri, boolean readOnly) throws IOException {
+ checkClosing();
+ RrdBackend backend = open(getPath(uri), readOnly);
+ backend.done(this, new ClosingReference(rrdDb, backend, refQueue));
+ return backend;
+ }
+
+ /**
+ * Determines if a storage with the given path already exists.
+ *
+ * @param path Storage path
+ * @throws java.io.IOException in case of I/O error.
+ * @return a boolean.
+ */
+ protected abstract boolean exists(String path) throws IOException;
+
+ /**
+ * Determines if a storage with the given URI already exists.
+ *
+ * @param uri Storage URI.
+ * @throws java.io.IOException in case of I/O error.
+ * @return a boolean.
+ */
+ protected boolean exists(URI uri) throws IOException {
+ return exists(getPath(uri));
+ }
+
+ /**
+ * Determines if the header should be validated.
+ *
+ * @param path Storage path
+ * @throws java.io.IOException if header validation fails
+ * @return a boolean.
+ */
+ protected boolean shouldValidateHeader(String path) throws IOException {
+ return validateHeader;
+ }
+
+ /**
+ * Determines if the header should be validated.
+ *
+ * @param uri Storage URI
+ * @throws java.io.IOException if header validation fails
+ * @return a boolean.
+ */
+ protected boolean shouldValidateHeader(URI uri) throws IOException {
+ return shouldValidateHeader(getPath(uri));
+ }
+
+ /**
+ * Returns the name (primary ID) for the factory.
+ *
+ * @return Name of the factory.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * A generic close handle, default implementation does nothing.
+ * @since 3.4
+ * @throws IOException
+ */
+ public void close() throws IOException {
+
+ }
+
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdByteArrayBackend.java b/apps/jrobin/java/src/org/rrd4j/core/RrdByteArrayBackend.java
new file mode 100644
index 0000000000..60fbcb8da3
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdByteArrayBackend.java
@@ -0,0 +1,79 @@
+package org.rrd4j.core;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Abstract byte array based backend.
+ *
+ */
+public abstract class RrdByteArrayBackend extends ByteBufferBackend {
+
+ private byte[] buffer;
+
+ /**
+ *
+ * // create new RRD definition
+ * RrdDef def = new RrdDef("test.rrd", 300);
+ * def.addDatasource("input", DsType.DT_COUNTER, 600, 0, Double.NaN);
+ * def.addDatasource("output", DsType.DT_COUNTER, 600, 0, Double.NaN);
+ * def.addArchive(ConsolFun.CF_AVERAGE, 0.5, 1, 600);
+ * def.addArchive(ConsolFun.CF_AVERAGE, 0.5, 6, 700);
+ * def.addArchive(ConsolFun.CF_AVERAGE, 0.5, 24, 797);
+ * def.addArchive(ConsolFun.CF_AVERAGE, 0.5, 288, 775);
+ * def.addArchive(ConsolFun.CF_MAX, 0.5, 1, 600);
+ * def.addArchive(ConsolFun.CF_MAX, 0.5, 6, 700);
+ * def.addArchive(ConsolFun.CF_MAX, 0.5, 24, 797);
+ * def.addArchive(ConsolFun.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 java.io.IOException Thrown in case of I/O error.
+ * @deprecated Use the builder instead.
+ */
+ @Deprecated
+ public RrdDb(RrdDef rrdDef) throws IOException {
+ this(rrdDef, null, null);
+ }
+
+ /**
+ *
+ * // create new RRD definition
+ * RrdDef def = new RrdDef("test.rrd", 300);
+ * def.addDatasource("input", DsType.DT_COUNTER, 600, 0, Double.NaN);
+ * def.addDatasource("output", DsType.DT_COUNTER, 600, 0, Double.NaN);
+ * def.addArchive(ConsolFun.CF_AVERAGE, 0.5, 1, 600);
+ * def.addArchive(ConsolFun.CF_AVERAGE, 0.5, 6, 700);
+ * def.addArchive(ConsolFun.CF_AVERAGE, 0.5, 24, 797);
+ * def.addArchive(ConsolFun.CF_AVERAGE, 0.5, 288, 775);
+ * def.addArchive(ConsolFun.CF_MAX, 0.5, 1, 600);
+ * def.addArchive(ConsolFun.CF_MAX, 0.5, 6, 700);
+ * def.addArchive(ConsolFun.CF_MAX, 0.5, 24, 797);
+ * def.addArchive(ConsolFun.CF_MAX, 0.5, 288, 775);
+ *
+ * // RRD definition is now completed, create the database!
+ * RrdDb rrd = RrdDb.of(def);
+ * // new RRD file has been created on your disk
+ *
+ *
+ * @param rrdDef Object describing the structure of the new RRD file.
+ * @throws java.io.IOException Thrown in case of I/O error.
+ */
+ public static RrdDb of(RrdDef rrdDef) throws IOException {
+ return new RrdDb(rrdDef, null, null);
+ }
+
+ /**
+ *
+ *
+ *
+ * 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 (readOnly
set to true
),
+ * IOException
will be thrown.
+ * @throws java.io.IOException Thrown in case of I/O error.
+ * @deprecated Use the builder instead.
+ */
+ @Deprecated
+ public RrdDb(String path, boolean readOnly) throws IOException {
+ this(path, null, readOnly, null, null);
+ }
+
+ /**
+ * 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 (readOnly
set to true
),
+ * IOException
will be thrown.
+ * @throws java.io.IOException Thrown in case of I/O error.
+ * @deprecated Use the builder instead.
+ */
+ @Deprecated
+ public RrdDb(URI uri, boolean readOnly) throws IOException {
+ this(null, uri, readOnly, null, null);
+ }
+
+ /**
+ * 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 (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 java.io.IOException Thrown in case of general I/O error (bad RRD file, for example).
+ * @see RrdBackendFactory
+ * @deprecated Use the builder instead.
+ */
+ @Deprecated
+ public RrdDb(String path, boolean readOnly, RrdBackendFactory factory) throws IOException {
+ this(path, null, readOnly, factory, null);
+ }
+
+ /**
+ *
+ *
+ * rrdtool dump
command).
+ *
+ * rrdtool dump original.rrd > original.xml
+ *
+ * original.xml
to create Rrd4j 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 Rrd4j 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.xml:/
or rrdtool:/
prefix.
+ * @throws java.io.IOException Thrown in case of I/O error
+ * @deprecated Use the builder instead.
+ */
+ @Deprecated
+ public RrdDb(String rrdPath, String externalPath) throws IOException {
+ this(rrdPath, null, externalPath, null, null, null);
+ }
+
+ /**
+ *
+ *
+ * rrdtool dump
command).
+ *
+ * rrdtool dump original.rrd > original.xml
+ *
+ * original.xml
to create Rrd4j 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 Rrd4j 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.xml:/
or rrdtool:/
prefix.
+ * @throws java.io.IOException Thrown in case of I/O error
+ * @deprecated Use the builder instead.
+ */
+ @Deprecated
+ public RrdDb(URI uri, String externalPath) throws IOException {
+ this(null, uri, externalPath, null, null, null);
+ }
+
+ /**
+ *
+ *
+ * rrdtool dump
command).
+ *
+ * rrdtool dump original.rrd > original.xml
+ *
+ * original.xml
to create Rrd4j 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 Rrd4j 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.xml:/
or rrdtool:/
prefix.
+ * @param factory Backend factory which will be used to create storage (backend) for this RRD.
+ * @throws java.io.IOException Thrown in case of I/O error
+ * @see RrdBackendFactory
+ * @deprecated Use the builder instead.
+ */
+ @Deprecated
+ public RrdDb(String rrdPath, String externalPath, RrdBackendFactory factory) throws IOException {
+ this(rrdPath, null, externalPath, null, factory, null);
+ }
+
+ private RrdDb(String rrdPath, URI rrdUri, String externalPath, DataImporter importer, RrdBackendFactory factory, RrdDbPool pool) throws IOException {
+ this.pool = pool;
+ rrdUri = Builder.buildUri(rrdPath, rrdUri, factory);
+ factory = Builder.checkFactory(rrdUri, factory);
+
+ backend = factory.getBackend(this, rrdUri, false);
+ try (DataImporter reader = Builder.resoleImporter(externalPath, importer)) {
+ 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);
+ }
+ } catch (IOException e) {
+ backend.rrdClose();
+ throw e;
+ }
+ }
+
+ /**
+ * Closes RRD. No further operations are allowed on this RrdDb object.
+ *
+ * @throws java.io.IOException Thrown in case of I/O related error.
+ */
+ @SuppressWarnings("deprecation")
+ public synchronized void close() throws IOException {
+ if (pool != null) {
+ pool.release(this);
+ } else {
+ internalClose();
+ }
+ }
+
+ void internalClose() throws IOException {
+ if (!closed) {
+ closed = true;
+ backend.rrdClose();
+ }
+ }
+
+ /**
+ * 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 java.io.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].getName();
+ }
+ return dsNames;
+ }
+
+ /**
+ * Sample
object to specify
+ * datasource values for the given timestamp. See documentation for
+ * {@link Sample Sample} for an explanation how to do this.Sample
object to specify
+ * datasource values for the current timestamp. See documentation for
+ * {@link Sample Sample} for an explanation how to do this.FetchRequest
object and its {@link org.rrd4j.core.FetchRequest#fetchData() fetchData()}
+ * method to actually fetch data from the RRD file.
+ *
+ * @param consolFun Consolidation function to be used in fetch request.
+ * @param fetchStart Starting timestamp for fetch request.
+ * @param fetchEnd Ending timestamp for fetch request.
+ * @param resolution Fetch resolution (see RRDTool's
+ * rrdfetch man page for an
+ * explanation of this parameter.
+ * @return Request object that should be used to actually fetch data from RRD
+ */
+ public FetchRequest createFetchRequest(ConsolFun consolFun, long fetchStart, long fetchEnd, long resolution) {
+ return new FetchRequest(this, consolFun, fetchStart, fetchEnd, resolution);
+ }
+
+ /**
+ * Prepares fetch request to be executed on this RRD. Use returned
+ * FetchRequest
object and its {@link org.rrd4j.core.FetchRequest#fetchData() fetchData()}
+ * method to actually fetch data from this RRD. Data will be fetched with the smallest
+ * possible resolution (see RRDTool's
+ * rrdfetch man page
+ * for the explanation of the resolution parameter).
+ *
+ * @param consolFun Consolidation function to be used in fetch request.
+ * @param fetchStart Starting timestamp for fetch request.
+ * @param fetchEnd Ending timestamp for fetch request.
+ * @return Request object that should be used to actually fetch data from RRD.
+ */
+ public FetchRequest createFetchRequest(ConsolFun consolFun, long fetchStart, long fetchEnd) {
+ return createFetchRequest(consolFun, fetchStart, fetchEnd, 1);
+ }
+
+ final synchronized void store(Sample sample) throws IOException {
+ if (closed) {
+ throw new IllegalStateException("RRD already closed, cannot store this sample");
+ }
+ long newTime = sample.getTime();
+ long lastTime = header.getLastUpdateTime();
+ if (lastTime >= newTime) {
+ throw new IllegalArgumentException("Bad sample time: " + newTime +
+ ". Last update time was " + lastTime + ", at least one second step is required");
+ }
+ double[] newValues = sample.getValues();
+ for (int i = 0; i < datasources.length; i++) {
+ double newValue = newValues[i];
+ datasources[i].process(newTime, newValue);
+ }
+ header.setLastUpdateTime(newTime);
+ }
+
+ synchronized FetchData fetchData(FetchRequest request) throws IOException {
+ if (closed) {
+ throw new IllegalStateException("RRD already closed, cannot fetch data");
+ }
+ Archive archive = findMatchingArchive(request);
+ return archive.fetchData(request);
+ }
+
+ /**
+ * findMatchingArchive.
+ *
+ * @param request a {@link org.rrd4j.core.FetchRequest} object.
+ * @return a {@link org.rrd4j.core.Archive} object.
+ * @throws java.io.IOException if any.
+ */
+ public Archive findMatchingArchive(FetchRequest request) throws IOException {
+ ConsolFun consolFun = request.getConsolFun();
+ long fetchStart = request.getFetchStart();
+ long fetchEnd = request.getFetchEnd();
+ long resolution = request.getResolution();
+ Archive bestFullMatch = null;
+ Archive bestPartialMatch = null;
+ long bestStepDiff = 0;
+ long bestMatch = 0;
+ for (Archive archive : archives) {
+ if (archive.getConsolFun() == consolFun) {
+ long arcStep = archive.getArcStep();
+ long arcStart = archive.getStartTime() - arcStep;
+ long fullMatch = fetchEnd - fetchStart;
+ // we need step difference in either full or partial case
+ long tmpStepDiff = Math.abs(archive.getArcStep() - resolution);
+ if (arcStart <= fetchStart) {
+ // best full match
+ if (bestFullMatch == null || tmpStepDiff < bestStepDiff) {
+ bestStepDiff = tmpStepDiff;
+ bestFullMatch = archive;
+ }
+ } else {
+ // best partial match
+ long tmpMatch = fullMatch;
+ tmpMatch -= (arcStart - fetchStart);
+ if (bestPartialMatch == null ||
+ bestMatch < tmpMatch ||
+ (bestMatch == tmpMatch && tmpStepDiff < bestStepDiff)) {
+ bestPartialMatch = archive;
+ bestMatch = tmpMatch;
+ }
+ }
+ }
+ }
+ if (bestFullMatch != null) {
+ return bestFullMatch;
+ } else if (bestPartialMatch != null) {
+ return bestPartialMatch;
+ } else {
+ throw new IllegalStateException("RRD file does not contain RRA: " + consolFun + " archive");
+ }
+ }
+
+ /**
+ * Finds the archive that best matches to the start time (time period being start-time until now)
+ * and requested resolution.
+ *
+ * @param consolFun Consolidation function of the datasource.
+ * @param startTime Start time of the time period in seconds.
+ * @param resolution Requested fetch resolution.
+ * @return Reference to the best matching archive.
+ * @throws java.io.IOException Thrown in case of I/O related error.
+ */
+ public Archive findStartMatchArchive(String consolFun, long startTime, long resolution) throws IOException {
+ long arcStep;
+ long diff;
+ int fallBackIndex = 0;
+ int arcIndex = -1;
+ long minDiff = Long.MAX_VALUE;
+ long fallBackDiff = Long.MAX_VALUE;
+
+ for (int i = 0; i < archives.length; i++) {
+ if (archives[i].getConsolFun().toString().equals(consolFun)) {
+ arcStep = archives[i].getArcStep();
+ diff = Math.abs(resolution - arcStep);
+
+ // Now compare start time, see if this archive encompasses the requested interval
+ if (startTime >= archives[i].getStartTime()) {
+ if (diff == 0) // Best possible match either way
+ {
+ return archives[i];
+ } else if (diff < minDiff) {
+ minDiff = diff;
+ arcIndex = i;
+ }
+ } else if (diff < fallBackDiff) {
+ fallBackDiff = diff;
+ fallBackIndex = i;
+ }
+ }
+ }
+
+ return (arcIndex >= 0 ? archives[arcIndex] : archives[fallBackIndex]);
+ }
+
+ /**
+ * Returns string representing complete internal RRD state. The returned
+ * string can be printed to stdout
and/or used for debugging purposes.
+ *
+ * @return String representing internal RRD state.
+ * @throws java.io.IOException Thrown in case of I/O related error.
+ */
+ public synchronized String dump() throws IOException {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(header.dump());
+ for (Datasource datasource : datasources) {
+ buffer.append(datasource.dump());
+ }
+ for (Archive archive : archives) {
+ buffer.append(archive.dump());
+ }
+ return buffer.toString();
+ }
+
+ final void archive(Datasource datasource, double value, double lastValue, long numUpdates) throws IOException {
+ int dsIndex = getDsIndex(datasource.getName());
+ for (Archive archive : archives) {
+ if (ConsolFun.AVERAGE.equals(archive.getConsolFun())) {
+ archive.archive(dsIndex, value, numUpdates);
+ } else {
+ archive.archive(dsIndex, lastValue, numUpdates);
+ }
+ }
+ }
+
+ /**
+ * Returns internal index number for the given datasource name.
+ *
+ * @param dsName Data source name.
+ * @return Internal index of the given data source name in this RRD.
+ * @throws java.io.IOException Thrown in case of I/O error.
+ */
+ public int getDsIndex(String dsName) throws IOException {
+ for (int i = 0; i < datasources.length; i++) {
+ if (datasources[i].getName().equals(dsName)) {
+ return i;
+ }
+ }
+ throw new IllegalArgumentException("Unknown datasource name: " + dsName);
+ }
+
+ /**
+ * Checks presence of a specific datasource.
+ *
+ * @param dsName Datasource name to check
+ * @return true
if datasource is present in this RRD, false
otherwise
+ * @throws java.io.IOException Thrown in case of I/O error.
+ */
+ public boolean containsDs(String dsName) throws IOException {
+ for (Datasource datasource : datasources) {
+ if (datasource.getName().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 java.io.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 a {@link java.io.OutputStream} object.
+ * @throws java.io.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 java.io.IOException Thrown in case of I/O related error
+ */
+ public synchronized String getXml() throws IOException {
+ ByteArrayOutputStream destination = new ByteArrayOutputStream(XML_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 java.io.IOException Thrown in case of I/O related error
+ */
+ public synchronized String exportXml() throws IOException {
+ return getXml();
+ }
+
+ /**
+ * Dumps internal RRD state to XML file.
+ * Use this XML file to convert your Rrd4j 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 java.io.IOException Thrown in case of I/O related error.
+ */
+ public synchronized void dumpXml(String filename) throws IOException {
+ try (OutputStream outputStream = new FileOutputStream(filename, false)) {
+ dumpXml(outputStream);
+ }
+ }
+
+ /**
+ * This method is just an alias for {@link #dumpXml(String) dumpXml(String)} method.
+ *
+ * @param filename a {@link java.lang.String} object.
+ * @throws java.io.IOException Thrown in case of I/O related error
+ */
+ public synchronized void exportXml(String filename) throws IOException {
+ dumpXml(filename);
+ }
+
+ /**
+ * Returns time of last update operation as timestamp (in seconds).
+ *
+ * @return Last update time (in seconds).
+ * @throws java.io.IOException if any.
+ */
+ public synchronized long getLastUpdateTime() throws IOException {
+ return header.getLastUpdateTime();
+ }
+
+ /**
+ *
+ * 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 java.io.IOException if any.
+ */
+ public synchronized RrdDef getRrdDef() throws IOException {
+ // set header
+ long startTime = header.getLastUpdateTime();
+ long step = header.getStep();
+ int version = header.getVersion();
+ String path = backend.getPath();
+ RrdDef rrdDef = new RrdDef(path, startTime, step, version);
+ // add datasources
+ for (Datasource datasource : datasources) {
+ DsDef dsDef = new DsDef(datasource.getName(),
+ datasource.getType(), 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;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
+ *
+ *
+ *
+ * @param uri {@link URI} to existing RRD file
+ * @return reference for the give RRD file
+ * @throws java.io.IOException Thrown in case of I/O error
+ * @deprecated Use the {@link org.rrd4j.core.RrdDb.Builder} instead.
+ */
+ @Deprecated
+ public RrdDb requestRrdDb(URI uri) throws IOException {
+ RrdBackendFactory factory = RrdBackendFactory.findFactory(uri);
+ return requestRrdDb(uri, factory);
+ }
+
+ RrdDb requestRrdDb(URI uri, RrdBackendFactory factory) throws IOException {
+ uri = factory.getCanonicalUri(uri);
+ RrdEntry ref = null;
+ try {
+ ref = getEntry(uri, true);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("request interrupted for " + uri, e);
+ }
+
+ //Someone might have already open it, rechecks
+ if (ref.count == 0) {
+ try {
+ ref.rrdDb = RrdDb.getBuilder().setPath(factory.getPath(uri)).setBackendFactory(factory).setPool(this).build();
+ } catch (IOException | RuntimeException e) {
+ passNext(ACTION.DROP, ref);
+ throw e;
+ }
+ }
+ ref.count++;
+ passNext(ACTION.SWAP, ref);
+ return ref.rrdDb;
+ }
+
+ /**
+ * Wait for a empty reference with no usage
+ * @param uri
+ * @return an reference with no usage
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ private RrdEntry waitEmpty(URI uri) throws IOException, InterruptedException {
+ RrdEntry ref = getEntry(uri, true);
+ try {
+ while (ref.count != 0) {
+ //Not empty, give it back, but wait for signal
+ passNext(ACTION.SWAP, ref);
+ ref.waitempty.await();
+ ref = getEntry(uri, true);
+ }
+ return ref;
+ } catch (InterruptedException e) {
+ passNext(ACTION.SWAP, ref);
+ Thread.currentThread().interrupt();
+ throw e;
+ }
+ }
+
+ /**
+ * Got an empty reference, use it only if slots are available
+ * But don't hold any lock waiting for it
+ * @param uri
+ * @return an reference with no usage
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ private RrdEntry requestEmpty(URI uri) throws InterruptedException, IOException {
+ RrdEntry ref = waitEmpty(uri);
+ ref.count = 1;
+ return ref;
+ }
+
+ /**
+ *
+ *
+ *
+ * @param rrdDef Definition of the RRD file to be created
+ * @return Reference to the newly created RRD file
+ * @throws java.io.IOException Thrown in case of I/O error
+ * @deprecated Use the {@link org.rrd4j.core.RrdDb.Builder} instead.
+ */
+ @Deprecated
+ public RrdDb requestRrdDb(RrdDef rrdDef) throws IOException {
+ return requestRrdDb(rrdDef, RrdBackendFactory.findFactory(rrdDef.getUri()));
+ }
+
+ RrdDb requestRrdDb(RrdDef rrdDef, RrdBackendFactory backend) throws IOException {
+ RrdEntry ref = null;
+ try {
+ URI uri = backend.getCanonicalUri(rrdDef.getUri());
+ ref = requestEmpty(uri);
+ ref.rrdDb = RrdDb.getBuilder().setRrdDef(rrdDef).setBackendFactory(backend).setPool(this).build();
+ return ref.rrdDb;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("request interrupted for new rrdDef " + rrdDef.getPath(), e);
+ } catch (RuntimeException e) {
+ passNext(ACTION.DROP, ref);
+ ref = null;
+ throw e;
+ } finally {
+ if (ref != null) {
+ passNext(ACTION.SWAP, ref);
+ }
+ }
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ * RrdDb
constructor
+ * argument (see documentation for {@link org.rrd4j.core.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.
+ *
+ * DEFAULTVERSION=2
*/
+ public static final int DEFAULTVERSION = 2;
+
+ private URI uri;
+ private long startTime = Util.getTime() + DEFAULT_INITIAL_SHIFT;
+ private long step = DEFAULT_STEP;
+ private int version = DEFAULTVERSION;
+
+ private ListRrdDb
constructor, new RRD will be created using the
+ * specified path.RrdDb
constructor, new RRD will be created using the
+ * specified path.
+ *
+ * @param uri URI to the new RRD.
+ */
+ public RrdDef(URI uri) {
+ this.uri = uri;
+ }
+
+ /**
+ * DsDef
.
+ *
+ * @param dsDef Datasource definition.
+ */
+ public void addDatasource(DsDef dsDef) {
+ if (dsDefs.contains(dsDef)) {
+ throw new IllegalArgumentException("Datasource already defined: " + dsDef.dump());
+ }
+ dsDefs.add(dsDef);
+ }
+
+ /**
+ * Double.NaN
if unknown.
+ * @param maxValue Maximal acceptable value. Use Double.NaN
if unknown.
+ * @throws java.lang.IllegalArgumentException Thrown if new datasource definition uses already used data
+ * source name.
+ */
+ public void addDatasource(String dsName, DsType dsType, long heartbeat, double minValue, double maxValue) {
+ addDatasource(new DsDef(dsName, dsType, heartbeat, minValue, maxValue));
+ }
+
+ /**
+ *
+ * DS:name:type:heartbeat:minValue:maxValue
+ *
+ *
+ * DS:input:COUNTER:600:0:U
+ *
+ * rrdcreate
+ * man page.ArcDef
.
+ *
+ * @param arcDef Archive definition.
+ * @throws java.lang.IllegalArgumentException Thrown if archive with the same consolidation function
+ * and the same number of steps is already added.
+ */
+ public void addArchive(ArcDef arcDef) {
+ if (arcDefs.contains(arcDef)) {
+ throw new IllegalArgumentException("Archive already defined: " + arcDef.dump());
+ }
+ arcDefs.add(arcDef);
+ }
+
+ /**
+ * Adds archive definitions to RRD definition in bulk.
+ *
+ * @param arcDefs Array of archive definition objects
+ * @throws java.lang.IllegalArgumentException Thrown if RRD definition already contains archive with
+ * the same consolidation function and the same number of steps.
+ */
+ public void addArchive(ArcDef... arcDefs) {
+ for (ArcDef arcDef : arcDefs) {
+ addArchive(arcDef);
+ }
+ }
+
+ /**
+ * Adds single archive definition by specifying its consolidation function, X-files factor,
+ * number of steps and rows. For the complete explanation of all archive
+ * definition parameters see RRDTool's
+ * rrdcreate man page.
+ *
+ * @param consolFun Consolidation function.
+ * @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 java.lang.IllegalArgumentException Thrown if archive with the same consolidation function
+ * and the same number of steps is already added.
+ */
+ public void addArchive(ConsolFun consolFun, double xff, int steps, int rows) {
+ addArchive(new ArcDef(consolFun, xff, steps, rows));
+ }
+
+ /**
+ *
+ * RRA:consolidationFunction:XFilesFactor:steps:rows
+ *
+ *
+ * RRA:AVERAGE:0.5:10:1000
+ *
+ * rrdcreate
+ * man page.create
command.
+ *
+ * @return Dumped content of RrdDb
object.
+ */
+ public String dump() {
+ StringBuilder sb = new StringBuilder("create \"");
+ sb.append(uri)
+ .append("\"")
+ .append(" --version ").append(getVersion())
+ .append(" --start ").append(getStartTime())
+ .append(" --step ").append(getStep()).append(" ");
+ for (DsDef dsDef : dsDefs) {
+ sb.append(dsDef.dump()).append(" ");
+ }
+ for (ArcDef arcDef : arcDefs) {
+ sb.append(arcDef.dump()).append(" ");
+ }
+ return sb.toString().trim();
+ }
+
+ String getRrdToolCommand() {
+ return dump();
+ }
+
+ void removeDatasource(String dsName) {
+ for (int i = 0; i < dsDefs.size(); i++) {
+ DsDef dsDef = dsDefs.get(i);
+ if (dsDef.getDsName().equals(dsName)) {
+ dsDefs.remove(i);
+ return;
+ }
+ }
+ throw new IllegalArgumentException("Could not find datasource named '" + dsName + "'");
+ }
+
+ void saveSingleDatasource(String dsName) {
+ Iteratorcompatible
is set to true, it returns an XML compatible with previous RRD4J's versions, using
+ * a path, instead of an URI.compatible
is set to true, it returns an XML compatible with previous RRD4J's versions, using
+ * a path, instead of an URI.compatible
is set to true, it returns an XML compatible with previous RRD4J versions, using
+ * a path, instead of an URI.
+ *
+ */
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof RrdDef)) {
+ return false;
+ }
+ RrdDef rrdDef2 = (RrdDef) obj;
+ // check primary RRD step
+ if (step != rrdDef2.step) {
+ return false;
+ }
+ // check datasources
+ DsDef[] dsDefs = getDsDefs(), dsDefs2 = rrdDef2.getDsDefs();
+ if (dsDefs.length != dsDefs2.length) {
+ return false;
+ }
+ for (DsDef dsDef : dsDefs) {
+ boolean matched = false;
+ for (DsDef aDsDefs2 : dsDefs2) {
+ if (dsDef.exactlyEqual(aDsDefs2)) {
+ matched = true;
+ break;
+ }
+ }
+ // this datasource could not be matched
+ if (!matched) {
+ return false;
+ }
+ }
+ // check archives
+ ArcDef[] arcDefs = getArcDefs(), arcDefs2 = rrdDef2.getArcDefs();
+ if (arcDefs.length != arcDefs2.length) {
+ return false;
+ }
+ for (ArcDef arcDef : arcDefs) {
+ boolean matched = false;
+ for (ArcDef anArcDefs2 : arcDefs2) {
+ if (arcDef.exactlyEqual(anArcDefs2)) {
+ matched = true;
+ break;
+ }
+ }
+ // this archive could not be matched
+ if (!matched) {
+ return false;
+ }
+ }
+ // everything matches
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((arcDefs == null) ? 0 : arcDefs.hashCode());
+ result = prime * result + ((dsDefs == null) ? 0 : dsDefs.hashCode());
+ result = prime * result + (int) (step ^ (step >>> 32));
+ return result;
+ }
+
+ /**
+ *
+ * <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>
+ *
+ *
+ *
+ * <some_tag>
and
+ * </some_tag>
) can be replaced with
+ * a variable of the following form: ${variable_name}
. Use
+ * {@link org.rrd4j.core.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 syntax 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 java.io.IOException Thrown in case of I/O error
+ * @throws java.lang.IllegalArgumentException Thrown in case of XML related error (parsing error, for example)
+ */
+ public RrdDefTemplate(InputSource xmlInputSource) throws IOException {
+ 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 java.io.IOException Thrown in case of I/O error
+ * @throws java.lang.IllegalArgumentException Thrown in case of XML related error (parsing error, for example)
+ */
+ public RrdDefTemplate(String xmlString) throws IOException {
+ 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 java.io.IOException Thrown in case of I/O error
+ * @throws java.lang.IllegalArgumentException Thrown in case of XML related error (parsing error, for example)
+ */
+ public RrdDefTemplate(File xmlFile) throws IOException {
+ 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 org.rrd4j.core.XmlTemplate#setVariable(String, String) setVariable()} methods. Once this method
+ * returns, all placeholder values are preserved. To remove them all, call inherited
+ * {@link org.rrd4j.core.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();
+ *
+ * columns
.rows
.new RrdDb(path)
call. To release allocated
+ * memory, you'll have to call {@link #delete(java.lang.String) delete(path)} method of this class.
+ *
+ */
+@RrdBackendAnnotation(name="MEMORY", shouldValidateHeader=false)
+public class RrdMemoryBackendFactory extends RrdBackendFactory {
+
+ protected final MapManaging the thread pool
+ * syncThreadPool
.syncThreadPool
.
+ *
+ *
+ * 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
). 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 java.io.IOException Thrown in case of I/O error
+ */
+ public static void setDsMinValue(String sourcePath, String datasourceName,
+ double newMinValue, boolean filterArchivedValues) throws IOException {
+ try (RrdDb rrd = RrdDb.getBuilder().setPath(sourcePath).build()) {
+ Datasource ds = rrd.getDatasource(datasourceName);
+ ds.setMinValue(newMinValue, filterArchivedValues);
+ }
+ }
+
+ /**
+ * Sets datasource max value to a new value.
+ *
+ * @param sourcePath Path to existing 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 java.io.IOException Thrown in case of I/O error
+ */
+ public static void setDsMaxValue(String sourcePath, String datasourceName,
+ double newMaxValue, boolean filterArchivedValues) throws IOException {
+ try (RrdDb rrd = RrdDb.getBuilder().setPath(sourcePath).build()) {
+ Datasource ds = rrd.getDatasource(datasourceName);
+ ds.setMaxValue(newMaxValue, filterArchivedValues);
+ }
+ }
+
+ /**
+ * Updates valid value range for the given datasource.
+ *
+ * @param sourcePath Path to existing 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 java.io.IOException Thrown in case of I/O error
+ */
+ public static void setDsMinMaxValue(String sourcePath, String datasourceName,
+ double newMinValue, double newMaxValue, boolean filterArchivedValues)
+ throws IOException {
+ try (RrdDb rrd = RrdDb.getBuilder().setPath(sourcePath).build()) {
+ Datasource ds = rrd.getDatasource(datasourceName);
+ ds.setMinMaxValue(newMinValue, newMaxValue, filterArchivedValues);
+ }
+ }
+
+ /**
+ * 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 steps of the target archive
+ * @param newXff New X-files factor for the target archive
+ * @throws java.io.IOException Thrown in case of I/O error
+ */
+ public static void setArcXff(String sourcePath, ConsolFun consolFun, int steps,
+ double newXff) throws IOException {
+ try (RrdDb rrd = RrdDb.getBuilder().setPath(sourcePath).build()) {
+ Archive arc = rrd.getArchive(consolFun, steps);
+ arc.setXff(newXff);
+ }
+ }
+
+ /**
+ * 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 java.io.IOException Thrown in case of I/O error
+ */
+ public static void resizeArchive(String sourcePath, String destPath, ConsolFun consolFun,
+ int numSteps, int newRows) throws IOException {
+ if (Util.sameFilePath(sourcePath, destPath)) {
+ throw new IllegalArgumentException(SOURCE_AND_DESTINATION_PATHS_ARE_THE_SAME);
+ }
+ if (newRows < 2) {
+ throw new IllegalArgumentException("New archive size must be at least 2");
+ }
+
+ try (RrdDb rrdSource = RrdDb.getBuilder().setPath(sourcePath).build()) {
+ 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();
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 java.io.IOException Thrown in case of I/O error
+ */
+ public static void resizeArchive(String sourcePath, ConsolFun consolFun,
+ int numSteps, int newRows, boolean saveBackup) throws IOException {
+ 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()) {
+ Files.delete(file.toPath());
+ }
+ }
+
+ /**
+ * 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 java.io.IOException Thrown in case of I/O error
+ */
+ public static void split(String sourcePath) throws IOException {
+ try (RrdDb rrdSource = RrdDb.getBuilder().setPath(sourcePath).build()) {
+ String[] dsNames = rrdSource.getDsNames();
+ for (String dsName : dsNames) {
+ RrdDef rrdDef = rrdSource.getRrdDef();
+ rrdDef.setPath(createSplitPath(dsName, sourcePath));
+ rrdDef.saveSingleDatasource(dsName);
+ try (RrdDb rrdDest = RrdDb.getBuilder().setRrdDef(rrdDef).build()) {
+ rrdSource.copyStateTo(rrdDest);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 java.io.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 RrdException("Not a directory: " + directory);
+ }
+ List
+ *
+ * Double.NaN
.Sample
object
+ * @throws java.lang.IllegalArgumentException Thrown if invalid data source name is supplied.
+ */
+ public Sample setValue(String dsName, double value) {
+ for (int i = 0; i < values.length; i++) {
+ if (dsNames[i].equals(dsName)) {
+ values[i] = value;
+ return this;
+ }
+ }
+ throw new IllegalArgumentException("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 java.lang.IllegalArgumentException Thrown if data source index is invalid.
+ */
+ public Sample setValue(int i, double value) {
+ if (i < values.length) {
+ values[i] = value;
+ return this;
+ }
+ throw new IllegalArgumentException("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 java.lang.IllegalArgumentException Thrown if the number of supplied values is zero or greater
+ * than the number of data sources defined in the RRD.
+ */
+ public Sample setValues(double... values) {
+ if (values.length <= this.values.length) {
+ System.arraycopy(values, 0, this.values, 0, values.length);
+ return this;
+ }
+ throw new IllegalArgumentException("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(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;
+ }
+
+ /**
+ * timestamp:value1:value2:...:valueN
.
+ * 1005234132:12.2:35.6:U:24.5
+ * NOW:12.2:35.6:U:24.5
+ *
+ * Sample
object
+ * @throws java.lang.IllegalArgumentException Thrown if too many datasource values are supplied
+ */
+ public Sample set(String timeAndValues) {
+ StringTokenizer tokenizer = new StringTokenizer(timeAndValues, ":", false);
+ int n = tokenizer.countTokens();
+ if (n > values.length + 1) {
+ throw new IllegalArgumentException("Invalid number of values specified (found " +
+ values.length + ", " + dsNames.length + " allowed)");
+ }
+ String timeToken = tokenizer.nextToken();
+ try {
+ time = Long.parseLong(timeToken);
+ }
+ catch (NumberFormatException nfe) {
+ if ("N".equalsIgnoreCase(timeToken) || "NOW".equalsIgnoreCase(timeToken)) {
+ time = Util.getTime();
+ }
+ else {
+ throw new IllegalArgumentException("Invalid sample timestamp: " + timeToken);
+ }
+ }
+ for (int i = 0; tokenizer.hasMoreTokens(); i++) {
+ try {
+ values[i] = Double.parseDouble(tokenizer.nextToken());
+ }
+ catch (NumberFormatException nfe) {
+ // NOP, value is already set to NaN
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Stores sample in the corresponding RRD. If the update operation succeeds,
+ * all datasource values in the sample will be set to Double.NaN (unknown) values.
+ *
+ * @throws java.io.IOException Thrown in case of I/O error.
+ */
+ public void update() throws IOException {
+ parentDb.store(this);
+ clearValues();
+ }
+
+ /**
+ * Creates sample with the timestamp and data source values supplied
+ * in the argument string and stores sample in the corresponding RRD.
+ * This method is just a shortcut for:
+ *
+ * set(timeAndValues);
+ * update();
+ *
+ *
+ * @param timeAndValues String made by concatenating sample timestamp with corresponding
+ * data source values delmited with colons. For example:
+ * 1005234132:12.2:35.6:U:24.5
+ * NOW:12.2:35.6:U:24.5
+ * @throws java.io.IOException Thrown in case of I/O error.
+ */
+ public void setAndUpdate(String timeAndValues) throws IOException {
+ set(timeAndValues);
+ update();
+ }
+
+ /**
+ * Dumps sample content using the syntax of RRDTool's update command.
+ *
+ * @return Sample dump.
+ */
+ public String dump() {
+ StringBuilder buffer = new StringBuilder("update \"");
+ buffer.append(parentDb.getRrdBackend().getPath()).append("\" ").append(time);
+ for (double value : values) {
+ buffer.append(':');
+ buffer.append(Util.formatDouble(value, "U", false));
+ }
+ return buffer.toString();
+ }
+
+ String getRrdToolCommand() {
+ return dump();
+ }
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/Util.java b/apps/jrobin/java/src/org/rrd4j/core/Util.java
new file mode 100644
index 0000000000..60cf2e6406
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/Util.java
@@ -0,0 +1,799 @@
+package org.rrd4j.core;
+
+import org.rrd4j.core.timespec.TimeParser;
+import org.rrd4j.core.timespec.TimeSpec;
+import org.rrd4j.ConsolFun;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.awt.*;
+import java.io.*;
+import java.net.JarURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * Class defines various utility functions used in Rrd4j.
+ *
+ * @author Sasa Markovic
+ */
+public class Util {
+
+ /** Constant MAX_LONG=Long.MAX_VALUE
*/
+ public static final long MAX_LONG = Long.MAX_VALUE;
+ /** Constant MIN_LONG=-Long.MAX_VALUE
*/
+ public static final long MIN_LONG = -Long.MAX_VALUE;
+
+ /** Constant MAX_DOUBLE=Double.MAX_VALUE
*/
+ public static final double MAX_DOUBLE = Double.MAX_VALUE;
+ /** Constant MIN_DOUBLE=-Double.MAX_VALUE
*/
+ public static final double MIN_DOUBLE = -Double.MAX_VALUE;
+
+ // pattern RRDTool uses to format doubles in XML files
+ static final String PATTERN = "0.0000000000E00";
+ // directory under $USER_HOME used for demo graphs storing
+ static final String RRD4J_DIR = "rrd4j-demo";
+
+ static final ThreadLocal(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.get().format(x);
+ }
+ return Double.toString(x);
+ }
+
+ static String formatDouble(double x, boolean forceExponents) {
+ return formatDouble(x, Double.toString(Double.NaN), forceExponents);
+ }
+
+ /**
+ * Formats double as a string using exponential notation (RRDTool like). Used for debugging
+ * through 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(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(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(int year, int month, int day, int hour, int min) {
+ 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);
+ }
+
+ /**
+ *
+ * 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(String s) {
+ try {
+ Double.parseDouble(s);
+ return true;
+ }
+ catch (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(String valueStr) {
+ return valueStr !=null && (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 java.lang.IllegalArgumentException If the input string is not 6 or 8 characters long (without optional '#')
+ */
+ public static Paint parseColor(String valueStr) {
+ String c = valueStr.startsWith("#") ? valueStr.substring(1) : valueStr;
+ if (c.length() != 6 && c.length() != 8) {
+ throw new IllegalArgumentException("Invalid color specification: " + valueStr);
+ }
+ 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 {
+ 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 Rrd4j demo graphs and creates it
+ * if necessary.
+ *
+ * @return Path to demo directory (defaults to $HOME/rrd4j/) if directory exists or
+ * was successfully created. Null if such directory could not be created.
+ */
+ public static String getRrd4jDemoDirectory() {
+ Path root;
+ if (System.getProperty("rrd4j.demopath") != null) {
+ root = Paths.get(System.getProperty("rrd4j.demopath"));
+ } else {
+ root = Paths.get(getUserHomeDirectory(), RRD4J_DIR);
+ }
+ try {
+ Files.createDirectories(root);
+ return root.toAbsolutePath().toString() + File.separator;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns full path to the file stored in the demo directory of Rrd4j
+ *
+ * @param filename Partial path to the file stored in the demo directory of Rrd4j
+ * (just name and extension, without parent directories)
+ * @return Full path to the file
+ */
+ public static String getRrd4jDemoPath(String filename) {
+ String demoDir = getRrd4jDemoDirectory();
+ if (demoDir != null) {
+ return demoDir + filename;
+ }
+ else {
+ return null;
+ }
+ }
+
+ static boolean sameFilePath(String pathname1, String pathname2) throws IOException {
+ Path path1 = Paths.get(pathname1);
+ Path path2 = Paths.get(pathname2);
+ if (Files.exists(path1) != Files.exists(path2)) {
+ return false;
+ } else if (Files.exists(path1) && Files.exists(path2)){
+ path1 = Paths.get(pathname1).toRealPath().normalize();
+ path2 = Paths.get(pathname2).toRealPath().normalize();
+ return Files.isSameFile(path1, path2);
+ } else {
+ return false;
+ }
+ }
+
+ static int getMatchingDatasourceIndex(RrdDb rrd1, int dsIndex, RrdDb rrd2) throws IOException {
+ String dsName = rrd1.getDatasource(dsIndex).getName();
+ try {
+ return rrd2.getDsIndex(dsName);
+ }
+ catch (IllegalArgumentException e) {
+ return -1;
+ }
+ }
+
+ static int getMatchingArchiveIndex(RrdDb rrd1, int arcIndex, RrdDb rrd2)
+ throws IOException {
+ Archive archive = rrd1.getArchive(arcIndex);
+ ConsolFun consolFun = archive.getConsolFun();
+ int steps = archive.getSteps();
+ try {
+ return rrd2.getArcIndex(consolFun, steps);
+ }
+ catch (IllegalArgumentException e) {
+ return -1;
+ }
+ }
+
+ static String getTmpFilename() throws IOException {
+ return File.createTempFile("rrd4j_", ".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(String timeStr) {
+ // try to parse it as long
+ try {
+ long timestamp = Long.parseLong(timeStr);
+ return Util.getCalendar(timestamp);
+ }
+ catch (NumberFormatException e) {
+ }
+ // not a long timestamp, try to parse it as data
+ SimpleDateFormat df = new SimpleDateFormat(ISO_DATE_FORMAT);
+ df.setLenient(false);
+ try {
+ Date date = df.parse(timeStr);
+ return Util.getCalendar(date);
+ }
+ catch (ParseException e) {
+ throw new IllegalArgumentException("Time/date not in " + ISO_DATE_FORMAT +
+ " format: " + timeStr);
+ }
+ }
+
+ /**
+ * Various DOM utility functions.
+ */
+ public static class Xml {
+
+ private static class SingletonHelper {
+ private static final DocumentBuilderFactory factory;
+ static {
+ factory = DocumentBuilderFactory.newInstance();
+ try {
+ factory.setIgnoringComments(true);
+ factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ factory.setValidating(false);
+ factory.setNamespaceAware(false);
+ } catch (ParserConfigurationException e) {
+ throw new UnsupportedOperationException("Missing DOM feature: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ private static final ErrorHandler eh = new ErrorHandler() {
+ public void error(SAXParseException exception) throws SAXException {
+ throw exception;
+ }
+ public void fatalError(SAXParseException exception) throws SAXException {
+ throw exception;
+ }
+ public void warning(SAXParseException exception) throws SAXException {
+ throw exception;
+ }
+ };
+
+ private Xml() {
+
+ }
+
+ public static Node[] getChildNodes(Node parentNode) {
+ return getChildNodes(parentNode, null);
+ }
+
+ public static Node[] getChildNodes(Node parentNode, String childName) {
+ ArrayListgetLapTime()
method call.
+ */
+ public static String getLapTime() {
+ long newLap = System.currentTimeMillis();
+ double seconds = (newLap - lastLap) / 1000.0;
+ lastLap = newLap;
+ return "[" + seconds + " sec]";
+ }
+
+ /**
+ * 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(double x, 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 java.io.IOException Thrown if canonical file path could not be resolved
+ */
+ public static String getCanonicalPath(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(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(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(double[] values) {
+ double max = Double.NaN;
+ for (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(double[] values) {
+ double min = Double.NaN;
+ for (double value : values) {
+ min = Util.min(min, value);
+ }
+ return min;
+ }
+
+ /**
+ * Equivalent of the C-style sprintf function.
+ *
+ * @param format Format string
+ * @param args Arbitrary list of arguments
+ * @return Formatted string
+ * @param l a {@link java.util.Locale} object.
+ */
+ public static String sprintf(Locale l, String format, Object... args) {
+ String fmt = SPRINTF_PATTERN.matcher(format).replaceAll("$1%$2$3");
+ return String.format(l, fmt, args);
+ }
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/XmlReader.java b/apps/jrobin/java/src/org/rrd4j/core/XmlReader.java
new file mode 100644
index 0000000000..fe8bf8ff3d
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/XmlReader.java
@@ -0,0 +1,127 @@
+package org.rrd4j.core;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.rrd4j.ConsolFun;
+import org.rrd4j.DsType;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+
+class XmlReader extends DataImporter {
+
+ private Element root;
+ private Node[] dsNodes, arcNodes;
+
+ XmlReader(String xmlFilePath) throws IOException {
+ root = Util.Xml.getRootElement(new File(xmlFilePath));
+ dsNodes = Util.Xml.getChildNodes(root, "ds");
+ arcNodes = Util.Xml.getChildNodes(root, "rra");
+ }
+
+ public String getVersion() {
+ return Util.Xml.getChildValue(root, "version");
+ }
+
+ public long getLastUpdateTime() {
+ return Util.Xml.getChildValueAsLong(root, "lastupdate");
+ }
+
+ public long getStep() {
+ return Util.Xml.getChildValueAsLong(root, "step");
+ }
+
+ public int getDsCount() {
+ return dsNodes.length;
+ }
+
+ public int getArcCount() {
+ return arcNodes.length;
+ }
+
+ public String getDsName(int dsIndex) {
+ return Util.Xml.getChildValue(dsNodes[dsIndex], "name");
+ }
+
+ @Override
+ public DsType getDsType(int dsIndex) {
+ String dsTypeName = Util.Xml.getChildValue(dsNodes[dsIndex], "type");
+ return DsType.valueOf(dsTypeName.toUpperCase(Locale.ENGLISH));
+ }
+
+ public long getHeartbeat(int dsIndex) {
+ return Util.Xml.getChildValueAsLong(dsNodes[dsIndex], "minimal_heartbeat");
+ }
+
+ public double getMinValue(int dsIndex) {
+ return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "min");
+ }
+
+ public double getMaxValue(int dsIndex) {
+ return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "max");
+ }
+
+ public double getLastValue(int dsIndex) {
+ return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "last_ds");
+ }
+
+ public double getAccumValue(int dsIndex) {
+ return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "value");
+ }
+
+ public long getNanSeconds(int dsIndex) {
+ return Util.Xml.getChildValueAsLong(dsNodes[dsIndex], "unknown_sec");
+ }
+
+ public ConsolFun getConsolFun(int arcIndex) {
+ return ConsolFun.valueOf(Util.Xml.getChildValue(arcNodes[arcIndex], "cf"));
+ }
+
+ public double getXff(int arcIndex) {
+ Node arc = arcNodes[arcIndex];
+ Node[] params = Util.Xml.getChildNodes(arc, "params");
+ //RRD4J xml, xff is in the archive definition
+ if(params.length == 0) {
+ return Util.Xml.getChildValueAsDouble(arc, "xff");
+ }
+ //RRDTool xml, xff is in the archive definition
+ else {
+ return Util.Xml.getChildValueAsDouble(params[0], "xff");
+ }
+ }
+
+ public int getSteps(int arcIndex) {
+ return Util.Xml.getChildValueAsInt(arcNodes[arcIndex], "pdp_per_row");
+ }
+
+ public double getStateAccumValue(int arcIndex, int dsIndex) {
+ Node cdpNode = Util.Xml.getFirstChildNode(arcNodes[arcIndex], "cdp_prep");
+ Node[] dsNodes = Util.Xml.getChildNodes(cdpNode, "ds");
+ return Util.Xml.getChildValueAsDouble(dsNodes[dsIndex], "value");
+ }
+
+ public int getStateNanSteps(int arcIndex, int dsIndex) {
+ Node cdpNode = Util.Xml.getFirstChildNode(arcNodes[arcIndex], "cdp_prep");
+ Node[] dsNodes = Util.Xml.getChildNodes(cdpNode, "ds");
+ return Util.Xml.getChildValueAsInt(dsNodes[dsIndex], "unknown_datapoints");
+ }
+
+ public int getRows(int arcIndex) {
+ Node dbNode = Util.Xml.getFirstChildNode(arcNodes[arcIndex], "database");
+ Node[] rows = Util.Xml.getChildNodes(dbNode, "row");
+ return rows.length;
+ }
+
+ public double[] getValues(int arcIndex, int dsIndex) {
+ 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;
+ }
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/XmlTemplate.java b/apps/jrobin/java/src/org/rrd4j/core/XmlTemplate.java
new file mode 100644
index 0000000000..e99665d561
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/XmlTemplate.java
@@ -0,0 +1,466 @@
+package org.rrd4j.core;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+import java.awt.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class used as a base class for various XML template related classes. Class provides
+ * methods for XML source parsing and XML tree traversing. XML source may have unlimited
+ * number of placeholders (variables) in the format ${variable_name}
.
+ * Methods are provided to specify variable values at runtime.
+ * Note that this class has limited functionality: XML source gets parsed, and variable
+ * values are collected. You have to extend this class to do something more useful.
+ */
+public abstract class XmlTemplate {
+ private static final String PATTERN_STRING = "\\$\\{(\\w+)\\}";
+ private static final Pattern PATTERN = Pattern.compile(PATTERN_STRING);
+
+ protected Element root;
+ private HashMap${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, Integer.valueOf(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, Long.valueOf(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, Double.valueOf(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) {
+ StringBuilder s = new StringBuilder(Integer.toHexString(i));
+ while (s.length() < 2) {
+ s.insert(0, "0");
+ }
+ return s.toString();
+ }
+
+ /**
+ * 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, Boolean.toString(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, Integer.toString(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, Long.toString(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 a {@link java.lang.String} object.
+ */
+ 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, Boolean.toString(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();
+ }
+
+ /**
+ * Writes XML comment to output stream
+ *
+ * @param comment comment string
+ */
+ public void writeComment(Object comment) {
+ if (comment instanceof Date) {
+ comment = ISOLIKE.get().format((Date) comment);
+ }
+ writer.println(indent + "");
+ }
+
+ private static String escape(String s) {
+ return s.replaceAll("<", "<").replaceAll(">", ">");
+ }
+
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/jrrd/Archive.java b/apps/jrobin/java/src/org/rrd4j/core/jrrd/Archive.java
new file mode 100644
index 0000000000..c0b34b92bf
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/jrrd/Archive.java
@@ -0,0 +1,380 @@
+package org.rrd4j.core.jrrd;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Instances of this class model an archive section of an RRD file.
+ *
+ * @author Ciaran Treanor
+ * @version $Revision: 1.1 $
+ */
+public class Archive {
+
+ private static enum rra_par_en {RRA_cdp_xff_val, RRA_hw_alpha};
+
+ final RRDatabase db;
+ /** Header offset within file in bytes */
+ final long headerOffset;
+ /** Header size in bytes */
+ private final long headerSize;
+ /** Data offset within file in bytes */
+ long dataOffset;
+ private final ConsolidationFunctionType type;
+ /** Data row count */
+ final int rowCount;
+ final int pdpCount;
+ final double xff;
+
+ /// Following fields are initialized during RRDatabase construction
+ /// and in fact immutable
+
+ /** Consolitation data points */
+ ListCDPStatusBlock
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 Iteratorvalues
.DS_NAM_SIZE=20
*/
+ final int DS_NAM_SIZE = 20;
+ /** Constant DST_SIZE=20
*/
+ final int DST_SIZE = 20;
+ /** Constant CF_NAM_SIZE=20
*/
+ final int CF_NAM_SIZE = 20;
+ /** Constant LAST_DS_LEN=30
*/
+ final int LAST_DS_LEN = 30;
+ /** Constant COOKIE="RRD"
*/
+ static final String COOKIE = "RRD";
+ /** Constant MAX_SUPPORTED_VERSION=3
*/
+ public static final int MAX_SUPPORTED_VERSION = 3;
+ /** Constant UNDEFINED_VERSION="UNDEF"
*/
+ public static final String UNDEFINED_VERSION = "UNDEF";
+ /** Constant UNDEFINED_VERSION_AS_INT=-1
*/
+ public static final int UNDEFINED_VERSION_AS_INT = -1;
+ /** Constant VERSION_WITH_LAST_UPDATE_SEC=3
*/
+ public static int VERSION_WITH_LAST_UPDATE_SEC = 3;
+ /** Constant FLOAT_COOKIE=8.642135E130
*/
+ static final double FLOAT_COOKIE = 8.642135E130;
+ /** Constant SIZE_OF_DOUBLE=8
*/
+ public static final int SIZE_OF_DOUBLE = 8;
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/jrrd/DataChunk.java b/apps/jrobin/java/src/org/rrd4j/core/jrrd/DataChunk.java
new file mode 100644
index 0000000000..a5c1fa12d5
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/jrrd/DataChunk.java
@@ -0,0 +1,129 @@
+package org.rrd4j.core.jrrd;
+
+import java.util.Map;
+
+import org.rrd4j.data.LinearInterpolator;
+import org.rrd4j.data.Plottable;
+
+/**
+ * Models a chunk of result data from an RRDatabase.
+ *
+ * @author Ciaran Treanor
+ * @version $Revision: 1.1 $
+ */
+public class DataChunk {
+
+ private static final String NEWLINE = System.getProperty("line.separator");
+ /** Start time in seconds since epoch */
+ private final long startTime;
+ /** Row number offset relative to current row. Can be negative */
+ final int startOffset;
+ /** Row number offset relative to current row */
+ final int endOffset;
+ /** Step in seconds */
+ private final long step;
+ /** Number of datasources must be equal to number of datasources in file */
+ final int dsCount;
+ final double[][] data;
+ private final int rows;
+ /** Map datasource name to datasource index */
+ private final Mapdata
.DataSource
s in the database.
+ *
+ * @return the number of DataSource
s in the database.
+ */
+ public int getDSCount() {
+ return dsCount;
+ }
+
+ /**
+ * Returns the number of Archive
s in the database.
+ *
+ * @return the number of Archive
s 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() {
+
+ StringBuilder sb = new StringBuilder("[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/rrd4j/core/jrrd/PDPStatusBlock.java b/apps/jrobin/java/src/org/rrd4j/core/jrrd/PDPStatusBlock.java
new file mode 100644
index 0000000000..95828f4e1e
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/jrrd/PDPStatusBlock.java
@@ -0,0 +1,81 @@
+package org.rrd4j.core.jrrd;
+
+import java.io.IOException;
+
+/**
+ * Instances of this class model the primary data point status from an RRD file.
+ *
+ * @author Ciaran Treanor
+ * @version $Revision: 1.1 $
+ */
+public class PDPStatusBlock {
+
+ private long offset;
+ private long size;
+ String lastReading;
+ int unknownSeconds;
+ double value;
+ private static enum pdp_par_en {PDP_unkn_sec_cnt, PDP_val};
+
+ PDPStatusBlock(RRDFile file) throws IOException {
+
+ offset = file.getFilePointer();
+ lastReading = file.readString(Constants.LAST_DS_LEN);
+ UnivalArray scratch = file.getUnivalArray(10);
+ unknownSeconds = (int) scratch.getLong(pdp_par_en.PDP_unkn_sec_cnt);
+ value = scratch.getDouble(pdp_par_en.PDP_val);
+
+ 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() {
+
+ StringBuilder sb = new StringBuilder("[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/rrd4j/core/jrrd/RRDFile.java b/apps/jrobin/java/src/org/rrd4j/core/jrrd/RRDFile.java
new file mode 100644
index 0000000000..aac650941b
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/jrrd/RRDFile.java
@@ -0,0 +1,218 @@
+package org.rrd4j.core.jrrd;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+import org.rrd4j.core.InvalidRrdException;
+
+/**
+ * This class is used read information from an RRD file. Writing
+ * to RRD files is not currently supported. It uses NIO's RandomAccessFile to read the file
+ *
+ * Currently this can read RRD files that were generated on Solaris (Sparc)
+ * and Linux (x86).
+ *
+ * @author Ciaran Treanor
+ * @version $Revision: 1.1 $
+ */
+class RRDFile implements Constants {
+
+ /** Constant FLOAT_COOKIE_BIG_ENDIAN={0x5B, 0x1F, 0x2B, 0x43,
+ (byte) 0xC7, (byte) 0xC0, 0x25,
+ 0x2F}
*/
+ private static final byte[] FLOAT_COOKIE_BIG_ENDIAN = {0x5B, 0x1F, 0x2B, 0x43,
+ (byte) 0xC7, (byte) 0xC0, 0x25,
+ 0x2F};
+ /** Constant FLOAT_COOKIE_LITTLE_ENDIAN={0x2F, 0x25, (byte) 0xC0,
+ (byte) 0xC7, 0x43, 0x2B, 0x1F,
+ 0x5B}
*/
+ private static final byte[] FLOAT_COOKIE_LITTLE_ENDIAN = {0x2F, 0x25, (byte) 0xC0,
+ (byte) 0xC7, 0x43, 0x2B, 0x1F,
+ 0x5B};
+
+ private int alignment;
+ private int longSize = 4;
+
+ private final FileInputStream underlying;
+ private final MappedByteBuffer mappedByteBuffer;
+
+ private ByteOrder order;
+
+ RRDFile(String name) throws IOException {
+ this(new File(name));
+ }
+
+ RRDFile(File file) throws IOException {
+ long len = file.length();
+ if (len > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ "RRDFile cannot read files larger than 2**31 because of limitations of java.nio.ByteBuffer");
+ }
+
+ boolean ok = false;
+ try {
+ underlying = new FileInputStream(file);
+ mappedByteBuffer = underlying.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, len);
+ initDataLayout(file);
+ ok = true;
+ } finally {
+ if (!ok) {
+ try {
+ close();
+ } catch (Throwable ignored) {
+ }
+ // and then rethrow
+ }
+ }
+ }
+
+ private void initDataLayout(File file) throws IOException {
+
+ if (file.exists()) { // Load the data formats from the file
+ byte[] buffer = new byte[32];
+ mappedByteBuffer.get(buffer);
+ ByteBuffer bbuffer = ByteBuffer.wrap(buffer);
+
+ int index;
+
+ if ((index = indexOf(FLOAT_COOKIE_BIG_ENDIAN, buffer)) != -1) {
+ order = ByteOrder.BIG_ENDIAN;
+ }
+ else if ((index = indexOf(FLOAT_COOKIE_LITTLE_ENDIAN, buffer))
+ != -1) {
+ order = ByteOrder.LITTLE_ENDIAN;
+ }
+ else {
+ throw new InvalidRrdException("Invalid RRD file");
+ }
+ mappedByteBuffer.order(order);
+ bbuffer.order(order);
+
+ switch (index) {
+
+ case 12:
+ alignment = 4;
+ break;
+
+ case 16:
+ alignment = 8;
+ break;
+
+ default:
+ throw new RuntimeException("Unsupported architecture");
+ }
+
+ bbuffer.position(index + 8);
+ //We cannot have dsCount && rracount == 0
+ //If one is 0, it's a 64 bits rrd
+ int int1 = bbuffer.getInt(); //Should be dsCount in ILP32
+ int int2 = bbuffer.getInt(); //Should be rraCount in ILP32
+ if(int1 == 0 || int2 ==0) {
+ longSize = 8;
+ }
+ }
+ else { // Default to data formats for this hardware architecture
+ }
+ // Reset file pointer to start of file
+ mappedByteBuffer.rewind();
+ }
+
+ private int indexOf(byte[] pattern, byte[] array) {
+ return (new String(array)).indexOf(new String(pattern));
+ }
+
+ boolean isBigEndian() {
+ return order == ByteOrder.BIG_ENDIAN;
+ }
+
+ int getAlignment() {
+ return alignment;
+ }
+
+ double readDouble() throws IOException {
+ return mappedByteBuffer.getDouble();
+ }
+
+ int readInt() throws IOException {
+ return mappedByteBuffer.getInt();
+ }
+
+ int readLong() throws IOException {
+ if(longSize == 4) {
+ return mappedByteBuffer.getInt();
+ }
+ else {
+ return (int) mappedByteBuffer.getLong();
+ }
+ }
+
+ String readString(int maxLength) throws IOException {
+ byte[] array = new byte[maxLength];
+ mappedByteBuffer.get(array);
+
+ return new String(array, 0, maxLength).trim();
+ }
+
+ void skipBytes(int n) throws IOException {
+ mappedByteBuffer.position(mappedByteBuffer.position() + n);
+ }
+
+ int align(int boundary) throws IOException {
+
+ int skip = (int) (boundary - (mappedByteBuffer.position() % boundary)) % boundary;
+
+ if (skip != 0) {
+ mappedByteBuffer.position(mappedByteBuffer.position() + skip);
+ }
+
+ return skip;
+ }
+
+ int align() throws IOException {
+ return align(alignment);
+ }
+
+ long info() throws IOException {
+ return mappedByteBuffer.position();
+ }
+
+ long getFilePointer() throws IOException {
+ return mappedByteBuffer.position();
+ }
+
+ void close() throws IOException {
+ if (underlying != null) {
+ underlying.close();
+ }
+ }
+
+ void read(ByteBuffer bb) throws IOException{
+ int count = bb.remaining();
+ bb.put((ByteBuffer) mappedByteBuffer.duplicate().limit(mappedByteBuffer.position() + count));
+ mappedByteBuffer.position(mappedByteBuffer.position() + count);
+ }
+
+ UnivalArray getUnivalArray(int size) throws IOException {
+ return new UnivalArray(this, size);
+ }
+
+ /**
+ * @return the long size in bits for this file
+ */
+ int getBits() {
+ return longSize * 8;
+ }
+
+ public void seek(long position) {
+ mappedByteBuffer.position((int) position);
+ }
+
+ public void seekToEndOfFile() {
+ mappedByteBuffer.position(mappedByteBuffer.limit());
+ }
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/jrrd/RRDatabase.java b/apps/jrobin/java/src/org/rrd4j/core/jrrd/RRDatabase.java
new file mode 100644
index 0000000000..c609224c1a
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/jrrd/RRDatabase.java
@@ -0,0 +1,529 @@
+package org.rrd4j.core.jrrd;
+
+import java.io.Closeable;
+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.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.rrd4j.core.RrdException;
+
+/**
+ * Instances of this class model
+ * Round Robin Database
+ * (RRD) files.
+ *
+ * @author Ciaran Treanor
+ * @version $Revision: 1.1 $
+ */
+public class RRDatabase implements Closeable {
+
+ final RRDFile rrdFile;
+
+ // RRD file name
+ private final String name;
+ final Header header;
+ private final ArrayListHeader
for this database.
+ *
+ * @return the Header
for this database.
+ */
+ public Header getHeader() {
+ return header;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * double
+ * is 0.0000000000E0.
+ *
+ * @param s the PrintStream to print the header information to.
+ */
+ public void printInfo(PrintStream s) {
+
+ NumberFormat numberFormat = new DecimalFormat("0.0000000000E0");
+
+ printInfo(s, numberFormat);
+ }
+
+ /**
+ * Returns data from the database corresponding to the given consolidation
+ * function and a step size of 1.
+ *
+ * @param type the consolidation function that should have been applied to
+ * the data.
+ * @return the raw data.
+ * @throws java.lang.IllegalArgumentException if there was a problem locating a data archive with
+ * the requested consolidation function.
+ * @throws java.io.IOException if there was a problem reading data from the database.
+ */
+ public DataChunk getData(ConsolidationFunctionType type) throws IOException {
+ Calendar endCal = Calendar.getInstance();
+
+ endCal.set(Calendar.MILLISECOND, 0);
+
+ Calendar startCal = (Calendar) endCal.clone();
+
+ startCal.add(Calendar.DATE, -1);
+
+ return getData(type, startCal.getTime(), endCal.getTime(), 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 java.lang.IllegalArgumentException if there was a problem locating a data archive with
+ * the requested consolidation function.
+ * @throws java.io.IOException if there was a problem reading data from the database.
+ * @param startDate a {@link java.util.Date} object.
+ * @param endDate a {@link java.util.Date} object.
+ */
+ public DataChunk getData(ConsolidationFunctionType type, Date startDate, Date endDate, long step)
+ throws IOException {
+ long end = endDate.getTime() / 1000;
+ long start = startDate.getTime() / 1000;
+ return getData(type, start, end, step);
+ }
+
+ /**
+ * double
s 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 (DataSource ds : dataSources) {
+ ds.printInfo(s, numberFormat);
+ }
+
+ int index = 0;
+
+ for (Archive archive : archives) {
+ archive.printInfo(s, numberFormat, index++);
+ }
+ }
+
+ /**
+ * Outputs the content of the database to the given print stream
+ * as a stream of XML. The XML format is almost identical to that produced by
+ * rrdtool dump
+ *
+ * A flush is issued at the end of the XML generation, so auto flush of the PrintStream can be set to false
+ *
+ * @param s the PrintStream to send the XML to.
+ */
+ public void toXml(PrintStream s) {
+
+ s.println("");
+ s.println("
+ *
+ *
+ * RRDatabase db = new RRDatabase("native.rrd");
+ * RrdGraphDef() gd = RrdGraphDef();
+ * Calendar endCal = Calendar.getInstance();
+ * endCal.set(Calendar.MILLISECOND, 0);
+ * Calendar startCal = (Calendar) endCal.clone();
+ * startCal.add(Calendar.DATE, -1);
+ * DataChunk chunk = db.getData(ConsolidationFunctionType.AVERAGE, startCal.getTime(), endCal.getTime(), 1L);
+ * for(String name: db.getDataSourcesName()) {
+ * gd.datasource(name, chunk.toPlottable(name));
+ * }
+ *
+ *
+ * @author Ciaran Treanor
+ * @version $Revision: 1.1 $
+ */
+package org.rrd4j.core.jrrd;
diff --git a/apps/jrobin/java/src/org/rrd4j/core/package-info.java b/apps/jrobin/java/src/org/rrd4j/core/package-info.java
new file mode 100644
index 0000000000..14848b8754
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * core RRD4J implementation.
+ */
+package org.rrd4j.core;
diff --git a/apps/jrobin/java/src/org/rrd4j/core/timespec/Epoch.java b/apps/jrobin/java/src/org/rrd4j/core/timespec/Epoch.java
new file mode 100644
index 0000000000..b5efe7c673
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/timespec/Epoch.java
@@ -0,0 +1,179 @@
+package org.rrd4j.core.timespec;
+
+import org.rrd4j.core.Util;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ *
+ *
+ * The current timestamp is displayed in the title bar :)
+ *
+ */
+public class Epoch extends JFrame {
+ 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"
+ };
+
+ @SuppressWarnings("unchecked")
+ private static final ThreadLocal
");
+ for (String supportedFormat : supportedFormats) {
+ tooltipBuff.append(supportedFormat).append("
");
+ }
+ tooltipBuff.append("AT-style time specification
");
+ tooltipBuff.append("timestamp
");
+ tooltipBuff.append("Copyright (c) 2013 The RRD4J Authors. Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. Copyright (c) 2013 The OpenNMS Group, Inc. Licensed under the Apache License, Version 2.0.");
+ 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 ThreadLocal
+ * TimeParser p = new TimeParser("now-1day");
+ * TimeSpec ts = p.parse();
+ * System.out.println("Timestamp was: " + ts.getTimestamp();
+ *
+ *
+ * @return Timestamp (in seconds, no milliseconds)
+ */
+ public long getTimestamp() {
+ 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 + ")";
+ }
+
+ /**
+ *
+ * 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
+ */
+ public static Calendar[] getTimes(TimeSpec spec1, TimeSpec spec2) {
+ if (spec1.type == TYPE_START || spec2.type == TYPE_END) {
+ throw new IllegalArgumentException("Recursive time specifications not allowed");
+ }
+ spec1.context = spec2;
+ spec2.context = spec1;
+ return new Calendar[]{
+ spec1.getTime(),
+ spec2.getTime()
+ };
+ }
+
+ /**
+ *
+ * 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)
+ */
+ public static long[] getTimestamps(TimeSpec spec1, TimeSpec spec2) {
+ Calendar[] gcs = getTimes(spec1, spec2);
+ return new long[] {
+ Util.getTimestamp(gcs[0]), Util.getTimestamp(gcs[1])
+ };
+ }
+}
+
diff --git a/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeToken.java b/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeToken.java
new file mode 100644
index 0000000000..0eb1e546cc
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeToken.java
@@ -0,0 +1,121 @@
+package org.rrd4j.core.timespec;
+
+class TimeToken {
+ /** Constant MIDNIGHT=1
*/
+ public static final int MIDNIGHT = 1;
+ /** Constant NOON=2
*/
+ public static final int NOON = 2;
+ /** Constant TEATIME=3
*/
+ public static final int TEATIME = 3;
+ /** Constant PM=4
*/
+ public static final int PM = 4;
+ /** Constant AM=5
*/
+ public static final int AM = 5;
+ /** Constant YESTERDAY=6
*/
+ public static final int YESTERDAY = 6;
+ /** Constant TODAY=7
*/
+ public static final int TODAY = 7;
+ /** Constant TOMORROW=8
*/
+ public static final int TOMORROW = 8;
+ /** Constant NOW=9
*/
+ public static final int NOW = 9;
+ /** Constant START=10
*/
+ public static final int START = 10;
+ /** Constant END=11
*/
+ public static final int END = 11;
+ /** Constant SECONDS=12
*/
+ public static final int SECONDS = 12;
+ /** Constant MINUTES=13
*/
+ public static final int MINUTES = 13;
+ /** Constant HOURS=14
*/
+ public static final int HOURS = 14;
+ /** Constant DAYS=15
*/
+ public static final int DAYS = 15;
+ /** Constant WEEKS=16
*/
+ public static final int WEEKS = 16;
+ /** Constant MONTHS=17
*/
+ public static final int MONTHS = 17;
+ /** Constant YEARS=18
*/
+ public static final int YEARS = 18;
+ /** Constant MONTHS_MINUTES=19
*/
+ public static final int MONTHS_MINUTES = 19;
+ /** Constant NUMBER=20
*/
+ public static final int NUMBER = 20;
+ /** Constant PLUS=21
*/
+ public static final int PLUS = 21;
+ /** Constant MINUS=22
*/
+ public static final int MINUS = 22;
+ /** Constant DOT=23
*/
+ public static final int DOT = 23;
+ /** Constant COLON=24
*/
+ public static final int COLON = 24;
+ /** Constant SLASH=25
*/
+ public static final int SLASH = 25;
+ /** Constant ID=26
*/
+ public static final int ID = 26;
+ /** Constant JUNK=27
*/
+ public static final int JUNK = 27;
+ /** Constant JAN=28
*/
+ public static final int JAN = 28;
+ /** Constant FEB=29
*/
+ public static final int FEB = 29;
+ /** Constant MAR=30
*/
+ public static final int MAR = 30;
+ /** Constant APR=31
*/
+ public static final int APR = 31;
+ /** Constant MAY=32
*/
+ public static final int MAY = 32;
+ /** Constant JUN=33
*/
+ public static final int JUN = 33;
+ /** Constant JUL=34
*/
+ public static final int JUL = 34;
+ /** Constant AUG=35
*/
+ public static final int AUG = 35;
+ /** Constant SEP=36
*/
+ public static final int SEP = 36;
+ /** Constant OCT=37
*/
+ public static final int OCT = 37;
+ /** Constant NOV=38
*/
+ public static final int NOV = 38;
+ /** Constant DEC=39
*/
+ public static final int DEC = 39;
+ /** Constant SUN=40
*/
+ public static final int SUN = 40;
+ /** Constant MON=41
*/
+ public static final int MON = 41;
+ /** Constant TUE=42
*/
+ public static final int TUE = 42;
+ /** Constant WED=43
*/
+ public static final int WED = 43;
+ /** Constant THU=44
*/
+ public static final int THU = 44;
+ /** Constant FRI=45
*/
+ public static final int FRI = 45;
+ /** Constant SAT=46
*/
+ public static final int SAT = 46;
+ /** Constant EOF=-1
*/
+ public static final int EOF = -1;
+
+ final String value; /* token name */
+ final int token_id; /* token id */
+
+ /**
+ *