RRD4J 3.6 (ticket #2716)

This commit is contained in:
zzz
2020-05-28 10:33:03 +00:00
parent 686fe88e61
commit 4fe9a27e2e
20 changed files with 644 additions and 416 deletions

View File

@ -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

View File

@ -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.
*

View File

@ -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);

View File

@ -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 {

View File

@ -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 cant 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.

View File

@ -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);
}
}

View File

@ -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();
}
/**

View File

@ -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();
}

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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));
}
/**

View File

@ -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("<", "&lt;").replaceAll(">", "&gt;");
}
@Override
public void close() {
writer.flush();
writer.close();
}
}

View File

@ -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) {
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;