forked from I2P_Developers/i2p.i2p
Add missing parts of rrd4j 3.5 omitted from previous checkin (ticket #2684)
Apache 2.0 and LGPLv2.1
This commit is contained in:
79
apps/jrobin/java/src/com/tomgibara/crinch/hashing/Hash.java
Normal file
79
apps/jrobin/java/src/com/tomgibara/crinch/hashing/Hash.java
Normal file
@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Implementations of this interface can generate one hash value for a given
|
||||
* object. Depending upon the implementation, null values may be supported.
|
||||
* </p>
|
||||
*
|
||||
* @author tomgibara
|
||||
*
|
||||
* @param <T>
|
||||
* the type of objects for which hashes may be generated
|
||||
*/
|
||||
|
||||
public interface Hash<T> {
|
||||
|
||||
HashRange getRange();
|
||||
|
||||
/**
|
||||
* The hash value as a {@link BigInteger}. This method may be useful in
|
||||
* circumstances where the generated hash is too large to be accomodated in
|
||||
* a single primitive value, eg. if cryptographic hashes are being used.
|
||||
*
|
||||
* @param value
|
||||
* the object to be hashed
|
||||
* @return the object's hash code, never null
|
||||
* @throws IllegalArgumentException
|
||||
* if the value cannot be hashed
|
||||
*/
|
||||
|
||||
BigInteger hashAsBigInt(T value) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* The hash value as an int. This method should provide better performance
|
||||
* for integer-ranged hashes. This value is not guaranteed to lie within the
|
||||
* indicated {@link com.tomgibara.crinch.hashing.HashRange}.
|
||||
*
|
||||
* @param value
|
||||
* the object to be hashed
|
||||
* @return the object's hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the value cannot be hashed
|
||||
*/
|
||||
|
||||
int hashAsInt(T value) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* The hash value as a long. This method should provide better performance
|
||||
* for long-ranged hashes. This value is not guaranteed to lie within the
|
||||
* indicated {@link com.tomgibara.crinch.hashing.HashRange}.
|
||||
*
|
||||
* @param value
|
||||
* the object to be hashed
|
||||
* @return the object's hash code
|
||||
* @throws IllegalArgumentException
|
||||
* if the value cannot be hashed
|
||||
*/
|
||||
|
||||
long hashAsLong(T value) throws IllegalArgumentException;
|
||||
|
||||
}
|
136
apps/jrobin/java/src/com/tomgibara/crinch/hashing/HashRange.java
Normal file
136
apps/jrobin/java/src/com/tomgibara/crinch/hashing/HashRange.java
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Records the range of values that a hash value may take. Both range values are inclusive.
|
||||
*
|
||||
* @author tomgibara
|
||||
*
|
||||
*/
|
||||
|
||||
//TODO really need to revisit the inclusivity of maximum bound
|
||||
public class HashRange {
|
||||
|
||||
// statics
|
||||
|
||||
private static final BigInteger INT_MINIMUM = BigInteger.valueOf(Integer.MIN_VALUE);
|
||||
private static final BigInteger INT_MAXIMUM = BigInteger.valueOf(Integer.MAX_VALUE);
|
||||
private static final BigInteger LONG_MINIMUM = BigInteger.valueOf(Long.MIN_VALUE);
|
||||
private static final BigInteger LONG_MAXIMUM = BigInteger.valueOf(Long.MAX_VALUE);
|
||||
|
||||
public static final HashRange FULL_INT_RANGE = new HashRange(INT_MINIMUM, INT_MAXIMUM);
|
||||
public static final HashRange POSITIVE_INT_RANGE = new HashRange(BigInteger.ONE, INT_MAXIMUM);
|
||||
public static final HashRange FULL_LONG_RANGE = new HashRange(LONG_MINIMUM, LONG_MAXIMUM);
|
||||
public static final HashRange POSITIVE_LONG_RANGE = new HashRange(BigInteger.ONE, LONG_MAXIMUM);
|
||||
|
||||
// fields
|
||||
|
||||
private final BigInteger minimum;
|
||||
private final BigInteger maximum;
|
||||
private final boolean intBounded;
|
||||
private final boolean longBounded;
|
||||
private BigInteger size = null;
|
||||
private Boolean intSized = null;
|
||||
private Boolean longSized = null;
|
||||
|
||||
// constructors
|
||||
|
||||
public HashRange(BigInteger minimum, BigInteger maximum) {
|
||||
if (minimum == null) throw new IllegalArgumentException();
|
||||
if (maximum == null) throw new IllegalArgumentException();
|
||||
if (minimum.compareTo(maximum) > 0) throw new IllegalArgumentException();
|
||||
this.minimum = minimum;
|
||||
this.maximum = maximum;
|
||||
intBounded = minimum.compareTo(INT_MINIMUM) >= 0 && maximum.compareTo(INT_MAXIMUM) <= 0;
|
||||
longBounded = minimum.compareTo(LONG_MINIMUM) >= 0 && maximum.compareTo(LONG_MAXIMUM) <= 0;
|
||||
// defer size related work - don't want to mem alloc in constructor
|
||||
}
|
||||
|
||||
public HashRange(int minimum, int maximum) {
|
||||
this(BigInteger.valueOf(minimum), BigInteger.valueOf(maximum));
|
||||
}
|
||||
|
||||
public HashRange(long minimum, long maximum) {
|
||||
this(BigInteger.valueOf(minimum), BigInteger.valueOf(maximum));
|
||||
}
|
||||
|
||||
// accessors
|
||||
|
||||
public boolean isZeroBased() {
|
||||
return minimum.signum() == 0;
|
||||
}
|
||||
|
||||
public boolean isIntBounded() {
|
||||
return intBounded;
|
||||
}
|
||||
|
||||
public boolean isLongBounded() {
|
||||
return longBounded;
|
||||
}
|
||||
|
||||
public BigInteger getMinimum() {
|
||||
return minimum;
|
||||
}
|
||||
|
||||
public BigInteger getMaximum() {
|
||||
return maximum;
|
||||
}
|
||||
|
||||
public BigInteger getSize() {
|
||||
return size == null ? size = maximum.subtract(minimum).add(BigInteger.ONE) : size;
|
||||
}
|
||||
|
||||
public boolean isIntSized() {
|
||||
if (intSized == null) intSized = getSize().compareTo(INT_MAXIMUM) <= 0;
|
||||
return intSized;
|
||||
}
|
||||
|
||||
public boolean isLongSized() {
|
||||
if (longSized == null) longSized = getSize().compareTo(LONG_MAXIMUM) <= 0;
|
||||
return longSized;
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
public HashRange zeroBased() {
|
||||
return isZeroBased() ? this : new HashRange(BigInteger.ZERO, maximum.subtract(minimum));
|
||||
}
|
||||
|
||||
// object methods
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof HashRange)) return false;
|
||||
HashRange that = (HashRange) obj;
|
||||
return this.minimum.equals(that.minimum) && this.maximum.equals(that.maximum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return minimum.hashCode() ^ 7 * maximum.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + minimum + ", " + maximum + "]";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,317 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A "minimal perfect hash" for Strings. After construction with an array of
|
||||
* <em>n</em> unique non-null strings, an instance of this class will return a
|
||||
* unique hash value <em>h</em> (0 <= h < n) for any string <em>s</em> in the
|
||||
* array. A negative has value will typically be returned for a string that is
|
||||
* not in the array.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* However, the supplied array is <em>not</em> 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
|
||||
* <em>n</em>.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <strong>IMPORTANT NOTE:</strong> The array of strings supplied to the
|
||||
* constructor will be mutated: it is re-ordered so that
|
||||
* <code>hash(a[i]) == i</code>. Application code must generally use this
|
||||
* information to map hash values back onto the appropriate string value.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <strong>NOTE:</strong> Good performance of this algorithm is predicated on
|
||||
* string hash values being cached by the <code>String</code> class. Experience
|
||||
* indicates that is is a good assumption.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @author Tom Gibara
|
||||
*/
|
||||
|
||||
public class PerfectStringHash implements Hash<String> {
|
||||
|
||||
// statics
|
||||
|
||||
/**
|
||||
* Comparator used to order the supplied string array. Hashcodes take
|
||||
* priority, we will do a binary search on those. Otherwise, lengths take
|
||||
* priority over character ordering because the hash algorithm prefers to
|
||||
* compare lengths, it's faster.
|
||||
*/
|
||||
|
||||
private static final Comparator<String> comparator = new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
final int h1 = s1.hashCode();
|
||||
final int h2 = s2.hashCode();
|
||||
if (h1 == h2) {
|
||||
final int d = s1.length() - s2.length();
|
||||
return d == 0 ? s1.compareTo(s2) : d;
|
||||
}
|
||||
return h1 < h2 ? -1 : 1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a (typically v. small) decision tree for distinguishing strings
|
||||
* that share the same hash value.
|
||||
*
|
||||
* @param values
|
||||
* the string values to distinguish
|
||||
* @param start
|
||||
* the index from which the values should be read
|
||||
* @param length
|
||||
* the number of string values that need to be distinguished
|
||||
* @param pivots
|
||||
* the array that will hold our decision nodes
|
||||
* @param pivotIndex
|
||||
* the index at which the tree should be written
|
||||
*/
|
||||
private static void generatePivots(String[] values, int start, int length, int[] pivots, int pivotIndex) {
|
||||
final int capacity = Integer.highestOneBit(length - 1) << 1;
|
||||
final int depth = Integer.numberOfTrailingZeros(capacity);
|
||||
pivots[ pivotIndex << 1 ] = depth;
|
||||
pivots[(pivotIndex << 1) + 1] = length;
|
||||
pivotIndex++;
|
||||
//build the array
|
||||
for (int i = 0; i < depth; i++) {
|
||||
int step = capacity >> i;
|
||||
for (int j = (1 << (depth-i-1)) - 1; j < capacity; j += step) {
|
||||
final int part;
|
||||
final int comp;
|
||||
if (j >= length - 1) {
|
||||
part = Integer.MIN_VALUE;
|
||||
comp = 0;
|
||||
} else {
|
||||
final String v1 = values[start + j];
|
||||
final String v2 = values[start + j + 1];
|
||||
final int l1 = v1.length();
|
||||
final int l2 = v2.length();
|
||||
if (l1 == l2) {
|
||||
int tPart = -1;
|
||||
int tComp = -1;
|
||||
for (int k = 0; k < l1; k++) {
|
||||
final char c1 = v1.charAt(k);
|
||||
final char c2 = v2.charAt(k);
|
||||
if (c1 == c2) continue;
|
||||
if (c1 < c2) { //must occur at some point because we have already checked that the two strings are unequal
|
||||
tPart = k;
|
||||
tComp = c1;
|
||||
} else {
|
||||
//shouldn't be possible - we've sorted the strings to avoid this case
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
break;
|
||||
}
|
||||
//check if we've been passed a duplicated value
|
||||
if (tPart == -1) throw new IllegalArgumentException("duplicate value: " + v1);
|
||||
part = tPart;
|
||||
comp = tComp;
|
||||
} else {
|
||||
part = -1;
|
||||
comp = l1;
|
||||
}
|
||||
}
|
||||
pivots[ pivotIndex<<1 ] = part;
|
||||
pivots[(pivotIndex<<1) + 1] = comp;
|
||||
pivotIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fields
|
||||
|
||||
/**
|
||||
* The hashcodes of the supplied strings.
|
||||
*/
|
||||
|
||||
private final int[] hashes;
|
||||
|
||||
/**
|
||||
* Stores two ints for every string, an offset into the pivot array (-1 if
|
||||
* not necessary) and the depth of the decision tree that is rooted there.
|
||||
*/
|
||||
|
||||
private final int[] offsets;
|
||||
|
||||
/**
|
||||
* Stores two ints for every decision, the index at which a character
|
||||
* comparison needs to be made, followed by the character value to be
|
||||
* compared against; or -1 to indicate a length comparison, followed by the
|
||||
* length to be compared against.
|
||||
*/
|
||||
|
||||
private final int[] pivots;
|
||||
|
||||
/**
|
||||
* Cache a range object which indicates the range of hash values generated.
|
||||
*/
|
||||
|
||||
private final HashRange range;
|
||||
|
||||
/**
|
||||
* Constructs a minimal perfect string hashing over the supplied strings.
|
||||
*
|
||||
* @param values
|
||||
* an array of unique non-null strings that will be reordered
|
||||
* such that <code>hash(values[i]) == i</code>.
|
||||
*/
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
}
|
133
apps/jrobin/java/src/org/rrd4j/core/ArcDef.java
Normal file
133
apps/jrobin/java/src/org/rrd4j/core/ArcDef.java
Normal file
@ -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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>consolidation function
|
||||
* <li>X-files factor
|
||||
* <li>number of steps
|
||||
* <li>number of rows.
|
||||
* </ul>
|
||||
* For the complete explanation of all archive definition parameters, see RRDTool's
|
||||
* <a href="http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html" target="man">rrdcreate man page</a>
|
||||
*
|
||||
*
|
||||
* @author Sasa Markovic
|
||||
*/
|
||||
public class ArcDef {
|
||||
private final ConsolFun consolFun;
|
||||
private final double xff;
|
||||
private final int steps;
|
||||
private int rows;
|
||||
|
||||
/**
|
||||
* Creates new archive definition object. This object should be passed as argument to
|
||||
* {@link org.rrd4j.core.RrdDef#addArchive(ArcDef) addArchive()} method of
|
||||
* {@link RrdDb RrdDb} object.
|
||||
* <p>For the complete explanation of all archive definition parameters, see RRDTool's
|
||||
* <a href="http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html" target="man">rrdcreate man page</a></p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
109
apps/jrobin/java/src/org/rrd4j/core/ArcState.java
Normal file
109
apps/jrobin/java/src/org/rrd4j/core/ArcState.java
Normal file
@ -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<ArcState> {
|
||||
private Archive parentArc;
|
||||
|
||||
private RrdDouble<ArcState> accumValue;
|
||||
private RrdLong<ArcState> nanSteps;
|
||||
|
||||
ArcState(Archive parentArc, boolean shouldInitialize) throws IOException {
|
||||
this.parentArc = parentArc;
|
||||
accumValue = new RrdDouble<>(this);
|
||||
nanSteps = new RrdLong<>(this);
|
||||
if (shouldInitialize) {
|
||||
Header header = parentArc.getParentDb().getHeader();
|
||||
long step = header.getStep();
|
||||
long lastUpdateTime = header.getLastUpdateTime();
|
||||
long arcStep = parentArc.getArcStep();
|
||||
long initNanSteps = (Util.normalize(lastUpdateTime, step) -
|
||||
Util.normalize(lastUpdateTime, arcStep)) / step;
|
||||
accumValue.set(Double.NaN);
|
||||
nanSteps.set(initNanSteps);
|
||||
}
|
||||
}
|
||||
|
||||
String dump() throws IOException {
|
||||
return "accumValue:" + accumValue.get() + " nanSteps:" + nanSteps.get() + "\n";
|
||||
}
|
||||
|
||||
void setNanSteps(long value) throws IOException {
|
||||
nanSteps.set(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of currently accumulated NaN steps.
|
||||
*
|
||||
* @return Number of currently accumulated NaN steps.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public long getNanSteps() throws IOException {
|
||||
return nanSteps.get();
|
||||
}
|
||||
|
||||
void setAccumValue(double value) throws IOException {
|
||||
accumValue.set(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value accumulated so far.
|
||||
*
|
||||
* @return Accumulated value
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public double getAccumValue() throws IOException {
|
||||
return accumValue.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Archive object to which this ArcState object belongs.
|
||||
*
|
||||
* @return Parent Archive object.
|
||||
*/
|
||||
public Archive getParent() {
|
||||
return parentArc;
|
||||
}
|
||||
|
||||
void appendXml(XmlWriter writer) throws IOException {
|
||||
writer.startTag("ds");
|
||||
writer.writeTag("value", accumValue.get());
|
||||
writer.writeTag("unknown_datapoints", nanSteps.get());
|
||||
writer.closeTag(); // ds
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Copies object's internal state to another ArcState object.
|
||||
*/
|
||||
public void copyStateTo(ArcState arcState) throws IOException {
|
||||
arcState.accumValue.set(accumValue.get());
|
||||
arcState.nanSteps.set(nanSteps.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying storage (backend) object which actually performs all
|
||||
* I/O operations.
|
||||
*
|
||||
* @return I/O backend object
|
||||
*/
|
||||
public RrdBackend getRrdBackend() {
|
||||
return parentArc.getRrdBackend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Required to implement RrdUpdater interface. You should never call this method directly.
|
||||
*
|
||||
* @return Allocator object
|
||||
*/
|
||||
public RrdAllocator getRrdAllocator() {
|
||||
return parentArc.getRrdAllocator();
|
||||
}
|
||||
}
|
419
apps/jrobin/java/src/org/rrd4j/core/Archive.java
Normal file
419
apps/jrobin/java/src/org/rrd4j/core/Archive.java
Normal file
@ -0,0 +1,419 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import org.rrd4j.ConsolFun;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Class to represent single RRD archive in a RRD with its internal state.
|
||||
* Normally, you don't need methods to manipulate archive objects directly
|
||||
* because Rrd4j framework does it automatically for you.
|
||||
* <p>
|
||||
* 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<Archive> {
|
||||
private final RrdDb parentDb;
|
||||
|
||||
// definition
|
||||
private final RrdEnum<Archive, ConsolFun> consolFun;
|
||||
protected final RrdDouble<Archive> xff;
|
||||
protected final RrdInt<Archive> steps;
|
||||
protected final RrdInt<Archive> rows;
|
||||
|
||||
// state
|
||||
private final Robin[] robins;
|
||||
private final ArcState[] states;
|
||||
|
||||
Archive(RrdDb parentDb, ArcDef arcDef) throws IOException {
|
||||
this.parentDb = parentDb;
|
||||
consolFun = new RrdEnum<>(this, false, ConsolFun.class); // Don't cache, as the enum type should be used instead
|
||||
xff = new RrdDouble<>(this);
|
||||
steps = new RrdInt<>(this, true); // constant, may be cached
|
||||
rows = new RrdInt<>(this, true); // constant, may be cached
|
||||
boolean shouldInitialize = arcDef != null;
|
||||
if (shouldInitialize) {
|
||||
consolFun.set(arcDef.getConsolFun());
|
||||
xff.set(arcDef.getXff());
|
||||
steps.set(arcDef.getSteps());
|
||||
rows.set(arcDef.getRows());
|
||||
}
|
||||
int n = parentDb.getHeader().getDsCount();
|
||||
int numRows = rows.get();
|
||||
states = new ArcState[n];
|
||||
int version = parentDb.getHeader().getVersion();
|
||||
if (version == 1) {
|
||||
robins = new RobinArray[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
states[i] = new ArcState(this, shouldInitialize);
|
||||
robins[i] = new RobinArray(this, numRows, shouldInitialize);
|
||||
}
|
||||
} else {
|
||||
@SuppressWarnings("unchecked")
|
||||
RrdInt<Archive>[] pointers = new RrdInt[n];
|
||||
robins = new RobinMatrix[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
pointers[i] = new RrdInt<>(this);
|
||||
//Purge old pointers content, avoid problems with file reuse
|
||||
if(shouldInitialize) {
|
||||
pointers[i].set(0);
|
||||
}
|
||||
states[i] = new ArcState(this, shouldInitialize);
|
||||
}
|
||||
RrdDoubleMatrix<Archive> values = new RrdDoubleMatrix<>(this, numRows, n, shouldInitialize);
|
||||
for (int i = 0; i < n; i++) {
|
||||
robins[i] = new RobinMatrix(this, values, pointers[i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read from XML
|
||||
Archive(RrdDb parentDb, DataImporter reader, int arcIndex) throws IOException {
|
||||
this(parentDb, new ArcDef(
|
||||
reader.getConsolFun(arcIndex), reader.getXff(arcIndex),
|
||||
reader.getSteps(arcIndex), reader.getRows(arcIndex)));
|
||||
int n = parentDb.getHeader().getDsCount();
|
||||
for (int i = 0; i < n; i++) {
|
||||
// restore state
|
||||
states[i].setAccumValue(reader.getStateAccumValue(arcIndex, i));
|
||||
states[i].setNanSteps(reader.getStateNanSteps(arcIndex, i));
|
||||
// restore robins
|
||||
double[] values = reader.getValues(arcIndex, i);
|
||||
robins[i].update(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns archive time step in seconds. Archive step is equal to RRD step
|
||||
* multiplied with the number of archive steps.
|
||||
*
|
||||
* @return Archive time step in seconds
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public long getArcStep() throws IOException {
|
||||
return parentDb.getHeader().getStep() * steps.get();
|
||||
}
|
||||
|
||||
String dump() throws IOException {
|
||||
StringBuilder sb = new StringBuilder("== ARCHIVE ==\n");
|
||||
sb.append("RRA:")
|
||||
.append(consolFun.name())
|
||||
.append(":")
|
||||
.append(xff.get())
|
||||
.append(":")
|
||||
.append(steps.get())
|
||||
.append(":")
|
||||
.append(rows.get())
|
||||
.append("\n")
|
||||
.append("interval [")
|
||||
.append(getStartTime())
|
||||
.append(", ")
|
||||
.append(getEndTime())
|
||||
.append("]" + "\n");
|
||||
for (int i = 0; i < robins.length; i++) {
|
||||
sb.append(states[i].dump());
|
||||
sb.append(robins[i].dump());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
RrdDb getParentDb() {
|
||||
return parentDb;
|
||||
}
|
||||
|
||||
void archive(int dsIndex, double value, long numUpdates) throws IOException {
|
||||
Robin robin = robins[dsIndex];
|
||||
ArcState state = states[dsIndex];
|
||||
long step = parentDb.getHeader().getStep();
|
||||
long lastUpdateTime = parentDb.getHeader().getLastUpdateTime();
|
||||
long updateTime = Util.normalize(lastUpdateTime, step) + step;
|
||||
long arcStep = getArcStep();
|
||||
// finish current step
|
||||
while (numUpdates > 0) {
|
||||
accumulate(state, value);
|
||||
numUpdates--;
|
||||
if (updateTime % arcStep == 0) {
|
||||
finalizeStep(state, robin);
|
||||
break;
|
||||
} else {
|
||||
updateTime += step;
|
||||
}
|
||||
}
|
||||
// update robin in bulk
|
||||
int bulkUpdateCount = (int) Math.min(numUpdates / steps.get(), (long) rows.get());
|
||||
robin.bulkStore(value, bulkUpdateCount);
|
||||
// update remaining steps
|
||||
long remainingUpdates = numUpdates % steps.get();
|
||||
for (long i = 0; i < remainingUpdates; i++) {
|
||||
accumulate(state, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void accumulate(ArcState state, double value) throws IOException {
|
||||
if (Double.isNaN(value)) {
|
||||
state.setNanSteps(state.getNanSteps() + 1);
|
||||
} else {
|
||||
switch (consolFun.get()) {
|
||||
case MIN:
|
||||
state.setAccumValue(Util.min(state.getAccumValue(), value));
|
||||
break;
|
||||
case MAX:
|
||||
state.setAccumValue(Util.max(state.getAccumValue(), value));
|
||||
break;
|
||||
case FIRST:
|
||||
if (Double.isNaN(state.getAccumValue())) {
|
||||
state.setAccumValue(value);
|
||||
}
|
||||
break;
|
||||
case LAST:
|
||||
state.setAccumValue(value);
|
||||
break;
|
||||
case AVERAGE:
|
||||
case TOTAL:
|
||||
state.setAccumValue(Util.sum(state.getAccumValue(), value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void finalizeStep(ArcState state, Robin robin) throws IOException {
|
||||
// should store
|
||||
long arcSteps = steps.get();
|
||||
double arcXff = xff.get();
|
||||
long nanSteps = state.getNanSteps();
|
||||
double accumValue = state.getAccumValue();
|
||||
if (nanSteps <= arcXff * arcSteps && !Double.isNaN(accumValue)) {
|
||||
if (consolFun.get() == ConsolFun.AVERAGE) {
|
||||
accumValue /= (arcSteps - nanSteps);
|
||||
}
|
||||
robin.store(accumValue);
|
||||
} else {
|
||||
robin.store(Double.NaN);
|
||||
}
|
||||
state.setAccumValue(Double.NaN);
|
||||
state.setNanSteps(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns archive consolidation function ("AVERAGE", "MIN", "MAX", "FIRST", "LAST" or "TOTAL").
|
||||
*
|
||||
* @return Archive consolidation function.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public ConsolFun getConsolFun() throws IOException {
|
||||
return consolFun.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns archive X-files factor.
|
||||
*
|
||||
* @return Archive X-files factor (between 0 and 1).
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public double getXff() throws IOException {
|
||||
return xff.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of archive steps.
|
||||
*
|
||||
* @return Number of archive steps.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public int getSteps() throws IOException {
|
||||
return steps.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of archive rows.
|
||||
*
|
||||
* @return Number of archive rows.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public int getRows() throws IOException {
|
||||
return rows.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current starting timestamp. This value is not constant.
|
||||
*
|
||||
* @return Timestamp corresponding to the first archive row
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public long getStartTime() throws IOException {
|
||||
long endTime = getEndTime();
|
||||
long arcStep = getArcStep();
|
||||
long numRows = rows.get();
|
||||
return endTime - (numRows - 1) * arcStep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current ending timestamp. This value is not constant.
|
||||
*
|
||||
* @return Timestamp corresponding to the last archive row
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public long getEndTime() throws IOException {
|
||||
long arcStep = getArcStep();
|
||||
long lastUpdateTime = parentDb.getHeader().getLastUpdateTime();
|
||||
return Util.normalize(lastUpdateTime, arcStep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying archive state object. Each datasource has its
|
||||
* corresponding ArcState object (archive states are managed independently
|
||||
* for each RRD datasource).
|
||||
*
|
||||
* @param dsIndex Datasource index
|
||||
* @return Underlying archive state object
|
||||
*/
|
||||
public ArcState getArcState(int dsIndex) {
|
||||
return states[dsIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying round robin archive. Robins are used to store actual
|
||||
* archive values on a per-datasource basis.
|
||||
*
|
||||
* @param dsIndex Index of the datasource in the RRD.
|
||||
* @return Underlying round robin archive for the given datasource.
|
||||
*/
|
||||
public Robin getRobin(int dsIndex) {
|
||||
return robins[dsIndex];
|
||||
}
|
||||
|
||||
FetchData fetchData(FetchRequest request) throws IOException {
|
||||
long arcStep = getArcStep();
|
||||
long fetchStart = Util.normalize(request.getFetchStart(), arcStep);
|
||||
long fetchEnd = Util.normalize(request.getFetchEnd(), arcStep);
|
||||
if (fetchEnd < request.getFetchEnd()) {
|
||||
fetchEnd += arcStep;
|
||||
}
|
||||
long startTime = getStartTime();
|
||||
long endTime = getEndTime();
|
||||
String[] dsToFetch = request.getFilter();
|
||||
if (dsToFetch == null) {
|
||||
dsToFetch = parentDb.getDsNames();
|
||||
}
|
||||
int dsCount = dsToFetch.length;
|
||||
int ptsCount = (int) ((fetchEnd - fetchStart) / arcStep + 1);
|
||||
long[] timestamps = new long[ptsCount];
|
||||
double[][] values = new double[dsCount][ptsCount];
|
||||
long matchStartTime = Math.max(fetchStart, startTime);
|
||||
long matchEndTime = Math.min(fetchEnd, endTime);
|
||||
double[][] robinValues = null;
|
||||
if (matchStartTime <= matchEndTime) {
|
||||
// preload robin values
|
||||
int matchCount = (int) ((matchEndTime - matchStartTime) / arcStep + 1);
|
||||
int matchStartIndex = (int) ((matchStartTime - startTime) / arcStep);
|
||||
robinValues = new double[dsCount][];
|
||||
for (int i = 0; i < dsCount; i++) {
|
||||
int dsIndex = parentDb.getDsIndex(dsToFetch[i]);
|
||||
robinValues[i] = robins[dsIndex].getValues(matchStartIndex, matchCount);
|
||||
}
|
||||
}
|
||||
for (int ptIndex = 0; ptIndex < ptsCount; ptIndex++) {
|
||||
long time = fetchStart + ptIndex * arcStep;
|
||||
timestamps[ptIndex] = time;
|
||||
for (int i = 0; i < dsCount; i++) {
|
||||
double value = Double.NaN;
|
||||
if (time >= matchStartTime && time <= matchEndTime) {
|
||||
// inbound time
|
||||
int robinValueIndex = (int) ((time - matchStartTime) / arcStep);
|
||||
assert robinValues != null;
|
||||
value = robinValues[i][robinValueIndex];
|
||||
}
|
||||
values[i][ptIndex] = value;
|
||||
}
|
||||
}
|
||||
FetchData fetchData = new FetchData(this, request);
|
||||
fetchData.setTimestamps(timestamps);
|
||||
fetchData.setValues(values);
|
||||
return fetchData;
|
||||
}
|
||||
|
||||
void appendXml(XmlWriter writer) throws IOException {
|
||||
writer.startTag("rra");
|
||||
writer.writeTag("cf", consolFun.name());
|
||||
writer.writeComment(getArcStep() + " seconds");
|
||||
writer.writeTag("pdp_per_row", steps.get());
|
||||
writer.startTag("params");
|
||||
writer.writeTag("xff", xff.get());
|
||||
writer.closeTag(); // params
|
||||
writer.startTag("cdp_prep");
|
||||
for (ArcState state : states) {
|
||||
state.appendXml(writer);
|
||||
}
|
||||
writer.closeTag(); // cdp_prep
|
||||
writer.startTag("database");
|
||||
long startTime = getStartTime();
|
||||
for (int i = 0; i < rows.get(); i++) {
|
||||
long time = startTime + i * getArcStep();
|
||||
writer.writeComment(Util.getDate(time) + " / " + time);
|
||||
writer.startTag("row");
|
||||
for (Robin robin : robins) {
|
||||
writer.writeTag("v", robin.getValue(i));
|
||||
}
|
||||
writer.closeTag(); // row
|
||||
}
|
||||
writer.closeTag(); // database
|
||||
writer.closeTag(); // rra
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Copies object's internal state to another Archive object.
|
||||
*/
|
||||
public void copyStateTo(Archive arc) throws IOException {
|
||||
if (arc.consolFun.get() != consolFun.get()) {
|
||||
throw new IllegalArgumentException("Incompatible consolidation functions");
|
||||
}
|
||||
if (arc.steps.get() != steps.get()) {
|
||||
throw new IllegalArgumentException("Incompatible number of steps");
|
||||
}
|
||||
int count = parentDb.getHeader().getDsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int j = Util.getMatchingDatasourceIndex(parentDb, i, arc.parentDb);
|
||||
if (j >= 0) {
|
||||
states[i].copyStateTo(arc.states[j]);
|
||||
robins[i].copyStateTo(arc.robins[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets X-files factor to a new value.
|
||||
*
|
||||
* @param xff New X-files factor value. Must be >= 0 and < 1.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public void setXff(double xff) throws IOException {
|
||||
if (xff < 0D || xff >= 1D) {
|
||||
throw new IllegalArgumentException("Invalid xff supplied (" + xff + "), must be >= 0 and < 1");
|
||||
}
|
||||
this.xff.set(xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying storage (backend) object which actually performs all
|
||||
* I/O operations.
|
||||
*
|
||||
* @return I/O backend object
|
||||
*/
|
||||
public RrdBackend getRrdBackend() {
|
||||
return parentDb.getRrdBackend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Required to implement RrdUpdater interface. You should never call this method directly.
|
||||
*
|
||||
* @return Allocator object
|
||||
*/
|
||||
public RrdAllocator getRrdAllocator() {
|
||||
return parentDb.getRrdAllocator();
|
||||
}
|
||||
}
|
191
apps/jrobin/java/src/org/rrd4j/core/ByteBufferBackend.java
Normal file
191
apps/jrobin/java/src/org/rrd4j/core/ByteBufferBackend.java
Normal file
@ -0,0 +1,191 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A backend that store and provides access to data using a {@link java.nio.ByteBuffer}, using java internal methods for
|
||||
* long, integer and others types.
|
||||
*
|
||||
* @author Fabrice Bacchella
|
||||
* @since 3.4
|
||||
*
|
||||
*/
|
||||
public abstract class ByteBufferBackend extends RrdBackend {
|
||||
|
||||
private volatile boolean dirty = false;
|
||||
|
||||
private ByteBuffer byteBuffer;
|
||||
|
||||
protected ByteBufferBackend(String path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
protected void setByteBuffer(ByteBuffer byteBuffer) {
|
||||
this.byteBuffer = byteBuffer;
|
||||
byteBuffer.order(BYTEORDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes bytes to the underlying RRD file on the disk
|
||||
*
|
||||
* @param offset Starting file offset
|
||||
* @param b Bytes to be written.
|
||||
* @throws java.io.IOException if any.
|
||||
* @throws java.lang.IllegalArgumentException if offset is bigger that the possible mapping position (2GiB).
|
||||
*/
|
||||
protected synchronized void write(long offset, byte[] b) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
byteBuffer.put(b, (int) offset, b.length);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeShort(long offset, short value) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
byteBuffer.putShort((int)offset, value);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInt(long offset, int value) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
byteBuffer.putInt((int)offset, value);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeLong(long offset, long value) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
byteBuffer.putLong((int)offset, value);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDouble(long offset, double value) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
byteBuffer.putDouble((int)offset, value);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDouble(long offset, double value, int count)
|
||||
throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
double[] values = new double[count];
|
||||
Arrays.fill(values, value);
|
||||
// position must be set in the original ByteByffer, as DoubleBuffer uses a "double" offset
|
||||
byteBuffer.position((int)offset);
|
||||
byteBuffer.asDoubleBuffer().put(values, 0, count);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDouble(long offset, double[] values) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
// position must be set in the original ByteByffer, as DoubleBuffer uses a "double" offset
|
||||
byteBuffer.position((int)offset);
|
||||
byteBuffer.asDoubleBuffer().put(values, 0, values.length);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeString(long offset, String value, int length) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
byteBuffer.position((int)offset);
|
||||
CharBuffer cbuff = byteBuffer.asCharBuffer();
|
||||
cbuff.limit(length);
|
||||
cbuff.put(value);
|
||||
while (cbuff.position() < cbuff.limit()) {
|
||||
cbuff.put(' ');
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a number of bytes from the RRD file on the disk
|
||||
*
|
||||
* @param offset Starting file offset
|
||||
* @param b Buffer which receives bytes read from the file.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
* @throws java.lang.IllegalArgumentException if offset is bigger that the possible mapping position (2GiB).
|
||||
*/
|
||||
protected synchronized void read(long offset, byte[] b) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
byteBuffer.get(b, (int) offset, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short readShort(long offset) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
return byteBuffer.getShort((int)offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int readInt(long offset) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
return byteBuffer.getInt((int)offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long readLong(long offset) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
return byteBuffer.getLong((int)offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble(long offset) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
return byteBuffer.getDouble((int)offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] readDouble(long offset, int count) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
double[] values = new double[count];
|
||||
// position must be set in the original ByteByffer, as DoubleBuffer is a "double" offset
|
||||
byteBuffer.position((int)offset);
|
||||
byteBuffer.asDoubleBuffer().get(values, 0, count);
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CharBuffer getCharBuffer(long offset, int size) throws RrdException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
byteBuffer.position((int)offset);
|
||||
CharBuffer cbuffer = byteBuffer.asCharBuffer();
|
||||
cbuffer.limit(size);
|
||||
return cbuffer;
|
||||
}
|
||||
|
||||
protected void close() throws IOException {
|
||||
byteBuffer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the conversion from long offset to integer offset will not overflow
|
||||
* @param offset
|
||||
* @throws RrdException
|
||||
*/
|
||||
private void checkOffsetAndByteBuffer(long offset) throws RrdException {
|
||||
if (offset < 0 || offset > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Illegal offset: " + offset);
|
||||
}
|
||||
if (byteBuffer == null) {
|
||||
throw new RrdException("Empty rrd");
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void rrdClose() throws IOException {
|
||||
super.rrdClose();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
}
|
84
apps/jrobin/java/src/org/rrd4j/core/DataImporter.java
Normal file
84
apps/jrobin/java/src/org/rrd4j/core/DataImporter.java
Normal file
@ -0,0 +1,84 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.rrd4j.ConsolFun;
|
||||
import org.rrd4j.DsType;
|
||||
|
||||
/**
|
||||
* <p>An abstract class to import data from external source.</p>
|
||||
* @author Fabrice Bacchella
|
||||
* @since 3.5
|
||||
*/
|
||||
public abstract class DataImporter implements Closeable {
|
||||
|
||||
// header
|
||||
public abstract String getVersion() throws IOException;
|
||||
|
||||
public abstract long getLastUpdateTime() throws IOException;
|
||||
|
||||
public abstract long getStep() throws IOException;
|
||||
|
||||
public abstract int getDsCount() throws IOException;
|
||||
|
||||
public abstract int getArcCount() throws IOException;
|
||||
|
||||
// datasource
|
||||
public abstract String getDsName(int dsIndex) throws IOException;
|
||||
|
||||
public abstract DsType getDsType(int dsIndex) throws IOException;
|
||||
|
||||
public abstract long getHeartbeat(int dsIndex) throws IOException;
|
||||
|
||||
public abstract double getMinValue(int dsIndex) throws IOException;
|
||||
|
||||
public abstract double getMaxValue(int dsIndex) throws IOException;
|
||||
|
||||
// datasource state
|
||||
public abstract double getLastValue(int dsIndex) throws IOException;
|
||||
|
||||
public abstract double getAccumValue(int dsIndex) throws IOException;
|
||||
|
||||
public abstract long getNanSeconds(int dsIndex) throws IOException;
|
||||
|
||||
// archive
|
||||
public abstract ConsolFun getConsolFun(int arcIndex) throws IOException;
|
||||
|
||||
public abstract double getXff(int arcIndex) throws IOException;
|
||||
|
||||
public abstract int getSteps(int arcIndex) throws IOException;
|
||||
|
||||
public abstract int getRows(int arcIndex) throws IOException;
|
||||
|
||||
// archive state
|
||||
public abstract double getStateAccumValue(int arcIndex, int dsIndex) throws IOException;
|
||||
|
||||
public abstract int getStateNanSteps(int arcIndex, int dsIndex) throws IOException;
|
||||
|
||||
public abstract double[] getValues(int arcIndex, int dsIndex) throws IOException;
|
||||
|
||||
protected long getEstimatedSize() throws IOException {
|
||||
int dsCount = getDsCount();
|
||||
int arcCount = getArcCount();
|
||||
int rowCount = 0;
|
||||
for (int i = 0; i < arcCount; i++) {
|
||||
rowCount += getRows(i);
|
||||
}
|
||||
String[] dsNames = new String[getDsCount()];
|
||||
for (int i = 0 ; i < dsNames.length; i++) {
|
||||
dsNames[i] = getDsName(i);
|
||||
}
|
||||
return RrdDef.calculateSize(dsCount, arcCount, rowCount, dsNames);
|
||||
}
|
||||
|
||||
void release() throws IOException {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
release();
|
||||
}
|
||||
|
||||
}
|
479
apps/jrobin/java/src/org/rrd4j/core/Datasource.java
Normal file
479
apps/jrobin/java/src/org/rrd4j/core/Datasource.java
Normal file
@ -0,0 +1,479 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.rrd4j.DsType;
|
||||
|
||||
/**
|
||||
* <p>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).</p>
|
||||
* <p>Normally, you don't need to manipulate Datasource objects directly, it's up to
|
||||
* Rrd4j framework to do it for you.</p>
|
||||
*
|
||||
* @author Sasa Markovic
|
||||
*/
|
||||
public class Datasource implements RrdUpdater<Datasource> {
|
||||
private static final double MAX_32_BIT = Math.pow(2, 32);
|
||||
private static final double MAX_64_BIT = Math.pow(2, 64);
|
||||
private static final String INVALID_MIN_MAX_VALUES = "Invalid min/max values: ";
|
||||
private double accumLastValue;
|
||||
|
||||
private final RrdDb parentDb;
|
||||
|
||||
// definition
|
||||
private final RrdString<Datasource> dsName;
|
||||
private final RrdEnum<Datasource, DsType> dsType;
|
||||
private final RrdLong<Datasource> heartbeat;
|
||||
private final RrdDouble<Datasource> minValue, maxValue;
|
||||
|
||||
// state variables
|
||||
private RrdDouble<Datasource> lastValue;
|
||||
private RrdLong<Datasource> nanSeconds;
|
||||
private RrdDouble<Datasource> accumValue;
|
||||
|
||||
Datasource(RrdDb parentDb, DsDef dsDef) throws IOException {
|
||||
boolean shouldInitialize = dsDef != null;
|
||||
this.parentDb = parentDb;
|
||||
dsName = new RrdString<>(this);
|
||||
dsType = new RrdEnum<>(this, DsType.class);
|
||||
heartbeat = new RrdLong<>(this);
|
||||
minValue = new RrdDouble<>(this);
|
||||
maxValue = new RrdDouble<>(this);
|
||||
lastValue = new RrdDouble<>(this);
|
||||
accumValue = new RrdDouble<>(this);
|
||||
nanSeconds = new RrdLong<>(this);
|
||||
accumLastValue = Double.NaN;
|
||||
if (shouldInitialize) {
|
||||
dsName.set(dsDef.getDsName());
|
||||
dsType.set(dsDef.getDsType());
|
||||
heartbeat.set(dsDef.getHeartbeat());
|
||||
minValue.set(dsDef.getMinValue());
|
||||
maxValue.set(dsDef.getMaxValue());
|
||||
lastValue.set(Double.NaN);
|
||||
accumValue.set(0.0);
|
||||
Header header = parentDb.getHeader();
|
||||
nanSeconds.set(header.getLastUpdateTime() % header.getStep());
|
||||
}
|
||||
}
|
||||
|
||||
Datasource(RrdDb parentDb, DataImporter reader, int dsIndex) throws IOException {
|
||||
this(parentDb, null);
|
||||
dsName.set(reader.getDsName(dsIndex));
|
||||
dsType.set(reader.getDsType(dsIndex));
|
||||
heartbeat.set(reader.getHeartbeat(dsIndex));
|
||||
minValue.set(reader.getMinValue(dsIndex));
|
||||
maxValue.set(reader.getMaxValue(dsIndex));
|
||||
lastValue.set(reader.getLastValue(dsIndex));
|
||||
accumValue.set(reader.getAccumValue(dsIndex));
|
||||
nanSeconds.set(reader.getNanSeconds(dsIndex));
|
||||
}
|
||||
|
||||
String dump() throws IOException {
|
||||
return "== DATASOURCE ==\n" +
|
||||
"DS:" + dsName.get() + ":" + dsType.name() + ":" +
|
||||
heartbeat.get() + ":" + minValue.get() + ":" +
|
||||
maxValue.get() + "\nlastValue:" + lastValue.get() +
|
||||
" nanSeconds:" + nanSeconds.get() +
|
||||
" accumValue:" + accumValue.get() + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns datasource name.
|
||||
*
|
||||
* @return Datasource name
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public String getName() throws IOException {
|
||||
return dsName.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns datasource type (GAUGE, COUNTER, DERIVE, ABSOLUTE).
|
||||
*
|
||||
* @return Datasource type.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public DsType getType() throws IOException {
|
||||
return dsType.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns datasource heartbeat
|
||||
*
|
||||
* @return Datasource heartbeat
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public long getHeartbeat() throws IOException {
|
||||
return heartbeat.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns minimal allowed value for this datasource.
|
||||
*
|
||||
* @return Minimal value allowed.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public double getMinValue() throws IOException {
|
||||
return minValue.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns maximal allowed value for this datasource.
|
||||
*
|
||||
* @return Maximal value allowed.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public double getMaxValue() throws IOException {
|
||||
return maxValue.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last known value of the datasource.
|
||||
*
|
||||
* @return Last datasource value.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public double getLastValue() throws IOException {
|
||||
return lastValue.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns value this datasource accumulated so far.
|
||||
*
|
||||
* @return Accumulated datasource value.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public double getAccumValue() throws IOException {
|
||||
return accumValue.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of accumulated NaN seconds.
|
||||
*
|
||||
* @return Accumulated NaN seconds.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public long getNanSeconds() throws IOException {
|
||||
return nanSeconds.get();
|
||||
}
|
||||
|
||||
final void process(long newTime, double newValue) throws IOException {
|
||||
Header header = parentDb.getHeader();
|
||||
long step = header.getStep();
|
||||
long oldTime = header.getLastUpdateTime();
|
||||
long startTime = Util.normalize(oldTime, step);
|
||||
long endTime = startTime + step;
|
||||
double oldValue = lastValue.get();
|
||||
double updateValue = calculateUpdateValue(oldTime, oldValue, newTime, newValue);
|
||||
if (newTime < endTime) {
|
||||
accumulate(oldTime, newTime, updateValue);
|
||||
}
|
||||
else {
|
||||
// should store something
|
||||
long boundaryTime = Util.normalize(newTime, step);
|
||||
accumulate(oldTime, boundaryTime, updateValue);
|
||||
double value = calculateTotal(startTime, boundaryTime);
|
||||
double lastCalculateValue = calculateLastTotal(startTime, boundaryTime);
|
||||
|
||||
// how many updates?
|
||||
long numSteps = (boundaryTime - endTime) / step + 1L;
|
||||
|
||||
// ACTION!
|
||||
parentDb.archive(this, value, lastCalculateValue, numSteps);
|
||||
|
||||
// cleanup
|
||||
nanSeconds.set(0);
|
||||
accumValue.set(0.0);
|
||||
accumLastValue = Double.NaN;
|
||||
|
||||
accumulate(boundaryTime, newTime, updateValue);
|
||||
}
|
||||
}
|
||||
|
||||
private double calculateUpdateValue(long oldTime, double oldValue,
|
||||
long newTime, double newValue) throws IOException {
|
||||
double updateValue = Double.NaN;
|
||||
if (newTime - oldTime <= heartbeat.get()) {
|
||||
switch (dsType.get()) {
|
||||
case GAUGE:
|
||||
updateValue = newValue;
|
||||
break;
|
||||
case COUNTER:
|
||||
if (!Double.isNaN(newValue) && !Double.isNaN(oldValue)) {
|
||||
double diff = newValue - oldValue;
|
||||
if (diff < 0) {
|
||||
diff += MAX_32_BIT;
|
||||
}
|
||||
if (diff < 0) {
|
||||
diff += MAX_64_BIT - MAX_32_BIT;
|
||||
}
|
||||
if (diff >= 0) {
|
||||
updateValue = diff / (newTime - oldTime);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ABSOLUTE:
|
||||
if (!Double.isNaN(newValue)) {
|
||||
updateValue = newValue / (newTime - oldTime);
|
||||
}
|
||||
break;
|
||||
case DERIVE:
|
||||
if (!Double.isNaN(newValue) && !Double.isNaN(oldValue)) {
|
||||
updateValue = (newValue - oldValue) / (newTime - oldTime);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Double.isNaN(updateValue)) {
|
||||
double minVal = minValue.get();
|
||||
double maxVal = maxValue.get();
|
||||
if (!Double.isNaN(minVal) && updateValue < minVal) {
|
||||
updateValue = Double.NaN;
|
||||
}
|
||||
if (!Double.isNaN(maxVal) && updateValue > maxVal) {
|
||||
updateValue = Double.NaN;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastValue.set(newValue);
|
||||
return updateValue;
|
||||
}
|
||||
|
||||
private void accumulate(long oldTime, long newTime, double updateValue) throws IOException {
|
||||
if (Double.isNaN(updateValue)) {
|
||||
nanSeconds.set(nanSeconds.get() + (newTime - oldTime));
|
||||
}
|
||||
else {
|
||||
accumValue.set(accumValue.get() + updateValue * (newTime - oldTime));
|
||||
accumLastValue = updateValue;
|
||||
}
|
||||
}
|
||||
|
||||
private double calculateTotal(long startTime, long boundaryTime) throws IOException {
|
||||
double totalValue = Double.NaN;
|
||||
long validSeconds = boundaryTime - startTime - nanSeconds.get();
|
||||
if (nanSeconds.get() <= heartbeat.get() && validSeconds > 0) {
|
||||
totalValue = accumValue.get() / validSeconds;
|
||||
}
|
||||
// IMPORTANT:
|
||||
// if datasource name ends with "!", we'll send zeros instead of NaNs
|
||||
// this might be handy from time to time
|
||||
if (Double.isNaN(totalValue) && dsName.get().endsWith(DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX)) {
|
||||
totalValue = 0D;
|
||||
}
|
||||
return totalValue;
|
||||
}
|
||||
|
||||
private double calculateLastTotal(long startTime, long boundaryTime) throws IOException {
|
||||
double totalValue = Double.NaN;
|
||||
long validSeconds = boundaryTime - startTime - nanSeconds.get();
|
||||
if (nanSeconds.get() <= heartbeat.get() && validSeconds > 0) {
|
||||
totalValue = accumLastValue;
|
||||
}
|
||||
|
||||
if (Double.isNaN(totalValue) && dsName.get().endsWith(DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX)) {
|
||||
totalValue = 0D;
|
||||
}
|
||||
return totalValue;
|
||||
}
|
||||
|
||||
void appendXml(XmlWriter writer) throws IOException {
|
||||
writer.startTag("ds");
|
||||
writer.writeTag("name", dsName.get());
|
||||
writer.writeTag("type", dsType.name());
|
||||
writer.writeTag("minimal_heartbeat", heartbeat.get());
|
||||
writer.writeTag("min", minValue.get());
|
||||
writer.writeTag("max", maxValue.get());
|
||||
writer.writeComment("PDP Status");
|
||||
writer.writeTag("last_ds", lastValue.get(), "UNKN");
|
||||
writer.writeTag("value", accumValue.get());
|
||||
writer.writeTag("unknown_sec", nanSeconds.get());
|
||||
writer.closeTag(); // ds
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Copies object's internal state to another Datasource object.
|
||||
*/
|
||||
public void copyStateTo(Datasource datasource) throws IOException {
|
||||
if (!datasource.dsName.get().equals(dsName.get())) {
|
||||
throw new IllegalArgumentException("Incompatible datasource names");
|
||||
}
|
||||
if (datasource.dsType.get() != dsType.get()) {
|
||||
throw new IllegalArgumentException("Incompatible datasource types");
|
||||
}
|
||||
datasource.lastValue.set(lastValue.get());
|
||||
datasource.nanSeconds.set(nanSeconds.get());
|
||||
datasource.accumValue.set(accumValue.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns index of this Datasource object in the RRD.
|
||||
*
|
||||
* @return Datasource index in the RRD.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public int getDsIndex() throws IOException {
|
||||
try {
|
||||
return parentDb.getDsIndex(dsName.get());
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets datasource heartbeat to a new value.
|
||||
*
|
||||
* @param heartbeat New heartbeat value
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
* @throws java.lang.IllegalArgumentException Thrown if invalid (non-positive) heartbeat value is specified.
|
||||
*/
|
||||
public void setHeartbeat(long heartbeat) throws IOException {
|
||||
if (heartbeat < 1L) {
|
||||
throw new IllegalArgumentException("Invalid heartbeat specified: " + heartbeat);
|
||||
}
|
||||
this.heartbeat.set(heartbeat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets datasource name to a new value
|
||||
*
|
||||
* @param newDsName New datasource name
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public void setDsName(String newDsName) throws IOException {
|
||||
if (parentDb.containsDs(newDsName)) {
|
||||
throw new IllegalArgumentException("Datasource already defined in this RRD: " + newDsName);
|
||||
}
|
||||
|
||||
this.dsName.set(newDsName);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Setter for the field <code>dsType</code>.</p>
|
||||
*
|
||||
* @param newDsType a {@link org.rrd4j.DsType} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public void setDsType(DsType newDsType) throws IOException {
|
||||
// set datasource type
|
||||
dsType.set(newDsType);
|
||||
// reset datasource status
|
||||
lastValue.set(Double.NaN);
|
||||
accumValue.set(0.0);
|
||||
// reset archive status
|
||||
int dsIndex = parentDb.getDsIndex(dsName.get());
|
||||
Archive[] archives = parentDb.getArchives();
|
||||
for (Archive archive : archives) {
|
||||
archive.getArcState(dsIndex).setAccumValue(Double.NaN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets minimum allowed value for this datasource. If <code>filterArchivedValues</code>
|
||||
* argument is set to true, all archived values less then <code>minValue</code> will
|
||||
* be fixed to NaN.
|
||||
*
|
||||
* @param minValue New minimal value. Specify <code>Double.NaN</code> 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 <code>filterArchivedValues</code>
|
||||
* argument is set to true, all archived values greater then <code>maxValue</code> will
|
||||
* be fixed to NaN.
|
||||
*
|
||||
* @param maxValue New maximal value. Specify <code>Double.NaN</code> 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 <code>filterArchivedValues</code>
|
||||
* argument is set to true, all archived values less then <code>minValue</code> or
|
||||
* greater then <code>maxValue</code> will be fixed to NaN.
|
||||
*
|
||||
* @param minValue New minimal value. Specify <code>Double.NaN</code> if no min
|
||||
* value should be set.
|
||||
* @param maxValue New maximal value. Specify <code>Double.NaN</code> 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();
|
||||
}
|
||||
}
|
||||
|
154
apps/jrobin/java/src/org/rrd4j/core/DsDef.java
Normal file
154
apps/jrobin/java/src/org/rrd4j/core/DsDef.java
Normal file
@ -0,0 +1,154 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import org.rrd4j.DsType;
|
||||
|
||||
/**
|
||||
* <p>Class to represent single data source definition within the RRD.
|
||||
* Datasource definition consists of the following five elements:</p>
|
||||
* <ul>
|
||||
* <li>data source name
|
||||
* <li>data source type
|
||||
* <li>heartbeat
|
||||
* <li>minimal value
|
||||
* <li>maximal value
|
||||
* </ul>
|
||||
* <p>For the complete explanation of all source definition parameters, see RRDTool's
|
||||
* <a href="http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html" target="man">rrdcreate man page</a>.</p>
|
||||
*
|
||||
* @author Sasa Markovic
|
||||
*/
|
||||
public class DsDef {
|
||||
static final String FORCE_ZEROS_FOR_NANS_SUFFIX = "!";
|
||||
|
||||
private final String dsName;
|
||||
private final DsType dsType;
|
||||
private final long heartbeat;
|
||||
private final double minValue, maxValue;
|
||||
|
||||
/**
|
||||
* Creates new data source definition object. This object should be passed as argument
|
||||
* to {@link org.rrd4j.core.RrdDef#addDatasource(DsDef) addDatasource()}
|
||||
* method of {@link RrdDb RrdDb} object.
|
||||
* <p>
|
||||
* For the complete explanation of all source definition parameters, see RRDTool's
|
||||
* <a href="http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html" target="man">rrdcreate man page</a>
|
||||
* <p>
|
||||
* <b>IMPORTANT NOTE:</b> 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 <code>Double.NaN</code> if unknown.
|
||||
* @param maxValue Maximal value. Use <code>Double.NaN</code> 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);
|
||||
}
|
||||
}
|
500
apps/jrobin/java/src/org/rrd4j/core/FetchData.java
Normal file
500
apps/jrobin/java/src/org/rrd4j/core/FetchData.java
Normal file
@ -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.
|
||||
* <p>
|
||||
*
|
||||
* 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).
|
||||
* <p>
|
||||
*
|
||||
* 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.
|
||||
* <p>
|
||||
*
|
||||
* 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.
|
||||
* <p>
|
||||
*
|
||||
* @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 <code>x</code> and <code>y</code>
|
||||
* in this FetchData and you want to calculate values for <code>(x+y)/2</code> use something like:
|
||||
* <p>
|
||||
* <code>getRpnValues("x,y,+,2,/");</code>
|
||||
*
|
||||
* @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 <code>x</code> and <code>y</code>
|
||||
* in this FetchData and you want to calculate MAX value of <code>(x+y)/2</code> use something like:
|
||||
* <p>
|
||||
* <code>getRpnAggregate("x,y,+,2,/", "MAX");</code>
|
||||
*
|
||||
* @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 <code>x</code> and <code>y</code>
|
||||
* in this FetchData and you want to calculate MIN, MAX, LAST, FIRST, AVERAGE and TOTAL value
|
||||
* of <code>(x+y)/2</code> use something like:
|
||||
* <p>
|
||||
* <code>getRpnAggregates("x,y,+,2,/");</code>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
*
|
||||
* 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.
|
||||
* <p>
|
||||
*
|
||||
* Read more about this topic at:<p>
|
||||
* <a href="http://www.red.net/support/resourcecentre/leasedline/percentile.php">Rednet</a> or<br>
|
||||
* <a href="http://www.bytemark.co.uk/support/tech/95thpercentile.html">Bytemark</a>.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
175
apps/jrobin/java/src/org/rrd4j/core/FetchRequest.java
Normal file
175
apps/jrobin/java/src/org/rrd4j/core/FetchRequest.java
Normal file
@ -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
|
||||
* <a href="http://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html" target="man">rrdfetch man page</a>.
|
||||
* <p>
|
||||
* You cannot create <code>FetchRequest</code> 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<String> filter) {
|
||||
this.filter = filter.toArray(new String[filter.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets request filter in order to fetch data only for
|
||||
* a single datasource (datasource name).
|
||||
* If not set (or set to null), fetched data will
|
||||
* contain values of all datasources defined in the corresponding RRD.
|
||||
* To fetch data for a single datasource only,
|
||||
* specify an array of datasource names as method argument.
|
||||
*
|
||||
* @param filter A single datasource (datasource name) to fetch data from.
|
||||
*/
|
||||
public void setFilter(String filter) {
|
||||
this.filter = (filter == null) ? null : (new String[]{filter});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns request filter. See {@link #setFilter(String...) setFilter()} for
|
||||
* complete explanation.
|
||||
*
|
||||
* @return Request filter (array of datasource names), null if not set.
|
||||
*/
|
||||
public String[] getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns consolidation function to be used during the fetch process.
|
||||
*
|
||||
* @return Consolidation function.
|
||||
*/
|
||||
public ConsolFun getConsolFun() {
|
||||
return consolFun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns starting timestamp to be used for the fetch request.
|
||||
*
|
||||
* @return Starting timestamp in seconds.
|
||||
*/
|
||||
public long getFetchStart() {
|
||||
return fetchStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ending timestamp to be used for the fetch request.
|
||||
*
|
||||
* @return Ending timestamp in seconds.
|
||||
*/
|
||||
public long getFetchEnd() {
|
||||
return fetchEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fetch resolution to be used for the fetch request.
|
||||
*
|
||||
* @return Fetch resolution in seconds.
|
||||
*/
|
||||
public long getResolution() {
|
||||
return resolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the content of fetch request using the syntax of RRDTool's fetch command.
|
||||
*
|
||||
* @return Fetch request dump.
|
||||
*/
|
||||
public String dump() {
|
||||
return "fetch \"" + parentDb.getRrdBackend().getPath() +
|
||||
"\" " + consolFun + " --start " + fetchStart + " --end " + fetchEnd +
|
||||
(resolution > 1 ? " --resolution " + resolution : "");
|
||||
}
|
||||
|
||||
String getRrdToolCommand() {
|
||||
return dump();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data from the underlying RRD and puts it in a single
|
||||
* {@link org.rrd4j.core.FetchData FetchData} object.
|
||||
*
|
||||
* @return FetchData object filled with timestamps and datasource values.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public FetchData fetchData() throws IOException {
|
||||
return parentDb.fetchData(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying RrdDb object.
|
||||
*
|
||||
* @return RrdDb object used to create this FetchRequest object.
|
||||
*/
|
||||
public RrdDb getParentDb() {
|
||||
return parentDb;
|
||||
}
|
||||
|
||||
}
|
239
apps/jrobin/java/src/org/rrd4j/core/Header.java
Normal file
239
apps/jrobin/java/src/org/rrd4j/core/Header.java
Normal file
@ -0,0 +1,239 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Class to represent RRD header. Header information is mainly static (once set, it
|
||||
* cannot be changed), with the exception of last update time (this value is changed whenever
|
||||
* RRD gets updated).
|
||||
* <p>
|
||||
*
|
||||
* Normally, you don't need to manipulate the Header object directly - Rrd4j framework
|
||||
* does it for you.
|
||||
* <p>
|
||||
*
|
||||
* @author Sasa Markovic*
|
||||
*/
|
||||
public class Header implements RrdUpdater<Header> {
|
||||
static final int SIGNATURE_LENGTH = 5;
|
||||
static final String SIGNATURE = "RRD4J";
|
||||
|
||||
static final String DEFAULT_SIGNATURE = "RRD4J, version 0.1";
|
||||
static final String RRDTOOL_VERSION1 = "0001";
|
||||
static final String RRDTOOL_VERSION3 = "0003";
|
||||
private static final String[] VERSIONS = {"version 0.1", "version 0.2"};
|
||||
|
||||
private RrdDb parentDb;
|
||||
private int version = -1;
|
||||
|
||||
private RrdString<Header> signature;
|
||||
private RrdLong<Header> step;
|
||||
private RrdInt<Header> dsCount, arcCount;
|
||||
private RrdLong<Header> lastUpdateTime;
|
||||
|
||||
Header(RrdDb parentDb, RrdDef rrdDef) throws IOException {
|
||||
this.parentDb = parentDb;
|
||||
|
||||
String initSignature = null;
|
||||
if(rrdDef != null) {
|
||||
version = rrdDef.getVersion();
|
||||
initSignature = SIGNATURE + ", " + VERSIONS[version - 1];
|
||||
}
|
||||
else {
|
||||
initSignature = DEFAULT_SIGNATURE;
|
||||
}
|
||||
|
||||
signature = new RrdString<>(this); // NOT constant, may be cached
|
||||
step = new RrdLong<>(this, true); // constant, may be cached
|
||||
dsCount = new RrdInt<>(this, true); // constant, may be cached
|
||||
arcCount = new RrdInt<>(this, true); // constant, may be cached
|
||||
lastUpdateTime = new RrdLong<>(this);
|
||||
|
||||
if (rrdDef != null) {
|
||||
signature.set(initSignature);
|
||||
step.set(rrdDef.getStep());
|
||||
dsCount.set(rrdDef.getDsCount());
|
||||
arcCount.set(rrdDef.getArcCount());
|
||||
lastUpdateTime.set(rrdDef.getStartTime());
|
||||
}
|
||||
}
|
||||
|
||||
Header(RrdDb parentDb, DataImporter reader) throws IOException {
|
||||
this(parentDb, (RrdDef) null);
|
||||
String importVersion = reader.getVersion();
|
||||
switch(importVersion) {
|
||||
case RRDTOOL_VERSION1:
|
||||
version = 1;
|
||||
break;
|
||||
case RRDTOOL_VERSION3:
|
||||
version = 2;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Could not get version " + version);
|
||||
}
|
||||
signature.set(SIGNATURE + ", " + VERSIONS[version - 1]);
|
||||
step.set(reader.getStep());
|
||||
dsCount.set(reader.getDsCount());
|
||||
arcCount.set(reader.getArcCount());
|
||||
lastUpdateTime.set(reader.getLastUpdateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns RRD signature. Initially, the returned string will be
|
||||
* of the form <b><i>Rrd4j, version x.x</i></b>.
|
||||
*
|
||||
* @return RRD signature
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public String getSignature() throws IOException {
|
||||
return signature.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getInfo.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public String getInfo() throws IOException {
|
||||
return getSignature().substring(SIGNATURE_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>setInfo.</p>
|
||||
*
|
||||
* @param info a {@link java.lang.String} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public void setInfo(String info) throws IOException {
|
||||
if (info != null && info.length() > 0) {
|
||||
signature.set(SIGNATURE + info);
|
||||
}
|
||||
else {
|
||||
signature.set(SIGNATURE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last update time of the RRD.
|
||||
*
|
||||
* @return Timestamp (Unix epoch, no milliseconds) corresponding to the last update time.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public long getLastUpdateTime() throws IOException {
|
||||
return lastUpdateTime.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns primary RRD time step.
|
||||
*
|
||||
* @return Primary time step in seconds
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public long getStep() throws IOException {
|
||||
return step.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of datasources defined in the RRD.
|
||||
*
|
||||
* @return Number of datasources defined
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public int getDsCount() throws IOException {
|
||||
return dsCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of archives defined in the RRD.
|
||||
*
|
||||
* @return Number of archives defined
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public int getArcCount() throws IOException {
|
||||
return arcCount.get();
|
||||
}
|
||||
|
||||
void setLastUpdateTime(long lastUpdateTime) throws IOException {
|
||||
this.lastUpdateTime.set(lastUpdateTime);
|
||||
}
|
||||
|
||||
String dump() throws IOException {
|
||||
return "== HEADER ==\n" +
|
||||
"signature:" + getSignature() +
|
||||
" lastUpdateTime:" + getLastUpdateTime() +
|
||||
" step:" + getStep() +
|
||||
" dsCount:" + getDsCount() +
|
||||
" arcCount:" + getArcCount() + "\n";
|
||||
}
|
||||
|
||||
void appendXml(XmlWriter writer) throws IOException {
|
||||
writer.writeComment(signature.get());
|
||||
writer.writeTag("version", RRDTOOL_VERSION3);
|
||||
writer.writeComment("Seconds");
|
||||
writer.writeTag("step", step.get());
|
||||
writer.writeComment(Util.getDate(lastUpdateTime.get()));
|
||||
writer.writeTag("lastupdate", lastUpdateTime.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Copies object's internal state to another Header object.
|
||||
*/
|
||||
public void copyStateTo(Header header) throws IOException {
|
||||
header.lastUpdateTime.set(lastUpdateTime.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying storage (backend) object which actually performs all
|
||||
* I/O operations.
|
||||
*
|
||||
* @return I/O backend object
|
||||
*/
|
||||
public RrdBackend getRrdBackend() {
|
||||
return parentDb.getRrdBackend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RRD version.
|
||||
*
|
||||
* @return RRD version
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public int getVersion() throws IOException {
|
||||
if(version < 0) {
|
||||
for(int i=0; i < VERSIONS.length; i++) {
|
||||
if(signature.get().endsWith(VERSIONS[i])) {
|
||||
version = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
boolean isRrd4jHeader() {
|
||||
try {
|
||||
return signature.get().startsWith(SIGNATURE) || signature.get().startsWith("JR"); // backwards compatible with JRobin
|
||||
} catch (IOException ioe) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void validateHeader() throws IOException {
|
||||
if (!isRrd4jHeader()) {
|
||||
throw new InvalidRrdException("Invalid file header. File [" + parentDb.getCanonicalPath() + "] is not a RRD4J RRD file");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Required to implement RrdUpdater interface. You should never call this method directly.
|
||||
*
|
||||
* @return Allocator object
|
||||
*/
|
||||
public RrdAllocator getRrdAllocator() {
|
||||
return parentDb.getRrdAllocator();
|
||||
}
|
||||
|
||||
}
|
20
apps/jrobin/java/src/org/rrd4j/core/InvalidRrdException.java
Normal file
20
apps/jrobin/java/src/org/rrd4j/core/InvalidRrdException.java
Normal file
@ -0,0 +1,20 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
/**
|
||||
* An exception indicating a corrupted RRD.
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public class InvalidRrdException extends RrdException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InvalidRrdException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidRrdException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
152
apps/jrobin/java/src/org/rrd4j/core/Robin.java
Normal file
152
apps/jrobin/java/src/org/rrd4j/core/Robin.java
Normal file
@ -0,0 +1,152 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Class to represent archive values for a single datasource. Robin class is the heart of
|
||||
* the so-called "round robin database" concept. Basically, each Robin object is a
|
||||
* fixed length array of double values. Each double value represents consolidated, archived
|
||||
* value for the specific timestamp. When the underlying array of double values gets completely
|
||||
* filled, new values will replace the oldest ones.
|
||||
* <p>
|
||||
* 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<Robin> {
|
||||
|
||||
/**
|
||||
* Fetches all archived values.
|
||||
*
|
||||
* @return Array of double archive values, starting from the oldest one.
|
||||
* @throws java.io.IOException Thrown in case of I/O specific error.
|
||||
*/
|
||||
double[] getValues() throws IOException;
|
||||
|
||||
/**
|
||||
* Updates archived values in bulk.
|
||||
*
|
||||
* @param newValues Array of double values to be stored in the archive
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
* @throws java.lang.IllegalArgumentException Thrown if the length of the input array is different from the length of
|
||||
* this archive
|
||||
*/
|
||||
void setValues(double... newValues) throws IOException;
|
||||
|
||||
/**
|
||||
* (Re)sets all values in this archive to the same value.
|
||||
*
|
||||
* @param newValue New value
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
void setValues(double newValue) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the i-th value from the Robin archive.
|
||||
*
|
||||
* @param index Value index
|
||||
* @return Value stored in the i-th position (the oldest value has zero index)
|
||||
* @throws java.io.IOException Thrown in case of I/O specific error.
|
||||
*/
|
||||
double getValue(int index) throws IOException;
|
||||
|
||||
/**
|
||||
* Sets the i-th value in the Robin archive.
|
||||
*
|
||||
* @param index index in the archive (the oldest value has zero index)
|
||||
* @param value value to be stored
|
||||
* @throws java.io.IOException Thrown in case of I/O specific error.
|
||||
*/
|
||||
void setValue(int index, double value) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the Archive object to which this Robin object belongs.
|
||||
*
|
||||
* @return Parent Archive object
|
||||
*/
|
||||
Archive getParent();
|
||||
|
||||
/**
|
||||
* Returns the size of the underlying array of archived values.
|
||||
*
|
||||
* @return Number of stored values
|
||||
*/
|
||||
int getSize();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Copies object's internal state to another Robin object.
|
||||
*/
|
||||
void copyStateTo(Robin other) throws IOException;
|
||||
|
||||
/**
|
||||
* Filters values stored in this archive based on the given boundary.
|
||||
* Archived values found to be outside of <code>[minValue, maxValue]</code> interval (inclusive)
|
||||
* will be silently replaced with <code>NaN</code>.
|
||||
*
|
||||
* @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();
|
||||
|
||||
/**
|
||||
* <p>update.</p>
|
||||
*
|
||||
* @param newValues an array of double.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
void update(double[] newValues) throws IOException;
|
||||
|
||||
/**
|
||||
* <p>dump.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
String dump() throws IOException;
|
||||
|
||||
/**
|
||||
* <p>store.</p>
|
||||
*
|
||||
* @param newValue a double.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
void store(double newValue) throws IOException;
|
||||
|
||||
/**
|
||||
* <p>bulkStore.</p>
|
||||
*
|
||||
* @param newValue a double.
|
||||
* @param bulkCount a int.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
void bulkStore(double newValue, int bulkCount) throws IOException;
|
||||
|
||||
/**
|
||||
* <p>getValues.</p>
|
||||
*
|
||||
* @param index a int.
|
||||
* @param count a int.
|
||||
* @return an array of double.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
double[] getValues(int index, int count) throws IOException;
|
||||
}
|
247
apps/jrobin/java/src/org/rrd4j/core/RobinArray.java
Normal file
247
apps/jrobin/java/src/org/rrd4j/core/RobinArray.java
Normal file
@ -0,0 +1,247 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Class to represent archive values for a single datasource. Robin class is the heart of
|
||||
* the so-called "round robin database" concept. Basically, each Robin object is a
|
||||
* fixed length array of double values. Each double value represents consolidated, archived
|
||||
* value for the specific timestamp. When the underlying array of double values gets completely
|
||||
* filled, new values will replace the oldest ones.<p>
|
||||
* <p/>
|
||||
* 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
|
||||
*/
|
||||
class RobinArray implements Robin {
|
||||
private final Archive parentArc;
|
||||
private final RrdInt<Robin> pointer;
|
||||
private final RrdDoubleArray<Robin> values;
|
||||
private int rows;
|
||||
|
||||
RobinArray(Archive parentArc, int rows, boolean shouldInitialize) throws IOException {
|
||||
this.parentArc = parentArc;
|
||||
this.pointer = new RrdInt<>(this);
|
||||
this.values = new RrdDoubleArray<>(this, rows);
|
||||
this.rows = rows;
|
||||
if (shouldInitialize) {
|
||||
pointer.set(0);
|
||||
values.set(0, Double.NaN, rows);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#getValues()
|
||||
*/
|
||||
/**
|
||||
* <p>Getter for the field <code>values</code>.</p>
|
||||
*
|
||||
* @return an array of double.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public double[] getValues() throws IOException {
|
||||
return getValues(0, rows);
|
||||
}
|
||||
|
||||
// stores single value
|
||||
/** {@inheritDoc} */
|
||||
public void store(double newValue) throws IOException {
|
||||
int position = pointer.get();
|
||||
values.set(position, newValue);
|
||||
pointer.set((position + 1) % rows);
|
||||
}
|
||||
|
||||
// stores the same value several times
|
||||
/** {@inheritDoc} */
|
||||
public void bulkStore(double newValue, int bulkCount) throws IOException {
|
||||
assert bulkCount <= rows: "Invalid number of bulk updates: " + bulkCount + " rows=" + rows;
|
||||
|
||||
int position = pointer.get();
|
||||
|
||||
// update tail
|
||||
int tailUpdateCount = Math.min(rows - position, bulkCount);
|
||||
|
||||
values.set(position, newValue, tailUpdateCount);
|
||||
pointer.set((position + tailUpdateCount) % rows);
|
||||
|
||||
// do we need to update from the start?
|
||||
int headUpdateCount = bulkCount - tailUpdateCount;
|
||||
if (headUpdateCount > 0) {
|
||||
values.set(0, newValue, headUpdateCount);
|
||||
pointer.set(headUpdateCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>update.</p>
|
||||
*
|
||||
* @param newValues an array of double.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public void update(double[] newValues) throws IOException {
|
||||
assert rows == newValues.length: "Invalid number of robin values supplied (" + newValues.length +
|
||||
"), exactly " + rows + " needed";
|
||||
pointer.set(0);
|
||||
values.writeDouble(0, newValues);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#setValues(double)
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
public void setValues(double... newValues) throws IOException {
|
||||
if (rows != newValues.length) {
|
||||
throw new IllegalArgumentException("Invalid number of robin values supplied (" + newValues.length +
|
||||
"), exactly " + rows + " needed");
|
||||
}
|
||||
update(newValues);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#setValues(double)
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
public void setValues(double newValue) throws IOException {
|
||||
double[] values = new double[rows];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = newValue;
|
||||
}
|
||||
update(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>dump.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public String dump() throws IOException {
|
||||
StringBuilder buffer = new StringBuilder("Robin " + pointer.get() + "/" + rows + ": ");
|
||||
double[] values = getValues();
|
||||
for (double value : values) {
|
||||
buffer.append(Util.formatDouble(value, true)).append(" ");
|
||||
}
|
||||
buffer.append("\n");
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#getValue(int)
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
public double getValue(int index) throws IOException {
|
||||
int arrayIndex = (pointer.get() + index) % rows;
|
||||
return values.get(arrayIndex);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#setValue(int, double)
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
public void setValue(int index, double value) throws IOException {
|
||||
int arrayIndex = (pointer.get() + index) % rows;
|
||||
values.set(arrayIndex, value);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public double[] getValues(int index, int count) throws IOException {
|
||||
assert count <= rows: "Too many values requested: " + count + " rows=" + rows;
|
||||
|
||||
int startIndex = (pointer.get() + index) % rows;
|
||||
int tailReadCount = Math.min(rows - startIndex, count);
|
||||
double[] tailValues = values.get(startIndex, tailReadCount);
|
||||
if (tailReadCount < count) {
|
||||
int headReadCount = count - tailReadCount;
|
||||
double[] headValues = values.get(0, headReadCount);
|
||||
double[] values = new double[count];
|
||||
int k = 0;
|
||||
for (double tailValue : tailValues) {
|
||||
values[k++] = tailValue;
|
||||
}
|
||||
for (double headValue : headValues) {
|
||||
values[k++] = headValue;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
else {
|
||||
return tailValues;
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#getParent()
|
||||
*/
|
||||
/**
|
||||
* <p>getParent.</p>
|
||||
*
|
||||
* @return a {@link org.rrd4j.core.Archive} object.
|
||||
*/
|
||||
public Archive getParent() {
|
||||
return parentArc;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#getSize()
|
||||
*/
|
||||
/**
|
||||
* <p>getSize.</p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getSize() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#copyStateTo(org.rrd4j.core.RrdUpdater)
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
public void copyStateTo(Robin robin) throws IOException {
|
||||
int rowsDiff = rows - robin.getSize();
|
||||
for (int i = 0; i < robin.getSize(); i++) {
|
||||
int j = i + rowsDiff;
|
||||
robin.store(j >= 0 ? getValue(j) : Double.NaN);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#filterValues(double, double)
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
public void filterValues(double minValue, double maxValue) throws IOException {
|
||||
for (int i = 0; i < rows; i++) {
|
||||
double value = values.get(i);
|
||||
if (!Double.isNaN(minValue) && !Double.isNaN(value) && minValue > value) {
|
||||
values.set(i, Double.NaN);
|
||||
}
|
||||
if (!Double.isNaN(maxValue) && !Double.isNaN(value) && maxValue < value) {
|
||||
values.set(i, Double.NaN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#getRrdBackend()
|
||||
*/
|
||||
/**
|
||||
* <p>getRrdBackend.</p>
|
||||
*
|
||||
* @return a {@link org.rrd4j.core.RrdBackend} object.
|
||||
*/
|
||||
public RrdBackend getRrdBackend() {
|
||||
return parentArc.getRrdBackend();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.rrd4j.core.Robin#getRrdAllocator()
|
||||
*/
|
||||
/**
|
||||
* <p>getRrdAllocator.</p>
|
||||
*
|
||||
* @return a {@link org.rrd4j.core.RrdAllocator} object.
|
||||
*/
|
||||
public RrdAllocator getRrdAllocator() {
|
||||
return parentArc.getRrdAllocator();
|
||||
}
|
||||
}
|
239
apps/jrobin/java/src/org/rrd4j/core/RobinMatrix.java
Normal file
239
apps/jrobin/java/src/org/rrd4j/core/RobinMatrix.java
Normal file
@ -0,0 +1,239 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Class to represent archive values for a single datasource. Robin class is the heart of
|
||||
* the so-called "round robin database" concept. Basically, each Robin object is a
|
||||
* fixed length array of double values. Each double value reperesents consolidated, archived
|
||||
* value for the specific timestamp. When the underlying array of double values gets completely
|
||||
* filled, new values will replace the oldest ones.<p>
|
||||
* <p/>
|
||||
* 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 Fabrice Bacchella
|
||||
*/
|
||||
class RobinMatrix implements Robin {
|
||||
private final Archive parentArc;
|
||||
private final RrdInt<Archive> pointer;
|
||||
private final RrdDoubleMatrix<Archive> values;
|
||||
private int rows;
|
||||
private int column;
|
||||
|
||||
RobinMatrix(Archive parentArc, RrdDoubleMatrix<Archive> values, RrdInt<Archive> pointer, int column) throws IOException {
|
||||
this.parentArc = parentArc;
|
||||
this.pointer = pointer;
|
||||
this.values = values;
|
||||
this.rows = values.getRows();
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all archived values.
|
||||
*
|
||||
* @return Array of double archive values, starting from the oldest one.
|
||||
* @throws java.io.IOException Thrown in case of I/O specific error.
|
||||
*/
|
||||
public double[] getValues() throws IOException {
|
||||
return getValues(0, rows);
|
||||
}
|
||||
|
||||
// stores single value
|
||||
/** {@inheritDoc} */
|
||||
public void store(double newValue) throws IOException {
|
||||
int position = pointer.get();
|
||||
values.set(column, position, newValue);
|
||||
pointer.set((position + 1) % rows);
|
||||
}
|
||||
|
||||
// stores the same value several times
|
||||
/** {@inheritDoc} */
|
||||
public void bulkStore(double newValue, int bulkCount) throws IOException {
|
||||
assert bulkCount <= rows: "Invalid number of bulk updates: " + bulkCount + " rows=" + rows;
|
||||
|
||||
int position = pointer.get();
|
||||
|
||||
// update tail
|
||||
int tailUpdateCount = Math.min(rows - position, bulkCount);
|
||||
|
||||
values.set(column, position, newValue, tailUpdateCount);
|
||||
pointer.set((position + tailUpdateCount) % rows);
|
||||
|
||||
// do we need to update from the start?
|
||||
int headUpdateCount = bulkCount - tailUpdateCount;
|
||||
if (headUpdateCount > 0) {
|
||||
values.set(column, 0, newValue, headUpdateCount);
|
||||
pointer.set(headUpdateCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>update.</p>
|
||||
*
|
||||
* @param newValues an array of double.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public void update(double[] newValues) throws IOException {
|
||||
assert rows == newValues.length: "Invalid number of robin values supplied (" + newValues.length +
|
||||
"), exactly " + rows + " needed";
|
||||
pointer.set(0);
|
||||
values.set(column, 0, newValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Updates archived values in bulk.
|
||||
*/
|
||||
public void setValues(double... newValues) throws IOException {
|
||||
if (rows != newValues.length) {
|
||||
throw new IllegalArgumentException("Invalid number of robin values supplied (" + newValues.length +
|
||||
"), exactly " + rows + " needed");
|
||||
}
|
||||
update(newValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* (Re)sets all values in this archive to the same value.
|
||||
*/
|
||||
public void setValues(double newValue) throws IOException {
|
||||
double[] values = new double[rows];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = newValue;
|
||||
}
|
||||
update(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>dump.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public String dump() throws IOException {
|
||||
StringBuilder buffer = new StringBuilder("Robin " + pointer.get() + "/" + rows + ": ");
|
||||
double[] values = getValues();
|
||||
for (double value : values) {
|
||||
buffer.append(Util.formatDouble(value, true)).append(" ");
|
||||
}
|
||||
buffer.append("\n");
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Returns the i-th value from the Robin archive.
|
||||
*/
|
||||
public double getValue(int index) throws IOException {
|
||||
int arrayIndex = (pointer.get() + index) % rows;
|
||||
return values.get(column, arrayIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Sets the i-th value in the Robin archive.
|
||||
*/
|
||||
public void setValue(int index, double value) throws IOException {
|
||||
int arrayIndex = (pointer.get() + index) % rows;
|
||||
values.set(column, arrayIndex, value);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public double[] getValues(int index, int count) throws IOException {
|
||||
assert count <= rows: "Too many values requested: " + count + " rows=" + rows;
|
||||
|
||||
int startIndex = (pointer.get() + index) % rows;
|
||||
int tailReadCount = Math.min(rows - startIndex, count);
|
||||
double[] tailValues = values.get(column, startIndex, tailReadCount);
|
||||
if (tailReadCount < count) {
|
||||
int headReadCount = count - tailReadCount;
|
||||
double[] headValues = values.get(column, 0, headReadCount);
|
||||
double[] values = new double[count];
|
||||
int k = 0;
|
||||
for (double tailValue : tailValues) {
|
||||
values[k++] = tailValue;
|
||||
}
|
||||
for (double headValue : headValues) {
|
||||
values[k++] = headValue;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
else {
|
||||
return tailValues;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Archive object to which this Robin object belongs.
|
||||
*
|
||||
* @return Parent Archive object
|
||||
*/
|
||||
public Archive getParent() {
|
||||
return parentArc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the underlying array of archived values.
|
||||
*
|
||||
* @return Number of stored values
|
||||
*/
|
||||
public int getSize() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Copies object's internal state to another Robin object.
|
||||
*/
|
||||
public void copyStateTo(Robin robin) throws IOException {
|
||||
int rowsDiff = rows - robin.getSize();
|
||||
for (int i = 0; i < robin.getSize(); i++) {
|
||||
int j = i + rowsDiff;
|
||||
robin.store(j >= 0 ? getValue(j) : Double.NaN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Filters values stored in this archive based on the given boundary.
|
||||
* Archived values found to be outside of <code>[minValue, maxValue]</code> interval (inclusive)
|
||||
* will be silently replaced with <code>NaN</code>.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
22
apps/jrobin/java/src/org/rrd4j/core/RrdAllocator.java
Normal file
22
apps/jrobin/java/src/org/rrd4j/core/RrdAllocator.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
431
apps/jrobin/java/src/org/rrd4j/core/RrdBackend.java
Normal file
431
apps/jrobin/java/src/org/rrd4j/core/RrdBackend.java
Normal file
@ -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;
|
||||
|
||||
/**
|
||||
* <p>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.:</p>
|
||||
* <ul>
|
||||
* <li>{@link org.rrd4j.core.RrdRandomAccessFileBackend}: objects of this class are created from the
|
||||
* {@link org.rrd4j.core.RrdRandomAccessFileBackendFactory} class. This was the default backend used in all
|
||||
* Rrd4j releases prior to 1.4.0. It uses java.io.* package and
|
||||
* RandomAccessFile class to store RRD data in files on the disk.
|
||||
*
|
||||
* <li>{@link org.rrd4j.core.RrdNioBackend}: objects of this class are created from the
|
||||
* {@link org.rrd4j.core.RrdNioBackendFactory} class. The backend uses java.io.* and java.nio.*
|
||||
* classes (mapped ByteBuffer) to store RRD data in files on the disk. This backend is fast, very fast,
|
||||
* but consumes a lot of memory (borrowed not from the JVM but from the underlying operating system
|
||||
* directly). <b>This is the default backend used in Rrd4j since 1.4.0 release.</b>
|
||||
*
|
||||
* <li>{@link org.rrd4j.core.RrdMemoryBackend}: objects of this class are created from the
|
||||
* {@link org.rrd4j.core.RrdMemoryBackendFactory} class. This backend stores all data in memory. Once
|
||||
* JVM exits, all data gets lost. The backend is extremely fast and memory hungry.
|
||||
* </ul>
|
||||
*
|
||||
* <p>To create your own backend in order to provide some custom type of RRD storage,
|
||||
* you should do the following:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Create your custom RrdBackend class (RrdCustomBackend, for example)
|
||||
* by extending RrdBackend class. You have to implement all abstract methods defined
|
||||
* in the base class.
|
||||
*
|
||||
* <li>Create your custom RrdBackendFactory class (RrdCustomBackendFactory,
|
||||
* for example) by extending RrdBackendFactory class. You have to implement all
|
||||
* abstract methods defined in the base class. Your custom factory class will actually
|
||||
* create custom backend objects when necessary.
|
||||
*
|
||||
* <li>Create instance of your custom RrdBackendFactory and register it as a regular
|
||||
* factory available to Rrd4j framework. See javadoc for {@link org.rrd4j.core.RrdBackendFactory} to
|
||||
* find out how to do this.
|
||||
* </ul>
|
||||
*
|
||||
* @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 PhantomReference<RrdDb> ref;
|
||||
|
||||
/**
|
||||
* Creates backend for a RRD storage with the given path.
|
||||
*
|
||||
* @param path String identifying RRD storage. For files on the disk, this
|
||||
* argument should represent file path. Other storage types might interpret
|
||||
* this argument differently.
|
||||
*/
|
||||
protected RrdBackend(String path) {
|
||||
this.path = path;
|
||||
instanceCreated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param factory the factory to set
|
||||
*/
|
||||
void done(RrdBackendFactory factory, PhantomReference<RrdDb> ref) {
|
||||
this.factory = factory;
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the factory
|
||||
*/
|
||||
public RrdBackendFactory getFactory() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to the storage.
|
||||
*
|
||||
* @return Storage path
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URI associated to this backend, using the factory to generate it from the path.
|
||||
*
|
||||
* @return URI to this backend's rrd.
|
||||
*/
|
||||
public URI getUri() {
|
||||
return factory.getUri(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an array of bytes to the underlying storage starting from the given
|
||||
* storage offset.
|
||||
*
|
||||
* @param offset Storage offset.
|
||||
* @param b Array of bytes that should be copied to the underlying storage
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
protected abstract void write(long offset, byte[] b) throws IOException;
|
||||
|
||||
/**
|
||||
* Reads an array of bytes from the underlying storage starting from the given
|
||||
* storage offset.
|
||||
*
|
||||
* @param offset Storage offset.
|
||||
* @param b Array which receives bytes from the underlying storage
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
protected abstract void read(long offset, byte[] b) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the number of RRD bytes in the underlying storage.
|
||||
*
|
||||
* @return Number of RRD bytes in the storage.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public abstract long getLength() throws IOException;
|
||||
|
||||
/**
|
||||
* Sets the number of bytes in the underlying RRD storage.
|
||||
* This method is called only once, immediately after a new RRD storage gets created.
|
||||
*
|
||||
* @param length Length of the underlying RRD storage in bytes.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
protected abstract void setLength(long length) throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the underlying backend. Used internally, should not be called from external code.
|
||||
*
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
protected abstract void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the underlying backend. Call by {@code RrdDb#close()} when it's closed. All subclass must keep calling it.
|
||||
*
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
protected void rrdClose() throws IOException {
|
||||
try {
|
||||
close();
|
||||
} finally {
|
||||
if (ref != null) {
|
||||
ref.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method suggests the caching policy to the Rrd4j frontend (high-level) classes. If <code>true</code>
|
||||
* is returned, frontend classes will cache frequently used parts of a RRD file in memory to improve
|
||||
* performance. If <code>false</code> is returned, high level classes will never cache RRD file sections
|
||||
* in memory.
|
||||
*
|
||||
* @return <code>true</code> if file caching is enabled, <code>false</code> otherwise. By default, the
|
||||
* method returns <code>true</code> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
20
apps/jrobin/java/src/org/rrd4j/core/RrdBackendException.java
Normal file
20
apps/jrobin/java/src/org/rrd4j/core/RrdBackendException.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
576
apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java
Normal file
576
apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java
Normal file
@ -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.
|
||||
* <p>
|
||||
*
|
||||
* 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:
|
||||
* <ul>
|
||||
* <li>{@link org.rrd4j.core.RrdRandomAccessFileBackend}: objects of this class are created from the
|
||||
* {@link org.rrd4j.core.RrdRandomAccessFileBackendFactory} class. This was the default backend used in all
|
||||
* Rrd4j releases before 1.4.0 release. It uses java.io.* package and RandomAccessFile class to store
|
||||
* RRD data in files on the disk.
|
||||
*
|
||||
* <li>{@link org.rrd4j.core.RrdSafeFileBackend}: objects of this class are created from the
|
||||
* {@link org.rrd4j.core.RrdSafeFileBackendFactory} class. It uses java.io.* package and RandomAccessFile class to store
|
||||
* RRD data in files on the disk. This backend is SAFE:
|
||||
* it locks the underlying RRD file during update/fetch operations, and caches only static
|
||||
* parts of a RRD file in memory. Therefore, this backend is safe to be used when RRD files should
|
||||
* be shared <b>between several JVMs</b> at the same time. However, this backend is *slow* since it does
|
||||
* not use fast java.nio.* package (it's still based on the RandomAccessFile class).
|
||||
*
|
||||
* <li>{@link org.rrd4j.core.RrdNioBackend}: objects of this class are created from the
|
||||
* {@link org.rrd4j.core.RrdNioBackendFactory} class. The backend uses java.io.* and java.nio.*
|
||||
* classes (mapped ByteBuffer) to store RRD data in files on the disk. This is the default backend
|
||||
* since 1.4.0 release.
|
||||
*
|
||||
* <li>{@link org.rrd4j.core.RrdMemoryBackend}: objects of this class are created from the
|
||||
* {@link org.rrd4j.core.RrdMemoryBackendFactory} class. This backend stores all data in memory. Once
|
||||
* JVM exits, all data gets lost. The backend is extremely fast and memory hungry.
|
||||
*
|
||||
* <li>{@link org.rrd4j.core.RrdBerkeleyDbBackend}: objects of this class are created from the
|
||||
* {@link org.rrd4j.core.RrdBerkeleyDbBackendFactory} class. It stores RRD data to ordinary disk files
|
||||
* using <a href="http://www.oracle.com/technetwork/database/berkeleydb/overview/index-093405.html">Oracle Berkeley DB</a> Java Edition.
|
||||
*
|
||||
* <li>{@link org.rrd4j.core.RrdMongoDBBackend}: objects of this class are created from the {@link org.rrd4j.core.RrdMongoDBBackendFactory} class.
|
||||
* It stores data in a {@link com.mongodb.DBCollection} from <a href="http://www.mongodb.org/">MongoDB</a>.
|
||||
* </ul>
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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 `/`.
|
||||
* <p>
|
||||
* 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<String, RrdBackendFactory> factories = new HashMap<>();
|
||||
static {
|
||||
RrdRandomAccessFileBackendFactory fileFactory = new RrdRandomAccessFileBackendFactory();
|
||||
factories.put(fileFactory.name, fileFactory);
|
||||
RrdMemoryBackendFactory memoryFactory = new RrdMemoryBackendFactory();
|
||||
factories.put(memoryFactory.name, memoryFactory);
|
||||
RrdNioBackendFactory nioFactory = new RrdNioBackendFactory();
|
||||
factories.put(nioFactory.name, nioFactory);
|
||||
RrdSafeFileBackendFactory safeFactory = new RrdSafeFileBackendFactory();
|
||||
factories.put(safeFactory.name, safeFactory);
|
||||
defaultFactory = factories.get(DEFAULTFACTORY);
|
||||
}
|
||||
private static RrdBackendFactory defaultFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default factory type. It will also put in the active factories list.
|
||||
*
|
||||
*/
|
||||
public static final String DEFAULTFACTORY = "NIO";
|
||||
|
||||
private static final List<RrdBackendFactory> activeFactories = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Returns backend factory for the given backend factory name.
|
||||
*
|
||||
* @param name Backend factory name. Initially supported names are:
|
||||
* <ul>
|
||||
* <li><b>FILE</b>: Default factory which creates backends based on the
|
||||
* java.io.* package. RRD data is stored in files on the disk
|
||||
* <li><b>SAFE</b>: Default factory which creates backends based on the
|
||||
* java.io.* package. RRD data is stored in files on the disk. This backend
|
||||
* is "safe". Being safe means that RRD files can be safely shared between
|
||||
* several JVM's.
|
||||
* <li><b>NIO</b>: Factory which creates backends based on the
|
||||
* java.nio.* package. RRD data is stored in files on the disk
|
||||
* <li><b>MEMORY</b>: Factory which creates memory-oriented backends.
|
||||
* RRD data is stored in memory, it gets lost as soon as JVM exits.
|
||||
* <li><b>BERKELEY</b>: a memory-oriented backend that ensure persistent
|
||||
* in a <a href="http://www.oracle.com/technetwork/database/berkeleydb/overview/index-093405.html">Berkeley Db</a> storage.
|
||||
* <li><b>MONGODB</b>: a memory-oriented backend that ensure persistent
|
||||
* in a <a href="http://www.mongodb.org/">MongoDB</a> storage.
|
||||
* </ul>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
* It also clear the list of actives factories and set it to the default factory.
|
||||
* <p>
|
||||
*
|
||||
* @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("^(?:(?<scheme>[a-zA-Z][a-zA-Z0-9+-\\.]*):)?(?://(?<authority>[^/\\?#]*))?(?<path>[^\\?#]*)(?:\\?(?<query>[^#]*))?(?:#(?<fragment>.*))?$");
|
||||
|
||||
/**
|
||||
* Try to detect an URI from a path. It's needed because of windows path that look's like an URI
|
||||
* and to URL-encode the path.
|
||||
*
|
||||
* @param rrdpath
|
||||
* @return an URI
|
||||
*/
|
||||
public static URI buildGenericUri(String rrdpath) {
|
||||
Matcher urimatcher = URIPATTERN.matcher(rrdpath);
|
||||
if (urimatcher.matches()) {
|
||||
String scheme = urimatcher.group("scheme");
|
||||
String authority = urimatcher.group("authority");
|
||||
String path = urimatcher.group("path");
|
||||
String query = urimatcher.group("query");
|
||||
String fragment = urimatcher.group("fragment");
|
||||
try {
|
||||
// If scheme is a single letter, it's not a scheme, but a windows path
|
||||
if (scheme != null && scheme.length() == 1) {
|
||||
return new File(rrdpath).toURI();
|
||||
}
|
||||
// A scheme and a not absolute path, it's an opaque URI
|
||||
if (scheme != null && path.charAt(0) != '/') {
|
||||
return new URI(scheme, path, query);
|
||||
}
|
||||
// A relative file was given, ensure that it's OK if it was on a non-unix plateform
|
||||
if (File.separatorChar != '/' && scheme == null) {
|
||||
path = path.replace(File.separatorChar, '/');
|
||||
}
|
||||
return new URI(scheme, authority, path, query, fragment);
|
||||
} catch (URISyntaxException ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Not an URI pattern");
|
||||
}
|
||||
|
||||
private static class ClosingReference extends PhantomReference<RrdDb> {
|
||||
private RrdBackend backend;
|
||||
public ClosingReference(RrdDb db, RrdBackend backend,
|
||||
ReferenceQueue<? super RrdDb> q) {
|
||||
super(db, q);
|
||||
this.backend = backend;
|
||||
}
|
||||
@Override
|
||||
public void clear() {
|
||||
try {
|
||||
backend.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
backend = null;
|
||||
super.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private final ReferenceQueue<RrdDb> refQueue = new ReferenceQueue<>();
|
||||
|
||||
protected final String name;
|
||||
protected final boolean cachingAllowed;
|
||||
protected final String scheme;
|
||||
protected final boolean validateHeader;
|
||||
|
||||
protected RrdBackendFactory() {
|
||||
RrdBackendAnnotation annotation = getClass().getAnnotation(RrdBackendAnnotation.class);
|
||||
if (annotation != null) {
|
||||
name = annotation.name();
|
||||
cachingAllowed = annotation.cachingAllowed();
|
||||
if (annotation.scheme() != null && ! annotation.scheme().isEmpty()) {
|
||||
scheme = annotation.scheme();
|
||||
} else {
|
||||
scheme = name.toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
validateHeader = annotation.shouldValidateHeader();
|
||||
} else {
|
||||
name = getName();
|
||||
cachingAllowed = RrdBackendAnnotation.DEFAULT_CACHING_ALLOWED;
|
||||
scheme = getName().toLowerCase(Locale.ENGLISH);
|
||||
validateHeader = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that all phantom reference are indeed safely closed.
|
||||
*/
|
||||
public void checkClosing() {
|
||||
while(true) {
|
||||
ClosingReference ref = (ClosingReference) refQueue.poll();
|
||||
if (ref == null) {
|
||||
break;
|
||||
} else if (ref.backend != null) {
|
||||
try {
|
||||
ref.backend.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the scheme name for URI, default to getName().toLowerCase()
|
||||
*/
|
||||
public String getScheme() {
|
||||
return scheme;
|
||||
}
|
||||
|
||||
protected URI getRootUri() {
|
||||
try {
|
||||
return new URI(getScheme(), null, "/", null, null);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("Invalid scheme " + getScheme());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canStore(URI uri) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to match an URI against a root URI using a few rules:
|
||||
* <ul>
|
||||
* <li>scheme must match if they are given.
|
||||
* <li>authority must match if they are given.
|
||||
* <li>if uri is opaque (scheme:nonabsolute), the scheme specific part is resolve as a relative path.
|
||||
* <li>query and fragment is kept as is.
|
||||
* </ul>
|
||||
*
|
||||
* @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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
79
apps/jrobin/java/src/org/rrd4j/core/RrdByteArrayBackend.java
Normal file
79
apps/jrobin/java/src/org/rrd4j/core/RrdByteArrayBackend.java
Normal file
@ -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;
|
||||
|
||||
/**
|
||||
* <p>Constructor for RrdByteArrayBackend.</p>
|
||||
*
|
||||
* @param path a {@link java.lang.String} object.
|
||||
*/
|
||||
protected RrdByteArrayBackend(String path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
protected void setBuffer(byte[] buffer) {
|
||||
this.buffer = buffer;
|
||||
setByteBuffer(ByteBuffer.wrap(buffer));
|
||||
}
|
||||
|
||||
protected byte[] getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>read.</p>
|
||||
*
|
||||
* @param offset a long.
|
||||
* @param bytes an array of byte.
|
||||
* @throws java.io.IOException if any.
|
||||
* @throws java.lang.IllegalArgumentException if offset is bigger that the possible length.
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void read(long offset, byte[] bytes) throws IOException {
|
||||
if (offset < 0 || offset > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Illegal offset: " + offset);
|
||||
}
|
||||
|
||||
if (offset + bytes.length <= buffer.length) {
|
||||
System.arraycopy(buffer, (int) offset, bytes, 0, bytes.length);
|
||||
}
|
||||
else {
|
||||
throw new RrdBackendException("Not enough bytes available in RRD buffer; RRD " + getPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return Number of RRD bytes held in memory.
|
||||
*/
|
||||
public long getLength() {
|
||||
return buffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>It will reserves a memory section as a RRD storage.</p>
|
||||
*
|
||||
* @throws java.lang.IllegalArgumentException if length is bigger that the possible length.
|
||||
*/
|
||||
protected void setLength(long length) throws IOException {
|
||||
if (length < 0 || length > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Illegal length: " + length);
|
||||
}
|
||||
|
||||
buffer = new byte[(int) length];
|
||||
setByteBuffer(ByteBuffer.wrap(buffer));
|
||||
}
|
||||
|
||||
}
|
1489
apps/jrobin/java/src/org/rrd4j/core/RrdDb.java
Normal file
1489
apps/jrobin/java/src/org/rrd4j/core/RrdDb.java
Normal file
File diff suppressed because it is too large
Load Diff
563
apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java
Normal file
563
apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java
Normal file
@ -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;
|
||||
|
||||
/**
|
||||
* <p>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).
|
||||
* </p>
|
||||
* <p>It should not be called directly. Use {@link RrdDb.Builder#usePool()} instead.</p>
|
||||
*/
|
||||
public class RrdDbPool {
|
||||
private static class RrdDbPoolSingletonHolder {
|
||||
static final RrdDbPool instance = new RrdDbPool();
|
||||
|
||||
private RrdDbPoolSingletonHolder() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial capacity of the pool i.e. maximum number of simultaneously open RRD files. The pool will
|
||||
* never open too many RRD files at the same time.
|
||||
*/
|
||||
public static final int INITIAL_CAPACITY = 200;
|
||||
|
||||
/*
|
||||
* The RrdEntry stored in the pool can be of tree kind:
|
||||
* - null, the URI is available, just for it and play
|
||||
* - placeholder is true, it's not the real RrdDb entry, just a place holder
|
||||
* meaning that some other thread is using it.
|
||||
* - placehold is false, this is the real entry pointing to a RrdDb. It's
|
||||
* only used by the current thread.
|
||||
*
|
||||
*/
|
||||
private static class RrdEntry {
|
||||
RrdDb rrdDb = null;
|
||||
int count = 0;
|
||||
final CountDownLatch waitempty;
|
||||
final CountDownLatch inuse;
|
||||
final boolean placeholder;
|
||||
final URI uri;
|
||||
RrdEntry(boolean placeholder, URI canonicalPath) {
|
||||
this.placeholder = placeholder;
|
||||
this.uri = canonicalPath;
|
||||
if (placeholder) {
|
||||
inuse = new CountDownLatch(1);
|
||||
waitempty = null;
|
||||
} else {
|
||||
inuse = null;
|
||||
waitempty = new CountDownLatch(1);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.placeholder) {
|
||||
return "RrdEntry [inuse=" + inuse.getCount()+ ", uri=" + uri + "]";
|
||||
} else {
|
||||
return "RrdEntry [rrdDb=" + rrdDb + ", count=" + count + ", uri=" + uri + "]";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single instance of the class on the first call,
|
||||
* or returns already existing one. Uses Initialization On Demand Holder idiom.
|
||||
*
|
||||
* @return Single instance of this class
|
||||
* @throws java.lang.RuntimeException Thrown if the default RRD backend is not derived from the {@link org.rrd4j.core.RrdFileBackendFactory}
|
||||
*/
|
||||
public static RrdDbPool getInstance() {
|
||||
return RrdDbPoolSingletonHolder.instance;
|
||||
}
|
||||
|
||||
private final AtomicInteger usage = new AtomicInteger(0);
|
||||
private final ReentrantLock countLock = new ReentrantLock();
|
||||
private final Condition full = countLock.newCondition();
|
||||
private int maxCapacity = INITIAL_CAPACITY;
|
||||
|
||||
private final ConcurrentMap<URI, RrdEntry> pool = new ConcurrentHashMap<>(INITIAL_CAPACITY);
|
||||
|
||||
private final RrdBackendFactory defaultFactory;
|
||||
|
||||
/**
|
||||
* Constructor for RrdDbPool.
|
||||
* @since 3.5
|
||||
*/
|
||||
public RrdDbPool() {
|
||||
defaultFactory = RrdBackendFactory.getDefaultFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of open RRD files.
|
||||
*
|
||||
* @return Number of currently open RRD files held in the pool.
|
||||
*/
|
||||
public int getOpenFileCount() {
|
||||
return usage.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of open file URI.
|
||||
*
|
||||
* @return Array with {@link URI} to open RRD files held in the pool.
|
||||
*/
|
||||
public URI[] getOpenUri() {
|
||||
//Direct toarray from keySet can fail
|
||||
Set<URI> files = new HashSet<>();
|
||||
files.addAll(pool.keySet());
|
||||
return files.toArray(new URI[files.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of open file path.
|
||||
*
|
||||
* @return Array with canonical path to open RRD files held in the pool.
|
||||
*/
|
||||
public String[] getOpenFiles() {
|
||||
//Direct toarray from keySet can fail
|
||||
Set<String> files = new HashSet<>();
|
||||
for (RrdEntry i: pool.values()) {
|
||||
files.add(i.rrdDb.getPath());
|
||||
}
|
||||
return files.toArray(new String[files.size()]);
|
||||
}
|
||||
|
||||
private RrdEntry getEntry(URI uri, boolean cancreate) throws InterruptedException {
|
||||
RrdEntry ref = null;
|
||||
try {
|
||||
do {
|
||||
ref = pool.get(uri);
|
||||
if (ref == null) {
|
||||
//Slot empty
|
||||
//If still absent put a place holder, and create the entry to return
|
||||
try {
|
||||
countLock.lockInterruptibly();
|
||||
while (ref == null && usage.get() >= maxCapacity && cancreate) {
|
||||
full.await();
|
||||
ref = pool.get(uri);
|
||||
}
|
||||
if (ref == null && cancreate) {
|
||||
ref = pool.putIfAbsent(uri, new RrdEntry(true, uri));
|
||||
if (ref == null) {
|
||||
ref = new RrdEntry(false, uri);
|
||||
usage.incrementAndGet();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
countLock.unlock();
|
||||
}
|
||||
} else if (! ref.placeholder) {
|
||||
// Real entry, try to put a place holder if some one didn't get it meanwhile
|
||||
if ( ! pool.replace(uri, ref, new RrdEntry(true, uri))) {
|
||||
//Dummy ref, a new iteration is needed
|
||||
ref = new RrdEntry(true, uri);
|
||||
}
|
||||
} else {
|
||||
// a place holder, wait for the using task to finish
|
||||
ref.inuse.await();
|
||||
}
|
||||
} while (ref != null && ref.placeholder);
|
||||
return ref;
|
||||
} catch (InterruptedException | RuntimeException e) {
|
||||
// Oups we were interrupted, put everything back and go away
|
||||
passNext(ACTION.SWAP, ref);
|
||||
Thread.currentThread().interrupt();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private enum ACTION {
|
||||
SWAP, DROP;
|
||||
}
|
||||
|
||||
private void passNext(ACTION a, RrdEntry e) {
|
||||
if (e == null) {
|
||||
return;
|
||||
}
|
||||
RrdEntry o = null;
|
||||
switch (a) {
|
||||
case SWAP:
|
||||
o = pool.put(e.uri, e);
|
||||
break;
|
||||
case DROP:
|
||||
o = pool.remove(e.uri);
|
||||
if(usage.decrementAndGet() < maxCapacity) {
|
||||
try {
|
||||
countLock.lockInterruptibly();
|
||||
full.signalAll();
|
||||
countLock.unlock();
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
//task finished, waiting on a place holder can go on
|
||||
if(o != null) {
|
||||
o.inuse.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases RrdDb reference previously obtained from the pool. When a reference is released, its usage
|
||||
* count is decremented by one. If usage count drops to zero, the underlying RRD file will be closed.
|
||||
*
|
||||
* @param rrdDb RrdDb reference to be returned to the pool
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
* @deprecated a pool remember if it was open directly or from the pool, no need to manage it manually any more
|
||||
*/
|
||||
@Deprecated
|
||||
public void release(RrdDb rrdDb) throws IOException {
|
||||
// null pointer should not kill the thread, just ignore it
|
||||
if (rrdDb == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
URI dburi = rrdDb.getUri();
|
||||
RrdEntry ref = null;
|
||||
try {
|
||||
ref = getEntry(dburi, false);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("release interrupted for " + rrdDb, e);
|
||||
}
|
||||
if (ref == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ref.count <= 0) {
|
||||
passNext(ACTION.DROP, ref);
|
||||
throw new IllegalStateException("Could not release [" + rrdDb.getPath() + "], the file was never requested");
|
||||
}
|
||||
if (--ref.count == 0) {
|
||||
if(ref.rrdDb == null) {
|
||||
passNext(ACTION.DROP, ref);
|
||||
throw new IllegalStateException("Could not release [" + rrdDb.getPath() + "], pool corruption");
|
||||
}
|
||||
try {
|
||||
ref.rrdDb.internalClose();
|
||||
} finally {
|
||||
passNext(ACTION.DROP, ref);
|
||||
//If someone is waiting for an empty entry, signal it
|
||||
ref.waitempty.countDown();
|
||||
}
|
||||
} else {
|
||||
passNext(ACTION.SWAP, ref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Requests a RrdDb reference for the given RRD file path.</p>
|
||||
* <ul>
|
||||
* <li>If the file is already open, previously returned RrdDb reference will be returned. Its usage count
|
||||
* will be incremented by one.
|
||||
* <li>If the file is not already open and the number of already open RRD files is less than
|
||||
* {@link #INITIAL_CAPACITY}, the file will be open and a new RrdDb reference will be returned.
|
||||
* If the file is not already open and the number of already open RRD files is equal to
|
||||
* {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed.
|
||||
* </ul>
|
||||
* <p>The path is transformed internally to URI using the default factory, that is the reference that will
|
||||
* be used elsewhere.</p>
|
||||
*
|
||||
* @param path Path 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(String path) throws IOException {
|
||||
return requestRrdDb(defaultFactory.getUri(path), defaultFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Requests a RrdDb reference for the given RRD file path.</p>
|
||||
* <ul>
|
||||
* <li>If the file is already open, previously returned RrdDb reference will be returned. Its usage count
|
||||
* will be incremented by one.
|
||||
* <li>If the file is not already open and the number of already open RRD files is less than
|
||||
* {@link #INITIAL_CAPACITY}, the file will be open and a new RrdDb reference will be returned.
|
||||
* If the file is not already open and the number of already open RRD files is equal to
|
||||
* {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed.
|
||||
* </ul>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Requests a RrdDb reference for the given RRD file definition object.</p>
|
||||
* <ul>
|
||||
* <li>If the file with the path specified in the RrdDef object is already open,
|
||||
* the method blocks until the file is closed.
|
||||
* <li>If the file is not already open and the number of already open RRD files is less than
|
||||
* {@link #INITIAL_CAPACITY}, a new RRD file will be created and a its RrdDb reference will be returned.
|
||||
* If the file is not already open and the number of already open RRD files is equal to
|
||||
* {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed.
|
||||
* </ul>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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).</p>
|
||||
* <ul>
|
||||
* <li>If the file with the path specified is already open,
|
||||
* the method blocks until the file is closed.
|
||||
* <li>If the file is not already open and the number of already open RRD files is less than
|
||||
* {@link #INITIAL_CAPACITY}, a new RRD file will be created and a its RrdDb reference will be returned.
|
||||
* If the file is not already open and the number of already open RRD files is equal to
|
||||
* {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed.
|
||||
* </ul>
|
||||
* <p>The path is transformed internally to URI using the default factory, that is the reference that will
|
||||
* be used elsewhere.</p>
|
||||
*
|
||||
* @param path Path to RRD file which should be created
|
||||
* @param sourcePath Path to external data which is to be converted to Rrd4j's native RRD file format
|
||||
* @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(String path, String sourcePath)
|
||||
throws IOException {
|
||||
URI uri = RrdBackendFactory.getDefaultFactory().getUri(path);
|
||||
RrdBackendFactory backend = RrdBackendFactory.getDefaultFactory();
|
||||
return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, backend);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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).</p>
|
||||
* <ul>
|
||||
* <li>If the file with the path specified is already open,
|
||||
* the method blocks until the file is closed.
|
||||
* <li>If the file is not already open and the number of already open RRD files is less than
|
||||
* {@link #INITIAL_CAPACITY}, a new RRD file will be created and a its RrdDb reference will be returned.
|
||||
* If the file is not already open and the number of already open RRD files is equal to
|
||||
* {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed.
|
||||
* </ul>
|
||||
* <p>The path is transformed internally to URI using the default factory, that is the reference that will
|
||||
* be used elsewhere.</p>
|
||||
*
|
||||
* @param uri Path to RRD file which should be created
|
||||
* @param sourcePath Path to external data which is to be converted to Rrd4j's native RRD file format
|
||||
* @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(URI uri, String sourcePath)
|
||||
throws IOException {
|
||||
RrdBackendFactory backend = RrdBackendFactory.getDefaultFactory();
|
||||
return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, backend);
|
||||
}
|
||||
|
||||
private RrdDb requestRrdDb(RrdDb.Builder builder, URI uri, RrdBackendFactory backend)
|
||||
throws IOException {
|
||||
RrdEntry ref = null;
|
||||
uri = backend.getCanonicalUri(uri);
|
||||
try {
|
||||
ref = requestEmpty(uri);
|
||||
ref.rrdDb = builder.setPath(uri).setBackendFactory(backend).setPool(this).build();
|
||||
return ref.rrdDb;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("request interrupted for new rrd " + uri, e);
|
||||
} catch (RuntimeException e) {
|
||||
passNext(ACTION.DROP, ref);
|
||||
ref = null;
|
||||
throw e;
|
||||
} finally {
|
||||
if (ref != null) {
|
||||
passNext(ACTION.SWAP, ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RrdDb requestRrdDb(URI uri, RrdBackendFactory backend, DataImporter importer) throws IOException {
|
||||
return requestRrdDb(RrdDb.getBuilder().setImporter(importer), uri, backend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of simultaneously open RRD files.
|
||||
*
|
||||
* @param newCapacity Maximum number of simultaneously open RRD files.
|
||||
*/
|
||||
public void setCapacity(int newCapacity) {
|
||||
int oldUsage = usage.getAndSet(maxCapacity);
|
||||
try {
|
||||
if (oldUsage != 0) {
|
||||
throw new RuntimeException("Can only be done on a empty pool");
|
||||
}
|
||||
} finally {
|
||||
usage.set(oldUsage);
|
||||
}
|
||||
maxCapacity = newCapacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of simultaneously open RRD files.
|
||||
*
|
||||
* @return maximum number of simultaneously open RRD files
|
||||
*/
|
||||
public int getCapacity() {
|
||||
return maxCapacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of usage for a RRD.
|
||||
*
|
||||
* @param rrdDb RrdDb reference for which informations is needed.
|
||||
* @return the number of request for this rrd
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public int getOpenCount(RrdDb rrdDb) throws IOException {
|
||||
return getOpenCount(rrdDb.getUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of usage for a RRD.
|
||||
*
|
||||
* @param path RRD's path for which informations is needed.
|
||||
* @return the number of request for this file
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public int getOpenCount(String path) throws IOException {
|
||||
return getOpenCount(defaultFactory.getUri(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of usage for a RRD.
|
||||
*
|
||||
* @param uri RRD's uri for which informations is needed.
|
||||
* @return the number of request for this file
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public int getOpenCount(URI uri) throws IOException {
|
||||
RrdEntry ref = null;
|
||||
try {
|
||||
ref = getEntry(uri, false);
|
||||
if (ref == null)
|
||||
return 0;
|
||||
else {
|
||||
return ref.count;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("getOpenCount interrupted", e);
|
||||
} finally {
|
||||
if (ref != null) {
|
||||
passNext(ACTION.SWAP, ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
862
apps/jrobin/java/src/org/rrd4j/core/RrdDef.java
Normal file
862
apps/jrobin/java/src/org/rrd4j/core/RrdDef.java
Normal file
@ -0,0 +1,862 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.rrd4j.ConsolFun;
|
||||
import org.rrd4j.DsType;
|
||||
|
||||
/**
|
||||
* <p>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 <code>RrdDb</code> constructor
|
||||
* argument (see documentation for {@link org.rrd4j.core.RrdDb RrdDb} class). <code>RrdDef</code>
|
||||
* object <b>does not</b> actually create new RRD. It just holds all necessary
|
||||
* information which will be used during the actual creation process.</p>
|
||||
*
|
||||
* <p>RRD definition (RrdDef object) consists of the following elements:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li> path to RRD that will be created
|
||||
* <li> starting timestamp
|
||||
* <li> step
|
||||
* <li> version, 1 for linear disposition of archives, 2 for matrix disposition
|
||||
* <li> one or more datasource definitions
|
||||
* <li> one or more archive definitions
|
||||
* </ul>
|
||||
* <p>RrdDef provides API to set all these elements. For the complete explanation of all
|
||||
* RRD definition parameters, see RRDTool's
|
||||
* <a href="../../../../man/rrdcreate.html" target="man">rrdcreate man page</a>.</p>
|
||||
*
|
||||
* @author Sasa Markovic
|
||||
*/
|
||||
public class RrdDef {
|
||||
/**
|
||||
* Default RRD step to be used if not specified in constructor (300 seconds).
|
||||
*/
|
||||
public static final long DEFAULT_STEP = 300L;
|
||||
|
||||
/**
|
||||
* If not specified in constructor, starting timestamp will be set to the
|
||||
* current timestamp plus DEFAULT_INITIAL_SHIFT seconds (-10).
|
||||
*/
|
||||
public static final long DEFAULT_INITIAL_SHIFT = -10L;
|
||||
|
||||
/** Constant <code>DEFAULTVERSION=2</code> */
|
||||
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 List<DsDef> dsDefs = new ArrayList<>();
|
||||
private List<ArcDef> arcDefs = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* <p>Creates new RRD definition object with the given path.
|
||||
* When this object is passed to
|
||||
* <code>RrdDb</code> constructor, new RRD will be created using the
|
||||
* specified path.</p>
|
||||
* <p>The will be transformed internally to an URI using the default backend factory.</p>
|
||||
*
|
||||
* @param rrdpath Path to new RRD.
|
||||
*/
|
||||
public RrdDef(String rrdpath) {
|
||||
if (rrdpath == null || rrdpath.length() == 0) {
|
||||
throw new IllegalArgumentException("No path specified");
|
||||
}
|
||||
this.uri = RrdBackendFactory.buildGenericUri(rrdpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new RRD definition object with the given path.
|
||||
* When this object is passed to
|
||||
* <code>RrdDb</code> constructor, new RRD will be created using the
|
||||
* specified path.
|
||||
*
|
||||
* @param uri URI to the new RRD.
|
||||
*/
|
||||
public RrdDef(URI uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates new RRD definition object with the given path and step.</p>
|
||||
* <p>The will be transformed internally to an URI using the default backend factory.</p>
|
||||
*
|
||||
* @param path URI to new RRD.
|
||||
* @param step RRD step.
|
||||
*/
|
||||
public RrdDef(String path, long step) {
|
||||
this(path);
|
||||
if (step <= 0) {
|
||||
throw new IllegalArgumentException("Invalid RRD step specified: " + step);
|
||||
}
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new RRD definition object with the given path and step.
|
||||
*
|
||||
* @param uri URI to new RRD.
|
||||
* @param step RRD step.
|
||||
*/
|
||||
public RrdDef(URI uri, long step) {
|
||||
this(uri);
|
||||
if (step <= 0) {
|
||||
throw new IllegalArgumentException("Invalid RRD step specified: " + step);
|
||||
}
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates new RRD definition object with the given path, starting timestamp
|
||||
* and step.</p>
|
||||
* <p>The will be transformed internally to an URI using the default backend factory.</p>
|
||||
*
|
||||
* @param path Path to new RRD.
|
||||
* @param startTime RRD starting timestamp.
|
||||
* @param step RRD step.
|
||||
*/
|
||||
public RrdDef(String path, long startTime, long step) {
|
||||
this(path, step);
|
||||
if (startTime < 0) {
|
||||
throw new IllegalArgumentException("Invalid RRD start time specified: " + startTime);
|
||||
}
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new RRD definition object with the given path, starting timestamp
|
||||
* and step.
|
||||
*
|
||||
* @param uri URI to new RRD.
|
||||
* @param startTime RRD starting timestamp.
|
||||
* @param step RRD step.
|
||||
*/
|
||||
public RrdDef(URI uri, long startTime, long step) {
|
||||
this(uri, step);
|
||||
if (startTime < 0) {
|
||||
throw new IllegalArgumentException("Invalid RRD start time specified: " + startTime);
|
||||
}
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates new RRD definition object with the given path, starting timestamp,
|
||||
* step and version.</p>
|
||||
* <p>The will be transformed internally to an URI using the default backend factory.</p>
|
||||
*
|
||||
* @param path Path to new RRD.
|
||||
* @param startTime RRD starting timestamp.
|
||||
* @param step RRD step.
|
||||
* @param version RRD's file version.
|
||||
*/
|
||||
public RrdDef(String path, long startTime, long step, int version) {
|
||||
this(path, startTime, step);
|
||||
if(startTime < 0) {
|
||||
throw new IllegalArgumentException("Invalid RRD start time specified: " + startTime);
|
||||
}
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new RRD definition object with the given path, starting timestamp,
|
||||
* step and version.
|
||||
*
|
||||
* @param uri URI to new RRD.
|
||||
* @param startTime RRD starting timestamp.
|
||||
* @param step RRD step.
|
||||
* @param version RRD's file version.
|
||||
*/
|
||||
public RrdDef(URI uri, long startTime, long step, int version) {
|
||||
this(uri, startTime, step);
|
||||
if(startTime < 0) {
|
||||
throw new IllegalArgumentException("Invalid RRD start time specified: " + startTime);
|
||||
}
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path for the new RRD. It's extracted from the URI. If it's an opaque URI, it return the scheme specific part.
|
||||
*
|
||||
* @return path to the new RRD which should be created
|
||||
*/
|
||||
public String getPath() {
|
||||
if (uri.isOpaque()) {
|
||||
return uri.getSchemeSpecificPart();
|
||||
} else {
|
||||
return uri.getPath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns URI for the new RRD
|
||||
*
|
||||
* @return URI to the new RRD which should be created
|
||||
*/
|
||||
public URI getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns starting time stamp for the RRD that should be created.
|
||||
*
|
||||
* @return RRD starting time stamp
|
||||
*/
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time step for the RRD that will be created.
|
||||
*
|
||||
* @return RRD step
|
||||
*/
|
||||
public long getStep() {
|
||||
return step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RRD file version
|
||||
*
|
||||
* @return the version
|
||||
*/
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets path to RRD.</p>
|
||||
* <p>The will be transformed internally to an URI using the default backend factory.</p>
|
||||
*
|
||||
* @param path path to new RRD.
|
||||
*/
|
||||
public void setPath(String path) {
|
||||
this.uri = RrdBackendFactory.getDefaultFactory().getUri(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets URI to RRD.
|
||||
*
|
||||
* @param uri URI to new RRD.
|
||||
*/
|
||||
public void setPath(URI uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets RRD's starting timestamp.
|
||||
*
|
||||
* @param startTime Starting timestamp.
|
||||
*/
|
||||
public void setStartTime(long startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets RRD's starting timestamp.
|
||||
*
|
||||
* @param date starting date
|
||||
*/
|
||||
public void setStartTime(Date date) {
|
||||
this.startTime = Util.getTimestamp(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets RRD's starting timestamp.
|
||||
*
|
||||
* @param gc starting date
|
||||
*/
|
||||
public void setStartTime(Calendar gc) {
|
||||
this.startTime = Util.getTimestamp(gc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets RRD's time step.
|
||||
*
|
||||
* @param step RRD time step.
|
||||
*/
|
||||
public void setStep(long step) {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets RRD's file version.
|
||||
*
|
||||
* @param version the version to set
|
||||
*/
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds single datasource definition represented with object of class <code>DsDef</code>.
|
||||
*
|
||||
* @param dsDef Datasource definition.
|
||||
*/
|
||||
public void addDatasource(DsDef dsDef) {
|
||||
if (dsDefs.contains(dsDef)) {
|
||||
throw new IllegalArgumentException("Datasource already defined: " + dsDef.dump());
|
||||
}
|
||||
dsDefs.add(dsDef);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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
|
||||
* <a href="../../../../man/rrdcreate.html" target="man">rrdcreate man page</a>.</p>
|
||||
* <p><b>IMPORTANT NOTE:</b> 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.</p>
|
||||
*
|
||||
* @param dsName Data source name.
|
||||
* @param dsType Data source type. Valid types are "COUNTER",
|
||||
* "GAUGE", "DERIVE" and "ABSOLUTE" (these string constants are conveniently defined in
|
||||
* the {@link org.rrd4j.DsType} class).
|
||||
* @param heartbeat Data source heartbeat.
|
||||
* @param minValue Minimal acceptable value. Use <code>Double.NaN</code> if unknown.
|
||||
* @param maxValue Maximal acceptable value. Use <code>Double.NaN</code> 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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:</p>
|
||||
* <pre>
|
||||
* DS:name:type:heartbeat:minValue:maxValue
|
||||
* </pre>
|
||||
* <p>For example:</p>
|
||||
* <pre>
|
||||
* DS:input:COUNTER:600:0:U
|
||||
* </pre>
|
||||
* <p>For more information on datasource definition parameters see <code>rrdcreate</code>
|
||||
* man page.</p>
|
||||
*
|
||||
* @param rrdToolDsDef Datasource definition string with the syntax borrowed from RRDTool.
|
||||
* @throws java.lang.IllegalArgumentException Thrown if invalid string is supplied.
|
||||
*/
|
||||
public void addDatasource(String rrdToolDsDef) {
|
||||
IllegalArgumentException illArgException = new IllegalArgumentException(
|
||||
"Wrong rrdtool-like datasource definition: " + rrdToolDsDef);
|
||||
|
||||
if (rrdToolDsDef == null) throw illArgException;
|
||||
|
||||
StringTokenizer tokenizer = new StringTokenizer(rrdToolDsDef, ":");
|
||||
if (tokenizer.countTokens() != 6) {
|
||||
throw illArgException;
|
||||
}
|
||||
String[] tokens = new String[6];
|
||||
for (int curTok = 0; tokenizer.hasMoreTokens(); curTok++) {
|
||||
tokens[curTok] = tokenizer.nextToken();
|
||||
}
|
||||
if (!"DS".equalsIgnoreCase(tokens[0])) {
|
||||
throw illArgException;
|
||||
}
|
||||
String dsName = tokens[1];
|
||||
DsType dsType = DsType.valueOf(tokens[2]);
|
||||
long dsHeartbeat;
|
||||
try {
|
||||
dsHeartbeat = Long.parseLong(tokens[3]);
|
||||
}
|
||||
catch (NumberFormatException nfe) {
|
||||
throw illArgException;
|
||||
}
|
||||
double minValue = Double.NaN;
|
||||
if (!"U".equalsIgnoreCase(tokens[4])) {
|
||||
try {
|
||||
minValue = Double.parseDouble(tokens[4]);
|
||||
}
|
||||
catch (NumberFormatException nfe) {
|
||||
throw illArgException;
|
||||
}
|
||||
}
|
||||
double maxValue = Double.NaN;
|
||||
if (!"U".equalsIgnoreCase(tokens[5])) {
|
||||
try {
|
||||
maxValue = Double.parseDouble(tokens[5]);
|
||||
}
|
||||
catch (NumberFormatException nfe) {
|
||||
throw illArgException;
|
||||
}
|
||||
}
|
||||
addDatasource(new DsDef(dsName, dsType, dsHeartbeat, minValue, maxValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data source definitions to RRD definition in bulk.
|
||||
*
|
||||
* @param dsDefs Array of data source definition objects.
|
||||
*/
|
||||
public void addDatasource(DsDef... dsDefs) {
|
||||
for (DsDef dsDef : dsDefs) {
|
||||
addDatasource(dsDef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds single archive definition represented with object of class <code>ArcDef</code>.
|
||||
*
|
||||
* @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
|
||||
* <a href="../../../../man/rrdcreate.html" target="man">rrdcreate man page</a>.
|
||||
*
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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:</p>
|
||||
* <pre>
|
||||
* RRA:consolidationFunction:XFilesFactor:steps:rows
|
||||
* </pre>
|
||||
* <p>For example:</p>
|
||||
* <pre>
|
||||
* RRA:AVERAGE:0.5:10:1000
|
||||
* </pre>
|
||||
* <p>For more information on archive definition parameters see <code>rrdcreate</code>
|
||||
* man page.</p>
|
||||
*
|
||||
* @param rrdToolArcDef Archive definition string with the syntax borrowed from RRDTool.
|
||||
* @throws java.lang.IllegalArgumentException Thrown if invalid string is supplied.
|
||||
*/
|
||||
public void addArchive(String rrdToolArcDef) {
|
||||
IllegalArgumentException illArgException = new IllegalArgumentException(
|
||||
"Wrong rrdtool-like archive definition: " + rrdToolArcDef);
|
||||
StringTokenizer tokenizer = new StringTokenizer(rrdToolArcDef, ":");
|
||||
if (tokenizer.countTokens() != 5) {
|
||||
throw illArgException;
|
||||
}
|
||||
String[] tokens = new String[5];
|
||||
for (int curTok = 0; tokenizer.hasMoreTokens(); curTok++) {
|
||||
tokens[curTok] = tokenizer.nextToken();
|
||||
}
|
||||
if (!"RRA".equalsIgnoreCase(tokens[0])) {
|
||||
throw illArgException;
|
||||
}
|
||||
ConsolFun consolFun = ConsolFun.valueOf(tokens[1]);
|
||||
double xff;
|
||||
try {
|
||||
xff = Double.parseDouble(tokens[2]);
|
||||
}
|
||||
catch (NumberFormatException nfe) {
|
||||
throw illArgException;
|
||||
}
|
||||
int steps;
|
||||
try {
|
||||
steps = Integer.parseInt(tokens[3]);
|
||||
}
|
||||
catch (NumberFormatException nfe) {
|
||||
throw illArgException;
|
||||
}
|
||||
int rows;
|
||||
try {
|
||||
rows = Integer.parseInt(tokens[4]);
|
||||
}
|
||||
catch (NumberFormatException nfe) {
|
||||
throw illArgException;
|
||||
}
|
||||
addArchive(new ArcDef(consolFun, xff, steps, rows));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all data source definition objects specified so far.
|
||||
*
|
||||
* @return Array of data source definition objects
|
||||
*/
|
||||
public DsDef[] getDsDefs() {
|
||||
return dsDefs.toArray(new DsDef[dsDefs.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all archive definition objects specified so far.
|
||||
*
|
||||
* @return Array of archive definition objects.
|
||||
*/
|
||||
public ArcDef[] getArcDefs() {
|
||||
return arcDefs.toArray(new ArcDef[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of defined datasources.
|
||||
*
|
||||
* @return Number of defined datasources.
|
||||
*/
|
||||
public int getDsCount() {
|
||||
return dsDefs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of defined archives.
|
||||
*
|
||||
* @return Number of defined archives.
|
||||
*/
|
||||
public int getArcCount() {
|
||||
return arcDefs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string that represents all specified RRD creation parameters. Returned string
|
||||
* has the syntax of RRDTool's <code>create</code> command.
|
||||
*
|
||||
* @return Dumped content of <code>RrdDb</code> 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) {
|
||||
Iterator<DsDef> it = dsDefs.iterator();
|
||||
while (it.hasNext()) {
|
||||
DsDef dsDef = it.next();
|
||||
if (!dsDef.getDsName().equals(dsName)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeArchive(ConsolFun consolFun, int steps) {
|
||||
ArcDef arcDef = findArchive(consolFun, steps);
|
||||
if (!arcDefs.remove(arcDef)) {
|
||||
throw new IllegalArgumentException("Could not remove archive " + consolFun + "/" + steps);
|
||||
}
|
||||
}
|
||||
|
||||
ArcDef findArchive(ConsolFun consolFun, int steps) {
|
||||
for (ArcDef arcDef : arcDefs) {
|
||||
if (arcDef.getConsolFun() == consolFun && arcDef.getSteps() == steps) {
|
||||
return arcDef;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Could not find archive " + consolFun + "/" + steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Exports RrdDef object to output stream in XML format. Generated XML code can be parsed
|
||||
* with {@link org.rrd4j.core.RrdDefTemplate} class.</p>
|
||||
* <p>It use a format compatible with previous RRD4J's version, using
|
||||
* a path, instead of an URI.</p>
|
||||
*
|
||||
* @param out Output stream
|
||||
*/
|
||||
public void exportXmlTemplate(OutputStream out) {
|
||||
exportXmlTemplate(out, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports RrdDef object to output stream in XML format. Generated XML code can be parsed
|
||||
* with {@link org.rrd4j.core.RrdDefTemplate} class.
|
||||
* <p>If <code>compatible</code> is set to true, it returns an XML compatible with previous RRD4J's versions, using
|
||||
* a path, instead of an URI.</p>
|
||||
*
|
||||
* @param out Output stream
|
||||
* @param compatible Compatible with previous versions.
|
||||
*/
|
||||
public void exportXmlTemplate(OutputStream out, boolean compatible) {
|
||||
XmlWriter xml = new XmlWriter(out);
|
||||
xml.startTag("rrd_def");
|
||||
if (compatible) {
|
||||
xml.writeTag("path", getPath());
|
||||
} else {
|
||||
xml.writeTag("uri", getUri());
|
||||
}
|
||||
xml.writeTag("step", getStep());
|
||||
xml.writeTag("start", getStartTime());
|
||||
// datasources
|
||||
DsDef[] dsDefs = getDsDefs();
|
||||
for (DsDef dsDef : dsDefs) {
|
||||
xml.startTag("datasource");
|
||||
xml.writeTag("name", dsDef.getDsName());
|
||||
xml.writeTag("type", dsDef.getDsType());
|
||||
xml.writeTag("heartbeat", dsDef.getHeartbeat());
|
||||
xml.writeTag("min", dsDef.getMinValue(), "U");
|
||||
xml.writeTag("max", dsDef.getMaxValue(), "U");
|
||||
xml.closeTag(); // datasource
|
||||
}
|
||||
ArcDef[] arcDefs = getArcDefs();
|
||||
for (ArcDef arcDef : arcDefs) {
|
||||
xml.startTag("archive");
|
||||
xml.writeTag("cf", arcDef.getConsolFun());
|
||||
xml.writeTag("xff", arcDef.getXff());
|
||||
xml.writeTag("steps", arcDef.getSteps());
|
||||
xml.writeTag("rows", arcDef.getRows());
|
||||
xml.closeTag(); // archive
|
||||
}
|
||||
xml.closeTag(); // rrd_def
|
||||
xml.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Exports RrdDef object to string in XML format. Generated XML string can be parsed
|
||||
* with {@link org.rrd4j.core.RrdDefTemplate} class.</p>
|
||||
* <p>If <code>compatible</code> is set to true, it returns an XML compatible with previous RRD4J's versions, using
|
||||
* a path, instead of an URI.</p>
|
||||
*
|
||||
*
|
||||
* @param compatible Compatible with previous versions.
|
||||
* @return XML formatted string representing this RrdDef object
|
||||
*/
|
||||
public String exportXmlTemplate(boolean compatible) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
exportXmlTemplate(out, compatible);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Exports RrdDef object to string in XML format. Generated XML string can be parsed
|
||||
* with {@link org.rrd4j.core.RrdDefTemplate} class.</p>
|
||||
* <p>It use a format compatible with previous RRD4J's version, using
|
||||
* a path, instead of an URI.</p>
|
||||
*
|
||||
* @return XML formatted string representing this RrdDef object
|
||||
*/
|
||||
public String exportXmlTemplate() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
exportXmlTemplate(out);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Exports RrdDef object to a file in XML format. Generated XML code can be parsed
|
||||
* with {@link org.rrd4j.core.RrdDefTemplate} class.</p>
|
||||
* <p>It use a format compatible with previous RRD4J's version, using
|
||||
* a path, instead of an URI.</p>
|
||||
*
|
||||
* @param filePath Path to the file
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public void exportXmlTemplate(String filePath) throws IOException {
|
||||
exportXmlTemplate(filePath, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Exports RrdDef object to a file in XML format. Generated XML code can be parsed
|
||||
* with {@link org.rrd4j.core.RrdDefTemplate} class.</p>
|
||||
* <p>If <code>compatible</code> is set to true, it returns an XML compatible with previous RRD4J versions, using
|
||||
* a path, instead of an URI.</p>
|
||||
*
|
||||
* @param filePath Path to the file
|
||||
* @param compatible Compatible with previous versions.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public void exportXmlTemplate(String filePath, boolean compatible) throws IOException {
|
||||
FileOutputStream out = new FileOutputStream(filePath, false);
|
||||
exportXmlTemplate(out, compatible);
|
||||
out.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of storage bytes required to create RRD from this
|
||||
* RrdDef object.
|
||||
*
|
||||
* @return Estimated byte count of the underlying RRD storage.
|
||||
*/
|
||||
public long getEstimatedSize() {
|
||||
int dsCount = dsDefs.size();
|
||||
int arcCount = arcDefs.size();
|
||||
int rowsCount = 0;
|
||||
for (ArcDef arcDef : arcDefs) {
|
||||
rowsCount += arcDef.getRows();
|
||||
}
|
||||
String[] dsNames = new String[dsCount];
|
||||
for (int i = 0; i < dsNames.length ; i++) {
|
||||
dsNames[i] = dsDefs.get(i).getDsName();
|
||||
}
|
||||
return calculateSize(dsCount, arcCount, rowsCount, dsNames);
|
||||
}
|
||||
|
||||
static long calculateSize(int dsCount, int arcCount, int rowsCount, String[] dsNames) {
|
||||
int postStorePayload = 0;
|
||||
for(String n: dsNames) {
|
||||
if (n.length() > RrdPrimitive.STRING_LENGTH) {
|
||||
postStorePayload += n.length() * 2 + Short.SIZE / 8;
|
||||
}
|
||||
}
|
||||
return (24L + 48L * dsCount + 16L * arcCount +
|
||||
20L * dsCount * arcCount + 8L * dsCount * rowsCount) +
|
||||
(1L + 2L * dsCount + arcCount) * 2L * RrdPrimitive.STRING_LENGTH +
|
||||
postStorePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>Compares the current RrdDef with another. RrdDefs are considered equal if:</p>
|
||||
* <ul>
|
||||
* <li>RRD steps match
|
||||
* <li>all datasources have exactly the same definition in both RrdDef objects (datasource names,
|
||||
* types, heartbeat, min and max values must match)
|
||||
* <li>all archives have exactly the same definition in both RrdDef objects (archive consolidation
|
||||
* functions, X-file factors, step and row counts must match)
|
||||
* </ul>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>hasDatasources.</p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean hasDatasources() {
|
||||
return !dsDefs.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>hasArchives.</p>
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean hasArchives() {
|
||||
return !arcDefs.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all datasource definitions.
|
||||
*/
|
||||
public void removeDatasources() {
|
||||
dsDefs.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all RRA archive definitions.
|
||||
*/
|
||||
public void removeArchives() {
|
||||
arcDefs.clear();
|
||||
}
|
||||
|
||||
}
|
228
apps/jrobin/java/src/org/rrd4j/core/RrdDefTemplate.java
Normal file
228
apps/jrobin/java/src/org/rrd4j/core/RrdDefTemplate.java
Normal file
@ -0,0 +1,228 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.rrd4j.DsType;
|
||||
import org.rrd4j.ConsolFun;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>Here is an example of a properly formatted XML template with all available
|
||||
* options in it (unwanted options can be removed):</p>
|
||||
* <pre>
|
||||
* <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>
|
||||
* </pre>
|
||||
* <p>Notes on the template syntax:</p>
|
||||
* <ul>
|
||||
* <li>There is a strong relation between the XML template syntax and the syntax of
|
||||
* {@link org.rrd4j.core.RrdDef} class methods. If you are not sure what some XML tag means, check javadoc
|
||||
* for the corresponding class.
|
||||
* <li>starting timestamp can be supplied either as a long integer
|
||||
* (like: 1000243567) or as an ISO formatted string (like: 2004-02-21 12:25:45)
|
||||
* <li>whitespaces are not harmful
|
||||
* <li>floating point values: anything that cannot be parsed will be treated as Double.NaN
|
||||
* (like: U, unknown, 12r.23)
|
||||
* <li>comments are allowed.
|
||||
* </ul>
|
||||
* <p>Any template value (text between <code><some_tag></code> and
|
||||
* <code></some_tag></code>) can be replaced with
|
||||
* a variable of the following form: <code>${variable_name}</code>. Use
|
||||
* {@link org.rrd4j.core.XmlTemplate#setVariable(String, String) setVariable()}
|
||||
* methods from the base class to replace template variables with real values
|
||||
* at runtime.</p>
|
||||
*
|
||||
* <p>Typical usage scenario:</p>
|
||||
* <ul>
|
||||
* <li>Create your XML template and save it to a file (template.xml, for example)
|
||||
* <li>Replace hardcoded template values with variables if you want to change them during runtime.
|
||||
* For example, RRD path should not be hardcoded in the template - you probably want to create
|
||||
* many different RRD files from the same XML template. For example, your XML
|
||||
* template could start with:
|
||||
* <pre>
|
||||
* <rrd_def>
|
||||
* <path>${path}</path>
|
||||
* <step>300</step>
|
||||
* ...
|
||||
* </pre>
|
||||
* <li>In your Java code, create RrdDefTemplate object using your XML template file:
|
||||
* <pre>
|
||||
* RrdDefTemplate t = new RrdDefTemplate(new File(template.xml));
|
||||
* </pre>
|
||||
* <li>Then, specify real values for template variables:
|
||||
* <pre>
|
||||
* t.setVariable("path", "demo/test.rrd");
|
||||
* </pre>
|
||||
* <li>Once all template variables are set, just use the template object to create RrdDef
|
||||
* object. This object is actually used to create Rrd4j RRD files:
|
||||
* <pre>
|
||||
* RrdDef def = t.getRrdDef();
|
||||
* RrdDb rrd = new RrdDb(def);
|
||||
* rrd.close();
|
||||
* </pre>
|
||||
* </ul>
|
||||
* 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.<p>
|
||||
*
|
||||
* @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 <rrd_def>");
|
||||
}
|
||||
validateTagsOnlyOnce(root, new String[]{
|
||||
"path*", "uri*", "start", "step", "datasource*", "archive*"
|
||||
});
|
||||
// PATH must be supplied or exception is thrown
|
||||
RrdDef rrdDef;
|
||||
if (hasChildNode(root, "path")) {
|
||||
String path = getChildValue(root, "path");
|
||||
rrdDef = new RrdDef(path);
|
||||
} else if (hasChildNode(root, "uri")) {
|
||||
String uri = getChildValue(root, "uri");
|
||||
try {
|
||||
rrdDef = new RrdDef(new URI(uri));
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("Wrong URI: " + uri);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Neither path or URI defined");
|
||||
}
|
||||
try {
|
||||
String startStr = getChildValue(root, "start");
|
||||
Calendar startGc = Util.getCalendar(startStr);
|
||||
rrdDef.setStartTime(startGc);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// START is not mandatory
|
||||
}
|
||||
try {
|
||||
long step = getChildValueAsLong(root, "step");
|
||||
rrdDef.setStep(step);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// STEP is not mandatory
|
||||
}
|
||||
// datsources
|
||||
Node[] dsNodes = getChildNodes(root, "datasource");
|
||||
for (Node dsNode : dsNodes) {
|
||||
validateTagsOnlyOnce(dsNode, new String[]{
|
||||
"name", "type", "heartbeat", "min", "max"
|
||||
});
|
||||
String name = getChildValue(dsNode, "name");
|
||||
DsType type = DsType.valueOf(getChildValue(dsNode, "type"));
|
||||
long heartbeat = getChildValueAsLong(dsNode, "heartbeat");
|
||||
double min = getChildValueAsDouble(dsNode, "min");
|
||||
double max = getChildValueAsDouble(dsNode, "max");
|
||||
rrdDef.addDatasource(name, type, heartbeat, min, max);
|
||||
}
|
||||
// archives
|
||||
Node[] arcNodes = getChildNodes(root, "archive");
|
||||
for (Node arcNode : arcNodes) {
|
||||
validateTagsOnlyOnce(arcNode, new String[]{
|
||||
"cf", "xff", "steps", "rows"
|
||||
});
|
||||
ConsolFun consolFun = ConsolFun.valueOf(getChildValue(arcNode, "cf"));
|
||||
double xff = getChildValueAsDouble(arcNode, "xff");
|
||||
int steps = getChildValueAsInt(arcNode, "steps");
|
||||
int rows = getChildValueAsInt(arcNode, "rows");
|
||||
rrdDef.addArchive(consolFun, xff, steps, rows);
|
||||
}
|
||||
return rrdDef;
|
||||
}
|
||||
}
|
41
apps/jrobin/java/src/org/rrd4j/core/RrdDouble.java
Normal file
41
apps/jrobin/java/src/org/rrd4j/core/RrdDouble.java
Normal file
@ -0,0 +1,41 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class RrdDouble<U extends RrdUpdater<U>> extends RrdPrimitive<U> {
|
||||
private double cache;
|
||||
private boolean cached = false;
|
||||
|
||||
RrdDouble(RrdUpdater<U> updater, boolean isConstant) throws IOException {
|
||||
super(updater, RrdDouble.RRD_DOUBLE, isConstant);
|
||||
}
|
||||
|
||||
RrdDouble(RrdUpdater<U> updater) throws IOException {
|
||||
super(updater, RrdDouble.RRD_DOUBLE, false);
|
||||
}
|
||||
|
||||
void set(double value) throws IOException {
|
||||
if (!isCachingAllowed()) {
|
||||
writeDouble(value);
|
||||
}
|
||||
// caching allowed
|
||||
else if (!cached || !Util.equal(cache, value)) {
|
||||
// update cache
|
||||
writeDouble(cache = value);
|
||||
cached = true;
|
||||
}
|
||||
}
|
||||
|
||||
double get() throws IOException {
|
||||
if (!isCachingAllowed()) {
|
||||
return readDouble();
|
||||
}
|
||||
else {
|
||||
if (!cached) {
|
||||
cache = readDouble();
|
||||
cached = true;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
}
|
35
apps/jrobin/java/src/org/rrd4j/core/RrdDoubleArray.java
Normal file
35
apps/jrobin/java/src/org/rrd4j/core/RrdDoubleArray.java
Normal file
@ -0,0 +1,35 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class RrdDoubleArray<U extends RrdUpdater<U>> extends RrdPrimitive<U> {
|
||||
private int length;
|
||||
|
||||
RrdDoubleArray(RrdUpdater<U> updater, int length) throws IOException {
|
||||
super(updater, RrdPrimitive.RRD_DOUBLE, length, false);
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
void set(int index, double value) throws IOException {
|
||||
set(index, value, 1);
|
||||
}
|
||||
|
||||
void set(int index, double value, int count) throws IOException {
|
||||
// rollovers not allowed!
|
||||
assert index + count <= length : "Invalid robin index supplied: index=" + index +
|
||||
", count=" + count + ", length=" + length;
|
||||
writeDouble(index, value, count);
|
||||
}
|
||||
|
||||
double get(int index) throws IOException {
|
||||
assert index < length : "Invalid index supplied: " + index + ", length=" + length;
|
||||
return readDouble(index);
|
||||
}
|
||||
|
||||
double[] get(int index, int count) throws IOException {
|
||||
assert index + count <= length : "Invalid index/count supplied: " + index +
|
||||
"/" + count + " (length=" + length + ")";
|
||||
return readDouble(index, count);
|
||||
}
|
||||
|
||||
}
|
79
apps/jrobin/java/src/org/rrd4j/core/RrdDoubleMatrix.java
Normal file
79
apps/jrobin/java/src/org/rrd4j/core/RrdDoubleMatrix.java
Normal file
@ -0,0 +1,79 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class RrdDoubleMatrix<U extends RrdUpdater<U>> extends RrdPrimitive<U> {
|
||||
private static final String LENGTH = ", length=";
|
||||
private final int rows;
|
||||
private final int columns;
|
||||
|
||||
RrdDoubleMatrix(RrdUpdater<U> updater, int row, int column, boolean shouldInitialize) throws IOException {
|
||||
super(updater, RrdPrimitive.RRD_DOUBLE, row * column, false);
|
||||
this.rows = row;
|
||||
this.columns = column;
|
||||
if (shouldInitialize)
|
||||
writeDouble(0, Double.NaN, rows * columns);
|
||||
}
|
||||
|
||||
void set(int column, int index, double value) throws IOException {
|
||||
writeDouble(columns * index + column, value);
|
||||
}
|
||||
|
||||
void set(int column, int index, double value, int count) throws IOException {
|
||||
// rollovers not allowed!
|
||||
assert index + count <= rows : "Invalid robin index supplied: index=" + index +
|
||||
", count=" + count + LENGTH + rows;
|
||||
for (int i = columns * index + column, c = 0; c < count; i += columns, c++)
|
||||
writeDouble(i, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>set.</p>
|
||||
*
|
||||
* @param column a int.
|
||||
* @param index a int.
|
||||
* @param newValues an array of double.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public void set(int column, int index, double[] newValues) throws IOException {
|
||||
int count = newValues.length;
|
||||
// rollovers not allowed!
|
||||
assert index + count <= rows : "Invalid robin index supplied: index=" + index +
|
||||
", count=" + count + LENGTH + rows;
|
||||
for (int i = columns * index + column, c = 0; c < count; i += columns, c++)
|
||||
writeDouble(i, newValues[c]);
|
||||
}
|
||||
|
||||
double get(int column, int index) throws IOException {
|
||||
assert index < rows : "Invalid index supplied: " + index + LENGTH + rows;
|
||||
return readDouble(columns * index + column);
|
||||
}
|
||||
|
||||
double[] get(int column, int index, int count) throws IOException {
|
||||
assert index + count <= rows : "Invalid index/count supplied: " + index +
|
||||
"/" + count + " (length=" + rows + ")";
|
||||
double[] values = new double[count];
|
||||
for (int i = columns * index + column, c = 0; c < count; i += columns, c++) {
|
||||
values[c] = readDouble(i);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Getter for the field <code>columns</code>.</p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getColumns() {
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Getter for the field <code>rows</code>.</p>
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getRows() {
|
||||
return rows;
|
||||
}
|
||||
}
|
46
apps/jrobin/java/src/org/rrd4j/core/RrdEnum.java
Normal file
46
apps/jrobin/java/src/org/rrd4j/core/RrdEnum.java
Normal file
@ -0,0 +1,46 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class RrdEnum<U extends RrdUpdater<U>, E extends Enum<E>> extends RrdPrimitive<U> {
|
||||
|
||||
private E cache;
|
||||
private final Class<E> clazz;
|
||||
|
||||
RrdEnum(RrdUpdater<U> updater, boolean isConstant, Class<E> clazz) throws IOException {
|
||||
super(updater, RrdPrimitive.RRD_STRING, isConstant);
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
RrdEnum(RrdUpdater<U> updater, Class<E> clazz) throws IOException {
|
||||
this(updater, false, clazz);
|
||||
}
|
||||
|
||||
void set(E value) throws IOException {
|
||||
if (!isCachingAllowed()) {
|
||||
writeEnum(value);
|
||||
}
|
||||
// caching allowed
|
||||
else if (cache == null || cache != value) {
|
||||
// update cache
|
||||
writeEnum((cache = value));
|
||||
}
|
||||
}
|
||||
|
||||
E get() throws IOException {
|
||||
if (!isCachingAllowed()) {
|
||||
return readEnum(clazz);
|
||||
}
|
||||
else {
|
||||
if (cache == null) {
|
||||
cache = readEnum(clazz);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
String name() throws IOException {
|
||||
return get().name();
|
||||
}
|
||||
|
||||
}
|
22
apps/jrobin/java/src/org/rrd4j/core/RrdException.java
Normal file
22
apps/jrobin/java/src/org/rrd4j/core/RrdException.java
Normal file
@ -0,0 +1,22 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A general purpose RRD4J exception.
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
public class RrdException extends IOException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RrdException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RrdException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
22
apps/jrobin/java/src/org/rrd4j/core/RrdFileBackend.java
Normal file
22
apps/jrobin/java/src/org/rrd4j/core/RrdFileBackend.java
Normal file
@ -0,0 +1,22 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An abstract backend which is used to store RRD data to ordinary files on the disk.
|
||||
* <p>
|
||||
* 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;
|
||||
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
41
apps/jrobin/java/src/org/rrd4j/core/RrdInt.java
Normal file
41
apps/jrobin/java/src/org/rrd4j/core/RrdInt.java
Normal file
@ -0,0 +1,41 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class RrdInt<U extends RrdUpdater<U>> extends RrdPrimitive<U> {
|
||||
private int cache;
|
||||
private boolean cached = false;
|
||||
|
||||
RrdInt(RrdUpdater<U> updater, boolean isConstant) throws IOException {
|
||||
super(updater, RrdPrimitive.RRD_INT, isConstant);
|
||||
}
|
||||
|
||||
RrdInt(RrdUpdater<U> 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;
|
||||
}
|
||||
}
|
||||
}
|
41
apps/jrobin/java/src/org/rrd4j/core/RrdLong.java
Normal file
41
apps/jrobin/java/src/org/rrd4j/core/RrdLong.java
Normal file
@ -0,0 +1,41 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class RrdLong<U extends RrdUpdater<U>> extends RrdPrimitive<U> {
|
||||
private long cache;
|
||||
private boolean cached = false;
|
||||
|
||||
RrdLong(RrdUpdater<U> updater, boolean isConstant) throws IOException {
|
||||
super(updater, RrdPrimitive.RRD_LONG, isConstant);
|
||||
}
|
||||
|
||||
RrdLong(RrdUpdater<U> 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;
|
||||
}
|
||||
}
|
||||
}
|
47
apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackend.java
Normal file
47
apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackend.java
Normal file
@ -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;
|
||||
/**
|
||||
* <p>Constructor for RrdMemoryBackend.</p>
|
||||
*
|
||||
* @param path a {@link java.lang.String} object.
|
||||
*/
|
||||
protected RrdMemoryBackend(String path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setLength(long length) throws IOException {
|
||||
if (length < 0 || length > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Illegal length: " + length);
|
||||
}
|
||||
dbb = ByteBuffer.allocate((int) length);
|
||||
setByteBuffer(dbb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength() throws IOException {
|
||||
return dbb.capacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required by the base class definition, but it does not
|
||||
* releases any memory resources at all.
|
||||
*
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
@Override
|
||||
protected void close() throws IOException {
|
||||
// Don't release ressources, as backend are cached by the factory and reused
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Factory class which creates actual {@link org.rrd4j.core.RrdMemoryBackend} objects. Rrd4j's support
|
||||
* for in-memory RRDs is still experimental. You should know that all active RrdMemoryBackend
|
||||
* objects are held in memory, each backend object stores RRD data in one big byte array. This
|
||||
* implementation is therefore quite basic and memory hungry but runs very fast.
|
||||
* <p>
|
||||
* 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 <code>new RrdDb(path)</code> 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 Map<String, RrdMemoryBackend> backends = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Creates RrdMemoryBackend object.
|
||||
*/
|
||||
protected RrdBackend open(String id, boolean readOnly) throws IOException {
|
||||
RrdMemoryBackend backend;
|
||||
if (backends.containsKey(id)) {
|
||||
backend = backends.get(id);
|
||||
}
|
||||
else {
|
||||
backend = new RrdMemoryBackend(id);
|
||||
backends.put(id, backend);
|
||||
}
|
||||
return backend;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canStore(URI uri) {
|
||||
return uri.getScheme().equals(getScheme());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Method to determine if a memory storage with the given ID already exists.
|
||||
*/
|
||||
protected boolean exists(String id) {
|
||||
return backends.containsKey(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the storage with the given ID from the memory.
|
||||
*
|
||||
* @param id Storage ID
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean delete(String id) {
|
||||
if (backends.containsKey(id)) {
|
||||
backends.remove(id);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
200
apps/jrobin/java/src/org/rrd4j/core/RrdNioBackend.java
Normal file
200
apps/jrobin/java/src/org/rrd4j/core/RrdNioBackend.java
Normal file
@ -0,0 +1,200 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Backend which is used to store RRD data to ordinary disk files
|
||||
* using java.nio.* package. This is the default backend engine.
|
||||
*
|
||||
*/
|
||||
public class RrdNioBackend extends ByteBufferBackend implements RrdFileBackend {
|
||||
|
||||
// The java 8- methods
|
||||
private static final Method cleanerMethod;
|
||||
private static final Method cleanMethod;
|
||||
// The java 9+ methods
|
||||
private static final Method invokeCleaner;
|
||||
private static final Object unsafe;
|
||||
static {
|
||||
// Temporary variable, because destinations variables are final
|
||||
// And it interfere with exceptions
|
||||
Method cleanerMethodTemp;
|
||||
Method cleanMethodTemp;
|
||||
Method invokeCleanerTemp;
|
||||
Object unsafeTemp;
|
||||
try {
|
||||
// The java 8- way, using sun.nio.ch.DirectBuffer.cleaner().clean()
|
||||
Class<?> directBufferClass = RrdRandomAccessFileBackend.class.getClassLoader().loadClass("sun.nio.ch.DirectBuffer");
|
||||
Class<?> cleanerClass = RrdNioBackend.class.getClassLoader().loadClass("sun.misc.Cleaner");
|
||||
cleanerMethodTemp = directBufferClass.getMethod("cleaner");
|
||||
cleanerMethodTemp.setAccessible(true);
|
||||
cleanMethodTemp = cleanerClass.getMethod("clean");
|
||||
cleanMethodTemp.setAccessible(true);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
|
||||
cleanerMethodTemp = null;
|
||||
cleanMethodTemp = null;
|
||||
}
|
||||
try {
|
||||
// The java 9+ way, using unsafe.invokeCleaner(buffer)
|
||||
Field singleoneInstanceField = RrdRandomAccessFileBackend.class.getClassLoader().loadClass("sun.misc.Unsafe").getDeclaredField("theUnsafe");
|
||||
singleoneInstanceField.setAccessible(true);
|
||||
unsafeTemp = singleoneInstanceField.get(null);
|
||||
invokeCleanerTemp = unsafeTemp.getClass().getMethod("invokeCleaner", ByteBuffer.class);
|
||||
} catch (NoSuchFieldException | SecurityException
|
||||
| IllegalArgumentException | IllegalAccessException
|
||||
| NoSuchMethodException | ClassNotFoundException e) {
|
||||
invokeCleanerTemp = null;
|
||||
unsafeTemp = null;
|
||||
}
|
||||
cleanerMethod = cleanerMethodTemp;
|
||||
cleanMethod = cleanMethodTemp;
|
||||
invokeCleaner = invokeCleanerTemp;
|
||||
unsafe = unsafeTemp;
|
||||
}
|
||||
|
||||
private MappedByteBuffer byteBuffer;
|
||||
private final FileChannel file;
|
||||
private final boolean readOnly;
|
||||
|
||||
private ScheduledFuture<?> syncRunnableHandle = null;
|
||||
|
||||
/**
|
||||
* Creates RrdFileBackend object for the given file path, backed by java.nio.* classes.
|
||||
*
|
||||
* @param path Path to a file
|
||||
* @param readOnly True, if file should be open in a read-only mode. False otherwise
|
||||
* @param syncPeriod See {@link org.rrd4j.core.RrdNioBackendFactory#setSyncPeriod(int)} for explanation
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
* @param threadPool a {@link org.rrd4j.core.RrdSyncThreadPool} object, it can be null.
|
||||
*/
|
||||
protected RrdNioBackend(String path, boolean readOnly, RrdSyncThreadPool threadPool, int syncPeriod) throws IOException {
|
||||
super(path);
|
||||
Set<StandardOpenOption> options = new HashSet<>(3);
|
||||
options.add(StandardOpenOption.READ);
|
||||
options.add(StandardOpenOption.CREATE);
|
||||
if (! readOnly) {
|
||||
options.add(StandardOpenOption.WRITE);
|
||||
}
|
||||
|
||||
file = FileChannel.open(Paths.get(path), options);
|
||||
this.readOnly = readOnly;
|
||||
try {
|
||||
mapFile(file.size());
|
||||
} catch (IOException | RuntimeException ex) {
|
||||
file.close();
|
||||
super.close();
|
||||
throw ex;
|
||||
}
|
||||
try {
|
||||
if (!readOnly && threadPool != null) {
|
||||
Runnable syncRunnable = new Runnable() {
|
||||
public void run() {
|
||||
sync();
|
||||
}
|
||||
};
|
||||
syncRunnableHandle = threadPool.scheduleWithFixedDelay(syncRunnable, syncPeriod, syncPeriod, TimeUnit.SECONDS);
|
||||
}
|
||||
} catch (RuntimeException rte) {
|
||||
unmapFile();
|
||||
file.close();
|
||||
super.close();
|
||||
throw rte;
|
||||
}
|
||||
}
|
||||
|
||||
private void mapFile(long length) throws IOException {
|
||||
if (length > 0) {
|
||||
FileChannel.MapMode mapMode =
|
||||
readOnly ? FileChannel.MapMode.READ_ONLY : FileChannel.MapMode.READ_WRITE;
|
||||
byteBuffer = file.map(mapMode, 0, length);
|
||||
setByteBuffer(byteBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void unmapFile() {
|
||||
if (byteBuffer != null && byteBuffer.isDirect()) {
|
||||
try {
|
||||
if (cleanMethod != null) {
|
||||
Object cleaner = cleanerMethod.invoke(byteBuffer);
|
||||
cleanMethod.invoke(cleaner);
|
||||
} else {
|
||||
invokeCleaner.invoke(unsafe, byteBuffer);
|
||||
}
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
byteBuffer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Sets length of the underlying RRD file. This method is called only once, immediately
|
||||
* after a new RRD file gets created.
|
||||
* @throws java.lang.IllegalArgumentException if the length is bigger that the possible mapping position (2GiB).
|
||||
*/
|
||||
protected synchronized void setLength(long newLength) throws IOException {
|
||||
if (newLength < 0 || newLength > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Illegal offset: " + newLength);
|
||||
}
|
||||
|
||||
unmapFile();
|
||||
file.truncate(newLength);
|
||||
mapFile(newLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying RRD file.
|
||||
*
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
// cancel synchronization
|
||||
try {
|
||||
if (!readOnly && syncRunnableHandle != null) {
|
||||
syncRunnableHandle.cancel(false);
|
||||
syncRunnableHandle = null;
|
||||
sync();
|
||||
}
|
||||
unmapFile();
|
||||
} finally {
|
||||
file.close();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method forces all data cached in memory but not yet stored in the file,
|
||||
* to be stored in it.
|
||||
*/
|
||||
protected synchronized void sync() {
|
||||
if (byteBuffer != null) {
|
||||
byteBuffer.force();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized long getLength() throws IOException {
|
||||
return file.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() throws IOException {
|
||||
return Paths.get(getPath()).toAbsolutePath().normalize().toString();
|
||||
}
|
||||
|
||||
}
|
198
apps/jrobin/java/src/org/rrd4j/core/RrdNioBackendFactory.java
Normal file
198
apps/jrobin/java/src/org/rrd4j/core/RrdNioBackendFactory.java
Normal file
@ -0,0 +1,198 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* Factory class which creates actual {@link org.rrd4j.core.RrdNioBackend} objects. This is the default factory since
|
||||
* 1.4.0 version.
|
||||
* <h3>Managing the thread pool</h3>
|
||||
* <p>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.</p>
|
||||
* <p>if sync period is negative, no sync thread will be launched</p>
|
||||
*
|
||||
*/
|
||||
@RrdBackendAnnotation(name="NIO", shouldValidateHeader=true)
|
||||
public class RrdNioBackendFactory extends RrdFileBackendFactory {
|
||||
/**
|
||||
* Period in seconds between consecutive synchronizations when
|
||||
* sync-mode is set to SYNC_BACKGROUND. By default in-memory cache will be
|
||||
* transferred to the disc every 300 seconds (5 minutes). Default value can be
|
||||
* changed via {@link #setSyncPeriod(int)} method.
|
||||
*/
|
||||
public static final int DEFAULT_SYNC_PERIOD = 300; // seconds
|
||||
|
||||
private static int defaultSyncPeriod = DEFAULT_SYNC_PERIOD;
|
||||
|
||||
/**
|
||||
* The core pool size for the sync executor. Defaults to 6.
|
||||
*/
|
||||
public static final int DEFAULT_SYNC_CORE_POOL_SIZE = 6;
|
||||
|
||||
private static int defaultSyncPoolSize = DEFAULT_SYNC_CORE_POOL_SIZE;
|
||||
|
||||
/**
|
||||
* Returns time between two consecutive background synchronizations. If not changed via
|
||||
* {@link #setSyncPeriod(int)} method call, defaults to {@link #DEFAULT_SYNC_PERIOD}.
|
||||
* See {@link #setSyncPeriod(int)} for more information.
|
||||
*
|
||||
* @return Time in seconds between consecutive background synchronizations.
|
||||
*/
|
||||
public static int getSyncPeriod() {
|
||||
return defaultSyncPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets time between consecutive background synchronizations. If negative, it will disabling syncing for
|
||||
* all NIO backend factory.
|
||||
*
|
||||
* @param syncPeriod Time in seconds between consecutive background synchronizations.
|
||||
*/
|
||||
public static void setSyncPeriod(int syncPeriod) {
|
||||
RrdNioBackendFactory.defaultSyncPeriod = syncPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of synchronizing threads. If not changed via
|
||||
* {@link #setSyncPoolSize(int)} method call, defaults to {@link #DEFAULT_SYNC_CORE_POOL_SIZE}.
|
||||
* See {@link #setSyncPoolSize(int)} for more information.
|
||||
*
|
||||
* @return Number of synchronizing threads.
|
||||
*/
|
||||
public static int getSyncPoolSize() {
|
||||
return defaultSyncPoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of synchronizing threads. It must be set before the first use of this factory.
|
||||
* It will not have any effect afterward.
|
||||
*
|
||||
* @param syncPoolSize Number of synchronizing threads.
|
||||
*/
|
||||
public static void setSyncPoolSize(int syncPoolSize) {
|
||||
RrdNioBackendFactory.defaultSyncPoolSize = syncPoolSize;
|
||||
}
|
||||
|
||||
private final int syncPeriod;
|
||||
|
||||
/**
|
||||
* The thread pool to pass to newly-created RrdNioBackend instances.
|
||||
*/
|
||||
private RrdSyncThreadPool syncThreadPool;
|
||||
|
||||
/**
|
||||
* Creates a new RrdNioBackendFactory with default settings.
|
||||
*/
|
||||
public RrdNioBackendFactory() {
|
||||
this(RrdNioBackendFactory.defaultSyncPeriod, DefaultSyncThreadPool.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RrdNioBackendFactory.
|
||||
*
|
||||
* @param syncPeriod If syncPeriod is negative or 0, sync threads are disabled.
|
||||
*/
|
||||
public RrdNioBackendFactory(int syncPeriod) {
|
||||
this(syncPeriod, syncPeriod > 0 ? DefaultSyncThreadPool.INSTANCE : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RrdNioBackendFactory.
|
||||
*
|
||||
* @param syncPeriod
|
||||
* @param syncPoolSize The number of threads to use to sync the mapped file to disk, if inferior to 0, sync threads are disabled.
|
||||
*/
|
||||
public RrdNioBackendFactory(int syncPeriod, int syncPoolSize) {
|
||||
this(syncPeriod, syncPoolSize > 0 ? new RrdSyncThreadPool(syncPoolSize) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RrdNioBackendFactory.
|
||||
*
|
||||
* @param syncPeriod
|
||||
* @param syncThreadPool If null, disable background sync threads
|
||||
*/
|
||||
public RrdNioBackendFactory(int syncPeriod, ScheduledExecutorService syncThreadPool) {
|
||||
this(syncPeriod, syncThreadPool != null ? new RrdSyncThreadPool(syncThreadPool) :null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RrdNioBackendFactory.
|
||||
*
|
||||
* @param syncPeriod
|
||||
* @param syncThreadPool If null, disable background sync threads
|
||||
*/
|
||||
public RrdNioBackendFactory(int syncPeriod, RrdSyncThreadPool syncThreadPool) {
|
||||
if (syncThreadPool != null && syncPeriod < 0) {
|
||||
throw new IllegalArgumentException("Both thread pool defined and negative sync period");
|
||||
}
|
||||
this.syncPeriod = syncPeriod;
|
||||
this.syncThreadPool = syncThreadPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Setter for the field <code>syncThreadPool</code>.</p>
|
||||
*
|
||||
* @param syncThreadPool the RrdSyncThreadPool to use to sync the memory-mapped files.
|
||||
* @deprecated Create a custom instance instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setSyncThreadPool(RrdSyncThreadPool syncThreadPool) {
|
||||
this.syncThreadPool = syncThreadPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Setter for the field <code>syncThreadPool</code>.</p>
|
||||
*
|
||||
* @param syncThreadPool the ScheduledExecutorService that will back the RrdSyncThreadPool used to sync the memory-mapped files.
|
||||
* @deprecated Create a custom instance instead
|
||||
*/
|
||||
@Deprecated
|
||||
public void setSyncThreadPool(ScheduledExecutorService syncThreadPool) {
|
||||
this.syncThreadPool = new RrdSyncThreadPool(syncThreadPool);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Creates RrdNioBackend object for the given file path.
|
||||
*/
|
||||
protected RrdBackend open(String path, boolean readOnly) throws IOException {
|
||||
return new RrdNioBackend(path, readOnly, syncThreadPool, syncPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link RrdSyncThreadPool} or null if syncing is disabled
|
||||
*/
|
||||
public RrdSyncThreadPool getSyncThreadPool() {
|
||||
return syncThreadPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (syncThreadPool != null) {
|
||||
syncThreadPool.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a holder class as per the "initialisation on demand" Java idiom. The only purpose of this holder class is
|
||||
* to ensure that the thread pool is created lazily the first time that it is needed, and not before.
|
||||
* <p/>
|
||||
* In practice this thread pool will be used if clients rely on the factory returned by {@link
|
||||
* org.rrd4j.core.RrdBackendFactory#getDefaultFactory()}, but not if clients provide their own backend instance when
|
||||
* creating {@code RrdDb} instances or syncing was not disabled.
|
||||
*/
|
||||
private static class DefaultSyncThreadPool
|
||||
{
|
||||
/**
|
||||
* The default thread pool used to periodically sync the mapped file to disk with.
|
||||
*/
|
||||
static final RrdSyncThreadPool INSTANCE = new RrdSyncThreadPool(defaultSyncPoolSize);
|
||||
|
||||
private DefaultSyncThreadPool() {}
|
||||
}
|
||||
|
||||
}
|
114
apps/jrobin/java/src/org/rrd4j/core/RrdPrimitive.java
Normal file
114
apps/jrobin/java/src/org/rrd4j/core/RrdPrimitive.java
Normal file
@ -0,0 +1,114 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
abstract class RrdPrimitive<U extends RrdUpdater<U>> {
|
||||
static final int STRING_LENGTH = 20;
|
||||
static final int RRD_INT = 0, RRD_LONG = 1, RRD_DOUBLE = 2, RRD_STRING = 3;
|
||||
static final int[] RRD_PRIM_SIZES = {4, 8, 8, 2 * STRING_LENGTH};
|
||||
|
||||
private RrdBackend backend;
|
||||
private int byteCount;
|
||||
private final long pointer;
|
||||
private final boolean cachingAllowed;
|
||||
|
||||
RrdPrimitive(RrdUpdater<U> updater, int type, boolean isConstant) throws IOException {
|
||||
this(updater, type, 1, isConstant);
|
||||
}
|
||||
|
||||
RrdPrimitive(RrdUpdater<U> updater, int type, int count, boolean isConstant) throws IOException {
|
||||
this.backend = updater.getRrdBackend();
|
||||
this.byteCount = RRD_PRIM_SIZES[type] * count;
|
||||
this.pointer = updater.getRrdAllocator().allocate(byteCount);
|
||||
this.cachingAllowed = isConstant || backend.isCachingAllowed();
|
||||
}
|
||||
|
||||
final byte[] readBytes() throws IOException {
|
||||
byte[] b = new byte[byteCount];
|
||||
backend.read(pointer, b);
|
||||
return b;
|
||||
}
|
||||
|
||||
final void writeBytes(byte[] b) throws IOException {
|
||||
assert b.length == byteCount : "Invalid number of bytes supplied to RrdPrimitive.write method";
|
||||
backend.write(pointer, b);
|
||||
}
|
||||
|
||||
final int readInt() throws IOException {
|
||||
return backend.readInt(pointer);
|
||||
}
|
||||
|
||||
final void writeInt(int value) throws IOException {
|
||||
backend.writeInt(pointer, value);
|
||||
}
|
||||
|
||||
final long readLong() throws IOException {
|
||||
return backend.readLong(pointer);
|
||||
}
|
||||
|
||||
final void writeLong(long value) throws IOException {
|
||||
backend.writeLong(pointer, value);
|
||||
}
|
||||
|
||||
final double readDouble() throws IOException {
|
||||
return backend.readDouble(pointer);
|
||||
}
|
||||
|
||||
final double readDouble(int index) throws IOException {
|
||||
long offset = pointer + index * RRD_PRIM_SIZES[RRD_DOUBLE];
|
||||
return backend.readDouble(offset);
|
||||
}
|
||||
|
||||
final double[] readDouble(int index, int count) throws IOException {
|
||||
long offset = pointer + index * RRD_PRIM_SIZES[RRD_DOUBLE];
|
||||
return backend.readDouble(offset, count);
|
||||
}
|
||||
|
||||
final void writeDouble(double value) throws IOException {
|
||||
backend.writeDouble(pointer, value);
|
||||
}
|
||||
|
||||
final void writeDouble(int index, double value) throws IOException {
|
||||
long offset = pointer + index * RRD_PRIM_SIZES[RRD_DOUBLE];
|
||||
backend.writeDouble(offset, value);
|
||||
}
|
||||
|
||||
final void writeDouble(int index, double value, int count) throws IOException {
|
||||
long offset = pointer + index * RRD_PRIM_SIZES[RRD_DOUBLE];
|
||||
backend.writeDouble(offset, value, count);
|
||||
}
|
||||
|
||||
final void writeDouble(int index, double[] values) throws IOException {
|
||||
long offset = pointer + index * RRD_PRIM_SIZES[RRD_DOUBLE];
|
||||
backend.writeDouble(offset, values);
|
||||
}
|
||||
|
||||
final String readString() throws IOException {
|
||||
return backend.readString(pointer);
|
||||
}
|
||||
|
||||
final void writeString(String value) throws IOException {
|
||||
backend.writeString(pointer, value);
|
||||
}
|
||||
|
||||
protected final <E extends Enum<E>> E readEnum(Class<E> clazz) throws IOException {
|
||||
String value = backend.readString(pointer);
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
return Enum.valueOf(clazz, value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidRrdException("Invalid value for " + clazz.getSimpleName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final <E extends Enum<E>> void writeEnum(E value) throws IOException {
|
||||
writeString(value.name());
|
||||
}
|
||||
|
||||
final boolean isCachingAllowed() {
|
||||
return cachingAllowed;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Backend which is used to store RRD data to ordinary files on the disk. This was the
|
||||
* default factory before 1.4.0 version. This backend is based on the RandomAccessFile class (java.io.* package).
|
||||
*
|
||||
*/
|
||||
public class RrdRandomAccessFileBackend extends RrdBackend implements RrdFileBackend {
|
||||
/**
|
||||
* Random access file handle.
|
||||
*/
|
||||
protected final RandomAccessFile rafile;
|
||||
|
||||
/**
|
||||
* Creates RrdFileBackend object for the given file path, backed by RandomAccessFile object.
|
||||
*
|
||||
* @param path Path to a file
|
||||
* @param readOnly True, if file should be open in a read-only mode. False otherwise
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
protected RrdRandomAccessFileBackend(String path, boolean readOnly) throws IOException {
|
||||
super(path);
|
||||
this.rafile = new RandomAccessFile(path, readOnly ? "r" : "rw");
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying RRD file.
|
||||
*
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
protected void close() throws IOException {
|
||||
rafile.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes bytes to the underlying RRD file on the disk
|
||||
*
|
||||
* @param offset Starting file offset
|
||||
* @param b Bytes to be written.
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
protected void write(long offset, byte[] b) throws IOException {
|
||||
rafile.seek(offset);
|
||||
rafile.write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a number of bytes from the RRD file on the disk
|
||||
*
|
||||
* @param offset Starting file offset
|
||||
* @param b Buffer which receives bytes read from the file.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public void read(long offset, byte[] b) throws IOException {
|
||||
rafile.seek(offset);
|
||||
if (rafile.read(b) != b.length) {
|
||||
throw new RrdBackendException("Not enough bytes available in file " + getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() throws IOException {
|
||||
return Paths.get(getPath()).toAbsolutePath().normalize().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public long getLength() throws IOException {
|
||||
return rafile.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Sets length of the underlying RRD file. This method is called only once, immediately
|
||||
* after a new RRD file gets created.
|
||||
*/
|
||||
@Override
|
||||
protected void setLength(long length) throws IOException {
|
||||
rafile.setLength(length);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Factory class which creates actual {@link org.rrd4j.core.RrdRandomAccessFileBackend} objects. This was the default
|
||||
* backend factory in Rrd4j before 1.4.0 release.
|
||||
*
|
||||
*/
|
||||
@RrdBackendAnnotation(name="FILE", shouldValidateHeader=true)
|
||||
public class RrdRandomAccessFileBackendFactory extends RrdFileBackendFactory {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Creates RrdFileBackend object for the given file path.
|
||||
*/
|
||||
protected RrdBackend open(String path, boolean readOnly) throws IOException {
|
||||
return new RrdRandomAccessFileBackend(path, readOnly);
|
||||
}
|
||||
|
||||
}
|
130
apps/jrobin/java/src/org/rrd4j/core/RrdSafeFileBackend.java
Normal file
130
apps/jrobin/java/src/org/rrd4j/core/RrdSafeFileBackend.java
Normal file
@ -0,0 +1,130 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Backend which is used to store RRD data to ordinary files on the disk, using locking. This backend
|
||||
* is SAFE: it locks the underlying RRD file during update/fetch operations, and caches only static
|
||||
* parts of a RRD file in memory. Therefore, this backend is safe to be used when RRD files should
|
||||
* be shared between several JVMs at the same time. However, this backend is a little bit slow
|
||||
* since it does not use fast java.nio.* package (it's still based on the RandomAccessFile class).
|
||||
*
|
||||
*/
|
||||
public class RrdSafeFileBackend extends RrdRandomAccessFileBackend {
|
||||
private static final Counters counters = new Counters();
|
||||
|
||||
private FileLock lock;
|
||||
|
||||
/**
|
||||
* Creates RrdFileBackend object for the given file path, backed by RandomAccessFile object.
|
||||
*
|
||||
* @param path Path to a file
|
||||
* @param lockWaitTime lock waiting time in milliseconds.
|
||||
* @param lockRetryPeriod lock retry period in milliseconds.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public RrdSafeFileBackend(String path, long lockWaitTime, long lockRetryPeriod)
|
||||
throws IOException {
|
||||
super(path, false);
|
||||
try {
|
||||
lockFile(lockWaitTime, lockRetryPeriod);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
super.close();
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
private void lockFile(long lockWaitTime, long lockRetryPeriod) throws IOException {
|
||||
long entryTime = System.currentTimeMillis();
|
||||
FileChannel channel = rafile.getChannel();
|
||||
lock = channel.tryLock(0, Long.MAX_VALUE, false);
|
||||
if (lock != null) {
|
||||
counters.registerQuickLock();
|
||||
return;
|
||||
}
|
||||
do {
|
||||
try {
|
||||
Thread.sleep(lockRetryPeriod);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
// NOP
|
||||
}
|
||||
lock = channel.tryLock(0, Long.MAX_VALUE, false);
|
||||
if (lock != null) {
|
||||
counters.registerDelayedLock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (System.currentTimeMillis() - entryTime <= lockWaitTime);
|
||||
counters.registerError();
|
||||
throw new RrdBackendException("Could not obtain exclusive lock on file: " + getPath() +
|
||||
"] after " + lockWaitTime + " milliseconds");
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>close.</p>
|
||||
*
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
if (lock != null) {
|
||||
lock.release();
|
||||
lock = null;
|
||||
counters.registerUnlock();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getLockInfo.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public static String getLockInfo() {
|
||||
return counters.getInfo();
|
||||
}
|
||||
|
||||
static class Counters {
|
||||
final AtomicLong locks = new AtomicLong(0);
|
||||
final AtomicLong quickLocks = new AtomicLong(0);
|
||||
final AtomicLong unlocks = new AtomicLong(0);
|
||||
final AtomicLong locked = new AtomicLong(0);
|
||||
final AtomicLong errors = new AtomicLong(0);
|
||||
|
||||
void registerQuickLock() {
|
||||
locks.getAndIncrement();
|
||||
quickLocks.getAndIncrement();
|
||||
locked.getAndIncrement();
|
||||
}
|
||||
|
||||
void registerDelayedLock() {
|
||||
locks.getAndIncrement();
|
||||
locked.getAndIncrement();
|
||||
}
|
||||
|
||||
void registerUnlock() {
|
||||
unlocks.getAndIncrement();
|
||||
locked.getAndDecrement();
|
||||
}
|
||||
|
||||
void registerError() {
|
||||
errors.getAndIncrement();
|
||||
}
|
||||
|
||||
String getInfo() {
|
||||
return "LOCKS=" + locks + ", " + "UNLOCKS=" + unlocks + ", " +
|
||||
"DELAYED_LOCKS=" + (locks.get() - quickLocks.get()) + ", " + "LOCKED=" + locked + ", " +
|
||||
"ERRORS=" + errors;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Factory class which creates actual {@link org.rrd4j.core.RrdSafeFileBackend} objects.
|
||||
*
|
||||
*/
|
||||
@RrdBackendAnnotation(name="SAFE", shouldValidateHeader=true, cachingAllowed=false)
|
||||
public class RrdSafeFileBackendFactory extends RrdRandomAccessFileBackendFactory {
|
||||
|
||||
/**
|
||||
* Default time (in milliseconds) this backend will wait for a file lock.
|
||||
*/
|
||||
public static final long LOCK_WAIT_TIME = 3000L;
|
||||
private static long defaultLockWaitTime = LOCK_WAIT_TIME;
|
||||
|
||||
/**
|
||||
* Default time between two consecutive file locking attempts.
|
||||
*/
|
||||
public static final long LOCK_RETRY_PERIOD = 50L;
|
||||
private static long defaultLockRetryPeriod = LOCK_RETRY_PERIOD;
|
||||
|
||||
private final long lockWaitTime;
|
||||
private final long lockRetryPeriod;
|
||||
|
||||
/**
|
||||
* Generate a factory using the default system wide lock settings
|
||||
*/
|
||||
public RrdSafeFileBackendFactory() {
|
||||
lockWaitTime = defaultLockWaitTime;
|
||||
lockRetryPeriod = defaultLockRetryPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a factory with custom lock settings
|
||||
* @param lockWaitTime
|
||||
* @param lockRetryPeriod
|
||||
*/
|
||||
public RrdSafeFileBackendFactory(long lockWaitTime, long lockRetryPeriod) {
|
||||
this.lockWaitTime = lockWaitTime;
|
||||
this.lockRetryPeriod = lockRetryPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Creates RrdSafeFileBackend object for the given file path.
|
||||
*/
|
||||
@Override
|
||||
protected RrdBackend open(String path, boolean readOnly) throws IOException {
|
||||
return new RrdSafeFileBackend(path, lockWaitTime, lockRetryPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time this backend will wait for a file lock.
|
||||
*
|
||||
* @return Time (in milliseconds) this backend will wait for a file lock.
|
||||
*/
|
||||
public static long getLockWaitTime() {
|
||||
return defaultLockWaitTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets time this backend will wait for a file lock.
|
||||
*
|
||||
* @param lockWaitTime Maximum lock wait time (in milliseconds)
|
||||
*/
|
||||
public static void setLockWaitTime(long lockWaitTime) {
|
||||
RrdSafeFileBackendFactory.defaultLockWaitTime = lockWaitTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time between two consecutive file locking attempts.
|
||||
*
|
||||
* @return Time (im milliseconds) between two consecutive file locking attempts.
|
||||
*/
|
||||
public static long getLockRetryPeriod() {
|
||||
return defaultLockRetryPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets time between two consecutive file locking attempts.
|
||||
*
|
||||
* @param lockRetryPeriod time (in milliseconds) between two consecutive file locking attempts.
|
||||
*/
|
||||
public static void setLockRetryPeriod(long lockRetryPeriod) {
|
||||
RrdSafeFileBackendFactory.defaultLockRetryPeriod = lockRetryPeriod;
|
||||
}
|
||||
|
||||
}
|
38
apps/jrobin/java/src/org/rrd4j/core/RrdString.java
Normal file
38
apps/jrobin/java/src/org/rrd4j/core/RrdString.java
Normal file
@ -0,0 +1,38 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class RrdString<U extends RrdUpdater<U>> extends RrdPrimitive<U> {
|
||||
private String cache;
|
||||
|
||||
RrdString(RrdUpdater<U> updater, boolean isConstant) throws IOException {
|
||||
super(updater, RrdPrimitive.RRD_STRING, isConstant);
|
||||
}
|
||||
|
||||
RrdString(RrdUpdater<U> updater) throws IOException {
|
||||
this(updater, false);
|
||||
}
|
||||
|
||||
void set(String value) throws IOException {
|
||||
if (!isCachingAllowed()) {
|
||||
writeString(value);
|
||||
}
|
||||
// caching allowed
|
||||
else if (cache == null || !cache.equals(value)) {
|
||||
// update cache
|
||||
writeString(cache = value);
|
||||
}
|
||||
}
|
||||
|
||||
String get() throws IOException {
|
||||
if (!isCachingAllowed()) {
|
||||
return readString();
|
||||
}
|
||||
else {
|
||||
if (cache == null) {
|
||||
cache = readString();
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
}
|
170
apps/jrobin/java/src/org/rrd4j/core/RrdSyncThreadPool.java
Normal file
170
apps/jrobin/java/src/org/rrd4j/core/RrdSyncThreadPool.java
Normal file
@ -0,0 +1,170 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Thread pool used by {@link org.rrd4j.core.RrdNioBackend} instances to periodically sync the mapped file to disk. Note that instances
|
||||
* of RrdSyncThreadPool must be disposed of by calling {@link #shutdown()}.
|
||||
* <p>
|
||||
* 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<Thread> shutdownHook = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* The {@link java.util.concurrent.ScheduledExecutorService} used to periodically sync the mapped file to disk with.
|
||||
* Defaults to {@value org.rrd4j.core.RrdNioBackendFactory#DEFAULT_SYNC_CORE_POOL_SIZE} threads.
|
||||
*/
|
||||
private final ScheduledExecutorService syncExecutor;
|
||||
|
||||
/**
|
||||
* Creates a new RrdSyncThreadPool with a default pool size of {@value org.rrd4j.core.RrdNioBackendFactory#DEFAULT_SYNC_CORE_POOL_SIZE}.
|
||||
*/
|
||||
public RrdSyncThreadPool() {
|
||||
this(RrdNioBackendFactory.DEFAULT_SYNC_CORE_POOL_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RrdSyncThreadPool with a user-provided ScheduledExecutorService.
|
||||
*
|
||||
* @param syncExecutor the ScheduledExecutorService to use for sync'ing mapped files to disk
|
||||
*/
|
||||
public RrdSyncThreadPool(ScheduledExecutorService syncExecutor)
|
||||
{
|
||||
if (syncExecutor == null) {
|
||||
throw new NullPointerException("syncExecutor");
|
||||
}
|
||||
|
||||
this.syncExecutor = syncExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RrdSyncThreadPool with the given pool size.
|
||||
*
|
||||
* @param syncPoolSize the number of threads to use to sync the mapped file to disk
|
||||
*/
|
||||
public RrdSyncThreadPool(int syncPoolSize) {
|
||||
this(syncPoolSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RrdSyncThreadPool with the given pool size. Threads will be created by {@code threadFactory}.
|
||||
*
|
||||
* @param syncPoolSize the number of threads to use to sync the mapped file to disk
|
||||
* @param threadFactory the ThreadFactory to use for creating threads
|
||||
*/
|
||||
public RrdSyncThreadPool(int syncPoolSize, ThreadFactory threadFactory) {
|
||||
ThreadFactory poolThreadFactory = threadFactory;
|
||||
if (poolThreadFactory == null) {
|
||||
poolThreadFactory = new DaemonThreadFactory("RRD4J Sync-ThreadPool for " + this);
|
||||
}
|
||||
|
||||
this.syncExecutor = Executors.newScheduledThreadPool(syncPoolSize, poolThreadFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a shutdown hook that destroys the underlying thread pool when when the JVM is about to quit.
|
||||
*
|
||||
* @return this
|
||||
* @see #unregisterShutdownHook()
|
||||
*/
|
||||
public RrdSyncThreadPool registerShutdownHook() {
|
||||
Thread shutdownThread = new ShutdownThread();
|
||||
|
||||
// if this returns null, then this pool has not registered a hook yet
|
||||
boolean wasNull = shutdownHook.compareAndSet(null, shutdownThread);
|
||||
if (wasNull) {
|
||||
// Add a shutdown hook to stop the thread pool gracefully when the application exits
|
||||
Runtime.getRuntime().addShutdownHook(shutdownThread);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the shutdown hook installed by {@link #registerShutdownHook()}. Has no effect if the hook is not
|
||||
* currently installed.
|
||||
*
|
||||
* @see #unregisterShutdownHook()
|
||||
*/
|
||||
public void unregisterShutdownHook() {
|
||||
// if this returns a non-null value, then the hook needs to be uninstalled
|
||||
Thread shutdownThread = shutdownHook.getAndSet(null);
|
||||
if (shutdownThread != null) {
|
||||
Runtime.getRuntime().removeShutdownHook(shutdownThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down this thread pool in an orderly manner. Has no effect if it has already been called previously.
|
||||
*/
|
||||
public void shutdown() {
|
||||
unregisterShutdownHook();
|
||||
syncExecutor.shutdown();
|
||||
}
|
||||
|
||||
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
|
||||
return syncExecutor.scheduleWithFixedDelay(command, initialDelay, delay, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Daemon thread factory used by the monitor executors.
|
||||
* <p>
|
||||
* 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 "<pool-name> Pool [Thread-M]", where M is the sequence number of the thread created by this factory.
|
||||
*/
|
||||
static class DaemonThreadFactory implements ThreadFactory
|
||||
{
|
||||
final ThreadGroup group;
|
||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
final String poolName;
|
||||
|
||||
DaemonThreadFactory(String poolName) {
|
||||
SecurityManager s = System.getSecurityManager();
|
||||
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
|
||||
this.poolName = poolName;
|
||||
}
|
||||
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(group, r, poolName + " [Thread-" + threadNumber.getAndIncrement() + "]");
|
||||
t.setDaemon(true);
|
||||
t.setContextClassLoader(null);
|
||||
if (t.getPriority() != Thread.NORM_PRIORITY)
|
||||
t.setPriority(Thread.NORM_PRIORITY);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
private class ShutdownThread extends Thread
|
||||
{
|
||||
public ShutdownThread() {
|
||||
// include the RrdSyncThreadPool's toString in the thread name
|
||||
super("RRD4J Sync-ThreadPool-Shutdown for " + RrdSyncThreadPool.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Progress and failure logging arising from the following code cannot be logged, since the
|
||||
// behavior of logging is undefined in shutdown hooks.
|
||||
syncExecutor.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
106
apps/jrobin/java/src/org/rrd4j/core/RrdToolReader.java
Normal file
106
apps/jrobin/java/src/org/rrd4j/core/RrdToolReader.java
Normal file
@ -0,0 +1,106 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.rrd4j.ConsolFun;
|
||||
import org.rrd4j.DsType;
|
||||
import org.rrd4j.core.jrrd.RRDatabase;
|
||||
|
||||
class RrdToolReader extends DataImporter {
|
||||
private RRDatabase rrd;
|
||||
|
||||
RrdToolReader(String rrdPath) throws IOException {
|
||||
rrd = new RRDatabase(rrdPath);
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return rrd.getHeader().getVersion();
|
||||
}
|
||||
|
||||
public long getLastUpdateTime() {
|
||||
return Util.getTimestamp(rrd.getLastUpdate());
|
||||
}
|
||||
|
||||
public long getStep() {
|
||||
return rrd.getHeader().getPDPStep();
|
||||
}
|
||||
|
||||
public int getDsCount() {
|
||||
return rrd.getHeader().getDSCount();
|
||||
}
|
||||
|
||||
public int getArcCount() throws IOException {
|
||||
return rrd.getNumArchives();
|
||||
}
|
||||
|
||||
public String getDsName(int dsIndex) {
|
||||
return rrd.getDataSource(dsIndex).getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DsType getDsType(int dsIndex) throws IOException {
|
||||
return rrd.getDataSource(dsIndex).getType().getDsType();
|
||||
}
|
||||
|
||||
public long getHeartbeat(int dsIndex) {
|
||||
return rrd.getDataSource(dsIndex).getMinimumHeartbeat();
|
||||
}
|
||||
|
||||
public double getMinValue(int dsIndex) {
|
||||
return rrd.getDataSource(dsIndex).getMinimum();
|
||||
}
|
||||
|
||||
public double getMaxValue(int dsIndex) {
|
||||
return rrd.getDataSource(dsIndex).getMaximum();
|
||||
}
|
||||
|
||||
public double getLastValue(int dsIndex) {
|
||||
String valueStr = rrd.getDataSource(dsIndex).getPDPStatusBlock().getLastReading();
|
||||
return Util.parseDouble(valueStr);
|
||||
}
|
||||
|
||||
public double getAccumValue(int dsIndex) {
|
||||
return rrd.getDataSource(dsIndex).getPDPStatusBlock().getValue();
|
||||
}
|
||||
|
||||
public long getNanSeconds(int dsIndex) {
|
||||
return rrd.getDataSource(dsIndex).getPDPStatusBlock().getUnknownSeconds();
|
||||
}
|
||||
|
||||
public ConsolFun getConsolFun(int arcIndex) {
|
||||
return rrd.getArchive(arcIndex).getType().getConsolFun();
|
||||
}
|
||||
|
||||
public double getXff(int arcIndex) {
|
||||
return rrd.getArchive(arcIndex).getXff();
|
||||
}
|
||||
|
||||
public int getSteps(int arcIndex) {
|
||||
return rrd.getArchive(arcIndex).getPdpCount();
|
||||
}
|
||||
|
||||
public int getRows(int arcIndex) throws IOException {
|
||||
return rrd.getArchive(arcIndex).getRowCount();
|
||||
}
|
||||
|
||||
public double getStateAccumValue(int arcIndex, int dsIndex) throws IOException {
|
||||
return rrd.getArchive(arcIndex).getCDPStatusBlock(dsIndex).getValue();
|
||||
}
|
||||
|
||||
public int getStateNanSteps(int arcIndex, int dsIndex) throws IOException {
|
||||
return rrd.getArchive(arcIndex).getCDPStatusBlock(dsIndex).getUnknownDatapoints();
|
||||
}
|
||||
|
||||
public double[] getValues(int arcIndex, int dsIndex) throws IOException {
|
||||
return rrd.getArchive(arcIndex).getValues()[dsIndex];
|
||||
}
|
||||
|
||||
@Override
|
||||
void release() throws IOException {
|
||||
if (rrd != null) {
|
||||
rrd.close();
|
||||
rrd = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
594
apps/jrobin/java/src/org/rrd4j/core/RrdToolkit.java
Normal file
594
apps/jrobin/java/src/org/rrd4j/core/RrdToolkit.java
Normal file
@ -0,0 +1,594 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import org.rrd4j.ConsolFun;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class used to perform various complex operations on RRD files. Use an instance of the
|
||||
* RrdToolkit class to:
|
||||
*
|
||||
* <ul>
|
||||
* <li>add datasource to a RRD file.
|
||||
* <li>add archive to a RRD file.
|
||||
* <li>remove datasource from a RRD file.
|
||||
* <li>remove archive from a RRD file.
|
||||
* </ul>
|
||||
*
|
||||
* 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).
|
||||
* <p>
|
||||
* <b><u>IMPORTANT</u></b>: 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<DsDef> newDatasources)
|
||||
throws IOException {
|
||||
if (Util.sameFilePath(sourcePath, destPath)) {
|
||||
throw new IllegalArgumentException(SOURCE_AND_DESTINATION_PATHS_ARE_THE_SAME);
|
||||
}
|
||||
try (RrdDb rrdSource = RrdDb.getBuilder().setPath(sourcePath).build()) {
|
||||
RrdDef rrdDef = rrdSource.getRrdDef();
|
||||
rrdDef.setPath(destPath);
|
||||
for (DsDef newDatasource : newDatasources) {
|
||||
rrdDef.addDatasource(newDatasource);
|
||||
}
|
||||
try (RrdDb rrdDest = RrdDb.getBuilder().setRrdDef(rrdDef).build()) {
|
||||
rrdSource.copyStateTo(rrdDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds one more datasource to a RRD file.</p>
|
||||
* <p>WARNING: This method is potentially dangerous! It will modify your RRD file.
|
||||
* It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
|
||||
* should be set to <code>true</code>). The backup file will be created in the same
|
||||
* directory as the original one with <code>.bak</code> extension added to the
|
||||
* original name.</p>
|
||||
* <p>Before applying this method, be sure that the specified RRD file is not in use
|
||||
* (not open)</p>
|
||||
*
|
||||
* @param sourcePath path to a RRD file to add datasource to.
|
||||
* @param newDatasource Datasource definition to be added to the RRD file
|
||||
* @param saveBackup true, if backup of the original file should be created;
|
||||
* false, otherwise
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void addDatasource(String sourcePath, DsDef newDatasource, boolean saveBackup) throws IOException {
|
||||
addDatasources(sourcePath, Collections.singleton(newDatasource), saveBackup);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds datasources to a RRD file.</p>
|
||||
* <p>WARNING: This method is potentially dangerous! It will modify your RRD file.
|
||||
* It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
|
||||
* should be set to <code>true</code>). The backup file will be created in the same
|
||||
* directory as the original one with <code>.bak</code> extension added to the
|
||||
* original name.</p>
|
||||
* <p>Before applying this method, be sure that the specified RRD file is not in use
|
||||
* (not open)</p>
|
||||
*
|
||||
* @param sourcePath path to a RRD file to add datasource to.
|
||||
* @param newDatasources Datasource definitions to be added to the RRD file
|
||||
* @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 addDatasources(String sourcePath, Iterable<DsDef> newDatasources, boolean saveBackup) throws IOException {
|
||||
String destPath = Util.getTmpFilename();
|
||||
addDatasources(sourcePath, destPath, newDatasources);
|
||||
copyFile(destPath, sourcePath, saveBackup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RRD file with one datasource removed. RRD file is created based on the
|
||||
* existing one (the original RRD file is not modified at all). All remaining data from
|
||||
* the original RRD file is copied to the new one.
|
||||
*
|
||||
* @param sourcePath path to a RRD file to import data from (will not be modified)
|
||||
* @param destPath path to a new RRD file (will be created)
|
||||
* @param dsName Name of the Datasource to be removed from the new RRD file
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void removeDatasource(String sourcePath, String destPath, String dsName)
|
||||
throws IOException {
|
||||
if (Util.sameFilePath(sourcePath, destPath)) {
|
||||
throw new IllegalArgumentException(SOURCE_AND_DESTINATION_PATHS_ARE_THE_SAME);
|
||||
}
|
||||
|
||||
try (RrdDb rrdSource = RrdDb.getBuilder().setPath(sourcePath).build()) {
|
||||
RrdDef rrdDef = rrdSource.getRrdDef();
|
||||
rrdDef.setPath(destPath);
|
||||
rrdDef.removeDatasource(dsName);
|
||||
try (RrdDb rrdDest = RrdDb.getBuilder().setRrdDef(rrdDef).build()) {
|
||||
rrdSource.copyStateTo(rrdDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Removes single datasource from a RRD file.</p>
|
||||
* <p>WARNING: This method is potentially dangerous! It will modify your RRD file.
|
||||
* It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
|
||||
* should be set to <code>true</code>). The backup file will be created in the same
|
||||
* directory as the original one with <code>.bak</code> extension added to the
|
||||
* original name.</p>
|
||||
* <p>Before applying this method, be sure that the specified RRD file is not in use
|
||||
* (not open)</p>
|
||||
*
|
||||
* @param sourcePath path to a RRD file to remove datasource from.
|
||||
* @param dsName Name of the Datasource to be removed from the RRD file
|
||||
* @param saveBackup true, if backup of the original file should be created;
|
||||
* false, otherwise
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void removeDatasource(String sourcePath, String dsName, boolean saveBackup) throws IOException {
|
||||
String destPath = Util.getTmpFilename();
|
||||
removeDatasource(sourcePath, destPath, dsName);
|
||||
copyFile(destPath, sourcePath, saveBackup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames single datasource in the given RRD file.
|
||||
*
|
||||
* @param sourcePath Path to a RRD file
|
||||
* @param oldDsName Old datasource name
|
||||
* @param newDsName New datasource name
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void renameDatasource(String sourcePath, String oldDsName, String newDsName) throws IOException {
|
||||
try (RrdDb rrd = RrdDb.getBuilder().setPath(sourcePath).build()) {
|
||||
if (rrd.containsDs(oldDsName)) {
|
||||
Datasource datasource = rrd.getDatasource(oldDsName);
|
||||
datasource.setDsName(newDsName);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not find datasource [" + oldDsName + "] in file " + sourcePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates single or all datasource names in the specified RRD file
|
||||
* by appending '!' (if not already present). Datasources with names ending with '!'
|
||||
* will never store NaNs in RRA archives (zero value will be used instead). Might be useful
|
||||
* from time to time
|
||||
*
|
||||
* @param sourcePath Path to a RRD file
|
||||
* @param dsName Datasource name or null if you want to rename all datasources
|
||||
* @return Number of datasources successfully renamed
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static int forceZerosForNans(String sourcePath, String dsName) throws IOException {
|
||||
try (RrdDb rrd = RrdDb.getBuilder().setPath(sourcePath).build()) {
|
||||
Datasource[] datasources;
|
||||
if (dsName == null) {
|
||||
datasources = rrd.getDatasources();
|
||||
} else {
|
||||
if (rrd.containsDs(dsName)) {
|
||||
datasources = new Datasource[]{rrd.getDatasource(dsName)};
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not find datasource [" + dsName + "] in file " + sourcePath);
|
||||
}
|
||||
}
|
||||
int count = 0;
|
||||
for (Datasource datasource : datasources) {
|
||||
String currentDsName = datasource.getName();
|
||||
if (!currentDsName.endsWith(DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX)) {
|
||||
datasource.setDsName(currentDsName + DsDef.FORCE_ZEROS_FOR_NANS_SUFFIX);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RRD file with one more archive in it. RRD file is created based on the
|
||||
* existing one (the original RRD file is not modified at all). All data from
|
||||
* the original RRD file is copied to the new one.
|
||||
*
|
||||
* @param sourcePath path to a RRD file to import data from (will not be modified)
|
||||
* @param destPath path to a new RRD file (will be created)
|
||||
* @param newArchive Archive definition to be added to the new RRD file
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void addArchive(String sourcePath, String destPath, ArcDef newArchive) throws IOException {
|
||||
if (Util.sameFilePath(sourcePath, destPath)) {
|
||||
throw new IllegalArgumentException(SOURCE_AND_DESTINATION_PATHS_ARE_THE_SAME);
|
||||
}
|
||||
try (RrdDb rrdSource = RrdDb.getBuilder().setPath(sourcePath).build()) {
|
||||
RrdDef rrdDef = rrdSource.getRrdDef();
|
||||
rrdDef.setPath(destPath);
|
||||
rrdDef.addArchive(newArchive);
|
||||
try (RrdDb rrdDest = RrdDb.getBuilder().setRrdDef(rrdDef).build()) {
|
||||
rrdSource.copyStateTo(rrdDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds one more archive to a RRD file.</p>
|
||||
* <p>WARNING: This method is potentially dangerous! It will modify your RRD file.
|
||||
* It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
|
||||
* should be set to <code>true</code>). The backup file will be created in the same
|
||||
* directory as the original one with <code>.bak</code> extension added to the
|
||||
* original name.</p>
|
||||
* <p>Before applying this method, be sure that the specified RRD file is not in use
|
||||
* (not open)</p>
|
||||
*
|
||||
* @param sourcePath path to a RRD file to add datasource to.
|
||||
* @param newArchive Archive definition to be added to the RRD file
|
||||
* @param saveBackup true, if backup of the original file should be created;
|
||||
* false, otherwise
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void addArchive(String sourcePath, ArcDef newArchive, boolean saveBackup) throws IOException {
|
||||
String destPath = Util.getTmpFilename();
|
||||
addArchive(sourcePath, destPath, newArchive);
|
||||
copyFile(destPath, sourcePath, saveBackup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RRD file with one archive removed. RRD file is created based on the
|
||||
* existing one (the original RRD file is not modified at all). All relevant data from
|
||||
* the original RRD file is copied to the new one.
|
||||
*
|
||||
* @param sourcePath path to a RRD file to import data from (will not be modified)
|
||||
* @param destPath path to a new RRD file (will be created)
|
||||
* @param consolFun Consolidation function of Archive which should be removed
|
||||
* @param steps Number of steps for Archive which should be removed
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void removeArchive(String sourcePath, String destPath, ConsolFun consolFun, int steps) throws IOException {
|
||||
if (Util.sameFilePath(sourcePath, destPath)) {
|
||||
throw new IllegalArgumentException(SOURCE_AND_DESTINATION_PATHS_ARE_THE_SAME);
|
||||
}
|
||||
|
||||
try (RrdDb rrdSource = RrdDb.getBuilder().setPath(sourcePath).build()) {
|
||||
RrdDef rrdDef = rrdSource.getRrdDef();
|
||||
rrdDef.setPath(destPath);
|
||||
rrdDef.removeArchive(consolFun, steps);
|
||||
try (RrdDb rrdDest = RrdDb.getBuilder().setRrdDef(rrdDef).build()) {
|
||||
rrdSource.copyStateTo(rrdDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Removes one archive from a RRD file.</p>
|
||||
* <p>WARNING: This method is potentially dangerous! It will modify your RRD file.
|
||||
* It is highly recommended to preserve the original RRD file (<i>saveBackup</i>
|
||||
* should be set to <code>true</code>). The backup file will be created in the same
|
||||
* directory as the original one with <code>.bak</code> extension added to the
|
||||
* original name.</p>
|
||||
* <p>Before applying this method, be sure that the specified RRD file is not in use
|
||||
* (not open)</p>
|
||||
*
|
||||
* @param sourcePath path to a RRD file to add datasource to.
|
||||
* @param consolFun Consolidation function of Archive which should be removed
|
||||
* @param steps Number of steps for Archive which should be removed
|
||||
* @param saveBackup true, if backup of the original file should be created;
|
||||
* false, otherwise
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void removeArchive(String sourcePath, ConsolFun consolFun, int steps, boolean saveBackup) throws IOException {
|
||||
String destPath = Util.getTmpFilename();
|
||||
removeArchive(sourcePath, destPath, consolFun, steps);
|
||||
copyFile(destPath, sourcePath, saveBackup);
|
||||
}
|
||||
|
||||
private static void copyFile(String sourcePath, String destPath, boolean saveBackup)
|
||||
throws IOException {
|
||||
File source = new File(sourcePath);
|
||||
File dest = new File(destPath);
|
||||
if (saveBackup) {
|
||||
String backupPath = getBackupPath(destPath);
|
||||
File backup = new File(backupPath);
|
||||
deleteFile(backup);
|
||||
if (!dest.renameTo(backup)) {
|
||||
throw new RrdException("Could not create backup file " + backupPath);
|
||||
}
|
||||
}
|
||||
deleteFile(dest);
|
||||
if (!source.renameTo(dest)) {
|
||||
//Rename failed so try to copy and erase
|
||||
try(FileChannel sourceStream = new FileInputStream(source).getChannel(); FileChannel destinationStream = new FileOutputStream(dest).getChannel()) {
|
||||
long count = 0;
|
||||
final long size = sourceStream.size();
|
||||
while(count < size) {
|
||||
count += destinationStream.transferFrom(sourceStream, count, size-count);
|
||||
}
|
||||
deleteFile(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getBackupPath(String destPath) {
|
||||
StringBuilder backupPath = new StringBuilder(destPath);
|
||||
do {
|
||||
backupPath.append( ".bak");
|
||||
}
|
||||
while (Util.fileExists(backupPath.toString()));
|
||||
return backupPath.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets datasource heartbeat 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 newHeartbeat New datasource heartbeat
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void setDsHeartbeat(String sourcePath, String datasourceName, long newHeartbeat) throws IOException {
|
||||
try (RrdDb rrd = RrdDb.getBuilder().setPath(sourcePath).build()) {
|
||||
Datasource ds = rrd.getDatasource(datasourceName);
|
||||
ds.setHeartbeat(newHeartbeat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets datasource heartbeat to a new value.
|
||||
*
|
||||
* @param sourcePath Path to existing RRD file (will be updated)
|
||||
* @param dsIndex Index of the datasource in the specified RRD file
|
||||
* @param newHeartbeat New datasource heartbeat
|
||||
* @throws java.io.IOException Thrown in case of I/O error
|
||||
*/
|
||||
public static void setDsHeartbeat(String sourcePath, int dsIndex, long newHeartbeat) throws IOException {
|
||||
try (RrdDb rrd = RrdDb.getBuilder().setPath(sourcePath).build()) {
|
||||
Datasource ds = rrd.getDatasource(dsIndex);
|
||||
ds.setHeartbeat(newHeartbeat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets datasource min 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 newMinValue New min value for the datasource
|
||||
* @param filterArchivedValues set to <code>true</code> if archived values less than
|
||||
* <code>newMinValue</code> 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 <code>true</code> if archived values greater than
|
||||
* <code>newMaxValue</code> 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 <code>true</code> 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<String> fileList = new LinkedList<>();
|
||||
traverseDirectory(new File(directory), extension, resursive, fileList);
|
||||
String[] result = fileList.toArray(new String[fileList.size()]);
|
||||
Arrays.sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void traverseDirectory(File directory, String extension, boolean recursive, List<String> list)
|
||||
throws IOException {
|
||||
File[] files = directory.listFiles();
|
||||
for (File file : files) {
|
||||
if (file.isDirectory() && recursive) {
|
||||
// traverse subdirectories only if recursive flag is specified
|
||||
traverseDirectory(file, extension, recursive, list);
|
||||
} else if (file.isFile() && file.getName().endsWith(extension)) {
|
||||
list.add(file.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String createSplitPath(String dsName, String sourcePath) {
|
||||
File file = new File(sourcePath);
|
||||
String newName = dsName + "-" + file.getName();
|
||||
String path = file.getAbsolutePath();
|
||||
String parentDir = path.substring(0, 1 + path.lastIndexOf(Util.getFileSeparator()));
|
||||
return parentDir + newName;
|
||||
}
|
||||
|
||||
}
|
||||
|
27
apps/jrobin/java/src/org/rrd4j/core/RrdUpdater.java
Normal file
27
apps/jrobin/java/src/org/rrd4j/core/RrdUpdater.java
Normal file
@ -0,0 +1,27 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
interface RrdUpdater<T extends RrdUpdater<T>> {
|
||||
/**
|
||||
* <p>getRrdBackend.</p>
|
||||
*
|
||||
* @return a {@link org.rrd4j.core.RrdBackend} object.
|
||||
*/
|
||||
RrdBackend getRrdBackend();
|
||||
|
||||
/**
|
||||
* <p>copyStateTo.</p>
|
||||
*
|
||||
* @param updater a {@link org.rrd4j.core.RrdUpdater} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
void copyStateTo(T updater) throws IOException;
|
||||
|
||||
/**
|
||||
* <p>getRrdAllocator.</p>
|
||||
*
|
||||
* @return a {@link org.rrd4j.core.RrdAllocator} object.
|
||||
*/
|
||||
RrdAllocator getRrdAllocator();
|
||||
}
|
236
apps/jrobin/java/src/org/rrd4j/core/Sample.java
Normal file
236
apps/jrobin/java/src/org/rrd4j/core/Sample.java
Normal file
@ -0,0 +1,236 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* <p>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
|
||||
* <a href="../../../../man/rrdupdate.html" target="man">rrdupdate man page</a>.</p>
|
||||
* <p>To update a RRD with Rrd4j use the following procedure:</p>
|
||||
* <ol>
|
||||
* <li>Obtain empty Sample object by calling method {@link org.rrd4j.core.RrdDb#createSample(long)
|
||||
* createSample()} on respective {@link RrdDb RrdDb} object.
|
||||
* <li>Adjust Sample timestamp if necessary (see {@link #setTime(long) setTime()} method).
|
||||
* <li>Supply data source values (see {@link #setValue(String, double) setValue()}).
|
||||
* <li>Call Sample's {@link #update() update()} method.
|
||||
* </ol>
|
||||
* <p>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 <code>Double.NaN</code>.</p>
|
||||
*
|
||||
* @author Sasa Markovic
|
||||
*/
|
||||
public class Sample {
|
||||
private final RrdDb parentDb;
|
||||
private long time;
|
||||
private final String[] dsNames;
|
||||
private final double[] values;
|
||||
|
||||
Sample(RrdDb parentDb, long time) throws IOException {
|
||||
this.parentDb = parentDb;
|
||||
this.time = time;
|
||||
|
||||
this.dsNames = parentDb.getDsNames();
|
||||
values = new double[dsNames.length];
|
||||
clearValues();
|
||||
}
|
||||
|
||||
private void clearValues() {
|
||||
Arrays.fill(values, Double.NaN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets single data source value in the sample.
|
||||
*
|
||||
* @param dsName Data source name.
|
||||
* @param value Data source value.
|
||||
* @return This <code>Sample</code> 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 <code>Sample</code> 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 <code>Sample</code> 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 <code>Sample</code> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets sample timestamp and data source values in a fashion similar to RRDTool.
|
||||
* Argument string should be composed in the following way:
|
||||
* <code>timestamp:value1:value2:...:valueN</code>.</p>
|
||||
* <p>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'.</p>
|
||||
*
|
||||
* @param timeAndValues <p>String made by concatenating sample timestamp with corresponding
|
||||
* data source values delmited with colons. For example:</p>
|
||||
*
|
||||
* <pre>
|
||||
* 1005234132:12.2:35.6:U:24.5
|
||||
* NOW:12.2:35.6:U:24.5
|
||||
* </pre>
|
||||
* <p>'N' stands for the current timestamp (can be replaced with 'NOW')<p>
|
||||
* 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.</p>
|
||||
* @return This <code>Sample</code> 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:
|
||||
* <pre>
|
||||
* set(timeAndValues);
|
||||
* update();
|
||||
* </pre>
|
||||
*
|
||||
* @param timeAndValues String made by concatenating sample timestamp with corresponding
|
||||
* data source values delmited with colons. For example:<br>
|
||||
* <code>1005234132:12.2:35.6:U:24.5</code><br>
|
||||
* <code>NOW:12.2:35.6:U:24.5</code>
|
||||
* @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();
|
||||
}
|
||||
}
|
799
apps/jrobin/java/src/org/rrd4j/core/Util.java
Normal file
799
apps/jrobin/java/src/org/rrd4j/core/Util.java
Normal file
@ -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 <code>MAX_LONG=Long.MAX_VALUE</code> */
|
||||
public static final long MAX_LONG = Long.MAX_VALUE;
|
||||
/** Constant <code>MIN_LONG=-Long.MAX_VALUE</code> */
|
||||
public static final long MIN_LONG = -Long.MAX_VALUE;
|
||||
|
||||
/** Constant <code>MAX_DOUBLE=Double.MAX_VALUE</code> */
|
||||
public static final double MAX_DOUBLE = Double.MAX_VALUE;
|
||||
/** Constant <code>MIN_DOUBLE=-Double.MAX_VALUE</code> */
|
||||
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<NumberFormat> df = new ThreadLocal<NumberFormat>() {
|
||||
@Override
|
||||
protected NumberFormat initialValue() {
|
||||
DecimalFormat ldf = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ENGLISH);
|
||||
ldf.applyPattern(PATTERN);
|
||||
ldf.setPositivePrefix("+");
|
||||
return ldf;
|
||||
}
|
||||
};
|
||||
|
||||
private static final Pattern SPRINTF_PATTERN = Pattern.compile("([^%]|^)%([^a-zA-Z%]*)l(f|g|e)");
|
||||
|
||||
private Util() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of long primitives to an array of doubles.
|
||||
*
|
||||
* @param array input array of long values.
|
||||
* @return Same array but with all values as double.
|
||||
*/
|
||||
public static double[] toDoubleArray(final long[] array) {
|
||||
double[] values = new double[array.length];
|
||||
for (int i = 0; i < array.length; i++)
|
||||
values[i] = array[i];
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current timestamp in seconds (without milliseconds). Returned timestamp
|
||||
* is obtained with the following expression:
|
||||
* <p>
|
||||
* <code>(System.currentTimeMillis() + 500L) / 1000L</code>
|
||||
*
|
||||
* @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:
|
||||
* <p>
|
||||
* <code>timestamp - timestamp % step;</code>
|
||||
*
|
||||
* @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 <code>Math.max()</code> 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 <code>Math.min()</code> 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 <code>Double.isNaN(x)? y: Double.isNaN(y)? x: x + y;</code>
|
||||
*/
|
||||
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 <code>Date</code> 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 <code>Calendar</code> 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 <code>Calendar</code> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Parses at-style time specification and returns the corresponding timestamp. For example:</p>
|
||||
* <pre>
|
||||
* long t = Util.getTimestamp("now-1d");
|
||||
* </pre>
|
||||
*
|
||||
* @param atStyleTimeSpec at-style time specification. For the complete explanation of the syntax
|
||||
* allowed see RRDTool's <code>rrdfetch</code> man page.<p>
|
||||
* @return timestamp in seconds since epoch.
|
||||
*/
|
||||
public static long getTimestamp(String atStyleTimeSpec) {
|
||||
TimeSpec timeSpec = new TimeParser(atStyleTimeSpec).parse();
|
||||
return timeSpec.getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Parses two related at-style time specifications and returns corresponding timestamps. For example:</p>
|
||||
* <pre>
|
||||
* long[] t = Util.getTimestamps("end-1d","now");
|
||||
* </pre>
|
||||
*
|
||||
* @param atStyleTimeSpec1 Starting at-style time specification. For the complete explanation of the syntax
|
||||
* allowed see RRDTool's <code>rrdfetch</code> man page.<p>
|
||||
* @param atStyleTimeSpec2 Ending at-style time specification. For the complete explanation of the syntax
|
||||
* allowed see RRDTool's <code>rrdfetch</code> man page.<p>
|
||||
* @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 <code>true</code> if the string can be parsed as double, <code>false</code> 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 <code>true</code>, if valueStr equals to 'true', 'on', 'yes', 'y' or '1';
|
||||
* <code>false</code> 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) {
|
||||
ArrayList<Node> nodes = new ArrayList<>();
|
||||
NodeList nodeList = parentNode.getChildNodes();
|
||||
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
if (node.getNodeType() == Node.ELEMENT_NODE && (childName == null || node.getNodeName().equals(childName))) {
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
return nodes.toArray(new Node[0]);
|
||||
}
|
||||
|
||||
public static Node getFirstChildNode(Node parentNode, String childName) {
|
||||
Node[] childs = getChildNodes(parentNode, childName);
|
||||
if (childs.length > 0) {
|
||||
return childs[0];
|
||||
}
|
||||
throw new IllegalArgumentException("XML Error, no such child: " + childName);
|
||||
}
|
||||
|
||||
public static boolean hasChildNode(Node parentNode, String childName) {
|
||||
Node[] childs = getChildNodes(parentNode, childName);
|
||||
return childs.length > 0;
|
||||
}
|
||||
|
||||
// -- Wrapper around getChildValue with trim
|
||||
public static String getChildValue(Node parentNode, String childName) {
|
||||
return getChildValue(parentNode, childName, true);
|
||||
}
|
||||
|
||||
public static String getChildValue(Node parentNode, String childName, boolean trim) {
|
||||
NodeList children = parentNode.getChildNodes();
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node child = children.item(i);
|
||||
if (child.getNodeName().equals(childName)) {
|
||||
return getValue(child, trim);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("XML Error, no such child: " + childName);
|
||||
}
|
||||
|
||||
// -- Wrapper around getValue with trim
|
||||
public static String getValue(Node node) {
|
||||
return getValue(node, true);
|
||||
}
|
||||
|
||||
public static String getValue(Node node, boolean trimValue) {
|
||||
String value = null;
|
||||
Node child = node.getFirstChild();
|
||||
if (child != null) {
|
||||
value = child.getNodeValue();
|
||||
if (value != null && trimValue) {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int getChildValueAsInt(Node parentNode, String childName) {
|
||||
String valueStr = getChildValue(parentNode, childName);
|
||||
return Integer.parseInt(valueStr);
|
||||
}
|
||||
|
||||
public static int getValueAsInt(Node node) {
|
||||
String valueStr = getValue(node);
|
||||
return Integer.parseInt(valueStr);
|
||||
}
|
||||
|
||||
public static long getChildValueAsLong(Node parentNode, String childName) {
|
||||
String valueStr = getChildValue(parentNode, childName);
|
||||
return Long.parseLong(valueStr);
|
||||
}
|
||||
|
||||
public static long getValueAsLong(Node node) {
|
||||
String valueStr = getValue(node);
|
||||
return Long.parseLong(valueStr);
|
||||
}
|
||||
|
||||
public static double getChildValueAsDouble(Node parentNode, String childName) {
|
||||
String valueStr = getChildValue(parentNode, childName);
|
||||
return Util.parseDouble(valueStr);
|
||||
}
|
||||
|
||||
public static double getValueAsDouble(Node node) {
|
||||
String valueStr = getValue(node);
|
||||
return Util.parseDouble(valueStr);
|
||||
}
|
||||
|
||||
public static boolean getChildValueAsBoolean(Node parentNode, String childName) {
|
||||
String valueStr = getChildValue(parentNode, childName);
|
||||
return Util.parseBoolean(valueStr);
|
||||
}
|
||||
|
||||
public static boolean getValueAsBoolean(Node node) {
|
||||
String valueStr = getValue(node);
|
||||
return Util.parseBoolean(valueStr);
|
||||
}
|
||||
|
||||
public static Element getRootElement(InputSource inputSource) throws IOException {
|
||||
try {
|
||||
DocumentBuilder builder = SingletonHelper.factory.newDocumentBuilder();
|
||||
builder.setErrorHandler(eh);
|
||||
Document doc = builder.parse(inputSource);
|
||||
return doc.getDocumentElement();
|
||||
}
|
||||
catch (ParserConfigurationException | SAXException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Element getRootElement(String xmlString) throws IOException {
|
||||
return getRootElement(new InputSource(new StringReader(xmlString)));
|
||||
}
|
||||
|
||||
public static Element getRootElement(File xmlFile) throws IOException {
|
||||
try (Reader reader = new FileReader(xmlFile)) {
|
||||
return getRootElement(new InputSource(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long lastLap = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* Function used for debugging purposes and performance bottlenecks detection.
|
||||
* Probably of no use for end users of Rrd4j.
|
||||
*
|
||||
* @return String representing time in seconds since last
|
||||
* <code>getLapTime()</code> method call.
|
||||
*/
|
||||
public static String getLapTime() {
|
||||
long newLap = System.currentTimeMillis();
|
||||
double seconds = (newLap - lastLap) / 1000.0;
|
||||
lastLap = newLap;
|
||||
return "[" + seconds + " sec]";
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the root directory of the Rrd4j distribution. Useful in some demo applications,
|
||||
* probably of no use anywhere else.</p>
|
||||
* <p>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).</p>
|
||||
*
|
||||
* @return absolute path to Rrd4j's home directory
|
||||
*/
|
||||
public static String getRrd4jHomeDirectory() {
|
||||
String homedir = null;
|
||||
try {
|
||||
String className = Util.class.getName().replace('.', '/');
|
||||
URI uri = Util.class.getResource("/" + className + ".class").toURI();
|
||||
if ("file".equals(uri.getScheme())) {
|
||||
homedir = Paths.get(uri).toString();
|
||||
}
|
||||
else if ("jar".equals(uri.getScheme())) {
|
||||
// JarURLConnection doesn't open the JAR
|
||||
JarURLConnection connection = (JarURLConnection) uri.toURL().openConnection();
|
||||
homedir = connection.getJarFileURL().getFile();
|
||||
}
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
}
|
||||
if (homedir != null) {
|
||||
return Paths.get(homedir).toAbsolutePath().toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two doubles but treats all NaNs as equal.
|
||||
* In Java (by default) Double.NaN == Double.NaN always returns <code>false</code>
|
||||
*
|
||||
* @param x the first value
|
||||
* @param y the second value
|
||||
* @return <code>true</code> if x and y are both equal to Double.NaN, or if x == y. <code>false</code> 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 <code>true</code> if file exists, <code>false</code> 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);
|
||||
}
|
||||
}
|
127
apps/jrobin/java/src/org/rrd4j/core/XmlReader.java
Normal file
127
apps/jrobin/java/src/org/rrd4j/core/XmlReader.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
466
apps/jrobin/java/src/org/rrd4j/core/XmlTemplate.java
Normal file
466
apps/jrobin/java/src/org/rrd4j/core/XmlTemplate.java
Normal file
@ -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 <code>${variable_name}</code>.
|
||||
* 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<String, Object> valueMap = new HashMap<>();
|
||||
private HashSet<Node> validatedNodes = new HashSet<>();
|
||||
|
||||
/**
|
||||
* <p>Constructor for XmlTemplate.</p>
|
||||
*
|
||||
* @param xmlSource a {@link org.xml.sax.InputSource} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
protected XmlTemplate(InputSource xmlSource) throws IOException {
|
||||
root = Util.Xml.getRootElement(xmlSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Constructor for XmlTemplate.</p>
|
||||
*
|
||||
* @param xmlString a {@link java.lang.String} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
protected XmlTemplate(String xmlString) throws IOException {
|
||||
root = Util.Xml.getRootElement(xmlString);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Constructor for XmlTemplate.</p>
|
||||
*
|
||||
* @param xmlFile a {@link java.io.File} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
protected XmlTemplate(File xmlFile) throws IOException {
|
||||
root = Util.Xml.getRootElement(xmlFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all placeholder-value mappings.
|
||||
*/
|
||||
public void clearValues() {
|
||||
valueMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value for a single XML template variable. Variable name should be specified
|
||||
* without leading '${' and ending '}' placeholder markers. For example, for a placeholder
|
||||
* <code>${start}</code>, specify <code>start</code> for the <code>name</code> 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
|
||||
* <code>${start}</code>, specify <code>start</code> for the <code>name</code> 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
|
||||
* <code>${start}</code>, specify <code>start</code> for the <code>name</code> 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
|
||||
* <code>${start}</code>, specify <code>start</code> for the <code>name</code> 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
|
||||
* <code>${start}</code>, specify <code>start</code> for the <code>name</code> 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
|
||||
* <code>${start}</code>, specify <code>start</code> for the <code>name</code> 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
|
||||
* <code>${start}</code>, specify <code>start</code> for the <code>name</code> 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
|
||||
* <code>${start}</code>, specify <code>start</code> for the <code>name</code> 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<String> list = new ArrayList<>();
|
||||
Matcher m = PATTERN.matcher(root.toString());
|
||||
|
||||
while (m.find()) {
|
||||
String var = m.group(1);
|
||||
if (!list.contains(var)) {
|
||||
list.add(var);
|
||||
}
|
||||
}
|
||||
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getChildNodes.</p>
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param childName a {@link java.lang.String} object.
|
||||
* @return an array of {@link org.w3c.dom.Node} objects.
|
||||
*/
|
||||
protected static Node[] getChildNodes(Node parentNode, String childName) {
|
||||
return Util.Xml.getChildNodes(parentNode, childName);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getChildNodes.</p>
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @return an array of {@link org.w3c.dom.Node} objects.
|
||||
*/
|
||||
protected static Node[] getChildNodes(Node parentNode) {
|
||||
return Util.Xml.getChildNodes(parentNode, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getFirstChildNode.</p>
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param childName a {@link java.lang.String} object.
|
||||
* @return a {@link org.w3c.dom.Node} object.
|
||||
*/
|
||||
protected static Node getFirstChildNode(Node parentNode, String childName) {
|
||||
return Util.Xml.getFirstChildNode(parentNode, childName);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>hasChildNode.</p>
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param childName a {@link java.lang.String} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected boolean hasChildNode(Node parentNode, String childName) {
|
||||
return Util.Xml.hasChildNode(parentNode, childName);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getChildValue.</p>
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param childName a {@link java.lang.String} object.
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
protected String getChildValue(Node parentNode, String childName) {
|
||||
return getChildValue(parentNode, childName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getChildValue.</p>
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param childName a {@link java.lang.String} object.
|
||||
* @param trim a boolean.
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
protected String getChildValue(Node parentNode, String childName, boolean trim) {
|
||||
String value = Util.Xml.getChildValue(parentNode, childName, trim);
|
||||
return resolveMappings(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getValue.</p>
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
protected String getValue(Node parentNode) {
|
||||
return getValue(parentNode, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getValue.</p>
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param trim a boolean.
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
protected String getValue(Node parentNode, boolean trim) {
|
||||
String value = Util.Xml.getValue(parentNode, trim);
|
||||
return resolveMappings(value);
|
||||
}
|
||||
|
||||
private String resolveMappings(String templateValue) {
|
||||
if (templateValue == null) {
|
||||
return null;
|
||||
}
|
||||
Matcher matcher = PATTERN.matcher(templateValue);
|
||||
StringBuilder result = new StringBuilder();
|
||||
int lastMatchEnd = 0;
|
||||
while (matcher.find()) {
|
||||
String var = matcher.group(1);
|
||||
if (valueMap.containsKey(var)) {
|
||||
// mapping found
|
||||
result.append(templateValue.substring(lastMatchEnd, matcher.start()));
|
||||
result.append(valueMap.get(var).toString());
|
||||
lastMatchEnd = matcher.end();
|
||||
}
|
||||
else {
|
||||
// no mapping found - this is illegal
|
||||
// throw runtime exception
|
||||
throw new IllegalArgumentException("No mapping found for template variable ${" + var + "}");
|
||||
}
|
||||
}
|
||||
result.append(templateValue.substring(lastMatchEnd));
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* getChildValueAsInt.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param childName a {@link java.lang.String} object.
|
||||
* @return a int.
|
||||
*/
|
||||
protected int getChildValueAsInt(Node parentNode, String childName) {
|
||||
String valueStr = getChildValue(parentNode, childName);
|
||||
return Integer.parseInt(valueStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* getValueAsInt.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @return a int.
|
||||
*/
|
||||
protected int getValueAsInt(Node parentNode) {
|
||||
String valueStr = getValue(parentNode);
|
||||
return Integer.parseInt(valueStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* getChildValueAsLong.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param childName a {@link java.lang.String} object.
|
||||
* @return a long.
|
||||
*/
|
||||
protected long getChildValueAsLong(Node parentNode, String childName) {
|
||||
String valueStr = getChildValue(parentNode, childName);
|
||||
return Long.parseLong(valueStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* getValueAsLong.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @return a long.
|
||||
*/
|
||||
protected long getValueAsLong(Node parentNode) {
|
||||
String valueStr = getValue(parentNode);
|
||||
return Long.parseLong(valueStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* getChildValueAsDouble.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param childName a {@link java.lang.String} object.
|
||||
* @return a double.
|
||||
*/
|
||||
protected double getChildValueAsDouble(Node parentNode, String childName) {
|
||||
String valueStr = getChildValue(parentNode, childName);
|
||||
return Util.parseDouble(valueStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* getValueAsDouble.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @return a double.
|
||||
*/
|
||||
protected double getValueAsDouble(Node parentNode) {
|
||||
String valueStr = getValue(parentNode);
|
||||
return Util.parseDouble(valueStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* getChildValueAsBoolean.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param childName a {@link java.lang.String} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected boolean getChildValueAsBoolean(Node parentNode, String childName) {
|
||||
String valueStr = getChildValue(parentNode, childName);
|
||||
return Util.parseBoolean(valueStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* getValueAsBoolean.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected boolean getValueAsBoolean(Node parentNode) {
|
||||
String valueStr = getValue(parentNode);
|
||||
return Util.parseBoolean(valueStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* getValueAsColor.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @return a {@link java.awt.Paint} object.
|
||||
*/
|
||||
protected Paint getValueAsColor(Node parentNode) {
|
||||
String rgbStr = getValue(parentNode);
|
||||
return Util.parseColor(rgbStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* isEmptyNode.
|
||||
*
|
||||
* @param node a {@link org.w3c.dom.Node} object.
|
||||
* @return a boolean.
|
||||
*/
|
||||
protected boolean isEmptyNode(Node node) {
|
||||
// comment node or empty text node
|
||||
return node.getNodeName().equals("#comment") ||
|
||||
(node.getNodeName().equals("#text") && node.getNodeValue().trim().length() == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* validateTagsOnlyOnce.
|
||||
*
|
||||
* @param parentNode a {@link org.w3c.dom.Node} object.
|
||||
* @param allowedChildNames an array of {@link java.lang.String} objects.
|
||||
*/
|
||||
protected void validateTagsOnlyOnce(Node parentNode, String[] allowedChildNames) {
|
||||
// validate node only once
|
||||
if (validatedNodes.contains(parentNode)) {
|
||||
return;
|
||||
}
|
||||
Node[] childs = getChildNodes(parentNode);
|
||||
main:
|
||||
for (Node child : childs) {
|
||||
String childName = child.getNodeName();
|
||||
for (int j = 0; j < allowedChildNames.length; j++) {
|
||||
if (allowedChildNames[j].equals(childName)) {
|
||||
// only one such tag is allowed
|
||||
allowedChildNames[j] = "<--removed-->";
|
||||
continue main;
|
||||
}
|
||||
else if (allowedChildNames[j].equals(childName + "*")) {
|
||||
// several tags allowed
|
||||
continue main;
|
||||
}
|
||||
}
|
||||
if (!isEmptyNode(child)) {
|
||||
throw new IllegalArgumentException("Unexpected tag encountered: <" + childName + ">");
|
||||
}
|
||||
}
|
||||
// everything is OK
|
||||
validatedNodes.add(parentNode);
|
||||
}
|
||||
}
|
210
apps/jrobin/java/src/org/rrd4j/core/XmlWriter.java
Normal file
210
apps/jrobin/java/src/org/rrd4j/core/XmlWriter.java
Normal file
@ -0,0 +1,210 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Extremely simple utility class used to create XML documents.
|
||||
*/
|
||||
public class XmlWriter {
|
||||
static final String INDENT_STR = " ";
|
||||
private static final String STYLE = "style";
|
||||
private static final ThreadLocal<SimpleDateFormat> ISOLIKE = new ThreadLocal<SimpleDateFormat>() {
|
||||
@Override
|
||||
protected SimpleDateFormat initialValue() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", Locale.ENGLISH);
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return sdf;
|
||||
}
|
||||
};
|
||||
|
||||
private final PrintWriter writer;
|
||||
private final StringBuilder indent = new StringBuilder("");
|
||||
private final Deque<String> openTags = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Creates XmlWriter with the specified output stream to send XML code to.
|
||||
*
|
||||
* @param stream Output stream which receives XML code
|
||||
*/
|
||||
public XmlWriter(OutputStream stream) {
|
||||
writer = new PrintWriter(stream, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates XmlWriter with the specified output stream to send XML code to.
|
||||
*
|
||||
* @param stream Output stream which receives XML code
|
||||
* @param autoFlush is the stream to be flushed automatically
|
||||
*/
|
||||
public XmlWriter(OutputStream stream, boolean autoFlush) {
|
||||
writer = new PrintWriter(stream, autoFlush);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens XML tag
|
||||
*
|
||||
* @param tag XML tag name
|
||||
*/
|
||||
public void startTag(String tag) {
|
||||
writer.println(indent + "<" + tag + ">");
|
||||
openTags.push(tag);
|
||||
indent.append(INDENT_STR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the corresponding XML tag
|
||||
*/
|
||||
public void closeTag() {
|
||||
String tag = openTags.pop();
|
||||
indent.setLength(indent.length() - INDENT_STR.length());
|
||||
writer.println(indent + "</" + tag + ">");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes <tag>value</tag> to output stream
|
||||
*
|
||||
* @param tag XML tag name
|
||||
* @param value value to be placed between <code><tag></code> and <code></tag></code>
|
||||
*/
|
||||
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 <code><tag></code> and <code></tag></code>
|
||||
*/
|
||||
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 <code><tag></code> and <code></tag></code>
|
||||
*/
|
||||
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 <code><tag></code> and <code></tag></code>
|
||||
* @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 <code><tag></code> and <code></tag></code>
|
||||
*/
|
||||
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 <code><tag></code> and <code></tag></code>
|
||||
*/
|
||||
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 <code><tag></code> and <code></tag></code>
|
||||
*/
|
||||
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 <code><tag></code> and <code></tag></code>
|
||||
*/
|
||||
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 <code><tag></code> and <code></tag></code>
|
||||
*/
|
||||
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 + "<!-- " + escape(comment.toString()) + " -->");
|
||||
}
|
||||
|
||||
private static String escape(String s) {
|
||||
return s.replaceAll("<", "<").replaceAll(">", ">");
|
||||
}
|
||||
|
||||
}
|
380
apps/jrobin/java/src/org/rrd4j/core/jrrd/Archive.java
Normal file
380
apps/jrobin/java/src/org/rrd4j/core/jrrd/Archive.java
Normal file
@ -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 <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @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 */
|
||||
List<CDPStatusBlock> cdpStatusBlocks;
|
||||
/** Row for last modification time of database */
|
||||
int currentRow;
|
||||
|
||||
/** Cached content */
|
||||
private double[][] values;
|
||||
|
||||
Archive(RRDatabase db) throws IOException {
|
||||
|
||||
this.db = db;
|
||||
|
||||
RRDFile file = db.rrdFile;
|
||||
|
||||
headerOffset = file.getFilePointer();
|
||||
type = ConsolidationFunctionType.valueOf(file.readString(Constants.CF_NAM_SIZE).toUpperCase());
|
||||
file.align(file.getBits() / 8);
|
||||
rowCount = file.readLong();
|
||||
pdpCount = file.readLong();
|
||||
|
||||
UnivalArray par = file.getUnivalArray(10);
|
||||
xff = par.getDouble(rra_par_en.RRA_cdp_xff_val);
|
||||
|
||||
headerSize = file.getFilePointer() - headerOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of function used to calculate the consolidated data point.
|
||||
*
|
||||
* @return the type of function used to calculate the consolidated data point.
|
||||
*/
|
||||
public ConsolidationFunctionType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
void loadCDPStatusBlocks(RRDFile file, int numBlocks) throws IOException {
|
||||
|
||||
cdpStatusBlocks = new ArrayList<CDPStatusBlock>();
|
||||
|
||||
for (int i = 0; i < numBlocks; i++) {
|
||||
cdpStatusBlocks.add(new CDPStatusBlock(file));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>CDPStatusBlock</code> at the specified position in this archive.
|
||||
*
|
||||
* @param index index of <code>CDPStatusBlock</code> to return.
|
||||
* @return the <code>CDPStatusBlock</code> at the specified position in this archive.
|
||||
*/
|
||||
public CDPStatusBlock getCDPStatusBlock(int index) {
|
||||
return cdpStatusBlocks.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the CDP status blocks in this archive in proper sequence.
|
||||
*
|
||||
* @return an iterator over the CDP status blocks in this archive in proper sequence.
|
||||
* @see CDPStatusBlock
|
||||
*/
|
||||
public Iterator<CDPStatusBlock> getCDPStatusBlocks() {
|
||||
return cdpStatusBlocks.iterator();
|
||||
}
|
||||
|
||||
void loadCurrentRow(RRDFile file) throws IOException {
|
||||
currentRow = file.readLong();
|
||||
}
|
||||
|
||||
void loadData(RRDFile file, int dsCount) throws IOException {
|
||||
|
||||
dataOffset = file.getFilePointer();
|
||||
|
||||
// Skip over the data to position ourselves at the start of the next archive
|
||||
file.skipBytes(Constants.SIZE_OF_DOUBLE * rowCount * dsCount);
|
||||
}
|
||||
|
||||
void loadData(DataChunk chunk)
|
||||
throws IOException {
|
||||
|
||||
long rowIndexPointer;
|
||||
|
||||
if (chunk.startOffset < 0) {
|
||||
rowIndexPointer = currentRow + 1L;
|
||||
}
|
||||
else {
|
||||
rowIndexPointer = currentRow + chunk.startOffset + 1L;
|
||||
}
|
||||
|
||||
if (rowIndexPointer < rowCount) {
|
||||
db.rrdFile.seek((dataOffset + (chunk.dsCount * rowIndexPointer * Constants.SIZE_OF_DOUBLE)));
|
||||
} else {
|
||||
// Safety net: prevent from reading random portions of file
|
||||
// if something went wrong
|
||||
db.rrdFile.seekToEndOfFile();
|
||||
}
|
||||
|
||||
double[][] data = chunk.data;
|
||||
|
||||
/*
|
||||
* This is also terrible - cleanup - CT
|
||||
*/
|
||||
int row = 0;
|
||||
for (int i = chunk.startOffset; i < rowCount - chunk.endOffset; i++, row++) {
|
||||
if (i < 0) { // no valid data yet
|
||||
Arrays.fill(data[row], Double.NaN);
|
||||
}
|
||||
else if (i >= rowCount) { // past valid data area
|
||||
Arrays.fill(data[row], Double.NaN);
|
||||
}
|
||||
else { // inside the valid are but the pointer has to be wrapped
|
||||
if (rowIndexPointer >= rowCount) {
|
||||
rowIndexPointer -= rowCount;
|
||||
|
||||
db.rrdFile.seek(dataOffset + (chunk.dsCount * rowIndexPointer * Constants.SIZE_OF_DOUBLE));
|
||||
}
|
||||
|
||||
for (int ii = 0; ii < chunk.dsCount; ii++) {
|
||||
data[row][ii] = db.rrdFile.readDouble();
|
||||
}
|
||||
|
||||
rowIndexPointer++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void printInfo(PrintStream s, NumberFormat numberFormat, int index) {
|
||||
|
||||
StringBuilder sb = new StringBuilder("rra[");
|
||||
|
||||
sb.append(index);
|
||||
s.print(sb);
|
||||
s.print("].cf = \"");
|
||||
s.print(type);
|
||||
s.println("\"");
|
||||
s.print(sb);
|
||||
s.print("].rows = ");
|
||||
s.println(rowCount);
|
||||
s.print(sb);
|
||||
s.print("].pdp_per_row = ");
|
||||
s.println(pdpCount);
|
||||
s.print(sb);
|
||||
s.print("].xff = ");
|
||||
s.println(xff);
|
||||
sb.append("].cdp_prep[");
|
||||
|
||||
int cdpIndex = 0;
|
||||
|
||||
for (Iterator<CDPStatusBlock> i = cdpStatusBlocks.iterator(); i.hasNext();) {
|
||||
CDPStatusBlock cdp = i.next();
|
||||
|
||||
s.print(sb);
|
||||
s.print(cdpIndex);
|
||||
s.print("].value = ");
|
||||
|
||||
double value = cdp.value;
|
||||
|
||||
s.println(Double.isNaN(value)
|
||||
? "NaN"
|
||||
: numberFormat.format(value));
|
||||
s.print(sb);
|
||||
s.print(cdpIndex++);
|
||||
s.print("].unknown_datapoints = ");
|
||||
s.println(cdp.unknownDatapoints);
|
||||
}
|
||||
}
|
||||
|
||||
void toXml(PrintStream s) {
|
||||
|
||||
try {
|
||||
s.println("\t<rra>");
|
||||
s.print("\t\t<cf> ");
|
||||
s.print(type);
|
||||
s.println(" </cf>");
|
||||
s.print("\t\t<pdp_per_row> ");
|
||||
s.print(pdpCount);
|
||||
s.print(" </pdp_per_row> <!-- ");
|
||||
s.print(db.header.pdpStep * pdpCount);
|
||||
s.println(" seconds -->");
|
||||
s.print("\t\t<xff> ");
|
||||
s.print(xff);
|
||||
s.println(" </xff>");
|
||||
s.println();
|
||||
s.println("\t\t<cdp_prep>");
|
||||
|
||||
for (int i = 0; i < cdpStatusBlocks.size(); i++) {
|
||||
cdpStatusBlocks.get(i).toXml(s);
|
||||
}
|
||||
|
||||
s.println("\t\t</cdp_prep>");
|
||||
s.println("\t\t<database>");
|
||||
|
||||
long timer = -(rowCount - 1);
|
||||
int counter = 0;
|
||||
int row = currentRow;
|
||||
|
||||
db.rrdFile.seek(dataOffset + (row + 1) * db.header.dsCount * Constants.SIZE_OF_DOUBLE);
|
||||
|
||||
long lastUpdate = db.lastUpdate.getTime() / 1000;
|
||||
int pdpStep = db.header.pdpStep;
|
||||
NumberFormat numberFormat = new DecimalFormat("0.0000000000E0", DecimalFormatSymbols.getInstance(Locale.US));
|
||||
SimpleDateFormat dateFormat =
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
|
||||
|
||||
while (counter++ < rowCount) {
|
||||
row++;
|
||||
|
||||
if (row == rowCount) {
|
||||
row = 0;
|
||||
|
||||
db.rrdFile.seek(dataOffset);
|
||||
}
|
||||
|
||||
long now = (lastUpdate - lastUpdate % (pdpCount * pdpStep))
|
||||
+ (timer * pdpCount * pdpStep);
|
||||
|
||||
timer++;
|
||||
|
||||
s.print("\t\t\t<!-- ");
|
||||
s.print(dateFormat.format(new Date(now * 1000)));
|
||||
s.print(" / ");
|
||||
s.print(now);
|
||||
s.print(" --> ");
|
||||
|
||||
s.println("<row>");
|
||||
for (int col = 0; col < db.header.dsCount; col++) {
|
||||
s.print("<v> ");
|
||||
|
||||
double value = db.rrdFile.readDouble();
|
||||
|
||||
// NumberFormat doesn't know how to handle NaN
|
||||
if (Double.isNaN(value)) {
|
||||
s.print("NaN");
|
||||
}
|
||||
else {
|
||||
s.print(numberFormat.format(value));
|
||||
}
|
||||
|
||||
s.print(" </v>");
|
||||
}
|
||||
|
||||
s.println("</row>");
|
||||
}
|
||||
|
||||
s.println("\t\t</database>");
|
||||
s.println("\t</rra>");
|
||||
}
|
||||
catch (IOException e) { // Is the best thing to do here?
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Getter for the field <code>values</code>.</p>
|
||||
*
|
||||
* @return an array of double.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public double[][] getValues() throws IOException {
|
||||
if (values != null) {
|
||||
return values;
|
||||
}
|
||||
values = new double[db.header.dsCount][rowCount];
|
||||
int row = currentRow;
|
||||
db.rrdFile.seek(dataOffset + (row + 1) * db.header.dsCount * Constants.SIZE_OF_DOUBLE);
|
||||
for (int counter = 0; counter < rowCount; counter++) {
|
||||
row++;
|
||||
if (row == rowCount) {
|
||||
row = 0;
|
||||
db.rrdFile.seek(dataOffset);
|
||||
}
|
||||
for (int col = 0; col < db.header.dsCount; col++) {
|
||||
double value = db.rrdFile.readDouble();
|
||||
values[col][counter] = value;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of primary data points required for a consolidated
|
||||
* data point in this archive.
|
||||
*
|
||||
* @return the number of primary data points required for a consolidated
|
||||
* data point in this archive.
|
||||
*/
|
||||
public int getPdpCount() {
|
||||
return pdpCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entries in this archive.
|
||||
*
|
||||
* @return the number of entries in this archive.
|
||||
*/
|
||||
public int getRowCount() {
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X-Files Factor for this archive.
|
||||
*
|
||||
* @return the X-Files Factor for this archive.
|
||||
*/
|
||||
public double getXff() {
|
||||
return xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary the contents of this archive.
|
||||
*
|
||||
* @return a summary of the information contained in this archive.
|
||||
*/
|
||||
public String toString() {
|
||||
|
||||
StringBuilder sb = new StringBuilder("[Archive: OFFSET=0x");
|
||||
|
||||
sb.append(Long.toHexString(headerOffset))
|
||||
.append(", SIZE=0x")
|
||||
.append(Long.toHexString(headerSize))
|
||||
.append(", type=")
|
||||
.append(type)
|
||||
.append(", rowCount=")
|
||||
.append(rowCount)
|
||||
.append(", pdpCount=")
|
||||
.append(pdpCount)
|
||||
.append(", xff=")
|
||||
.append(xff)
|
||||
.append(", currentRow=")
|
||||
.append(currentRow)
|
||||
.append("]");
|
||||
|
||||
for(CDPStatusBlock cdp: cdpStatusBlocks) {
|
||||
sb.append("\n\t\t");
|
||||
sb.append(cdp.toString());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
93
apps/jrobin/java/src/org/rrd4j/core/jrrd/CDPStatusBlock.java
Normal file
93
apps/jrobin/java/src/org/rrd4j/core/jrrd/CDPStatusBlock.java
Normal file
@ -0,0 +1,93 @@
|
||||
package org.rrd4j.core.jrrd;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
/**
|
||||
* Instances of this class model the consolidation data point status from an RRD file.
|
||||
*
|
||||
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class CDPStatusBlock {
|
||||
|
||||
private static enum cdp_par_en {
|
||||
CDP_val, CDP_unkn_pdp_cnt, CDP_hw_intercept, CDP_hw_last_intercept, CDP_hw_slope,
|
||||
CDP_hw_last_slope, CDP_null_count,
|
||||
CDP_last_null_count, CDP_primary_val, CDP_secondary_val
|
||||
}
|
||||
|
||||
/** Byte offset within file */
|
||||
final long offset;
|
||||
/** Size of block in bytes */
|
||||
final long size;
|
||||
final int unknownDatapoints;
|
||||
final double value;
|
||||
|
||||
final double secondary_value;
|
||||
final double primary_value;
|
||||
|
||||
CDPStatusBlock(RRDFile file) throws IOException {
|
||||
//Should read MAX_CDP_PAR_EN = 10
|
||||
//Size should be 0x50
|
||||
offset = file.getFilePointer();
|
||||
UnivalArray scratch = file.getUnivalArray(10);
|
||||
value = scratch.getDouble(cdp_par_en.CDP_val);
|
||||
unknownDatapoints = (int) scratch.getDouble(cdp_par_en.CDP_unkn_pdp_cnt);
|
||||
primary_value = scratch.getDouble(cdp_par_en.CDP_primary_val);
|
||||
secondary_value = scratch.getDouble(cdp_par_en.CDP_secondary_val);
|
||||
|
||||
size = file.getFilePointer() - offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of unknown primary data points that were integrated.
|
||||
*
|
||||
* @return the number of unknown primary data points that were integrated.
|
||||
*/
|
||||
public int getUnknownDatapoints() {
|
||||
return unknownDatapoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this consolidated data point.
|
||||
*
|
||||
* @return the value of this consolidated data point.
|
||||
*/
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
void toXml(PrintStream s) {
|
||||
s.print("\t\t\t<ds><value> ");
|
||||
s.print(value);
|
||||
s.print(" </value> <unknown_datapoints> ");
|
||||
s.print(unknownDatapoints);
|
||||
s.println(" </unknown_datapoints></ds>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary the contents of this CDP status block.
|
||||
*
|
||||
* @return a summary of the information contained in the CDP status block.
|
||||
*/
|
||||
public String toString() {
|
||||
|
||||
StringBuilder sb = new StringBuilder("[CDPStatusBlock: OFFSET=0x");
|
||||
|
||||
sb.append(Long.toHexString(offset));
|
||||
sb.append(", SIZE=0x");
|
||||
sb.append(Long.toHexString(size));
|
||||
sb.append(", unknownDatapoints=");
|
||||
sb.append(unknownDatapoints);
|
||||
sb.append(", value=");
|
||||
sb.append(value);
|
||||
sb.append(", primaryValue=");
|
||||
sb.append(primary_value);
|
||||
sb.append(", secondaryValue=");
|
||||
sb.append(secondary_value);
|
||||
sb.append("]");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package org.rrd4j.core.jrrd;
|
||||
|
||||
import org.rrd4j.ConsolFun;
|
||||
|
||||
/**
|
||||
* Class ConsolidationFunctionType
|
||||
*
|
||||
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public enum ConsolidationFunctionType {
|
||||
AVERAGE {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
return ConsolFun.AVERAGE;
|
||||
}
|
||||
},
|
||||
MIN {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
return ConsolFun.MIN;
|
||||
}
|
||||
},
|
||||
MAX {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
return ConsolFun.MAX;
|
||||
}
|
||||
},
|
||||
LAST {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
return ConsolFun.LAST;
|
||||
}
|
||||
},
|
||||
HWPREDICT {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
throw new UnsupportedOperationException("HWPREDICT not supported");
|
||||
}
|
||||
},
|
||||
SEASONAL {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
throw new UnsupportedOperationException("SEASONAL not supported");
|
||||
}
|
||||
},
|
||||
DEVPREDICT {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
throw new UnsupportedOperationException("DEVPREDICT not supported");
|
||||
}
|
||||
},
|
||||
DEVSEASONAL {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
throw new UnsupportedOperationException("DEVSEASONAL not supported");
|
||||
}
|
||||
},
|
||||
FAILURES {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
throw new UnsupportedOperationException("FAILURES not supported");
|
||||
}
|
||||
},
|
||||
MHWPREDICT {
|
||||
@Override
|
||||
public ConsolFun getConsolFun() {
|
||||
throw new UnsupportedOperationException("MHWPREDICT not supported");
|
||||
}
|
||||
};
|
||||
|
||||
public abstract ConsolFun getConsolFun();
|
||||
}
|
26
apps/jrobin/java/src/org/rrd4j/core/jrrd/Constants.java
Normal file
26
apps/jrobin/java/src/org/rrd4j/core/jrrd/Constants.java
Normal file
@ -0,0 +1,26 @@
|
||||
package org.rrd4j.core.jrrd;
|
||||
|
||||
interface Constants {
|
||||
/** Constant <code>DS_NAM_SIZE=20</code> */
|
||||
final int DS_NAM_SIZE = 20;
|
||||
/** Constant <code>DST_SIZE=20</code> */
|
||||
final int DST_SIZE = 20;
|
||||
/** Constant <code>CF_NAM_SIZE=20</code> */
|
||||
final int CF_NAM_SIZE = 20;
|
||||
/** Constant <code>LAST_DS_LEN=30</code> */
|
||||
final int LAST_DS_LEN = 30;
|
||||
/** Constant <code>COOKIE="RRD"</code> */
|
||||
static final String COOKIE = "RRD";
|
||||
/** Constant <code>MAX_SUPPORTED_VERSION=3</code> */
|
||||
public static final int MAX_SUPPORTED_VERSION = 3;
|
||||
/** Constant <code>UNDEFINED_VERSION="UNDEF"</code> */
|
||||
public static final String UNDEFINED_VERSION = "UNDEF";
|
||||
/** Constant <code>UNDEFINED_VERSION_AS_INT=-1</code> */
|
||||
public static final int UNDEFINED_VERSION_AS_INT = -1;
|
||||
/** Constant <code>VERSION_WITH_LAST_UPDATE_SEC=3</code> */
|
||||
public static int VERSION_WITH_LAST_UPDATE_SEC = 3;
|
||||
/** Constant <code>FLOAT_COOKIE=8.642135E130</code> */
|
||||
static final double FLOAT_COOKIE = 8.642135E130;
|
||||
/** Constant <code>SIZE_OF_DOUBLE=8</code> */
|
||||
public static final int SIZE_OF_DOUBLE = 8;
|
||||
}
|
129
apps/jrobin/java/src/org/rrd4j/core/jrrd/DataChunk.java
Normal file
129
apps/jrobin/java/src/org/rrd4j/core/jrrd/DataChunk.java
Normal file
@ -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 <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @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 Map<String, Integer> nameindex;
|
||||
|
||||
DataChunk(Map<String, Integer> nameindex, long startTime, int startOffset, int endOffset, long step, int dsCount, int rows) {
|
||||
this.nameindex = nameindex;
|
||||
this.startTime = startTime;
|
||||
this.startOffset = startOffset;
|
||||
this.endOffset = endOffset;
|
||||
this.step = step;
|
||||
this.dsCount = dsCount;
|
||||
this.rows = rows;
|
||||
data = new double[rows][dsCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary of the contents of this data chunk. The first column is
|
||||
* the time (RRD format) and the following columns are the data source
|
||||
* values.
|
||||
*
|
||||
* @return a summary of the contents of this data chunk.
|
||||
*/
|
||||
public String toString() {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
long time = startTime;
|
||||
|
||||
for (int row = 0; row < rows; row++, time += step) {
|
||||
sb.append(time);
|
||||
sb.append(": ");
|
||||
|
||||
for (int ds = 0; ds < dsCount; ds++) {
|
||||
sb.append(data[row][ds]);
|
||||
sb.append(" ");
|
||||
}
|
||||
|
||||
sb.append(NEWLINE);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return startOffset;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return endOffset;
|
||||
}
|
||||
|
||||
public long getStep() {
|
||||
return step;
|
||||
}
|
||||
|
||||
public int getDsCount() {
|
||||
return dsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Getter for the field <code>data</code>.</p>
|
||||
*
|
||||
* @return the data
|
||||
*/
|
||||
public double[][] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Getter for the time stamps values.</p>
|
||||
*
|
||||
* @return array of time stamps in seconds
|
||||
*/
|
||||
public long[] getTimestamps() {
|
||||
long[] date = new long[rows];
|
||||
long time = startTime;
|
||||
for (int row = 0; row < rows; row++, time += step) {
|
||||
date[row] = time;
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a datasource from the datachunck given is name as a Plottable
|
||||
*
|
||||
* @param name the datasource name
|
||||
* @return a plottable for the datasource
|
||||
*/
|
||||
public Plottable toPlottable(String name) {
|
||||
Integer dsId = nameindex.get(name);
|
||||
if(dsId == null)
|
||||
throw new RuntimeException("datasource not not found: " + name);
|
||||
long[] date = new long[rows];
|
||||
double[] results = new double[rows];
|
||||
long time = startTime;
|
||||
for (int row = 0; row < rows; row++, time += step) {
|
||||
date[row] = time;
|
||||
results[row] = data[row][dsId];
|
||||
}
|
||||
return new LinearInterpolator(date, results);
|
||||
}
|
||||
|
||||
}
|
198
apps/jrobin/java/src/org/rrd4j/core/jrrd/DataSource.java
Normal file
198
apps/jrobin/java/src/org/rrd4j/core/jrrd/DataSource.java
Normal file
@ -0,0 +1,198 @@
|
||||
package org.rrd4j.core.jrrd;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* Instances of this class model a data source in an RRD file.
|
||||
*
|
||||
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class DataSource {
|
||||
|
||||
private static enum ds_param_en { DS_mrhb_cnt, DS_min_val, DS_max_val, DS_cde }
|
||||
|
||||
private final long offset;
|
||||
private final long size;
|
||||
private final String name;
|
||||
private final DataSourceType type;
|
||||
private final int minimumHeartbeat;
|
||||
private final double minimum;
|
||||
private final double maximum;
|
||||
// initialized during RRDatabase construction
|
||||
private PDPStatusBlock pdpStatusBlock;
|
||||
|
||||
DataSource(RRDFile file) throws IOException {
|
||||
|
||||
offset = file.getFilePointer();
|
||||
name = file.readString(Constants.DS_NAM_SIZE);
|
||||
type = DataSourceType.valueOf(file.readString(Constants.DST_SIZE).toUpperCase());
|
||||
|
||||
UnivalArray par = file.getUnivalArray(10);
|
||||
minimumHeartbeat = (int) par.getLong(ds_param_en.DS_mrhb_cnt);
|
||||
minimum = par.getDouble(ds_param_en.DS_min_val);
|
||||
maximum = par.getDouble(ds_param_en.DS_max_val);
|
||||
size = file.getFilePointer() - offset;
|
||||
}
|
||||
|
||||
void loadPDPStatusBlock(RRDFile file) throws IOException {
|
||||
pdpStatusBlock = new PDPStatusBlock(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary data point status block for this data source.
|
||||
*
|
||||
* @return the primary data point status block for this data source.
|
||||
*/
|
||||
public PDPStatusBlock getPDPStatusBlock() {
|
||||
return pdpStatusBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum required heartbeat for this data source.
|
||||
*
|
||||
* @return the minimum required heartbeat for this data source.
|
||||
*/
|
||||
public int getMinimumHeartbeat() {
|
||||
return minimumHeartbeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum value input to this data source can have.
|
||||
*
|
||||
* @return the minimum value input to this data source can have.
|
||||
*/
|
||||
public double getMinimum() {
|
||||
return minimum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type this data source is.
|
||||
*
|
||||
* @return the type this data source is.
|
||||
* @see DataSourceType
|
||||
*/
|
||||
public DataSourceType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum value input to this data source can have.
|
||||
*
|
||||
* @return the maximum value input to this data source can have.
|
||||
*/
|
||||
public double getMaximum() {
|
||||
return maximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this data source.
|
||||
*
|
||||
* @return the name of this data source.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
void printInfo(PrintStream s, NumberFormat numberFormat) {
|
||||
|
||||
StringBuilder sb = new StringBuilder("ds[");
|
||||
|
||||
sb.append(name);
|
||||
s.print(sb);
|
||||
s.print("].type = \"");
|
||||
s.print(type);
|
||||
s.println("\"");
|
||||
s.print(sb);
|
||||
s.print("].minimal_heartbeat = ");
|
||||
s.println(minimumHeartbeat);
|
||||
s.print(sb);
|
||||
s.print("].min = ");
|
||||
s.println(Double.isNaN(minimum)
|
||||
? "NaN"
|
||||
: numberFormat.format(minimum));
|
||||
s.print(sb);
|
||||
s.print("].max = ");
|
||||
s.println(Double.isNaN(maximum)
|
||||
? "NaN"
|
||||
: numberFormat.format(maximum));
|
||||
s.print(sb);
|
||||
s.print("].last_ds = ");
|
||||
s.println(pdpStatusBlock.lastReading);
|
||||
s.print(sb);
|
||||
s.print("].value = ");
|
||||
|
||||
double value = pdpStatusBlock.value;
|
||||
|
||||
s.println(Double.isNaN(value)
|
||||
? "NaN"
|
||||
: numberFormat.format(value));
|
||||
s.print(sb);
|
||||
s.print("].unknown_sec = ");
|
||||
s.println(pdpStatusBlock.unknownSeconds);
|
||||
}
|
||||
|
||||
void toXml(PrintStream s) {
|
||||
|
||||
s.println("\t<ds>");
|
||||
s.print("\t\t<name> ");
|
||||
s.print(name);
|
||||
s.println(" </name>");
|
||||
s.print("\t\t<type> ");
|
||||
s.print(type);
|
||||
s.println(" </type>");
|
||||
s.print("\t\t<minimal_heartbeat> ");
|
||||
s.print(minimumHeartbeat);
|
||||
s.println(" </minimal_heartbeat>");
|
||||
s.print("\t\t<min> ");
|
||||
s.print(minimum);
|
||||
s.println(" </min>");
|
||||
s.print("\t\t<max> ");
|
||||
s.print(maximum);
|
||||
s.println(" </max>");
|
||||
s.println();
|
||||
s.println("\t\t<!-- PDP Status -->");
|
||||
s.print("\t\t<last_ds> ");
|
||||
s.print(pdpStatusBlock.lastReading);
|
||||
s.println(" </last_ds>");
|
||||
s.print("\t\t<value> ");
|
||||
s.print(pdpStatusBlock.value);
|
||||
s.println(" </value>");
|
||||
s.print("\t\t<unknown_sec> ");
|
||||
s.print(pdpStatusBlock.unknownSeconds);
|
||||
s.println(" </unknown_sec>");
|
||||
s.println("\t</ds>");
|
||||
s.println();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary the contents of this data source.
|
||||
*
|
||||
* @return a summary of the information contained in this data source.
|
||||
*/
|
||||
public String toString() {
|
||||
|
||||
StringBuilder sb = new StringBuilder("[DataSource: OFFSET=0x");
|
||||
|
||||
sb.append(Long.toHexString(offset));
|
||||
sb.append(", SIZE=0x");
|
||||
sb.append(Long.toHexString(size));
|
||||
sb.append(", name=");
|
||||
sb.append(name);
|
||||
sb.append(", type=");
|
||||
sb.append(type.toString());
|
||||
sb.append(", minHeartbeat=");
|
||||
sb.append(minimumHeartbeat);
|
||||
sb.append(", min=");
|
||||
sb.append(minimum);
|
||||
sb.append(", max=");
|
||||
sb.append(maximum);
|
||||
sb.append("]");
|
||||
sb.append("\n\t\t");
|
||||
sb.append(pdpStatusBlock.toString());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
44
apps/jrobin/java/src/org/rrd4j/core/jrrd/DataSourceType.java
Normal file
44
apps/jrobin/java/src/org/rrd4j/core/jrrd/DataSourceType.java
Normal file
@ -0,0 +1,44 @@
|
||||
package org.rrd4j.core.jrrd;
|
||||
|
||||
import org.rrd4j.DsType;
|
||||
|
||||
/**
|
||||
* Class DataSourceType
|
||||
*
|
||||
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public enum DataSourceType {
|
||||
COUNTER {
|
||||
@Override
|
||||
public DsType getDsType() {
|
||||
return DsType.COUNTER;
|
||||
}
|
||||
},
|
||||
ABSOLUTE {
|
||||
@Override
|
||||
public DsType getDsType() {
|
||||
return DsType.ABSOLUTE;
|
||||
}
|
||||
},
|
||||
GAUGE {
|
||||
@Override
|
||||
public DsType getDsType() {
|
||||
return DsType.GAUGE;
|
||||
}
|
||||
},
|
||||
DERIVE {
|
||||
@Override
|
||||
public DsType getDsType() {
|
||||
return DsType.DERIVE;
|
||||
}
|
||||
},
|
||||
CDEF {
|
||||
@Override
|
||||
public DsType getDsType() {
|
||||
throw new UnsupportedOperationException("CDEF not supported");
|
||||
}
|
||||
};
|
||||
|
||||
public abstract DsType getDsType();
|
||||
}
|
127
apps/jrobin/java/src/org/rrd4j/core/jrrd/Header.java
Normal file
127
apps/jrobin/java/src/org/rrd4j/core/jrrd/Header.java
Normal file
@ -0,0 +1,127 @@
|
||||
package org.rrd4j.core.jrrd;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.rrd4j.core.InvalidRrdException;
|
||||
|
||||
/**
|
||||
* Instances of this class model the header section of an RRD file.
|
||||
*
|
||||
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class Header implements Constants {
|
||||
|
||||
private static final double FLOAT_COOKIE = 8.642135E130;
|
||||
private static final long offset = 0;
|
||||
private final long size;
|
||||
final String version;
|
||||
private final int iVersion;
|
||||
/** Number of data sources */
|
||||
final int dsCount;
|
||||
/** Number of archives within file */
|
||||
final int rraCount;
|
||||
final int pdpStep;
|
||||
|
||||
Header(RRDFile file) throws IOException {
|
||||
|
||||
if (!file.readString(4).equals(COOKIE)) {
|
||||
throw new InvalidRrdException("Invalid COOKIE");
|
||||
}
|
||||
version = file.readString(5);
|
||||
try {
|
||||
iVersion = Integer.parseInt(version);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new RuntimeException("Unsupported RRD version (" + version + ")");
|
||||
}
|
||||
if (iVersion > MAX_SUPPORTED_VERSION) {
|
||||
throw new RuntimeException("Unsupported RRD version (" + version + ")");
|
||||
}
|
||||
|
||||
file.align();
|
||||
|
||||
// Consume the FLOAT_COOKIE
|
||||
double cookie = file.readDouble();
|
||||
if(cookie != FLOAT_COOKIE) {
|
||||
throw new RuntimeException("This RRD was created on another architecture");
|
||||
}
|
||||
|
||||
dsCount = file.readLong();
|
||||
rraCount = file.readLong();
|
||||
pdpStep = file.readLong();
|
||||
|
||||
// Skip rest of stat_head_t.par
|
||||
@SuppressWarnings("unused")
|
||||
UnivalArray par = file.getUnivalArray(10);
|
||||
|
||||
size = file.getFilePointer() - offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the database.
|
||||
*
|
||||
* @return the version of the database.
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the database as an integer.
|
||||
*
|
||||
* @return the version of the database.
|
||||
*/
|
||||
public int getVersionAsInt() {
|
||||
return iVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of <code>DataSource</code>s in the database.
|
||||
*
|
||||
* @return the number of <code>DataSource</code>s in the database.
|
||||
*/
|
||||
public int getDSCount() {
|
||||
return dsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of <code>Archive</code>s in the database.
|
||||
*
|
||||
* @return the number of <code>Archive</code>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();
|
||||
}
|
||||
}
|
81
apps/jrobin/java/src/org/rrd4j/core/jrrd/PDPStatusBlock.java
Normal file
81
apps/jrobin/java/src/org/rrd4j/core/jrrd/PDPStatusBlock.java
Normal file
@ -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 <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @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();
|
||||
}
|
||||
}
|
218
apps/jrobin/java/src/org/rrd4j/core/jrrd/RRDFile.java
Normal file
218
apps/jrobin/java/src/org/rrd4j/core/jrrd/RRDFile.java
Normal file
@ -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
|
||||
* <p/>
|
||||
* Currently this can read RRD files that were generated on Solaris (Sparc)
|
||||
* and Linux (x86).
|
||||
*
|
||||
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
class RRDFile implements Constants {
|
||||
|
||||
/** Constant <code>FLOAT_COOKIE_BIG_ENDIAN={0x5B, 0x1F, 0x2B, 0x43,
|
||||
(byte) 0xC7, (byte) 0xC0, 0x25,
|
||||
0x2F}</code> */
|
||||
private static final byte[] FLOAT_COOKIE_BIG_ENDIAN = {0x5B, 0x1F, 0x2B, 0x43,
|
||||
(byte) 0xC7, (byte) 0xC0, 0x25,
|
||||
0x2F};
|
||||
/** Constant <code>FLOAT_COOKIE_LITTLE_ENDIAN={0x2F, 0x25, (byte) 0xC0,
|
||||
(byte) 0xC7, 0x43, 0x2B, 0x1F,
|
||||
0x5B}</code> */
|
||||
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());
|
||||
}
|
||||
}
|
529
apps/jrobin/java/src/org/rrd4j/core/jrrd/RRDatabase.java
Normal file
529
apps/jrobin/java/src/org/rrd4j/core/jrrd/RRDatabase.java
Normal file
@ -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
|
||||
* <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/">Round Robin Database</a>
|
||||
* (RRD) files.
|
||||
*
|
||||
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class RRDatabase implements Closeable {
|
||||
|
||||
final RRDFile rrdFile;
|
||||
|
||||
// RRD file name
|
||||
private final String name;
|
||||
final Header header;
|
||||
private final ArrayList<DataSource> dataSources;
|
||||
private final ArrayList<Archive> archives;
|
||||
/** Timestamp of last data modification */
|
||||
final Date lastUpdate;
|
||||
/** Data source name to index */
|
||||
private final Map<String, Integer> nameindex;
|
||||
|
||||
/**
|
||||
* Creates a database to read from.
|
||||
*
|
||||
* @param name the filename of the file to read from.
|
||||
* @throws java.io.IOException if an I/O error occurs.
|
||||
*/
|
||||
public RRDatabase(String name) throws IOException {
|
||||
this(new File(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a database to read from.
|
||||
*
|
||||
* @param file the file to read from.
|
||||
* @throws java.io.IOException if an I/O error occurs.
|
||||
*/
|
||||
public RRDatabase(File file) throws IOException {
|
||||
/*
|
||||
* read the raw data according to the c-structure rrd_t (from rrd source
|
||||
* distribution file rrd_format.h)
|
||||
*/
|
||||
name = file.getName();
|
||||
rrdFile = new RRDFile(file);
|
||||
header = new Header(rrdFile);
|
||||
|
||||
nameindex = new HashMap<String, Integer>(header.dsCount);
|
||||
|
||||
// Load the data sources
|
||||
dataSources = new ArrayList<DataSource>(header.dsCount);
|
||||
|
||||
for (int i = 0; i < header.dsCount; i++) {
|
||||
DataSource ds = new DataSource(rrdFile);
|
||||
nameindex.put(ds.getName(), i);
|
||||
dataSources.add(ds);
|
||||
}
|
||||
|
||||
// Load the archives
|
||||
archives = new ArrayList<Archive>(header.rraCount);
|
||||
|
||||
for (int i = 0; i < header.rraCount; i++) {
|
||||
Archive archive = new Archive(this);
|
||||
archives.add(archive);
|
||||
}
|
||||
|
||||
long last_up = (long) rrdFile.readLong() * 1000;
|
||||
|
||||
/* rrd v >= 3 last_up with us */
|
||||
if (header.getVersionAsInt() >= Constants.VERSION_WITH_LAST_UPDATE_SEC) {
|
||||
long last_up_usec = rrdFile.readLong();
|
||||
last_up += last_up_usec / 1000;
|
||||
}
|
||||
lastUpdate = new Date(last_up);
|
||||
|
||||
// Load PDPStatus(s)
|
||||
for (int i = 0; i < header.dsCount; i++) {
|
||||
DataSource ds = dataSources.get(i);
|
||||
ds.loadPDPStatusBlock(rrdFile);
|
||||
}
|
||||
// Load CDPStatus(s)
|
||||
for (int i = 0; i < header.rraCount; i++) {
|
||||
Archive archive = archives.get(i);
|
||||
archive.loadCDPStatusBlocks(rrdFile, header.dsCount);
|
||||
}
|
||||
// Load current row information for each archive
|
||||
for (int i = 0; i < header.rraCount; i++) {
|
||||
Archive archive = archives.get(i);
|
||||
archive.loadCurrentRow(rrdFile);
|
||||
}
|
||||
// Now load the data
|
||||
for (int i = 0; i < header.rraCount; i++) {
|
||||
Archive archive = archives.get(i);
|
||||
archive.loadData(rrdFile, header.dsCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>Header</code> for this database.
|
||||
*
|
||||
* @return the <code>Header</code> for this database.
|
||||
*/
|
||||
public Header getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getDataSourcesName.</p>
|
||||
*
|
||||
* @return a {@link java.util.Set} object.
|
||||
*/
|
||||
public Set<String> getDataSourcesName() {
|
||||
return nameindex.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date this database was last updated. To convert this date to
|
||||
* the form returned by <code>rrdtool last</code> call Date.getTime() and
|
||||
* divide the result by 1000.
|
||||
*
|
||||
* @return the date this database was last updated.
|
||||
*/
|
||||
public Date getLastUpdate() {
|
||||
return lastUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>DataSource</code> at the specified position in this database.
|
||||
*
|
||||
* @param index index of <code>DataSource</code> to return.
|
||||
* @return the <code>DataSource</code> at the specified position in this database
|
||||
*/
|
||||
public DataSource getDataSource(int index) {
|
||||
return dataSources.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the data sources in this database in proper sequence.
|
||||
*
|
||||
* @return an iterator over the data sources in this database in proper sequence.
|
||||
*/
|
||||
public Iterator<DataSource> getDataSources() {
|
||||
return dataSources.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>Archive</code> at the specified position in this database.
|
||||
*
|
||||
* @param index index of <code>Archive</code> to return.
|
||||
* @return the <code>Archive</code> at the specified position in this database.
|
||||
*/
|
||||
public Archive getArchive(int index) {
|
||||
return archives.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getArchive.</p>
|
||||
*
|
||||
* @param name a {@link java.lang.String} object.
|
||||
* @return a {@link org.rrd4j.core.jrrd.Archive} object.
|
||||
*/
|
||||
public Archive getArchive(String name) {
|
||||
return archives.get(nameindex.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the archives in this database in proper sequence.
|
||||
*
|
||||
* @return an iterator over the archives in this database in proper sequence.
|
||||
*/
|
||||
public Iterator<Archive> getArchives() {
|
||||
return archives.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of archives in this database.
|
||||
*
|
||||
* @return the number of archives in this database.
|
||||
*/
|
||||
public int getNumArchives() {
|
||||
return header.rraCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the archives in this database of the given type
|
||||
* in proper sequence.
|
||||
*
|
||||
* @param type the consolidation function that should have been applied to
|
||||
* the data.
|
||||
* @return an iterator over the archives in this database of the given type
|
||||
* in proper sequence.
|
||||
*/
|
||||
public Iterator<Archive> getArchives(ConsolidationFunctionType type) {
|
||||
return getArchiveList(type).iterator();
|
||||
}
|
||||
|
||||
ArrayList<Archive> getArchiveList(ConsolidationFunctionType type) {
|
||||
|
||||
ArrayList<Archive> subset = new ArrayList<Archive>();
|
||||
|
||||
for (Archive archive : archives) {
|
||||
if (archive.getType().equals(type)) {
|
||||
subset.add(archive);
|
||||
}
|
||||
}
|
||||
|
||||
return subset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this database stream and releases any associated system resources.
|
||||
*
|
||||
* @throws java.io.IOException if an I/O error occurs.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
rrdFile.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the header information of the database to the given print stream
|
||||
* using the default number format. The default format for <code>double</code>
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getData.</p>
|
||||
*
|
||||
* @param type a {@link org.rrd4j.core.jrrd.ConsolidationFunctionType} object.
|
||||
* @param startTime seconds since epoch
|
||||
* @param endTime seconds since epoch
|
||||
* @param stepSeconds in seconds
|
||||
* @return a {@link org.rrd4j.core.jrrd.DataChunk} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public DataChunk getData(ConsolidationFunctionType type, long startTime, long endTime, long stepSeconds)
|
||||
throws IOException {
|
||||
|
||||
ArrayList<Archive> possibleArchives = getArchiveList(type);
|
||||
|
||||
if (possibleArchives.size() == 0) {
|
||||
throw new IllegalArgumentException("Database does not contain an Archive of consolidation function type "
|
||||
+ type);
|
||||
}
|
||||
|
||||
Archive archive = findBestArchive(startTime, endTime, stepSeconds, possibleArchives);
|
||||
|
||||
if (archive == null) {
|
||||
throw new RrdException("No matching archive found");
|
||||
}
|
||||
|
||||
// Tune the parameters
|
||||
stepSeconds = (long) header.pdpStep * archive.pdpCount;
|
||||
startTime -= startTime % stepSeconds;
|
||||
|
||||
if (endTime % stepSeconds != 0) {
|
||||
endTime += stepSeconds - endTime % stepSeconds;
|
||||
}
|
||||
|
||||
int rows = (int) ((endTime - startTime) / stepSeconds + 1);
|
||||
|
||||
// Find start and end offsets
|
||||
// This is terrible - some of this should be encapsulated in Archive - CT.
|
||||
long lastUpdateLong = lastUpdate.getTime() / 1000;
|
||||
long archiveEndTime = lastUpdateLong - (lastUpdateLong % stepSeconds);
|
||||
long archiveStartTime = archiveEndTime - (stepSeconds * (archive.rowCount - 1));
|
||||
int startOffset = (int) ((startTime - archiveStartTime) / stepSeconds);
|
||||
int endOffset = (int) ((archiveEndTime - endTime) / stepSeconds);
|
||||
|
||||
DataChunk chunk = new DataChunk(nameindex, startTime, startOffset, endOffset,
|
||||
stepSeconds, header.dsCount, rows);
|
||||
|
||||
archive.loadData(chunk);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is almost a verbatim copy of the original C code by Tobias Oetiker.
|
||||
* I need to put more of a Java style on it - CT
|
||||
*/
|
||||
private Archive findBestArchive(long start, long end, long step,
|
||||
ArrayList<Archive> archives) {
|
||||
|
||||
Archive archive = null;
|
||||
Archive bestFullArchive = null;
|
||||
Archive bestPartialArchive = null;
|
||||
long lastUpdateLong = lastUpdate.getTime() / 1000;
|
||||
int firstPart = 1;
|
||||
int firstFull = 1;
|
||||
long bestMatch = 0;
|
||||
long bestStepDiff = 0;
|
||||
long tmpStepDiff;
|
||||
|
||||
for (Archive archive1 : archives) {
|
||||
archive = archive1;
|
||||
|
||||
long calEnd = lastUpdateLong
|
||||
- (lastUpdateLong
|
||||
% (archive.pdpCount * header.pdpStep));
|
||||
long calStart = calEnd
|
||||
- (archive.pdpCount * archive.rowCount
|
||||
* header.pdpStep);
|
||||
long fullMatch = end - start;
|
||||
|
||||
if ((calEnd >= end) && (calStart < start)) { // Best full match
|
||||
tmpStepDiff = Math.abs(step - (header.pdpStep * archive.pdpCount));
|
||||
|
||||
if ((firstFull != 0) || (tmpStepDiff < bestStepDiff)) {
|
||||
firstFull = 0;
|
||||
bestStepDiff = tmpStepDiff;
|
||||
bestFullArchive = archive;
|
||||
}
|
||||
}
|
||||
else { // Best partial match
|
||||
long tmpMatch = fullMatch;
|
||||
|
||||
if (calStart > start) {
|
||||
tmpMatch -= calStart - start;
|
||||
}
|
||||
|
||||
if (calEnd < end) {
|
||||
tmpMatch -= end - calEnd;
|
||||
}
|
||||
|
||||
if ((firstPart != 0) || (bestMatch < tmpMatch)) {
|
||||
firstPart = 0;
|
||||
bestMatch = tmpMatch;
|
||||
bestPartialArchive = archive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See how the matching went
|
||||
// optimize this
|
||||
if (firstFull == 0) {
|
||||
archive = bestFullArchive;
|
||||
}
|
||||
else if (firstPart == 0) {
|
||||
archive = bestPartialArchive;
|
||||
}
|
||||
|
||||
return archive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the header information of the database to the given print stream
|
||||
* using the given number format. The format is almost identical to that
|
||||
* produced by
|
||||
* <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/rrdinfo.html">rrdtool info</a>
|
||||
*
|
||||
* @param s the PrintStream to print the header information to.
|
||||
* @param numberFormat the format to print <code>double</code>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
|
||||
* <a href="http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/manual/rrddump.html">rrdtool dump</a>
|
||||
*
|
||||
* 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(" Round Robin RRDatabase Dump ");
|
||||
s.println(" Generated by jRRD <ciaran@codeloop.com>");
|
||||
s.println("-->");
|
||||
s.println("<rrd>");
|
||||
s.print("\t<version> ");
|
||||
s.print(header.version);
|
||||
s.println(" </version>");
|
||||
s.print("\t<step> ");
|
||||
s.print(header.pdpStep);
|
||||
s.println(" </step> <!-- Seconds -->");
|
||||
s.print("\t<lastupdate> ");
|
||||
s.print(lastUpdate.getTime() / 1000);
|
||||
s.print(" </lastupdate> <!-- ");
|
||||
s.print(lastUpdate.toString());
|
||||
s.println(" -->");
|
||||
s.println();
|
||||
|
||||
for (int i = 0; i < header.dsCount; i++) {
|
||||
DataSource ds = dataSources.get(i);
|
||||
|
||||
ds.toXml(s);
|
||||
}
|
||||
|
||||
s.println("<!-- Round Robin Archives -->");
|
||||
|
||||
for (int i = 0; i < header.rraCount; i++) {
|
||||
Archive archive = archives.get(i);
|
||||
|
||||
archive.toXml(s);
|
||||
}
|
||||
|
||||
s.println("</rrd>");
|
||||
s.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary the contents of this database.
|
||||
*
|
||||
* @return a summary of the information contained in this database.
|
||||
*/
|
||||
public String toString() {
|
||||
|
||||
String endianness;
|
||||
if(rrdFile.isBigEndian())
|
||||
endianness = "Big";
|
||||
else
|
||||
endianness = "Little";
|
||||
|
||||
|
||||
StringBuilder sb = new StringBuilder(endianness + " endian" + ", " + rrdFile.getBits() + " bits\n");
|
||||
|
||||
sb.append(header.toString());
|
||||
|
||||
sb.append(", lastupdate: ");
|
||||
sb.append(lastUpdate.getTime() / 1000);
|
||||
|
||||
|
||||
for (DataSource ds : dataSources) {
|
||||
sb.append("\n\t");
|
||||
sb.append(ds.toString());
|
||||
}
|
||||
|
||||
for (Archive archive : archives) {
|
||||
sb.append("\n\t");
|
||||
sb.append(archive.toString());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
60
apps/jrobin/java/src/org/rrd4j/core/jrrd/UnivalArray.java
Normal file
60
apps/jrobin/java/src/org/rrd4j/core/jrrd/UnivalArray.java
Normal file
@ -0,0 +1,60 @@
|
||||
package org.rrd4j.core.jrrd;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* This class is used to read a unival from a file
|
||||
* unival is a rrdtool type, defined in rrd_format.h
|
||||
* @author Fabrice Bacchella <fbacchella@spamcop.net>
|
||||
*
|
||||
*/
|
||||
class UnivalArray {
|
||||
private final ByteBuffer buffer;
|
||||
private final int sizeoflong;
|
||||
|
||||
/**
|
||||
* Read an UnivalArray from a rrd native file at the current position
|
||||
*
|
||||
* @param file the RRdFile
|
||||
* @param size the numer of elements in the array
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public UnivalArray(RRDFile file, int size) throws IOException {
|
||||
sizeoflong = file.getBits();
|
||||
buffer = ByteBuffer.allocate(size * 8);
|
||||
if(file.isBigEndian())
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
else
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
file.align();
|
||||
file.read(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getLong.</p>
|
||||
*
|
||||
* @param e a {@link java.lang.Enum} object.
|
||||
* @return a long.
|
||||
*/
|
||||
public long getLong(Enum<?> e) {
|
||||
buffer.position(8 * e.ordinal());
|
||||
if(sizeoflong == 64)
|
||||
return buffer.getLong();
|
||||
else
|
||||
return buffer.getInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>getDouble.</p>
|
||||
*
|
||||
* @param e a {@link java.lang.Enum} object.
|
||||
* @return a double.
|
||||
*/
|
||||
public double getDouble(Enum<?> e) {
|
||||
buffer.position(8 * e.ordinal());
|
||||
return buffer.getDouble();
|
||||
}
|
||||
|
||||
}
|
31
apps/jrobin/java/src/org/rrd4j/core/jrrd/package-info.java
Normal file
31
apps/jrobin/java/src/org/rrd4j/core/jrrd/package-info.java
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* <p>This package provides read-only access to natives RRD file.</p>
|
||||
*
|
||||
* Currently this can read RRD files that were generated big or little endian machines, 32 or 64 bits word, and 4 or 8 bytes alignment.
|
||||
* So it's know to work on a least
|
||||
* <ul>
|
||||
* <li> x86 Linux
|
||||
* <li> x86_64 Linux
|
||||
* <li> x86_64 Solaris
|
||||
* <li> sparc v8 (32 bits) Solaris
|
||||
* <li> sparc v9 (64 bits) Solaris
|
||||
* </ul>
|
||||
* <p>But it should work on other environments too.</p>
|
||||
* <p>Typical usage:</p>
|
||||
* <pre>
|
||||
* 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));
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
package org.rrd4j.core.jrrd;
|
4
apps/jrobin/java/src/org/rrd4j/core/package-info.java
Normal file
4
apps/jrobin/java/src/org/rrd4j/core/package-info.java
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* core RRD4J implementation.
|
||||
*/
|
||||
package org.rrd4j.core;
|
179
apps/jrobin/java/src/org/rrd4j/core/timespec/Epoch.java
Normal file
179
apps/jrobin/java/src/org/rrd4j/core/timespec/Epoch.java
Normal file
@ -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;
|
||||
|
||||
/**
|
||||
* <p>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:</p>
|
||||
* <ul>
|
||||
* <li>MM/dd/yy HH:mm:ss
|
||||
* <li>dd.MM.yy HH:mm:ss
|
||||
* <li>dd.MM.yy HH:mm:ss
|
||||
* <li>MM/dd/yy HH:mm
|
||||
* <li>dd.MM.yy HH:mm
|
||||
* <li>yy-MM-dd HH:mm
|
||||
* <li>MM/dd/yy
|
||||
* <li>dd.MM.yy
|
||||
* <li>yy-MM-dd
|
||||
* <li>HH:mm MM/dd/yy
|
||||
* <li>HH:mm dd.MM.yy
|
||||
* <li>HH:mm yy-MM-dd
|
||||
* <li>HH:mm:ss MM/dd/yy
|
||||
* <li>HH:mm:ss dd.MM.yy
|
||||
* <li>HH:mm:ss yy-MM-dd
|
||||
* </ul>
|
||||
* 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<SimpleDateFormat>[] parsers = new ThreadLocal[supportedFormats.length];
|
||||
private static final String helpText;
|
||||
|
||||
private Timer timer = new Timer(1000, new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
showTimestamp();
|
||||
}
|
||||
});
|
||||
|
||||
static {
|
||||
for (int i = 0; i < parsers.length; i++) {
|
||||
final String format = supportedFormats[i];
|
||||
parsers[i] = new ThreadLocal<SimpleDateFormat>() {
|
||||
@Override
|
||||
protected SimpleDateFormat initialValue() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format);
|
||||
sdf.setLenient(true);
|
||||
return sdf;
|
||||
}
|
||||
};
|
||||
}
|
||||
StringBuilder tooltipBuff = new StringBuilder("<html><b>Supported input formats:</b><br>");
|
||||
for (String supportedFormat : supportedFormats) {
|
||||
tooltipBuff.append(supportedFormat).append("<br>");
|
||||
}
|
||||
tooltipBuff.append("<b>AT-style time specification</b><br>");
|
||||
tooltipBuff.append("timestamp<br><br>");
|
||||
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.</html>");
|
||||
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<SimpleDateFormat> OUTPUT_DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
|
||||
@Override
|
||||
protected SimpleDateFormat initialValue() {
|
||||
return new SimpleDateFormat("MM/dd/yy HH:mm:ss EEE");
|
||||
}
|
||||
};
|
||||
|
||||
Epoch() {
|
||||
super("Epoch");
|
||||
constructUI();
|
||||
timer.start();
|
||||
}
|
||||
|
||||
private void constructUI() {
|
||||
JPanel c = (JPanel) getContentPane();
|
||||
c.setLayout(new BorderLayout(3, 3));
|
||||
c.add(topLabel, BorderLayout.NORTH);
|
||||
c.add(inputField, BorderLayout.WEST);
|
||||
c.add(convertButton, BorderLayout.CENTER);
|
||||
convertButton.setToolTipText(helpText);
|
||||
convertButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
convert();
|
||||
}
|
||||
});
|
||||
c.add(helpButton, BorderLayout.EAST);
|
||||
helpButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JOptionPane.showMessageDialog(helpButton, helpText, "Epoch Help", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
inputField.requestFocus();
|
||||
getRootPane().setDefaultButton(convertButton);
|
||||
setResizable(false);
|
||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
pack();
|
||||
centerOnScreen();
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
void centerOnScreen() {
|
||||
Toolkit t = Toolkit.getDefaultToolkit();
|
||||
Dimension screenSize = t.getScreenSize();
|
||||
Dimension frameSize = getPreferredSize();
|
||||
double x = (screenSize.getWidth() - frameSize.getWidth()) / 2;
|
||||
double y = (screenSize.getHeight() - frameSize.getHeight()) / 2;
|
||||
setLocation((int) x, (int) y);
|
||||
}
|
||||
|
||||
private void convert() {
|
||||
String time = inputField.getText().trim();
|
||||
if (time.length() > 0) {
|
||||
// try simple timestamp
|
||||
try {
|
||||
long timestamp = Long.parseLong(time);
|
||||
Date date = new Date(timestamp * 1000L);
|
||||
formatDate(date);
|
||||
}
|
||||
catch (NumberFormatException nfe) {
|
||||
// failed, try as a date
|
||||
try {
|
||||
inputField.setText(Long.toString(parseDate(time)));
|
||||
}
|
||||
catch (Exception e) {
|
||||
inputField.setText("Could not convert, sorry");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showTimestamp() {
|
||||
long timestamp = Util.getTime();
|
||||
setTitle(timestamp + " seconds since epoch");
|
||||
}
|
||||
|
||||
void formatDate(Date date) {
|
||||
inputField.setText(OUTPUT_DATE_FORMAT.get().format(date));
|
||||
}
|
||||
|
||||
private long parseDate(String time) {
|
||||
for (ThreadLocal<SimpleDateFormat> parser : parsers) {
|
||||
try {
|
||||
return Util.getTimestamp(parser.get().parse(time));
|
||||
}
|
||||
catch (ParseException e) {
|
||||
}
|
||||
}
|
||||
return new TimeParser(time).parse().getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method which runs this utility.
|
||||
*
|
||||
* @param args Not used.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
new Epoch();
|
||||
}
|
||||
}
|
404
apps/jrobin/java/src/org/rrd4j/core/timespec/TimeParser.java
Normal file
404
apps/jrobin/java/src/org/rrd4j/core/timespec/TimeParser.java
Normal file
@ -0,0 +1,404 @@
|
||||
package org.rrd4j.core.timespec;
|
||||
|
||||
import org.rrd4j.core.Util;
|
||||
|
||||
/**
|
||||
* Class which parses at-style time specification (described in detail on the rrdfetch man page),
|
||||
* used in all RRDTool commands. This code is in most parts just a java port of Tobi's parsetime.c
|
||||
* code.
|
||||
*
|
||||
*/
|
||||
public class TimeParser {
|
||||
private static final int PREVIOUS_OP = -1;
|
||||
|
||||
TimeToken token;
|
||||
TimeScanner scanner;
|
||||
TimeSpec spec;
|
||||
|
||||
int op = TimeToken.PLUS;
|
||||
int prev_multiplier = -1;
|
||||
|
||||
/**
|
||||
* Constructs TimeParser instance from the given input string.
|
||||
*
|
||||
* @param dateString at-style time specification (read rrdfetch man page
|
||||
* for the complete explanation)
|
||||
*/
|
||||
public TimeParser(String dateString) {
|
||||
scanner = new TimeScanner(dateString);
|
||||
spec = new TimeSpec(dateString);
|
||||
}
|
||||
|
||||
private void expectToken(int desired, String errorMessage) {
|
||||
token = scanner.nextToken();
|
||||
if (token.token_id != desired) {
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void plusMinus(int doop) {
|
||||
if (doop >= 0) {
|
||||
op = doop;
|
||||
expectToken(TimeToken.NUMBER, "There should be number after " +
|
||||
(op == TimeToken.PLUS ? '+' : '-'));
|
||||
prev_multiplier = -1; /* reset months-minutes guessing mechanics */
|
||||
}
|
||||
int delta = Integer.parseInt(token.value);
|
||||
token = scanner.nextToken();
|
||||
if (token.token_id == TimeToken.MONTHS_MINUTES) {
|
||||
/* hard job to guess what does that -5m means: -5mon or -5min? */
|
||||
switch (prev_multiplier) {
|
||||
case TimeToken.DAYS:
|
||||
case TimeToken.WEEKS:
|
||||
case TimeToken.MONTHS:
|
||||
case TimeToken.YEARS:
|
||||
token = scanner.resolveMonthsMinutes(TimeToken.MONTHS);
|
||||
break;
|
||||
case TimeToken.SECONDS:
|
||||
case TimeToken.MINUTES:
|
||||
case TimeToken.HOURS:
|
||||
token = scanner.resolveMonthsMinutes(TimeToken.MINUTES);
|
||||
break;
|
||||
default:
|
||||
if (delta < 6) {
|
||||
token = scanner.resolveMonthsMinutes(TimeToken.MONTHS);
|
||||
}
|
||||
else {
|
||||
token = scanner.resolveMonthsMinutes(TimeToken.MINUTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
prev_multiplier = token.token_id;
|
||||
delta *= (op == TimeToken.PLUS) ? +1 : -1;
|
||||
switch (token.token_id) {
|
||||
case TimeToken.YEARS:
|
||||
spec.dyear += delta;
|
||||
return;
|
||||
case TimeToken.MONTHS:
|
||||
spec.dmonth += delta;
|
||||
return;
|
||||
case TimeToken.WEEKS:
|
||||
delta *= 7;
|
||||
/* FALLTHRU */
|
||||
case TimeToken.DAYS:
|
||||
spec.dday += delta;
|
||||
return;
|
||||
case TimeToken.HOURS:
|
||||
spec.dhour += delta;
|
||||
return;
|
||||
case TimeToken.MINUTES:
|
||||
spec.dmin += delta;
|
||||
return;
|
||||
case TimeToken.SECONDS:
|
||||
default: // default is 'seconds'
|
||||
spec.dsec += delta;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and read a "timeofday" specification. This method will be called
|
||||
* when we see a plain number at the start of a time, which means we could be
|
||||
* reading a time, or a day. If it turns out to be a date, then this method restores
|
||||
* the scanner state to what it was at entry, and returns without setting anything.
|
||||
*/
|
||||
private void timeOfDay() {
|
||||
int hour, minute = 0;
|
||||
/* save token status in case we must abort */
|
||||
scanner.saveState();
|
||||
/* first pick out the time of day - we assume a HH (COLON|DOT) MM time */
|
||||
if (token.value.length() > 2) {
|
||||
//Definitely not an hour specification; probably a date or something. Give up now
|
||||
return;
|
||||
}
|
||||
hour = Integer.parseInt(token.value);
|
||||
token = scanner.nextToken();
|
||||
if (token.token_id == TimeToken.SLASH) {
|
||||
/* guess we are looking at a date */
|
||||
token = scanner.restoreState();
|
||||
return;
|
||||
}
|
||||
if (token.token_id == TimeToken.COLON || token.token_id == TimeToken.DOT) {
|
||||
expectToken(TimeToken.NUMBER, "Parsing HH:MM or HH.MM syntax, expecting MM as number, got none");
|
||||
minute = Integer.parseInt(token.value);
|
||||
if (minute > 59) {
|
||||
throw new IllegalArgumentException("Parsing HH:MM or HH.MM syntax, got MM = " +
|
||||
minute + " (>59!)");
|
||||
}
|
||||
token = scanner.nextToken();
|
||||
if(token.token_id == TimeToken.DOT) {
|
||||
//Oh look, another dot; must have actually been a date in DD.MM.YYYY format. Give up and return
|
||||
token = scanner.restoreState();
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* check if an AM or PM specifier was given */
|
||||
if (token.token_id == TimeToken.AM || token.token_id == TimeToken.PM) {
|
||||
if (hour > 12) {
|
||||
throw new IllegalArgumentException("There cannot be more than 12 AM or PM hours");
|
||||
}
|
||||
if (token.token_id == TimeToken.PM) {
|
||||
if (hour != 12) {
|
||||
/* 12:xx PM is 12:xx, not 24:xx */
|
||||
hour += 12;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (hour == 12) {
|
||||
/* 12:xx AM is 00:xx, not 12:xx */
|
||||
hour = 0;
|
||||
}
|
||||
}
|
||||
token = scanner.nextToken();
|
||||
}
|
||||
else if (hour > 23) {
|
||||
/* guess it was not a time then, probably a date ... */
|
||||
token = scanner.restoreState();
|
||||
return;
|
||||
}
|
||||
spec.hour = hour;
|
||||
spec.min = minute;
|
||||
spec.sec = 0;
|
||||
if (spec.hour == 24) {
|
||||
spec.hour = 0;
|
||||
spec.day++;
|
||||
}
|
||||
}
|
||||
|
||||
private void assignDate(long mday, long mon, long year) {
|
||||
if (year > 138) {
|
||||
if (year > 1970) {
|
||||
year -= 1900;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Invalid year " + year + " (should be either 00-99 or >1900)");
|
||||
}
|
||||
}
|
||||
else if (year >= 0 && year < 38) {
|
||||
year += 100; /* Allow year 2000-2037 to be specified as */
|
||||
} /* 00-37 until the problem of 2038 year will */
|
||||
/* arise for unices with 32-bit time_t */
|
||||
if (year < 70) {
|
||||
throw new IllegalArgumentException("Won't handle dates before epoch (01/01/1970), sorry");
|
||||
}
|
||||
spec.year = (int) year;
|
||||
spec.month = (int) mon;
|
||||
spec.day = (int) mday;
|
||||
}
|
||||
|
||||
private void day() {
|
||||
long mday = 0, wday, mon, year = spec.year;
|
||||
switch (token.token_id) {
|
||||
case TimeToken.YESTERDAY:
|
||||
spec.day--;
|
||||
/* FALLTRHU */
|
||||
case TimeToken.TODAY: /* force ourselves to stay in today - no further processing */
|
||||
token = scanner.nextToken();
|
||||
break;
|
||||
case TimeToken.TOMORROW:
|
||||
spec.day++;
|
||||
token = scanner.nextToken();
|
||||
break;
|
||||
case TimeToken.JAN:
|
||||
case TimeToken.FEB:
|
||||
case TimeToken.MAR:
|
||||
case TimeToken.APR:
|
||||
case TimeToken.MAY:
|
||||
case TimeToken.JUN:
|
||||
case TimeToken.JUL:
|
||||
case TimeToken.AUG:
|
||||
case TimeToken.SEP:
|
||||
case TimeToken.OCT:
|
||||
case TimeToken.NOV:
|
||||
case TimeToken.DEC:
|
||||
/* do month mday [year] */
|
||||
mon = (token.token_id - TimeToken.JAN);
|
||||
expectToken(TimeToken.NUMBER, "the day of the month should follow month name");
|
||||
mday = Long.parseLong(token.value);
|
||||
token = scanner.nextToken();
|
||||
if (token.token_id == TimeToken.NUMBER) {
|
||||
year = Long.parseLong(token.value);
|
||||
token = scanner.nextToken();
|
||||
}
|
||||
else {
|
||||
year = spec.year;
|
||||
}
|
||||
assignDate(mday, mon, year);
|
||||
break;
|
||||
case TimeToken.SUN:
|
||||
case TimeToken.MON:
|
||||
case TimeToken.TUE:
|
||||
case TimeToken.WED:
|
||||
case TimeToken.THU:
|
||||
case TimeToken.FRI:
|
||||
case TimeToken.SAT:
|
||||
/* do a particular day of the week */
|
||||
wday = (token.token_id - TimeToken.SUN);
|
||||
spec.day += (wday - spec.wday);
|
||||
token = scanner.nextToken();
|
||||
break;
|
||||
case TimeToken.NUMBER:
|
||||
/* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY */
|
||||
mon = Long.parseLong(token.value);
|
||||
if (mon > 10L * 365L * 24L * 60L * 60L) {
|
||||
spec.localtime(mon);
|
||||
token = scanner.nextToken();
|
||||
break;
|
||||
}
|
||||
if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */
|
||||
year = mon / 10000;
|
||||
mday = mon % 100;
|
||||
mon = (mon / 100) % 100;
|
||||
token = scanner.nextToken();
|
||||
}
|
||||
else {
|
||||
token = scanner.nextToken();
|
||||
if (mon <= 31 && (token.token_id == TimeToken.SLASH || token.token_id == TimeToken.DOT)) {
|
||||
int sep = token.token_id;
|
||||
expectToken(TimeToken.NUMBER, "there should be " +
|
||||
(sep == TimeToken.DOT ? "month" : "day") +
|
||||
" number after " +
|
||||
(sep == TimeToken.DOT ? '.' : '/'));
|
||||
mday = Long.parseLong(token.value);
|
||||
token = scanner.nextToken();
|
||||
if (token.token_id == sep) {
|
||||
expectToken(TimeToken.NUMBER, "there should be year number after " +
|
||||
(sep == TimeToken.DOT ? '.' : '/'));
|
||||
year = Long.parseLong(token.value);
|
||||
token = scanner.nextToken();
|
||||
}
|
||||
/* flip months and days for European timing */
|
||||
if (sep == TimeToken.DOT) {
|
||||
long x = mday;
|
||||
mday = mon;
|
||||
mon = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
mon--;
|
||||
if (mon < 0 || mon > 11) {
|
||||
throw new IllegalArgumentException("Did you really mean month " + (mon + 1));
|
||||
}
|
||||
if (mday < 1 || mday > 31) {
|
||||
throw new IllegalArgumentException("I'm afraid that " + mday + " is not a valid day of the month");
|
||||
}
|
||||
assignDate(mday, mon, year);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the input string specified in the constructor.
|
||||
*
|
||||
* @return Object representing parsed date/time.
|
||||
*/
|
||||
public TimeSpec parse() {
|
||||
long now = Util.getTime();
|
||||
int hr = 0;
|
||||
/* this MUST be initialized to zero for midnight/noon/teatime */
|
||||
/* establish the default time reference */
|
||||
spec.localtime(now);
|
||||
token = scanner.nextToken();
|
||||
switch (token.token_id) {
|
||||
case TimeToken.PLUS:
|
||||
case TimeToken.MINUS:
|
||||
break; /* jump to OFFSET-SPEC part */
|
||||
case TimeToken.START:
|
||||
spec.type = TimeSpec.TYPE_START;
|
||||
/* FALLTHRU */
|
||||
case TimeToken.END:
|
||||
if (spec.type != TimeSpec.TYPE_START) {
|
||||
spec.type = TimeSpec.TYPE_END;
|
||||
}
|
||||
spec.year = spec.month = spec.day = spec.hour = spec.min = spec.sec = 0;
|
||||
/* FALLTHRU */
|
||||
case TimeToken.NOW:
|
||||
int time_reference = token.token_id;
|
||||
token = scanner.nextToken();
|
||||
if (token.token_id == TimeToken.PLUS || token.token_id == TimeToken.MINUS) {
|
||||
break;
|
||||
}
|
||||
if (time_reference != TimeToken.NOW) {
|
||||
throw new IllegalArgumentException("Words 'start' or 'end' MUST be followed by +|- offset");
|
||||
}
|
||||
else if (token.token_id != TimeToken.EOF) {
|
||||
throw new IllegalArgumentException("If 'now' is followed by a token it must be +|- offset");
|
||||
}
|
||||
break;
|
||||
/* Only absolute time specifications below */
|
||||
case TimeToken.NUMBER:
|
||||
timeOfDay();
|
||||
/* fix month parsing */
|
||||
case TimeToken.JAN:
|
||||
case TimeToken.FEB:
|
||||
case TimeToken.MAR:
|
||||
case TimeToken.APR:
|
||||
case TimeToken.MAY:
|
||||
case TimeToken.JUN:
|
||||
case TimeToken.JUL:
|
||||
case TimeToken.AUG:
|
||||
case TimeToken.SEP:
|
||||
case TimeToken.OCT:
|
||||
case TimeToken.NOV:
|
||||
case TimeToken.DEC:
|
||||
case TimeToken.TODAY:
|
||||
case TimeToken.YESTERDAY:
|
||||
case TimeToken.TOMORROW:
|
||||
day();
|
||||
if (token.token_id != TimeToken.NUMBER) {
|
||||
break;
|
||||
}
|
||||
//Allows (but does not require) the time to be specified after the day. This extends the rrdfetch specification
|
||||
timeOfDay();
|
||||
break;
|
||||
|
||||
/* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized
|
||||
* hr to zero up above, then fall into this case in such a
|
||||
* way so we add +12 +4 hours to it for teatime, +12 hours
|
||||
* to it for noon, and nothing at all for midnight, then
|
||||
* set our rettime to that hour before leaping into the
|
||||
* month scanner
|
||||
*/
|
||||
case TimeToken.TEATIME:
|
||||
hr += 4;
|
||||
/* FALLTHRU */
|
||||
case TimeToken.NOON:
|
||||
hr += 12;
|
||||
/* FALLTHRU */
|
||||
case TimeToken.MIDNIGHT:
|
||||
spec.hour = hr;
|
||||
spec.min = 0;
|
||||
spec.sec = 0;
|
||||
token = scanner.nextToken();
|
||||
day();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unparsable time: " + token.value);
|
||||
}
|
||||
|
||||
/*
|
||||
* the OFFSET-SPEC part
|
||||
*
|
||||
* (NOTE, the sc_tokid was prefetched for us by the previous code)
|
||||
*/
|
||||
if (token.token_id == TimeToken.PLUS || token.token_id == TimeToken.MINUS) {
|
||||
scanner.setContext(false);
|
||||
while (token.token_id == TimeToken.PLUS || token.token_id == TimeToken.MINUS ||
|
||||
token.token_id == TimeToken.NUMBER) {
|
||||
if (token.token_id == TimeToken.NUMBER) {
|
||||
plusMinus(PREVIOUS_OP);
|
||||
}
|
||||
else {
|
||||
plusMinus(token.token_id);
|
||||
}
|
||||
token = scanner.nextToken();
|
||||
/* We will get EOF eventually but that's OK, since
|
||||
token() will return us as many EOFs as needed */
|
||||
}
|
||||
}
|
||||
/* now we should be at EOF */
|
||||
if (token.token_id != TimeToken.EOF) {
|
||||
throw new IllegalArgumentException("Unparsable trailing text: " + token.value);
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
}
|
191
apps/jrobin/java/src/org/rrd4j/core/timespec/TimeScanner.java
Normal file
191
apps/jrobin/java/src/org/rrd4j/core/timespec/TimeScanner.java
Normal file
@ -0,0 +1,191 @@
|
||||
package org.rrd4j.core.timespec;
|
||||
|
||||
class TimeScanner {
|
||||
private String dateString;
|
||||
|
||||
private int pos, pos_save;
|
||||
private TimeToken token, token_save;
|
||||
|
||||
static final TimeToken[] WORDS = {
|
||||
new TimeToken("midnight", TimeToken.MIDNIGHT), /* 00:00:00 of today or tomorrow */
|
||||
new TimeToken("noon", TimeToken.NOON), /* 12:00:00 of today or tomorrow */
|
||||
new TimeToken("teatime", TimeToken.TEATIME), /* 16:00:00 of today or tomorrow */
|
||||
new TimeToken("am", TimeToken.AM), /* morning times for 0-12 clock */
|
||||
new TimeToken("pm", TimeToken.PM), /* evening times for 0-12 clock */
|
||||
new TimeToken("tomorrow", TimeToken.TOMORROW),
|
||||
new TimeToken("yesterday", TimeToken.YESTERDAY),
|
||||
new TimeToken("today", TimeToken.TODAY),
|
||||
new TimeToken("now", TimeToken.NOW),
|
||||
new TimeToken("n", TimeToken.NOW),
|
||||
new TimeToken("start", TimeToken.START),
|
||||
new TimeToken("s", TimeToken.START),
|
||||
new TimeToken("end", TimeToken.END),
|
||||
new TimeToken("e", TimeToken.END),
|
||||
new TimeToken("jan", TimeToken.JAN),
|
||||
new TimeToken("feb", TimeToken.FEB),
|
||||
new TimeToken("mar", TimeToken.MAR),
|
||||
new TimeToken("apr", TimeToken.APR),
|
||||
new TimeToken("may", TimeToken.MAY),
|
||||
new TimeToken("jun", TimeToken.JUN),
|
||||
new TimeToken("jul", TimeToken.JUL),
|
||||
new TimeToken("aug", TimeToken.AUG),
|
||||
new TimeToken("sep", TimeToken.SEP),
|
||||
new TimeToken("oct", TimeToken.OCT),
|
||||
new TimeToken("nov", TimeToken.NOV),
|
||||
new TimeToken("dec", TimeToken.DEC),
|
||||
new TimeToken("january", TimeToken.JAN),
|
||||
new TimeToken("february", TimeToken.FEB),
|
||||
new TimeToken("march", TimeToken.MAR),
|
||||
new TimeToken("april", TimeToken.APR),
|
||||
new TimeToken("may", TimeToken.MAY),
|
||||
new TimeToken("june", TimeToken.JUN),
|
||||
new TimeToken("july", TimeToken.JUL),
|
||||
new TimeToken("august", TimeToken.AUG),
|
||||
new TimeToken("september", TimeToken.SEP),
|
||||
new TimeToken("october", TimeToken.OCT),
|
||||
new TimeToken("november", TimeToken.NOV),
|
||||
new TimeToken("december", TimeToken.DEC),
|
||||
new TimeToken("sunday", TimeToken.SUN),
|
||||
new TimeToken("sun", TimeToken.SUN),
|
||||
new TimeToken("monday", TimeToken.MON),
|
||||
new TimeToken("mon", TimeToken.MON),
|
||||
new TimeToken("tuesday", TimeToken.TUE),
|
||||
new TimeToken("tue", TimeToken.TUE),
|
||||
new TimeToken("wednesday", TimeToken.WED),
|
||||
new TimeToken("wed", TimeToken.WED),
|
||||
new TimeToken("thursday", TimeToken.THU),
|
||||
new TimeToken("thu", TimeToken.THU),
|
||||
new TimeToken("friday", TimeToken.FRI),
|
||||
new TimeToken("fri", TimeToken.FRI),
|
||||
new TimeToken("saturday", TimeToken.SAT),
|
||||
new TimeToken("sat", TimeToken.SAT),
|
||||
new TimeToken(null, 0) /*** SENTINEL ***/
|
||||
};
|
||||
|
||||
static TimeToken[] MULTIPLIERS = {
|
||||
new TimeToken("second", TimeToken.SECONDS), /* seconds multiplier */
|
||||
new TimeToken("seconds", TimeToken.SECONDS), /* (pluralized) */
|
||||
new TimeToken("sec", TimeToken.SECONDS), /* (generic) */
|
||||
new TimeToken("s", TimeToken.SECONDS), /* (short generic) */
|
||||
new TimeToken("minute", TimeToken.MINUTES), /* minutes multiplier */
|
||||
new TimeToken("minutes", TimeToken.MINUTES), /* (pluralized) */
|
||||
new TimeToken("min", TimeToken.MINUTES), /* (generic) */
|
||||
new TimeToken("m", TimeToken.MONTHS_MINUTES), /* (short generic) */
|
||||
new TimeToken("hour", TimeToken.HOURS), /* hours ... */
|
||||
new TimeToken("hours", TimeToken.HOURS), /* (pluralized) */
|
||||
new TimeToken("hr", TimeToken.HOURS), /* (generic) */
|
||||
new TimeToken("h", TimeToken.HOURS), /* (short generic) */
|
||||
new TimeToken("day", TimeToken.DAYS), /* days ... */
|
||||
new TimeToken("days", TimeToken.DAYS), /* (pluralized) */
|
||||
new TimeToken("d", TimeToken.DAYS), /* (short generic) */
|
||||
new TimeToken("week", TimeToken.WEEKS), /* week ... */
|
||||
new TimeToken("weeks", TimeToken.WEEKS), /* (pluralized) */
|
||||
new TimeToken("wk", TimeToken.WEEKS), /* (generic) */
|
||||
new TimeToken("w", TimeToken.WEEKS), /* (short generic) */
|
||||
new TimeToken("month", TimeToken.MONTHS), /* week ... */
|
||||
new TimeToken("months", TimeToken.MONTHS), /* (pluralized) */
|
||||
new TimeToken("mon", TimeToken.MONTHS), /* (generic) */
|
||||
new TimeToken("year", TimeToken.YEARS), /* year ... */
|
||||
new TimeToken("years", TimeToken.YEARS), /* (pluralized) */
|
||||
new TimeToken("yr", TimeToken.YEARS), /* (generic) */
|
||||
new TimeToken("y", TimeToken.YEARS), /* (short generic) */
|
||||
new TimeToken(null, 0) /*** SENTINEL ***/
|
||||
};
|
||||
|
||||
TimeToken[] specials = WORDS;
|
||||
|
||||
/**
|
||||
* <p>Constructor for TimeScanner.</p>
|
||||
*
|
||||
* @param dateString The date as {@link java.lang.String} to parse.
|
||||
*/
|
||||
public TimeScanner(String dateString) {
|
||||
this.dateString = dateString;
|
||||
}
|
||||
|
||||
void setContext(boolean parsingWords) {
|
||||
specials = parsingWords ? WORDS : MULTIPLIERS;
|
||||
}
|
||||
|
||||
TimeToken nextToken() {
|
||||
StringBuilder buffer = new StringBuilder("");
|
||||
while (pos < dateString.length()) {
|
||||
char c = dateString.charAt(pos++);
|
||||
if (Character.isWhitespace(c) || c == '_' || c == ',') {
|
||||
continue;
|
||||
}
|
||||
buffer.append(c);
|
||||
if (Character.isDigit(c)) {
|
||||
// pick as many digits as possible
|
||||
while (pos < dateString.length()) {
|
||||
char next = dateString.charAt(pos);
|
||||
if (Character.isDigit(next)) {
|
||||
buffer.append(next);
|
||||
pos++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
String value = buffer.toString();
|
||||
return token = new TimeToken(value, TimeToken.NUMBER);
|
||||
}
|
||||
if (Character.isLetter(c)) {
|
||||
// pick as many letters as possible
|
||||
while (pos < dateString.length()) {
|
||||
char next = dateString.charAt(pos);
|
||||
if (Character.isLetter(next)) {
|
||||
buffer.append(next);
|
||||
pos++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
String value = buffer.toString();
|
||||
return token = new TimeToken(value, parseToken(value));
|
||||
}
|
||||
switch (c) {
|
||||
case ':':
|
||||
return token = new TimeToken(":", TimeToken.COLON);
|
||||
case '.':
|
||||
return token = new TimeToken(".", TimeToken.DOT);
|
||||
case '+':
|
||||
return token = new TimeToken("+", TimeToken.PLUS);
|
||||
case '-':
|
||||
return token = new TimeToken("-", TimeToken.MINUS);
|
||||
case '/':
|
||||
return token = new TimeToken("/", TimeToken.SLASH);
|
||||
default:
|
||||
pos--;
|
||||
return token = new TimeToken(null, TimeToken.EOF);
|
||||
}
|
||||
}
|
||||
return token = new TimeToken(null, TimeToken.EOF);
|
||||
}
|
||||
|
||||
TimeToken resolveMonthsMinutes(int newId) {
|
||||
assert token.token_id == TimeToken.MONTHS_MINUTES;
|
||||
assert newId == TimeToken.MONTHS || newId == TimeToken.MINUTES;
|
||||
return token = new TimeToken(token.value, newId);
|
||||
}
|
||||
|
||||
void saveState() {
|
||||
token_save = token;
|
||||
pos_save = pos;
|
||||
}
|
||||
|
||||
TimeToken restoreState() {
|
||||
pos = pos_save;
|
||||
return token = token_save;
|
||||
}
|
||||
|
||||
private int parseToken(String arg) {
|
||||
for (int i = 0; specials[i].value != null; i++) {
|
||||
if (specials[i].value.equalsIgnoreCase(arg)) {
|
||||
return specials[i].token_id;
|
||||
}
|
||||
}
|
||||
return TimeToken.ID;
|
||||
}
|
||||
}
|
140
apps/jrobin/java/src/org/rrd4j/core/timespec/TimeSpec.java
Normal file
140
apps/jrobin/java/src/org/rrd4j/core/timespec/TimeSpec.java
Normal file
@ -0,0 +1,140 @@
|
||||
package org.rrd4j.core.timespec;
|
||||
|
||||
import org.rrd4j.core.Util;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
/**
|
||||
* Simple class to represent time obtained by parsing at-style date specification (described
|
||||
* in detail on the rrdfetch man page. See javadoc for {@link org.rrd4j.core.timespec.TimeParser}
|
||||
* for more information.
|
||||
*
|
||||
*/
|
||||
public class TimeSpec {
|
||||
static final int TYPE_ABSOLUTE = 0;
|
||||
static final int TYPE_START = 1;
|
||||
static final int TYPE_END = 2;
|
||||
|
||||
int type = TYPE_ABSOLUTE;
|
||||
int year, month, day, hour, min, sec;
|
||||
int wday;
|
||||
int dyear, dmonth, dday, dhour, dmin, dsec;
|
||||
|
||||
String dateString;
|
||||
|
||||
TimeSpec context;
|
||||
|
||||
TimeSpec(String dateString) {
|
||||
this.dateString = dateString;
|
||||
}
|
||||
|
||||
void localtime(long timestamp) {
|
||||
GregorianCalendar date = new GregorianCalendar();
|
||||
date.setTime(new Date(timestamp * 1000L));
|
||||
year = date.get(Calendar.YEAR) - 1900;
|
||||
month = date.get(Calendar.MONTH);
|
||||
day = date.get(Calendar.DAY_OF_MONTH);
|
||||
hour = date.get(Calendar.HOUR_OF_DAY);
|
||||
min = date.get(Calendar.MINUTE);
|
||||
sec = date.get(Calendar.SECOND);
|
||||
wday = date.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY;
|
||||
}
|
||||
|
||||
GregorianCalendar getTime() {
|
||||
GregorianCalendar gc;
|
||||
// absolute time, this is easy
|
||||
if (type == TYPE_ABSOLUTE) {
|
||||
gc = new GregorianCalendar(year + 1900, month, day, hour, min, sec);
|
||||
}
|
||||
// relative time, we need a context to evaluate it
|
||||
else if (context != null && context.type == TYPE_ABSOLUTE) {
|
||||
gc = context.getTime();
|
||||
}
|
||||
// how would I guess what time it was?
|
||||
else {
|
||||
throw new IllegalStateException("Relative times like '" +
|
||||
dateString + "' require proper absolute context to be evaluated");
|
||||
}
|
||||
gc.add(Calendar.YEAR, dyear);
|
||||
gc.add(Calendar.MONTH, dmonth);
|
||||
gc.add(Calendar.DAY_OF_MONTH, dday);
|
||||
gc.add(Calendar.HOUR_OF_DAY, dhour);
|
||||
gc.add(Calendar.MINUTE, dmin);
|
||||
gc.add(Calendar.SECOND, dsec);
|
||||
return gc;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the corresponding timestamp (seconds since Epoch). Example:</p>
|
||||
* <pre>
|
||||
* TimeParser p = new TimeParser("now-1day");
|
||||
* TimeSpec ts = p.parse();
|
||||
* System.out.println("Timestamp was: " + ts.getTimestamp();
|
||||
* </pre>
|
||||
*
|
||||
* @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 + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Use this static method to resolve relative time references and obtain the corresponding
|
||||
* Calendar objects. Example:</p>
|
||||
* <pre>
|
||||
* 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);
|
||||
* </pre>
|
||||
*
|
||||
* @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()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Use this static method to resolve relative time references and obtain the corresponding
|
||||
* timestamps (seconds since epoch). Example:</p>
|
||||
* <pre>
|
||||
* 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);
|
||||
* </pre>
|
||||
*
|
||||
* @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])
|
||||
};
|
||||
}
|
||||
}
|
||||
|
121
apps/jrobin/java/src/org/rrd4j/core/timespec/TimeToken.java
Normal file
121
apps/jrobin/java/src/org/rrd4j/core/timespec/TimeToken.java
Normal file
@ -0,0 +1,121 @@
|
||||
package org.rrd4j.core.timespec;
|
||||
|
||||
class TimeToken {
|
||||
/** Constant <code>MIDNIGHT=1</code> */
|
||||
public static final int MIDNIGHT = 1;
|
||||
/** Constant <code>NOON=2</code> */
|
||||
public static final int NOON = 2;
|
||||
/** Constant <code>TEATIME=3</code> */
|
||||
public static final int TEATIME = 3;
|
||||
/** Constant <code>PM=4</code> */
|
||||
public static final int PM = 4;
|
||||
/** Constant <code>AM=5</code> */
|
||||
public static final int AM = 5;
|
||||
/** Constant <code>YESTERDAY=6</code> */
|
||||
public static final int YESTERDAY = 6;
|
||||
/** Constant <code>TODAY=7</code> */
|
||||
public static final int TODAY = 7;
|
||||
/** Constant <code>TOMORROW=8</code> */
|
||||
public static final int TOMORROW = 8;
|
||||
/** Constant <code>NOW=9</code> */
|
||||
public static final int NOW = 9;
|
||||
/** Constant <code>START=10</code> */
|
||||
public static final int START = 10;
|
||||
/** Constant <code>END=11</code> */
|
||||
public static final int END = 11;
|
||||
/** Constant <code>SECONDS=12</code> */
|
||||
public static final int SECONDS = 12;
|
||||
/** Constant <code>MINUTES=13</code> */
|
||||
public static final int MINUTES = 13;
|
||||
/** Constant <code>HOURS=14</code> */
|
||||
public static final int HOURS = 14;
|
||||
/** Constant <code>DAYS=15</code> */
|
||||
public static final int DAYS = 15;
|
||||
/** Constant <code>WEEKS=16</code> */
|
||||
public static final int WEEKS = 16;
|
||||
/** Constant <code>MONTHS=17</code> */
|
||||
public static final int MONTHS = 17;
|
||||
/** Constant <code>YEARS=18</code> */
|
||||
public static final int YEARS = 18;
|
||||
/** Constant <code>MONTHS_MINUTES=19</code> */
|
||||
public static final int MONTHS_MINUTES = 19;
|
||||
/** Constant <code>NUMBER=20</code> */
|
||||
public static final int NUMBER = 20;
|
||||
/** Constant <code>PLUS=21</code> */
|
||||
public static final int PLUS = 21;
|
||||
/** Constant <code>MINUS=22</code> */
|
||||
public static final int MINUS = 22;
|
||||
/** Constant <code>DOT=23</code> */
|
||||
public static final int DOT = 23;
|
||||
/** Constant <code>COLON=24</code> */
|
||||
public static final int COLON = 24;
|
||||
/** Constant <code>SLASH=25</code> */
|
||||
public static final int SLASH = 25;
|
||||
/** Constant <code>ID=26</code> */
|
||||
public static final int ID = 26;
|
||||
/** Constant <code>JUNK=27</code> */
|
||||
public static final int JUNK = 27;
|
||||
/** Constant <code>JAN=28</code> */
|
||||
public static final int JAN = 28;
|
||||
/** Constant <code>FEB=29</code> */
|
||||
public static final int FEB = 29;
|
||||
/** Constant <code>MAR=30</code> */
|
||||
public static final int MAR = 30;
|
||||
/** Constant <code>APR=31</code> */
|
||||
public static final int APR = 31;
|
||||
/** Constant <code>MAY=32</code> */
|
||||
public static final int MAY = 32;
|
||||
/** Constant <code>JUN=33</code> */
|
||||
public static final int JUN = 33;
|
||||
/** Constant <code>JUL=34</code> */
|
||||
public static final int JUL = 34;
|
||||
/** Constant <code>AUG=35</code> */
|
||||
public static final int AUG = 35;
|
||||
/** Constant <code>SEP=36</code> */
|
||||
public static final int SEP = 36;
|
||||
/** Constant <code>OCT=37</code> */
|
||||
public static final int OCT = 37;
|
||||
/** Constant <code>NOV=38</code> */
|
||||
public static final int NOV = 38;
|
||||
/** Constant <code>DEC=39</code> */
|
||||
public static final int DEC = 39;
|
||||
/** Constant <code>SUN=40</code> */
|
||||
public static final int SUN = 40;
|
||||
/** Constant <code>MON=41</code> */
|
||||
public static final int MON = 41;
|
||||
/** Constant <code>TUE=42</code> */
|
||||
public static final int TUE = 42;
|
||||
/** Constant <code>WED=43</code> */
|
||||
public static final int WED = 43;
|
||||
/** Constant <code>THU=44</code> */
|
||||
public static final int THU = 44;
|
||||
/** Constant <code>FRI=45</code> */
|
||||
public static final int FRI = 45;
|
||||
/** Constant <code>SAT=46</code> */
|
||||
public static final int SAT = 46;
|
||||
/** Constant <code>EOF=-1</code> */
|
||||
public static final int EOF = -1;
|
||||
|
||||
final String value; /* token name */
|
||||
final int token_id; /* token id */
|
||||
|
||||
/**
|
||||
* <p>Constructor for TimeToken.</p>
|
||||
*
|
||||
* @param value a {@link java.lang.String} object.
|
||||
* @param token_id a int.
|
||||
*/
|
||||
public TimeToken(String value, int token_id) {
|
||||
this.value = value;
|
||||
this.token_id = token_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>toString.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String toString() {
|
||||
return value + " [" + token_id + "]";
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
2020-02-25 zzz
|
||||
* Replace jrobin with rrd4j 3.5 (ticket #2684)
|
||||
* Graphs: Replace jrobin with rrd4j 3.5 (ticket #2684)
|
||||
* NetDB: Don't send 'fake hash' for exploration any more
|
||||
|
||||
* 2020-02-25 0.9.45 released
|
||||
|
||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
||||
/** deprecated */
|
||||
public final static String ID = "Monotone";
|
||||
public final static String VERSION = CoreVersion.VERSION;
|
||||
public final static long BUILD = 1;
|
||||
public final static long BUILD = 2;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
Reference in New Issue
Block a user