forked from I2P_Developers/i2p.i2p
RRD4J 3.6 (ticket #2716)
This commit is contained in:
@ -114,7 +114,8 @@ public abstract class ByteBufferBackend extends RrdBackend {
|
||||
*/
|
||||
protected synchronized void read(long offset, byte[] b) throws IOException {
|
||||
checkOffsetAndByteBuffer(offset);
|
||||
byteBuffer.get(b, (int) offset, b.length);
|
||||
byteBuffer.position((int)offset);
|
||||
byteBuffer.get(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -8,6 +8,7 @@ import java.io.OutputStream;
|
||||
import org.rrd4j.ConsolFun;
|
||||
import org.rrd4j.data.Aggregates;
|
||||
import org.rrd4j.data.DataProcessor;
|
||||
import org.rrd4j.data.Variable;
|
||||
|
||||
/**
|
||||
* Class used to represent data fetched from the RRD.
|
||||
@ -405,39 +406,40 @@ public class FetchData {
|
||||
*/
|
||||
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]);
|
||||
try (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(); // values
|
||||
writer.closeTag(); // row
|
||||
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();
|
||||
}
|
||||
writer.closeTag(); // data
|
||||
writer.closeTag(); // fetch_data
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dumps fetch data to file in XML format.
|
||||
*
|
||||
|
@ -339,10 +339,10 @@ public abstract class RrdBackend {
|
||||
/**
|
||||
* Extract a CharBuffer from the backend, used by readString
|
||||
*
|
||||
* @param offset
|
||||
* @param size
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @param offset the offset in the rrd
|
||||
* @param size the size of the buffer, in character
|
||||
* @return a new CharBuffer
|
||||
* @throws IOException if the read fails
|
||||
*/
|
||||
protected CharBuffer getCharBuffer(long offset, int size) throws IOException {
|
||||
ByteBuffer bbuf = ByteBuffer.allocate(size * 2);
|
||||
|
@ -256,10 +256,10 @@ public abstract class RrdBackendFactory implements Closeable {
|
||||
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
|
||||
* Try to detect an URI from a path. It's needed because of Microsoft Windows path that look's like an URI
|
||||
* and to URL-encode the path.
|
||||
*
|
||||
* @param rrdpath
|
||||
* @param rrdpath a file URI that can be a Windows path
|
||||
* @return an URI
|
||||
*/
|
||||
public static URI buildGenericUri(String rrdpath) {
|
||||
@ -380,9 +380,9 @@ public abstract class RrdBackendFactory implements Closeable {
|
||||
* <li>query and fragment is kept as is.
|
||||
* </ul>
|
||||
*
|
||||
* @param rootUri
|
||||
* @param uri
|
||||
* @param relative
|
||||
* @param rootUri the URI to match against
|
||||
* @param uri an URI that the current backend can handle.
|
||||
* @param relative if true, return an URI relative to the {@code rootUri}
|
||||
* @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) {
|
||||
@ -442,8 +442,8 @@ public abstract class RrdBackendFactory implements Closeable {
|
||||
/**
|
||||
* Transform an path in a valid URI for this backend.
|
||||
*
|
||||
* @param path
|
||||
* @return
|
||||
* @param path a path local to the current backend.
|
||||
* @return an URI that the current backend can handle.
|
||||
*/
|
||||
public URI getUri(String path) {
|
||||
URI rootUri = getRootUri();
|
||||
@ -561,7 +561,7 @@ public abstract class RrdBackendFactory implements Closeable {
|
||||
/**
|
||||
* A generic close handle, default implementation does nothing.
|
||||
* @since 3.4
|
||||
* @throws IOException
|
||||
* @throws IOException if the close fails
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
|
||||
|
@ -65,7 +65,7 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link RrdDb} instance.
|
||||
* Builds or imports a {@link RrdDb} instance.
|
||||
*
|
||||
* @return a new build RrdDb
|
||||
* @throws IOException in case of I/O error.
|
||||
@ -108,12 +108,12 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
|
||||
/**
|
||||
* Import an external rrd data, import definition must have been done using {@link #setExternalPath(String)}
|
||||
* or {@link #setImporter(DataImporter)}
|
||||
* or {@link #setImporter(DataImporter)}.<p>
|
||||
* It can be used when it's not need to keep a reference to the rrd.
|
||||
*
|
||||
* @throws IOException in case of I/O error.
|
||||
* @throws IllegalArgumentException if the builder settings were incomplete
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void doimport() throws IOException {
|
||||
if (rrdDef != null || (importer == null && externalPath == null)) {
|
||||
throw new IllegalArgumentException("Not an importing configuration");
|
||||
@ -128,8 +128,8 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
}
|
||||
try (DataImporter rrdImporter = resoleImporter(externalPath, importer)) {
|
||||
if (usePool) {
|
||||
RrdDb db = resolvePool(pool).requestRrdDb(rrdUri, factory, importer);
|
||||
resolvePool(pool).release(db);
|
||||
try (RrdDb db = resolvePool(pool).requestRrdDb(rrdUri, factory, importer)) {
|
||||
};
|
||||
} else {
|
||||
try (RrdDb db = new RrdDb(path, rrdUri, null, rrdImporter, factory, null)) {
|
||||
}
|
||||
@ -151,16 +151,29 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param factory The backend factory to use for that rrd.
|
||||
* @return the same builder.
|
||||
*/
|
||||
public Builder setBackendFactory(RrdBackendFactory factory) {
|
||||
this.factory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param readOnly true if the rrd is to be read only
|
||||
* @return the same builder.
|
||||
*/
|
||||
public Builder setReadOnly(boolean readOnly) {
|
||||
this.readOnly = readOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rrd as readonly
|
||||
*
|
||||
* @return the same builder.
|
||||
*/
|
||||
public Builder readOnly() {
|
||||
this.readOnly = true;
|
||||
return this;
|
||||
@ -174,7 +187,7 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
/**
|
||||
* Activate the pool usage
|
||||
*
|
||||
* @return
|
||||
* @return the same builder.
|
||||
*/
|
||||
public Builder usePool() {
|
||||
this.usePool = true;
|
||||
@ -185,14 +198,19 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
* Set the pool that will be used if {@link #usePool} is true. If not defined,
|
||||
* the singleton instance will be used.
|
||||
*
|
||||
* @param pool
|
||||
* @return
|
||||
* @param pool true if a pool is going to be used
|
||||
* @return the same builder.
|
||||
*/
|
||||
public Builder setPool(RrdDbPool pool) {
|
||||
this.pool = pool;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the builder will be used to import external data with a predefined source: XML or RRDTool.
|
||||
* @param externalPath an URI-like indication of RRD data to import
|
||||
* @return the same builder.
|
||||
*/
|
||||
public Builder setExternalPath(String externalPath) {
|
||||
this.externalPath = externalPath;
|
||||
this.importer = null;
|
||||
@ -201,6 +219,11 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the builder will be used to import external data with a custom source.
|
||||
* @param importer a custom import
|
||||
* @return the same builder.
|
||||
*/
|
||||
public Builder setImporter(DataImporter importer) {
|
||||
this.importer = importer;
|
||||
this.externalPath = null;
|
||||
@ -209,6 +232,12 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the builder will be used to import a RRDTool file.
|
||||
* @param externalPath the path to a RRDTool file
|
||||
* @return the same builder.
|
||||
* @throws IOException if the RRDTool file can‘t be read
|
||||
*/
|
||||
public Builder setRrdToolImporter(String externalPath) throws IOException {
|
||||
this.importer = new RrdToolReader(externalPath);
|
||||
this.externalPath = null;
|
||||
@ -217,6 +246,10 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rrdDef a {@link RrdDef} to a new rrd file.
|
||||
* @return the same builder.
|
||||
*/
|
||||
public Builder setRrdDef(RrdDef rrdDef) {
|
||||
this.rrdDef = rrdDef;
|
||||
this.importer = null;
|
||||
@ -439,6 +472,32 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Opens an existing RRD with read/write access.
|
||||
* The path will be parsed as an URI and checked against the active factories.
|
||||
* If it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
|
||||
*
|
||||
* @param path Path to existing RRD.
|
||||
* @return a {link RrdDb} opened with default settings
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public static RrdDb of(String path) throws IOException {
|
||||
return new RrdDb(path, null, false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Opens an existing RRD with read/write access.
|
||||
* The URI will checked against the active factories.
|
||||
* If it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
|
||||
*
|
||||
* @param uri URI to existing RRD.
|
||||
* @return a {link RrdDb} opened with default settings
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public static RrdDb of(URI uri) throws IOException {
|
||||
return new RrdDb(null, uri, false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Constructor used to open already existing RRD. The path will be parsed as an URI and checked against the active factories. If
|
||||
* it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
|
||||
@ -513,18 +572,6 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
this(path, null, false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Opens an existing RRD with read/write access.
|
||||
* The path will be parsed as an URI and checked against the active factories.
|
||||
* If it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
|
||||
*
|
||||
* @param path Path to existing RRD.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public static RrdDb of(String path) throws IOException {
|
||||
return new RrdDb(path, null, false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Constructor used to open already existing RRD. The URI will checked against the active factories. If
|
||||
* it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
|
||||
@ -539,18 +586,6 @@ public class RrdDb implements RrdUpdater<RrdDb>, Closeable {
|
||||
this(null, uri, false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Opens an existing RRD with read/write access.
|
||||
* The URI will checked against the active factories.
|
||||
* If it's a relative URI (no scheme given, or just a plain path), the default factory will be used.</p>
|
||||
*
|
||||
* @param uri URI to existing RRD.
|
||||
* @throws java.io.IOException Thrown in case of I/O error.
|
||||
*/
|
||||
public static RrdDb of(URI uri) throws IOException {
|
||||
return new RrdDb(null, uri, false, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used to open already existing RRD in R/W mode with a storage (backend) type
|
||||
* different from default.
|
||||
|
@ -1,15 +1,21 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
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.ExecutionException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* <p>This class should be used to synchronize access to RRD files
|
||||
@ -25,6 +31,12 @@ public class RrdDbPool {
|
||||
private RrdDbPoolSingletonHolder() {}
|
||||
}
|
||||
|
||||
private static class PoolFullException extends RuntimeException {
|
||||
PoolFullException() {
|
||||
super("", null, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -33,10 +45,10 @@ public class RrdDbPool {
|
||||
|
||||
/*
|
||||
* The RrdEntry stored in the pool can be of tree kind:
|
||||
* - null, the URI is available, just for it and play
|
||||
* - null, the URI is available, just take 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
|
||||
* meaning that some other thread is using it. Wait until the real entry is put back.
|
||||
* - placeholder is false, this is the active entry pointing to a RrdDb. It's
|
||||
* only used by the current thread.
|
||||
*
|
||||
*/
|
||||
@ -44,29 +56,33 @@ public class RrdDbPool {
|
||||
RrdDb rrdDb = null;
|
||||
int count = 0;
|
||||
final CountDownLatch waitempty;
|
||||
final CountDownLatch inuse;
|
||||
final ReentrantReadWriteLock inuse;
|
||||
final Lock lock;
|
||||
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);
|
||||
}
|
||||
RrdEntry(URI canonicalPath) throws InterruptedException {
|
||||
placeholder = false;
|
||||
uri = canonicalPath;
|
||||
inuse = new ReentrantReadWriteLock();
|
||||
lock = inuse.writeLock();
|
||||
waitempty = new CountDownLatch(1);
|
||||
}
|
||||
RrdEntry(RrdEntry parent) throws InterruptedException {
|
||||
assert ! parent.placeholder;
|
||||
placeholder = true;
|
||||
uri = parent.uri;
|
||||
inuse = null;
|
||||
lock = parent.inuse.readLock();
|
||||
waitempty = null;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.placeholder) {
|
||||
return "RrdEntry [inuse=" + inuse.getCount()+ ", uri=" + uri + "]";
|
||||
if (placeholder) {
|
||||
return String.format("RrdEntry [placeholder, uri=%s]", uri);
|
||||
} else {
|
||||
return "RrdEntry [rrdDb=" + rrdDb + ", count=" + count + ", uri=" + uri + "]";
|
||||
return String.format("RrdEntry [count=%d, rrdDb=%s, uri%s]", count, rrdDb, uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,21 +96,37 @@ public class RrdDbPool {
|
||||
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 Semaphore usage = new Semaphore(maxCapacity);
|
||||
private final ReentrantReadWriteLock.WriteLock usageWLock;
|
||||
private final ReentrantReadWriteLock.ReadLock usageRLock;
|
||||
private final Condition fullCondition;
|
||||
// Needed because external threads can detect waiting condition
|
||||
private final AtomicBoolean waitFull = new AtomicBoolean(false);
|
||||
|
||||
private final ConcurrentMap<URI, RrdEntry> pool = new ConcurrentHashMap<>(INITIAL_CAPACITY);
|
||||
|
||||
private final RrdBackendFactory defaultFactory;
|
||||
private RrdBackendFactory defaultFactory;
|
||||
|
||||
/**
|
||||
* Constructor for RrdDbPool.
|
||||
* @since 3.5
|
||||
*/
|
||||
public RrdDbPool() {
|
||||
defaultFactory = RrdBackendFactory.getDefaultFactory();
|
||||
this(RrdBackendFactory.getDefaultFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for RrdDbPool.
|
||||
* @param defaultFactory the default factory used when given simple path of a rrdDb.
|
||||
* @since 3.6
|
||||
*/
|
||||
public RrdDbPool(RrdBackendFactory defaultFactory) {
|
||||
this.defaultFactory = defaultFactory;
|
||||
ReentrantReadWriteLock usageLock = new ReentrantReadWriteLock(true);
|
||||
usageWLock = usageLock.writeLock();
|
||||
usageRLock = usageLock.readLock();
|
||||
fullCondition = usageWLock.newCondition();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,7 +135,7 @@ public class RrdDbPool {
|
||||
* @return Number of currently open RRD files held in the pool.
|
||||
*/
|
||||
public int getOpenFileCount() {
|
||||
return usage.get();
|
||||
return pool.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,9 +145,9 @@ public class RrdDbPool {
|
||||
*/
|
||||
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()]);
|
||||
Set<URI> uris = new HashSet<>(pool.size());
|
||||
pool.forEach((k,v) -> uris.add(k));
|
||||
return uris.toArray(new URI[uris.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,53 +157,84 @@ public class RrdDbPool {
|
||||
*/
|
||||
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()]);
|
||||
Set<String> uris = new HashSet<>(pool.size());
|
||||
pool.forEach((k,v) -> uris.add(k.getPath()));
|
||||
return uris.toArray(new String[uris.size()]);
|
||||
}
|
||||
|
||||
private RrdEntry getEntry(URI uri, boolean cancreate) throws InterruptedException {
|
||||
RrdEntry ref = null;
|
||||
try {
|
||||
CompletableFuture<RrdEntry> holder = new CompletableFuture<>();
|
||||
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();
|
||||
try {
|
||||
ref = pool.compute(uri, (u, e) -> {
|
||||
try {
|
||||
if (e == null) {
|
||||
if (cancreate) {
|
||||
usageRLock.lockInterruptibly();
|
||||
try {
|
||||
if (! usage.tryAcquire()) {
|
||||
throw new PoolFullException();
|
||||
} else {
|
||||
RrdEntry r = new RrdEntry(u);
|
||||
holder.complete(r);
|
||||
r.lock.lock();
|
||||
return new RrdEntry(r);
|
||||
}
|
||||
} finally {
|
||||
usageRLock.unlock();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown URI in pool: " + u);
|
||||
}
|
||||
} else {
|
||||
if (e.placeholder) {
|
||||
return e;
|
||||
} else {
|
||||
e.lock.lock();
|
||||
holder.complete(e);
|
||||
return new RrdEntry(e);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
holder.completeExceptionally(ex);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (PoolFullException e) {
|
||||
ref = null;
|
||||
try {
|
||||
usageWLock.lockInterruptibly();
|
||||
waitFull.set(true);
|
||||
fullCondition.await();
|
||||
} catch (InterruptedException ex) {
|
||||
holder.completeExceptionally(ex);
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
countLock.unlock();
|
||||
if (usageWLock.isHeldByCurrentThread()) {
|
||||
waitFull.set(false);
|
||||
usageWLock.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;
|
||||
if (ref != null && !holder.isDone()) {
|
||||
// Wait for a signal from the active entry, it's available
|
||||
ref.lock.lockInterruptibly();
|
||||
ref.lock.unlock();
|
||||
}
|
||||
} while (! holder.isDone());
|
||||
return holder.get();
|
||||
} catch (ExecutionException e) {
|
||||
InterruptedException ex = (InterruptedException) e.getCause();
|
||||
Thread.currentThread().interrupt();
|
||||
throw ex;
|
||||
} catch (InterruptedException | RuntimeException e) {
|
||||
// Oups we were interrupted, put everything back and go away
|
||||
passNext(ACTION.SWAP, ref);
|
||||
Thread.currentThread().interrupt();
|
||||
if (e instanceof InterruptedException) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@ -191,21 +254,26 @@ public class RrdDbPool {
|
||||
break;
|
||||
case DROP:
|
||||
o = pool.remove(e.uri);
|
||||
if(usage.decrementAndGet() < maxCapacity) {
|
||||
usage.release();
|
||||
assert o == null || o.placeholder;
|
||||
if (waitFull.get()) {
|
||||
try {
|
||||
countLock.lockInterruptibly();
|
||||
full.signalAll();
|
||||
countLock.unlock();
|
||||
usageWLock.lockInterruptibly();
|
||||
fullCondition.signalAll();
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new UndeclaredThrowableException(ex);
|
||||
} finally {
|
||||
if (usageWLock.isHeldByCurrentThread()) {
|
||||
usageWLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
assert o != e : String.format("Same entry, action=%s, entry=%s\n", a, e);
|
||||
assert o == null || ((e.placeholder && ! o.placeholder) || (o.placeholder && ! e.placeholder)) : String.format("Inconsistent entry, action=%s, in=%s out=%s\n", a, e, o);
|
||||
//task finished, waiting on a place holder can go on
|
||||
if(o != null) {
|
||||
o.inuse.countDown();
|
||||
}
|
||||
e.lock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,11 +282,12 @@ public class RrdDbPool {
|
||||
*
|
||||
* @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 a db 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
|
||||
// They can happens in case of failures or interruptions at wrong place
|
||||
if (rrdDb == null) {
|
||||
return;
|
||||
}
|
||||
@ -229,23 +298,23 @@ public class RrdDbPool {
|
||||
ref = getEntry(dburi, false);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("release interrupted for " + rrdDb, e);
|
||||
throw new IllegalStateException("Release interrupted for " + rrdDb.getPath(), e);
|
||||
}
|
||||
if (ref == null) {
|
||||
return;
|
||||
throw new IllegalStateException("Could not release [" + rrdDb.getPath() + "], not using pool for it");
|
||||
}
|
||||
if (ref.rrdDb == null) {
|
||||
passNext(ACTION.DROP, ref);
|
||||
throw new IllegalStateException("Could not release [" + rrdDb.getPath() + "], pool corruption");
|
||||
}
|
||||
|
||||
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();
|
||||
ref.rrdDb = null;
|
||||
} finally {
|
||||
passNext(ACTION.DROP, ref);
|
||||
//If someone is waiting for an empty entry, signal it
|
||||
@ -272,9 +341,7 @@ public class RrdDbPool {
|
||||
* @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);
|
||||
}
|
||||
@ -293,38 +360,12 @@ public class RrdDbPool {
|
||||
* @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
|
||||
@ -359,10 +400,78 @@ public class RrdDbPool {
|
||||
*/
|
||||
private RrdEntry requestEmpty(URI uri) throws InterruptedException, IOException {
|
||||
RrdEntry ref = waitEmpty(uri);
|
||||
ref.count = 1;
|
||||
return ref;
|
||||
}
|
||||
|
||||
RrdDb requestRrdDb(URI uri, RrdBackendFactory factory) throws IOException {
|
||||
uri = factory.getCanonicalUri(uri);
|
||||
RrdEntry ref = null;
|
||||
try {
|
||||
ref = getEntry(uri, true);
|
||||
|
||||
// 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;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("request interrupted for " + uri, e);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
ref.count = 1;
|
||||
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 {
|
||||
passNext(ACTION.SWAP, ref);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
ref.count = 1;
|
||||
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 {
|
||||
passNext(ACTION.SWAP, ref);
|
||||
}
|
||||
}
|
||||
|
||||
RrdDb requestRrdDb(URI uri, RrdBackendFactory backend, DataImporter importer) throws IOException {
|
||||
return requestRrdDb(RrdDb.getBuilder().setImporter(importer), uri, backend);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Requests a RrdDb reference for the given RRD file definition object.</p>
|
||||
* <ul>
|
||||
@ -377,34 +486,11 @@ public class RrdDbPool {
|
||||
* @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>
|
||||
@ -416,21 +502,17 @@ public class RrdDbPool {
|
||||
* 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>
|
||||
* <p>The path is transformed internally to an URI using the default factory of the pool.</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);
|
||||
URI uri = defaultFactory.getUri(path);
|
||||
return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, defaultFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -451,56 +533,57 @@ public class RrdDbPool {
|
||||
* @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);
|
||||
return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, RrdBackendFactory.findFactory(uri));
|
||||
}
|
||||
|
||||
private RrdDb requestRrdDb(RrdDb.Builder builder, URI uri, RrdBackendFactory backend)
|
||||
throws IOException {
|
||||
RrdEntry ref = null;
|
||||
uri = backend.getCanonicalUri(uri);
|
||||
/**
|
||||
* Sets the default factory to use when obtaining rrdDb from simple path and not URI.
|
||||
*
|
||||
* @param defaultFactory The factory to used.
|
||||
* @throws IllegalStateException if done will the pool is not empty or the thread was interrupted.
|
||||
*/
|
||||
public void setDefaultFactory(RrdBackendFactory defaultFactory) {
|
||||
try {
|
||||
ref = requestEmpty(uri);
|
||||
ref.rrdDb = builder.setPath(uri).setBackendFactory(backend).setPool(this).build();
|
||||
return ref.rrdDb;
|
||||
usageWLock.lockInterruptibly();
|
||||
if (usage.availablePermits() != maxCapacity) {
|
||||
throw new IllegalStateException("Can only be done on a empty pool");
|
||||
}
|
||||
this.defaultFactory = defaultFactory;
|
||||
} 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;
|
||||
throw new IllegalStateException("Factory not changed");
|
||||
} finally {
|
||||
if (ref != null) {
|
||||
passNext(ACTION.SWAP, ref);
|
||||
if (usageWLock.isHeldByCurrentThread()) {
|
||||
usageWLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
* @throws IllegalStateException if done will the pool is not empty or the thread was interrupted.
|
||||
*/
|
||||
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");
|
||||
usageWLock.lockInterruptibly();
|
||||
if (usage.availablePermits() != maxCapacity) {
|
||||
throw new IllegalStateException("Can only be done on a empty pool");
|
||||
}
|
||||
maxCapacity = newCapacity;
|
||||
usage = new Semaphore(maxCapacity);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("Resizing interrupted");
|
||||
} finally {
|
||||
usage.set(oldUsage);
|
||||
if (usageWLock.isHeldByCurrentThread()) {
|
||||
usageWLock.unlock();
|
||||
}
|
||||
}
|
||||
maxCapacity = newCapacity;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -509,7 +592,17 @@ public class RrdDbPool {
|
||||
* @return maximum number of simultaneously open RRD files
|
||||
*/
|
||||
public int getCapacity() {
|
||||
return maxCapacity;
|
||||
try {
|
||||
usageRLock.lockInterruptibly();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("Interrupted, can't get pool size");
|
||||
}
|
||||
try {
|
||||
return maxCapacity;
|
||||
} finally {
|
||||
usageRLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -545,18 +638,12 @@ public class RrdDbPool {
|
||||
RrdEntry ref = null;
|
||||
try {
|
||||
ref = getEntry(uri, false);
|
||||
if (ref == null)
|
||||
return 0;
|
||||
else {
|
||||
return ref.count;
|
||||
}
|
||||
return Optional.ofNullable(ref).map(e -> e.count).orElse(0);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("getOpenCount interrupted", e);
|
||||
} finally {
|
||||
if (ref != null) {
|
||||
passNext(ACTION.SWAP, ref);
|
||||
}
|
||||
passNext(ACTION.SWAP, ref);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ 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).
|
||||
@ -631,37 +631,37 @@ public class RrdDef {
|
||||
* @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());
|
||||
try (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.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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,8 +1,11 @@
|
||||
package org.rrd4j.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* An abstract backend factory which is used to store RRD data to ordinary files on the disk.
|
||||
@ -20,7 +23,7 @@ public abstract class RrdFileBackendFactory extends RrdBackendFactory {
|
||||
*/
|
||||
@Override
|
||||
protected boolean exists(String path) {
|
||||
return Util.fileExists(path);
|
||||
return Files.exists(Paths.get(path));
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@ -37,25 +40,27 @@ public abstract class RrdFileBackendFactory extends RrdBackendFactory {
|
||||
|
||||
@Override
|
||||
public URI getCanonicalUri(URI uri) {
|
||||
// Resolve only parent, to avoid failing if the file is missing
|
||||
Path file;
|
||||
try {
|
||||
if (uri.isOpaque()) {
|
||||
return new File(uri.getSchemeSpecificPart()).getCanonicalFile().toURI();
|
||||
} else if (uri.isAbsolute()) {
|
||||
return new File(uri).getCanonicalFile().toURI();
|
||||
if (uri.isOpaque() || uri.getScheme() == null) {
|
||||
file = Paths.get(uri.getSchemeSpecificPart());
|
||||
} else {
|
||||
return new File(uri.getPath()).getCanonicalFile().toURI();
|
||||
file = Paths.get(uri);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("can't get canonical URI from " + uri + ": " + e);
|
||||
Path parent = file.getParent().toRealPath();
|
||||
return parent.resolve(file.getFileName()).toUri();
|
||||
} catch (IOError | IOException e) {
|
||||
throw new IllegalArgumentException("can't get canonical URI from " + uri + ": " + e, 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);
|
||||
return Paths.get(path).normalize().toUri();
|
||||
} catch (IOError e) {
|
||||
throw new IllegalArgumentException("can't get URI from path " + path + ": " + e, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +69,7 @@ public abstract class RrdFileBackendFactory extends RrdBackendFactory {
|
||||
if (uri.isOpaque()) {
|
||||
return uri.getSchemeSpecificPart();
|
||||
} else if (uri.isAbsolute()) {
|
||||
return new File(uri).getPath();
|
||||
return Paths.get(uri).normalize().toString();
|
||||
} else {
|
||||
return uri.getPath();
|
||||
}
|
||||
|
@ -90,9 +90,9 @@ public class RrdNioBackendFactory extends RrdFileBackendFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RrdNioBackendFactory.
|
||||
* Creates a new RrdNioBackendFactory, using a default {@link RrdSyncThreadPool}.
|
||||
*
|
||||
* @param syncPeriod If syncPeriod is negative or 0, sync threads are disabled.
|
||||
* @param syncPeriod the sync period, in seconds. If negative or 0, sync threads are disabled.
|
||||
*/
|
||||
public RrdNioBackendFactory(int syncPeriod) {
|
||||
this(syncPeriod, syncPeriod > 0 ? DefaultSyncThreadPool.INSTANCE : null);
|
||||
@ -101,8 +101,8 @@ public class RrdNioBackendFactory extends RrdFileBackendFactory {
|
||||
/**
|
||||
* 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.
|
||||
* @param syncPeriod the sync period, in seconds.
|
||||
* @param syncPoolSize The number of threads to use to sync the mapped file to disk, if negative or 0, sync threads are disabled.
|
||||
*/
|
||||
public RrdNioBackendFactory(int syncPeriod, int syncPoolSize) {
|
||||
this(syncPeriod, syncPoolSize > 0 ? new RrdSyncThreadPool(syncPoolSize) : null);
|
||||
@ -111,7 +111,7 @@ public class RrdNioBackendFactory extends RrdFileBackendFactory {
|
||||
/**
|
||||
* Creates a new RrdNioBackendFactory.
|
||||
*
|
||||
* @param syncPeriod
|
||||
* @param syncPeriod the sync period, in seconds.
|
||||
* @param syncThreadPool If null, disable background sync threads
|
||||
*/
|
||||
public RrdNioBackendFactory(int syncPeriod, ScheduledExecutorService syncThreadPool) {
|
||||
@ -121,7 +121,7 @@ public class RrdNioBackendFactory extends RrdFileBackendFactory {
|
||||
/**
|
||||
* Creates a new RrdNioBackendFactory.
|
||||
*
|
||||
* @param syncPeriod
|
||||
* @param syncPeriod the sync period, in seconds.
|
||||
* @param syncThreadPool If null, disable background sync threads
|
||||
*/
|
||||
public RrdNioBackendFactory(int syncPeriod, RrdSyncThreadPool syncThreadPool) {
|
||||
|
@ -34,8 +34,8 @@ public class RrdSafeFileBackendFactory extends RrdRandomAccessFileBackendFactory
|
||||
|
||||
/**
|
||||
* Generate a factory with custom lock settings
|
||||
* @param lockWaitTime
|
||||
* @param lockRetryPeriod
|
||||
* @param lockWaitTime wait time in ms
|
||||
* @param lockRetryPeriod retry period in ms
|
||||
*/
|
||||
public RrdSafeFileBackendFactory(long lockWaitTime, long lockRetryPeriod) {
|
||||
this.lockWaitTime = lockWaitTime;
|
||||
|
@ -1,18 +1,18 @@
|
||||
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.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.rrd4j.ConsolFun;
|
||||
|
||||
/**
|
||||
* Class used to perform various complex operations on RRD files. Use an instance of the
|
||||
* RrdToolkit class to:
|
||||
@ -323,28 +323,14 @@ public class RrdToolkit {
|
||||
|
||||
private static void copyFile(String sourcePath, String destPath, boolean saveBackup)
|
||||
throws IOException {
|
||||
File source = new File(sourcePath);
|
||||
File dest = new File(destPath);
|
||||
|
||||
Path source = Paths.get(sourcePath);
|
||||
Path destination = Paths.get(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);
|
||||
}
|
||||
Files.move(destination, Paths.get(backupPath), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
private static String getBackupPath(String destPath) {
|
||||
@ -515,12 +501,6 @@ public class RrdToolkit {
|
||||
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
|
||||
|
@ -29,6 +29,7 @@ import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@ -236,6 +237,7 @@ public class Util {
|
||||
|
||||
/**
|
||||
* Returns timestamp (unix epoch) for the given year, month, day, hour and minute.
|
||||
* <p>The date is resolved in the current time zone</p>
|
||||
*
|
||||
* @param year Year
|
||||
* @param month Month (zero-based)
|
||||
@ -253,6 +255,7 @@ public class Util {
|
||||
|
||||
/**
|
||||
* Returns timestamp (unix epoch) for the given year, month and day.
|
||||
* <p>The date is resolved in the current time zone</p>
|
||||
*
|
||||
* @param year Year
|
||||
* @param month Month (zero-based)
|
||||
@ -403,8 +406,8 @@ public class Util {
|
||||
root = Paths.get(getUserHomeDirectory(), RRD4J_DIR);
|
||||
}
|
||||
try {
|
||||
Files.createDirectories(root);
|
||||
return root.toAbsolutePath().toString() + File.separator;
|
||||
root = Files.createDirectories(root.toAbsolutePath().normalize());
|
||||
return root.toString() + File.separator;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
@ -731,7 +734,7 @@ public class Util {
|
||||
* @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();
|
||||
return Paths.get(path).toRealPath().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -739,9 +742,27 @@ public class Util {
|
||||
*
|
||||
* @param file File object representing file on the disk
|
||||
* @return Last modification time in seconds (without milliseconds)
|
||||
* @deprecated use #getLastModifiedTime, that can throws exceptions if needed
|
||||
*/
|
||||
@Deprecated
|
||||
public static long getLastModified(String file) {
|
||||
return (new File(file).lastModified() + 500L) / 1000L;
|
||||
try {
|
||||
return Files.getLastModifiedTime(Paths.get(file)).to(TimeUnit.SECONDS);
|
||||
} catch (IOException e) {
|
||||
// For compatibility with old API
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* @throws IOException
|
||||
*/
|
||||
public static long getLastModifiedTime(String file) throws IOException {
|
||||
return Files.getLastModifiedTime(Paths.get(file)).to(TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -751,7 +772,7 @@ public class Util {
|
||||
* @return <code>true</code> if file exists, <code>false</code> otherwise
|
||||
*/
|
||||
public static boolean fileExists(String filename) {
|
||||
return new File(filename).exists();
|
||||
return Files.exists(Paths.get(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,17 +15,13 @@ import java.util.TimeZone;
|
||||
/**
|
||||
* Extremely simple utility class used to create XML documents.
|
||||
*/
|
||||
public class XmlWriter {
|
||||
public class XmlWriter implements AutoCloseable {
|
||||
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 static final SimpleDateFormat ISOLIKE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ", Locale.ENGLISH);
|
||||
static {
|
||||
ISOLIKE.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private final PrintWriter writer;
|
||||
private final StringBuilder indent = new StringBuilder("");
|
||||
@ -198,7 +194,9 @@ public class XmlWriter {
|
||||
*/
|
||||
public void writeComment(Object comment) {
|
||||
if (comment instanceof Date) {
|
||||
comment = ISOLIKE.get().format((Date) comment);
|
||||
synchronized (ISOLIKE) {
|
||||
comment = ISOLIKE.format((Date) comment);
|
||||
}
|
||||
}
|
||||
writer.println(indent + "<!-- " + escape(comment.toString()) + " -->");
|
||||
}
|
||||
@ -207,4 +205,10 @@ public class XmlWriter {
|
||||
return s.replaceAll("<", "<").replaceAll(">", ">");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,33 +35,34 @@ import java.util.Date;
|
||||
*
|
||||
*/
|
||||
public class Epoch extends JFrame {
|
||||
private static final String[] supportedFormats = {
|
||||
private 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 final SimpleDateFormat[] parsers = new SimpleDateFormat[supportedFormats.length];
|
||||
private final String helpText;
|
||||
|
||||
private Timer timer = new Timer(1000, new ActionListener() {
|
||||
private final Timer timer = new Timer(1000, new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
showTimestamp();
|
||||
}
|
||||
});
|
||||
|
||||
static {
|
||||
private final JLabel topLabel = new JLabel("Enter timestamp or readable date:");
|
||||
private final JTextField inputField = new JTextField(25);
|
||||
private final JButton convertButton = new JButton("Convert");
|
||||
private final JButton helpButton = new JButton("Help");
|
||||
|
||||
private final SimpleDateFormat OUTPUT_DATE_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss EEE");
|
||||
|
||||
private Epoch() {
|
||||
super("Epoch");
|
||||
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;
|
||||
}
|
||||
};
|
||||
String format = supportedFormats[i];
|
||||
parsers[i] = new SimpleDateFormat(format);
|
||||
parsers[i].setLenient(true);
|
||||
}
|
||||
StringBuilder tooltipBuff = new StringBuilder("<html><b>Supported input formats:</b><br>");
|
||||
for (String supportedFormat : supportedFormats) {
|
||||
@ -69,24 +70,8 @@ public class Epoch extends JFrame {
|
||||
}
|
||||
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>");
|
||||
tooltipBuff.append("Copyright (c) 2013-2020 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();
|
||||
}
|
||||
@ -118,7 +103,7 @@ public class Epoch extends JFrame {
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
void centerOnScreen() {
|
||||
private void centerOnScreen() {
|
||||
Toolkit t = Toolkit.getDefaultToolkit();
|
||||
Dimension screenSize = t.getScreenSize();
|
||||
Dimension frameSize = getPreferredSize();
|
||||
@ -153,14 +138,14 @@ public class Epoch extends JFrame {
|
||||
setTitle(timestamp + " seconds since epoch");
|
||||
}
|
||||
|
||||
void formatDate(Date date) {
|
||||
inputField.setText(OUTPUT_DATE_FORMAT.get().format(date));
|
||||
private synchronized void formatDate(Date date) {
|
||||
inputField.setText(OUTPUT_DATE_FORMAT.format(date));
|
||||
}
|
||||
|
||||
private long parseDate(String time) {
|
||||
for (ThreadLocal<SimpleDateFormat> parser : parsers) {
|
||||
private synchronized long parseDate(String time) {
|
||||
for (SimpleDateFormat parser : parsers) {
|
||||
try {
|
||||
return Util.getTimestamp(parser.get().parse(time));
|
||||
return Util.getTimestamp(parser.parse(time));
|
||||
}
|
||||
catch (ParseException e) {
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ public class DataProcessor {
|
||||
/**
|
||||
* Defines the {@link org.rrd4j.core.RrdDbPool RrdDbPool} to use. If not defined, but {{@link #setPoolUsed(boolean)}
|
||||
* set to true, the default {@link RrdDbPool#getInstance()} will be used.
|
||||
* @param pool
|
||||
* @param pool an optional pool to use.
|
||||
*/
|
||||
public void setPool(RrdDbPool pool) {
|
||||
this.pool = pool;
|
||||
|
@ -3,17 +3,14 @@ package org.rrd4j.graph;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Paint;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Stroke;
|
||||
import java.awt.TexturePaint;
|
||||
import java.awt.font.LineMetrics;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -28,8 +25,7 @@ import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
class ImageWorker {
|
||||
private static final String DUMMY_TEXT = "Dummy";
|
||||
|
||||
static final int IMG_BUFFER_CAPACITY = 10000; // bytes
|
||||
private static final int IMG_BUFFER_CAPACITY = 10000; // bytes
|
||||
|
||||
private BufferedImage img;
|
||||
private Graphics2D g2d;
|
||||
@ -224,16 +220,9 @@ class ImageWorker {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>loadImage.</p>
|
||||
*
|
||||
* @param imageFile a {@link java.lang.String} object.
|
||||
* @throws java.io.IOException if any.
|
||||
*/
|
||||
public void loadImage(String imageFile) throws IOException {
|
||||
BufferedImage wpImage = ImageIO.read(new File(imageFile));
|
||||
TexturePaint paint = new TexturePaint(wpImage, new Rectangle(0, 0, wpImage.getWidth(), wpImage.getHeight()));
|
||||
g2d.setPaint(paint);
|
||||
g2d.fillRect(0, 0, wpImage.getWidth(), wpImage.getHeight());
|
||||
void loadImage(RrdGraphDef.ImageSource imageSource, int x, int y, int w, int h) throws IOException {
|
||||
BufferedImage wpImage = imageSource.apply(w, h).getSubimage(0, 0, w, h);
|
||||
g2d.drawImage(wpImage, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
package org.rrd4j.graph;
|
||||
|
||||
import org.rrd4j.graph.ImageParameters;
|
||||
import org.rrd4j.graph.RrdGraphDef;
|
||||
|
||||
class Mapper {
|
||||
private final RrdGraphDef gdef;
|
||||
private final ImageParameters im;
|
||||
|
@ -175,7 +175,7 @@ public class RrdGraph implements RrdGraphConstants {
|
||||
|
||||
private void drawOverlay() throws IOException {
|
||||
if (gdef.overlayImage != null) {
|
||||
worker.loadImage(gdef.overlayImage);
|
||||
worker.loadImage(gdef.overlayImage, 0, 0, im.xgif, im.ygif);
|
||||
}
|
||||
}
|
||||
|
||||
@ -393,7 +393,10 @@ public class RrdGraph implements RrdGraphConstants {
|
||||
private void drawBackground() throws IOException {
|
||||
worker.fillRect(0, 0, im.xgif, im.ygif, gdef.getColor(ElementsNames.back));
|
||||
if (gdef.backgroundImage != null) {
|
||||
worker.loadImage(gdef.backgroundImage);
|
||||
worker.loadImage(gdef.backgroundImage, 0, 0, im.xgif, im.ygif);
|
||||
}
|
||||
if (gdef.canvasImage != null) {
|
||||
worker.loadImage(gdef.canvasImage, im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize);
|
||||
}
|
||||
worker.fillRect(im.xorigin, im.yorigin - im.ysize, im.xsize, im.ysize, gdef.getColor(ElementsNames.canvas));
|
||||
}
|
||||
@ -666,14 +669,14 @@ public class RrdGraph implements RrdGraphConstants {
|
||||
im.end = gdef.endTime;
|
||||
}
|
||||
|
||||
private boolean lazyCheck() {
|
||||
private boolean lazyCheck() throws IOException {
|
||||
// redraw if lazy option is not set or file does not exist
|
||||
if (!gdef.lazy || !Util.fileExists(gdef.filename)) {
|
||||
return false; // 'false' means 'redraw'
|
||||
}
|
||||
// redraw if not enough time has passed
|
||||
long secPerPixel = (gdef.endTime - gdef.startTime) / gdef.width;
|
||||
long elapsed = Util.getTimestamp() - Util.getLastModified(gdef.filename);
|
||||
long elapsed = Util.getTimestamp() - Util.getLastModifiedTime(gdef.filename);
|
||||
return elapsed <= secPerPixel;
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,18 @@ import java.awt.BasicStroke;
|
||||
import java.awt.Font;
|
||||
import java.awt.Paint;
|
||||
import java.awt.Stroke;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.rrd4j.ConsolFun;
|
||||
import org.rrd4j.core.FetchData;
|
||||
import org.rrd4j.core.RrdBackendFactory;
|
||||
@ -47,6 +53,49 @@ import org.rrd4j.data.Variable;
|
||||
* the string to disable the auto justification.</p>
|
||||
*/
|
||||
public class RrdGraphDef implements RrdGraphConstants {
|
||||
|
||||
/**
|
||||
* <p>Implementations of this class can be used to generate image than can be
|
||||
* layered on graph. The can be used for background image, a background image
|
||||
* draw on canvas or an overlay image.</p>
|
||||
* @author Fabrice Bacchella
|
||||
*
|
||||
*/
|
||||
public interface ImageSource {
|
||||
/**
|
||||
* A image of the required size that will be applied. If the generated image is too big, it will be clipped before being applied.
|
||||
* @param w the width of the requested image
|
||||
* @param h the high of the requested image
|
||||
* @return an image to draw.
|
||||
* @throws IOException
|
||||
*/
|
||||
BufferedImage apply(int w, int h) throws IOException;
|
||||
}
|
||||
|
||||
private static class FileImageSource implements ImageSource {
|
||||
private final File imagesource;
|
||||
|
||||
FileImageSource(String imagesource) {
|
||||
this.imagesource = new File(imagesource);
|
||||
}
|
||||
|
||||
public BufferedImage apply(int w, int h) throws IOException {
|
||||
return ImageIO.read(imagesource);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UrlImageSource implements ImageSource {
|
||||
private final URL imagesource;
|
||||
|
||||
private UrlImageSource(URL imagesource) {
|
||||
this.imagesource = imagesource;
|
||||
}
|
||||
|
||||
public BufferedImage apply(int w, int h) throws IOException {
|
||||
return ImageIO.read(imagesource);
|
||||
}
|
||||
}
|
||||
|
||||
boolean poolUsed = false; // ok
|
||||
boolean antiAliasing = false; // ok
|
||||
boolean textAntiAliasing = false; // ok
|
||||
@ -69,8 +118,9 @@ public class RrdGraphDef implements RrdGraphConstants {
|
||||
String imageInfo = null; // ok
|
||||
String imageFormat = DEFAULT_IMAGE_FORMAT; // ok
|
||||
float imageQuality = DEFAULT_IMAGE_QUALITY; // ok
|
||||
String backgroundImage = null; // ok
|
||||
String overlayImage = null; // ok
|
||||
ImageSource backgroundImage = null; // ok
|
||||
ImageSource canvasImage = null; // ok
|
||||
ImageSource overlayImage = null; // ok
|
||||
String unit = null; // ok
|
||||
boolean lazy = false; // ok
|
||||
double minValue = Double.NaN; // ok
|
||||
@ -466,6 +516,7 @@ public class RrdGraphDef implements RrdGraphConstants {
|
||||
|
||||
/**
|
||||
* Sets image format.
|
||||
* ImageIO is used to save the image, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
|
||||
*
|
||||
* @param imageFormat Any value as return by {@link javax.imageio.ImageIO#getReaderFormatNames}
|
||||
*/
|
||||
@ -474,22 +525,90 @@ public class RrdGraphDef implements RrdGraphConstants {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets background image - currently, only PNG images can be used as background.
|
||||
* Sets background image.
|
||||
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
|
||||
*
|
||||
* @param backgroundImage Path to background image
|
||||
*/
|
||||
public void setBackgroundImage(String backgroundImage) {
|
||||
this.backgroundImage = new FileImageSource(backgroundImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets background image.
|
||||
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
|
||||
*
|
||||
* @param backgroundImageUrl URL to background image
|
||||
*/
|
||||
public void setBackgroundImage(URL backgroundImageUrl) {
|
||||
this.backgroundImage = new UrlImageSource(backgroundImageUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets background image.
|
||||
*
|
||||
* @param backgroundImage An {@link ImageSource} that will provides a {@link BufferedImage}
|
||||
*/
|
||||
public void setBackgroundImage(ImageSource backgroundImage) {
|
||||
this.backgroundImage = backgroundImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets overlay image - currently, only PNG images can be used as overlay. Overlay image is
|
||||
* printed on the top of the image, once it is completely created.
|
||||
* Sets canvas background image. Canvas image is printed on canvas area, under canvas color and plot.
|
||||
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
|
||||
*
|
||||
* @param canvasImage Path to canvas image
|
||||
*/
|
||||
public void setCanvasImage(String canvasImage) {
|
||||
this.canvasImage = new FileImageSource(canvasImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets canvas background image. Canvas image is printed on canvas area, under canvas color and plot.
|
||||
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
|
||||
*
|
||||
* @param canvasUrl URL to canvas image
|
||||
*/
|
||||
public void setCanvasImage(URL canvasUrl) {
|
||||
this.canvasImage = new UrlImageSource(canvasUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets canvas background image. Canvas image is printed on canvas area, under canvas color and plot.
|
||||
*
|
||||
* @param canvasImageSource An {@link ImageSource} that will provides a {@link BufferedImage}
|
||||
*/
|
||||
public void setCanvasImage(ImageSource canvasImageSource) {
|
||||
this.canvasImage = canvasImageSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets overlay image. Overlay image is printed on the top of the image, once it is completely created.
|
||||
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
|
||||
*
|
||||
* @param overlayImage Path to overlay image
|
||||
*/
|
||||
public void setOverlayImage(String overlayImage) {
|
||||
this.overlayImage = overlayImage;
|
||||
this.overlayImage = new FileImageSource(overlayImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets overlay image. Overlay image is printed on the top of the image, once it is completely created.
|
||||
* ImageIO is used to download, so any supported format by ImageIO can be used, and it can be extended using https://github.com/geosolutions-it/imageio-ext.
|
||||
*
|
||||
* @param overlayImage URL to overlay image
|
||||
*/
|
||||
public void setOverlayImage(URL overlayImage) {
|
||||
this.overlayImage = new UrlImageSource(overlayImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets overlay image. Overlay image is printed on the top of the image, once it is completely created.
|
||||
*
|
||||
* @param overlayImageSource An {@link ImageSource} that will provides a {@link BufferedImage}
|
||||
*/
|
||||
public void setOverlayImage(ImageSource overlayImageSource) {
|
||||
this.overlayImage = overlayImageSource;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -598,8 +717,8 @@ public class RrdGraphDef implements RrdGraphConstants {
|
||||
|
||||
/**
|
||||
* Overrides the colors for the standard elements of the graph.
|
||||
* @param colorTag
|
||||
* @param color
|
||||
* @param colorTag The element to change color.
|
||||
* @param color The color of the element.
|
||||
*/
|
||||
public void setColor(ElementsNames colorTag, Paint color) {
|
||||
colors[colorTag.ordinal()] = color;
|
||||
|
Reference in New Issue
Block a user