diff --git a/LICENSE.txt b/LICENSE.txt index adb9fe164..45ebda19c 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -271,7 +271,7 @@ Applications: See licenses/LICENSE-Apache2.0.txt See licenses/LICENSE-ECLIPSE-1.0.html - RRD4J 3.6 (jrobin.jar): + RRD4J 3.8 (jrobin.jar): Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor. Copyright (c) 2011 The OpenNMS Group, Inc. Copyright 2011 The RRD4J Authors. diff --git a/apps/jrobin/java/src/org/rrd4j/core/DataHolder.java b/apps/jrobin/java/src/org/rrd4j/core/DataHolder.java new file mode 100644 index 000000000..dfd3514d5 --- /dev/null +++ b/apps/jrobin/java/src/org/rrd4j/core/DataHolder.java @@ -0,0 +1,207 @@ +package org.rrd4j.core; + +import java.net.URI; +import java.util.TimeZone; + +import org.rrd4j.ConsolFun; +import org.rrd4j.data.IPlottable; +import org.rrd4j.data.Variable; + +/** + * @author Fabrice Bacchella + * @since 3;7 + */ +public interface DataHolder { + + /** + * Constant that defines the default {@link RrdDbPool} usage policy. Defaults to false + * (i.e. the pool will not be used to fetch data from RRD files) + */ + public static final boolean DEFAULT_POOL_USAGE_POLICY = false; + + /** + * Returns boolean value representing {@link org.rrd4j.core.RrdDbPool RrdDbPool} usage policy. + * + * @return true, if the pool will be used internally to fetch data from RRD files, false otherwise. + */ + boolean isPoolUsed(); + + /** + * Sets the {@link org.rrd4j.core.RrdDbPool RrdDbPool} usage policy. + * + * @param poolUsed true, if the pool will be used to fetch data from RRD files, false otherwise. + */ + void setPoolUsed(boolean poolUsed); + + RrdDbPool getPool(); + + /** + * 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 an optional pool to use. + */ + void setPool(RrdDbPool pool); + + /** + * Set the time zone used for the legend. + * + * @param tz the time zone to set + */ + void setTimeZone(TimeZone tz); + + TimeZone getTimeZone(); + + /** + * Sets the time when the graph should end. Time in seconds since epoch + * (1970-01-01) is required. Negative numbers are relative to the current time. + * + * @param time Ending time for the graph in seconds since epoch + */ + void setEndTime(long time); + + /** + * Returns ending timestamp. + * + * @return Ending timestamp in seconds + */ + long getEndTime(); + + /** + * Sets the time when the graph should start. Time in seconds since epoch + * (1970-01-01) is required. Negative numbers are relative to the current time. + * + * @param time Starting time for the graph in seconds since epoch + */ + void setStartTime(long time); + + /** + * Returns starting timestamp. + * + * @return Starting timestamp in seconds + */ + long getStartTime(); + + /** + * Sets starting and ending time for the for the graph. Timestamps in seconds since epoch are + * required. Negative numbers are relative to the current time. + * + * @param startTime Starting time in seconds since epoch + * @param endTime Ending time in seconds since epoch + */ + void setTimeSpan(long startTime, long endTime); + + /** + * Set the step for timestamp interval. + */ + void setStep(long step); + + /** + * Returns the time step used for timestamp interval. + * + * @return Step used for data processing. + */ + long getStep(); + + /** + * Defines virtual datasource. This datasource can then be used + * in other methods like {@link #datasource(String, String)}. + * + * @param name Source name + * @param rrdPath Path to RRD file + * @param dsName Datasource name in the specified RRD file + * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) + */ + void datasource(String name, String rrdPath, String dsName, + ConsolFun consolFun); + + /** + * Defines virtual datasource. This datasource can then be used + * in other methods like {@link #datasource(String, String)}. + * + * @param name Source name + * @param rrdUri rrdUri to RRD file + * @param dsName Datasource name in the specified RRD file + * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) + */ + void datasource(String name, URI rrdUri, String dsName, + ConsolFun consolFun); + + /** + * Defines virtual datasource. This datasource can then be used + * in other methods like {@link #datasource(String, String)}. + * + * @param name Source name + * @param rrdPath Path to RRD file + * @param dsName Datasource name in the specified RRD file + * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) + * @param backend Backend to be used while fetching data from a RRD file. + */ + void datasource(String name, String rrdPath, String dsName, + ConsolFun consolFun, RrdBackendFactory backend); + + /** + * Defines virtual datasource. This datasource can then be used + * in other methods like {@link #datasource(String, String)}. + * + * @param name Source name + * @param rrdUri URI to RRD file + * @param dsName Datasource name in the specified RRD file + * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) + * @param backend Backend to be used while fetching data from a RRD file. + */ + void datasource(String name, URI rrdUri, String dsName, + ConsolFun consolFun, RrdBackendFactory backend); + + /** + * Create a new virtual datasource by evaluating a mathematical + * expression, specified in Reverse Polish Notation (RPN). + * + * @param name Source name + * @param rpnExpression RPN expression. + */ + void datasource(String name, String rpnExpression); + + /** + * Creates a datasource that performs a variable calculation on an + * another named datasource to yield a single combined timestamp/value. + * + * Requires that the other datasource has already been defined; otherwise, it'll + * end up with no data + * + * @param name - the new virtual datasource name + * @param defName - the datasource from which to extract the percentile. Must be a previously + * defined virtual datasource + * @param var - a new instance of a Variable used to do the calculation + */ + void datasource(String name, String defName, Variable var); + + /** + * Creates a new (plottable) datasource. Datasource values are obtained from the given plottable + * object. + * + * @param name Source name. + * @param plottable IPlottable object. + */ + void datasource(String name, IPlottable plottable); + + /** + * Creates a new 'fetched' datasource. Datasource values are obtained from the + * given {@link org.rrd4j.core.FetchData} object. + * + * @param name Source name. + * @param fetchData FetchData object. + */ + void datasource(String name, FetchData fetchData); + + /** + * Creates a new 'fetched' datasource. Datasource values are obtained from the + * given {@link org.rrd4j.core.FetchData} object. + * Values will be extracted from the datasource dsName in the fetchData + * + * @param name Source name. + * @param dsName Source name in fetchData. + * @param fetchData FetchData object. + */ + void datasource(String name, String dsName, FetchData fetchData); + +} diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java index ab88da23e..124114e0d 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java @@ -17,6 +17,7 @@ import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; /** * Base (abstract) backend factory class which holds references to all concrete @@ -210,6 +211,15 @@ public abstract class RrdBackendFactory implements Closeable { activeFactories.clear(); activeFactories.addAll(Arrays.asList(newFactories)); } + + /** + * Return the current active factories as a stream. + * @return + * @since 3.7 + */ + public static synchronized Stream getActiveFactories() { + return activeFactories.stream(); + } /** * Add factories to the list of active factories, i.e. the factory used to resolve URI. diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdDb.java b/apps/jrobin/java/src/org/rrd4j/core/RrdDb.java index d6aca30c5..d45f7d930 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdDb.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdDb.java @@ -70,6 +70,7 @@ public class RrdDb implements RrdUpdater, Closeable { * @return a new build RrdDb * @throws IOException in case of I/O error. * @throws IllegalArgumentException if the builder settings were incomplete + * @throws java.lang.IllegalStateException if the thread was interrupted in pool usage */ public RrdDb build() throws IOException { if (rrdDef != null) { @@ -113,6 +114,7 @@ public class RrdDb implements RrdUpdater, Closeable { * * @throws IOException in case of I/O error. * @throws IllegalArgumentException if the builder settings were incomplete + * @throws java.lang.IllegalStateException if the thread was interrupted in pool usage */ public void doimport() throws IOException { if (rrdDef != null || (importer == null && externalPath == null)) { @@ -185,7 +187,8 @@ public class RrdDb implements RrdUpdater, Closeable { } /** - * Activate the pool usage + * Activate the pool usage. If the pool is not declared using + * {@link #setPool(RrdDbPool)}, the singleton instance will be used. * * @return the same builder. */ @@ -195,18 +198,29 @@ public class RrdDb implements RrdUpdater, Closeable { } /** - * Set the pool that will be used if {@link #usePool} is true. If not defined, - * the singleton instance will be used. + * Set the pool that will be used and set usePool to true. * * @param pool true if a pool is going to be used * @return the same builder. */ public Builder setPool(RrdDbPool pool) { this.pool = pool; + this.usePool = pool != null; return this; } /** + * Internal method used to memorized the pool, without generating a loop + * @param pool + * @return + */ + Builder setPoolInternal(RrdDbPool pool) { + this.pool = pool; + this.usePool = false; + 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. @@ -396,6 +410,7 @@ public class RrdDb implements RrdUpdater, Closeable { * * * @param rrdDef Object describing the structure of the new RRD file. + * @return a new Rrdb created from the definition. * @throws java.io.IOException Thrown in case of I/O error. */ public static RrdDb of(RrdDef rrdDef) throws IOException { @@ -807,6 +822,7 @@ public class RrdDb implements RrdUpdater, Closeable { * Closes RRD. No further operations are allowed on this RrdDb object. * * @throws java.io.IOException Thrown in case of I/O related error. + * @throws java.lang.IllegalStateException if the thread was interrupted in pool usage. */ @SuppressWarnings("deprecation") public synchronized void close() throws IOException { @@ -1381,6 +1397,10 @@ public class RrdDb implements RrdUpdater, Closeable { return backend.getUri(); } + public URI getCanonicalUri() { + return backend.getFactory().getCanonicalUri(getUri()); + } + /** * Returns backend object for this RRD which performs actual I/O operations. * diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java b/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java index d09a690c0..edd850dc0 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java @@ -1,28 +1,28 @@ 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.ExecutionException; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Stream; /** *

This class should be used to synchronize access to RRD files * in a multithreaded environment. This class should be also used to prevent opening of * too many RRD files at the same time (thus avoiding operating system limits). *

- *

It should not be called directly. Use {@link RrdDb.Builder#usePool()} instead.

+ *

It can also be used a factory for RrdDb, using a default backend factory.

+ *

In case of interruptions, it throws IllegalStateException. */ public class RrdDbPool { private static class RrdDbPoolSingletonHolder { @@ -38,8 +38,8 @@ public class RrdDbPool { } /** - * Initial capacity of the pool i.e. maximum number of simultaneously open RRD files. The pool will - * never open too many RRD files at the same time. + * Initial capacity of the pool i.e. maximum number of simultaneously open RRD. The pool will + * never open too many RRD at the same time. */ public static final int INITIAL_CAPACITY = 200; @@ -80,7 +80,7 @@ public class RrdDbPool { if (placeholder) { return String.format("RrdEntry [placeholder, uri=%s]", uri); } else { - return String.format("RrdEntry [count=%d, rrdDb=%s, uri%s]", count, rrdDb, uri); + return String.format("RrdEntry [count=%d, rrdDb=%s, uri %s]", count, rrdDb, uri); } } } @@ -90,7 +90,6 @@ public class RrdDbPool { * or returns already existing one. Uses Initialization On Demand Holder idiom. * * @return Single instance of this class - * @throws java.lang.RuntimeException Thrown if the default RRD backend is not derived from the {@link org.rrd4j.core.RrdFileBackendFactory} */ public static RrdDbPool getInstance() { return RrdDbPoolSingletonHolder.instance; @@ -109,7 +108,7 @@ public class RrdDbPool { private RrdBackendFactory defaultFactory; /** - * Constructor for RrdDbPool. + * Constructor for RrdDbPool. It will use the default backend factory. * @since 3.5 */ public RrdDbPool() { @@ -118,7 +117,7 @@ public class RrdDbPool { /** * Constructor for RrdDbPool. - * @param defaultFactory the default factory used when given simple path of a rrdDb. + * @param defaultFactory the default factory used when given a simple path of a RRD. * @since 3.6 */ public RrdDbPool(RrdBackendFactory defaultFactory) { @@ -130,36 +129,40 @@ public class RrdDbPool { } /** - * Returns the number of open RRD files. + * Returns the number of open RRD. * - * @return Number of currently open RRD files held in the pool. + * @return Number of currently open RRD held in the pool. */ public int getOpenFileCount() { return pool.size(); } /** - * Returns an array of open file URI. + * Returns an array of open RRD URI. * - * @return Array with {@link URI} to open RRD files held in the pool. + * @return Array with {@link URI} to open RRD held in the pool. */ public URI[] getOpenUri() { - //Direct toarray from keySet can fail - Set uris = new HashSet<>(pool.size()); - pool.forEach((k,v) -> uris.add(k)); - return uris.toArray(new URI[uris.size()]); + return pool.keySet().stream().toArray(URI[]::new); } /** - * Returns an array of open file path. + * Returns an stream open RRD. * - * @return Array with canonical path to open RRD files held in the pool. + * @return Stream with canonical URI to open RRD path held in the pool. + * @since 3.7 + */ + public Stream getOpenUriStream() { + return pool.keySet().stream(); + } + + /** + * Returns an array of open RRD. + * + * @return Array with canonical path to open RRD path held in the pool. */ public String[] getOpenFiles() { - //Direct toarray from keySet can fail - Set uris = new HashSet<>(pool.size()); - pool.forEach((k,v) -> uris.add(k.getPath())); - return uris.toArray(new String[uris.size()]); + return pool.keySet().stream().map(URI::getPath).toArray(String[]::new); } private RrdEntry getEntry(URI uri, boolean cancreate) throws InterruptedException { @@ -260,8 +263,9 @@ public class RrdDbPool { try { usageWLock.lockInterruptibly(); fullCondition.signalAll(); - } catch (InterruptedException ex) { - throw new UndeclaredThrowableException(ex); + } catch (InterruptedException e1) { + // Lost slot available notification + Thread.currentThread().interrupt(); } finally { if (usageWLock.isHeldByCurrentThread()) { usageWLock.unlock(); @@ -278,11 +282,12 @@ public class RrdDbPool { /** * Releases RrdDb reference previously obtained from the pool. When a reference is released, its usage - * count is decremented by one. If usage count drops to zero, the underlying RRD file will be closed. + * count is decremented by one. If usage count drops to zero, the underlying RRD will be closed. * * @param rrdDb RrdDb reference to be returned to the pool * @throws java.io.IOException Thrown in case of I/O error - * @deprecated a db remember if it was open directly or from the pool, no need to manage it manually any more + * @throws java.lang.IllegalStateException if the thread was interrupted + * @deprecated A RrdDb remember if it was open directly or from a pool, no need to manage it manually any more */ @Deprecated public void release(RrdDb rrdDb) throws IOException { @@ -291,8 +296,7 @@ public class RrdDbPool { if (rrdDb == null) { return; } - - URI dburi = rrdDb.getUri(); + URI dburi = rrdDb.getCanonicalUri(); RrdEntry ref = null; try { ref = getEntry(dburi, false); @@ -326,20 +330,19 @@ public class RrdDbPool { } /** - *

Requests a RrdDb reference for the given RRD file path.

+ *

Requests a RrdDb reference for the given RRD path.

*
    - *
  • If the file is already open, previously returned RrdDb reference will be returned. Its usage count + *
  • If the RRD is already open, previously returned RrdDb reference will be returned. Its usage count * will be incremented by one. - *
  • If the file is not already open and the number of already open RRD files is less than - * {@link #INITIAL_CAPACITY}, the file will be open and a new RrdDb reference will be returned. - * If the file is not already open and the number of already open RRD files is equal to - * {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed. + *
  • If the RRD is not already open and the number of already open RRD is less than + * {@link #getCapacity()}, it will be opened and a new RrdDb reference will be returned. + * If the RRD is not already open and the number of already open RRD is equal to + * {@link #getCapacity()}, the method blocks until some RRD are closed. *
- *

The path is transformed internally to URI using the default factory, that is the reference that will - * be used elsewhere.

+ *

The path is transformed to an URI using the default factory defined at the creation of the pool.

* - * @param path Path to existing RRD file - * @return reference for the give RRD file + * @param path Path to existing RRD. + * @return reference for the given RRD. * @throws java.io.IOException Thrown in case of I/O error */ public RrdDb requestRrdDb(String path) throws IOException { @@ -347,23 +350,25 @@ public class RrdDbPool { } /** - *

Requests a RrdDb reference for the given RRD file path.

+ *

Requests a RrdDb reference for the given RRD URI.

*
    - *
  • If the file is already open, previously returned RrdDb reference will be returned. Its usage count + *
  • If the RRD is already open, previously returned RrdDb reference will be returned. Its usage count * will be incremented by one. - *
  • If the file is not already open and the number of already open RRD files is less than - * {@link #INITIAL_CAPACITY}, the file will be open and a new RrdDb reference will be returned. - * If the file is not already open and the number of already open RRD files is equal to - * {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed. + *
  • If the RRD is not already open and the number of already open RRD is less than + * {@link #getCapacity()}, it will be opened and a new RrdDb reference will be returned. + * If the RRD is not already open and the number of already open RRD is equal to + * {@link #getCapacity()}, the method blocks until some RRD are closed. *
+ *

+ * If the default backend factory for the pool can handle this URI, it will be used, + * or else {@link RrdBackendFactory#findFactory(URI)} will be used to find the backend factory used. * * @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 */ public RrdDb requestRrdDb(URI uri) throws IOException { - RrdBackendFactory factory = RrdBackendFactory.findFactory(uri); - return requestRrdDb(uri, factory); + return requestRrdDb(uri, checkFactory(uri)); } /** @@ -412,7 +417,7 @@ public class RrdDbPool { // 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(); + ref.rrdDb = RrdDb.getBuilder().setPath(factory.getPath(uri)).setBackendFactory(factory).setPoolInternal(this).build(); } catch (IOException | RuntimeException e) { passNext(ACTION.DROP, ref); throw e; @@ -427,17 +432,17 @@ public class RrdDbPool { } } - RrdDb requestRrdDb(RrdDef rrdDef, RrdBackendFactory backend) throws IOException { + RrdDb requestRrdDb(RrdDef rrdDef, RrdBackendFactory factory) throws IOException { RrdEntry ref = null; try { - URI uri = backend.getCanonicalUri(rrdDef.getUri()); + URI uri = factory.getCanonicalUri(rrdDef.getUri()); ref = requestEmpty(uri); - ref.rrdDb = RrdDb.getBuilder().setRrdDef(rrdDef).setBackendFactory(backend).setPool(this).build(); + ref.rrdDb = RrdDb.getBuilder().setRrdDef(rrdDef).setBackendFactory(factory).setPoolInternal(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); + throw new IllegalStateException("request interrupted for new rrdDef " + rrdDef.getPath(), e); } catch (RuntimeException e) { passNext(ACTION.DROP, ref); ref = null; @@ -447,18 +452,18 @@ public class RrdDbPool { } } - private RrdDb requestRrdDb(RrdDb.Builder builder, URI uri, RrdBackendFactory backend) + private RrdDb requestRrdDb(RrdDb.Builder builder, URI uri, RrdBackendFactory factory) throws IOException { RrdEntry ref = null; - uri = backend.getCanonicalUri(uri); + uri = factory.getCanonicalUri(uri); try { ref = requestEmpty(uri); - ref.rrdDb = builder.setPath(uri).setBackendFactory(backend).setPool(this).build(); + ref.rrdDb = builder.setPath(uri).setBackendFactory(factory).setPoolInternal(this).build(); ref.count = 1; return ref.rrdDb; } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new RuntimeException("request interrupted for new rrd " + uri, e); + throw new IllegalStateException("request interrupted for new rrd " + uri, e); } catch (RuntimeException e) { passNext(ACTION.DROP, ref); ref = null; @@ -468,46 +473,51 @@ public class RrdDbPool { } } - RrdDb requestRrdDb(URI uri, RrdBackendFactory backend, DataImporter importer) throws IOException { - return requestRrdDb(RrdDb.getBuilder().setImporter(importer), uri, backend); + RrdDb requestRrdDb(URI uri, RrdBackendFactory factory, DataImporter importer) throws IOException { + return requestRrdDb(RrdDb.getBuilder().setImporter(importer), uri, factory); } /** - *

Requests a RrdDb reference for the given RRD file definition object.

+ *

Requests a RrdDb reference for the given RRD definition object.

*
    - *
  • If the file with the path specified in the RrdDef object is already open, + *
  • If the RRD with the path specified in the RrdDef object is already open, * the method blocks until the file is closed. - *
  • If the file is not already open and the number of already open RRD files is less than - * {@link #INITIAL_CAPACITY}, a new RRD file will be created and a its RrdDb reference will be returned. - * If the file is not already open and the number of already open RRD files is equal to - * {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed. + *
  • If the RRD is not already open and the number of already open RRD is less than + * {@link #getCapacity()}, a new RRD will be created and it's RrdDb reference will be returned. + * If the RRD is not already open and the number of already open RRD is equal to + * {@link #getCapacity()}, the method blocks until some RrdDb references are closed. *
+ *

+ * If the factory defined when creating the pool can handle the URI, it will be used, + * or else {@link RrdBackendFactory#findFactory(URI)} will be used. * - * @param rrdDef Definition of the RRD file to be created - * @return Reference to the newly created RRD file + * @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 + * @throws java.lang.IllegalStateException if the thread was interrupted */ public RrdDb requestRrdDb(RrdDef rrdDef) throws IOException { - return requestRrdDb(rrdDef, RrdBackendFactory.findFactory(rrdDef.getUri())); + return requestRrdDb(rrdDef, checkFactory(rrdDef.getUri())); } /** - *

Requests a RrdDb reference for the given path. The file will be created from + *

Requests a RrdDb reference for the given path. The RRD will be created from * external data (from XML dump or RRDTool's binary RRD file).

*
    - *
  • If the file with the path specified is already open, + *
  • If the RRD with the path specified is already open, * the method blocks until the file is closed. - *
  • If the file is not already open and the number of already open RRD files is less than - * {@link #INITIAL_CAPACITY}, a new RRD file will be created and a its RrdDb reference will be returned. - * If the file is not already open and the number of already open RRD files is equal to - * {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed. + *
  • If the RRD is not already open and the number of already open RRD is less than + * {@link #getCapacity()}, a new RRD will be created and it's RrdDb reference will be returned. + * If the RRD is not already open and the number of already open RRD is equal to + * {@link #getCapacity()}, the method blocks until some RrdDb references are closed. *
- *

The path is transformed internally to an URI using the default factory of the pool.

+ *

The path is transformed to an URI using the default factory of the pool.

* - * @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 + * @param path Path to the RRD that 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. * @throws java.io.IOException Thrown in case of I/O error + * @throws java.lang.IllegalStateException if the thread was interrupted */ public RrdDb requestRrdDb(String path, String sourcePath) throws IOException { @@ -516,35 +526,39 @@ public class RrdDbPool { } /** - *

Requests a RrdDb reference for the given path. The file will be created from + *

Requests a RrdDb reference for the given URI. The RRD will be created from * external data (from XML dump or RRDTool's binary RRD file).

*
    - *
  • If the file with the path specified is already open, + *
  • If the RRD with the URI specified is already open, * the method blocks until the file is closed. - *
  • If the file is not already open and the number of already open RRD files is less than - * {@link #INITIAL_CAPACITY}, a new RRD file will be created and a its RrdDb reference will be returned. - * If the file is not already open and the number of already open RRD files is equal to - * {@link #INITIAL_CAPACITY}, the method blocks until some RRD file is closed. + *
  • If the RRD is not already open and the number of already open RRD is less than + * {@link #getCapacity()}, a new RRD will be created and it's RrdDb reference will be returned. + * If the RRD is not already open and the number of already open RRD is equal to + * {@link #getCapacity()}, the method blocks until some RrdDb references are closed. *
- *

The path is transformed internally to URI using the default factory, that is the reference that will - * be used elsewhere.

+ * If the factory defined when creating the pool can handle the URI, it will be used, + * or else {@link RrdBackendFactory#findFactory(URI)} will be used to choose the factory. * - * @param uri Path to RRD file which should be created + * @param uri URI to the RRD that 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 + * @return Reference to the newly created RRD * @throws java.io.IOException Thrown in case of I/O error + * @throws java.lang.IllegalStateException if the thread was interrupted */ public RrdDb requestRrdDb(URI uri, String sourcePath) throws IOException { - return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, RrdBackendFactory.findFactory(uri)); + return requestRrdDb(RrdDb.getBuilder().setExternalPath(sourcePath), uri, checkFactory(uri)); } /** - * Sets the default factory to use when obtaining rrdDb from simple path and not URI. + * Sets the default factory to use when obtaining RrdDb reference 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. + * @param defaultFactory The factory to use. + * @throws IllegalStateException if called while the pool is not empty or the thread was interrupted + * @throws java.lang.IllegalStateException if the thread was interrupted + * @deprecated the pool is no longer a singleton, create a new pool instead of changing it. */ + @Deprecated public void setDefaultFactory(RrdBackendFactory defaultFactory) { try { usageWLock.lockInterruptibly(); @@ -563,10 +577,10 @@ public class RrdDbPool { } /** - * Sets the maximum number of simultaneously open RRD files. + * Sets the maximum number of simultaneously open RRD. * - * @param newCapacity Maximum number of simultaneously open RRD files. - * @throws IllegalStateException if done will the pool is not empty or the thread was interrupted. + * @param newCapacity Maximum number of simultaneously open RRD. + * @throws IllegalStateException if called while the pool is not empty or the thread was interrupted. */ public void setCapacity(int newCapacity) { try { @@ -587,9 +601,10 @@ public class RrdDbPool { } /** - * Returns the maximum number of simultaneously open RRD files. + * Returns the maximum number of simultaneously open RRD. * - * @return maximum number of simultaneously open RRD files + * @return maximum number of simultaneously open RRD + * @throws java.lang.IllegalStateException if the thread was interrupted */ public int getCapacity() { try { @@ -609,42 +624,76 @@ public class RrdDbPool { * Returns the number of usage for a RRD. * * @param rrdDb RrdDb reference for which informations is needed. - * @return the number of request for this rrd - * @throws java.io.IOException if any. + * @return the number of request for this RRD. + * @throws java.io.IOException if any + * @throws java.lang.IllegalStateException if the thread was interrupted */ public int getOpenCount(RrdDb rrdDb) throws IOException { - return getOpenCount(rrdDb.getUri()); + return getCanonicalUriUsage(rrdDb.getCanonicalUri()); } /** * Returns the number of usage for a RRD. + *

The path is transformed to an URI using the default factory.

* * @param path RRD's path for which informations is needed. - * @return the number of request for this file - * @throws java.io.IOException if any. + * @return the number of request for this RRD. + * @throws java.io.IOException if any + * @throws java.lang.IllegalStateException if the thread was interrupted */ public int getOpenCount(String path) throws IOException { - return getOpenCount(defaultFactory.getUri(path)); + return getCanonicalUriUsage(defaultFactory.getCanonicalUri(defaultFactory.getUri(path))); } /** * Returns the number of usage for a RRD. * - * @param uri RRD's uri for which informations is needed. - * @return the number of request for this file - * @throws java.io.IOException if any. + * @param uri RRD's URI for which informations is needed. + * @return the number of request for this RRD. + * @throws java.io.IOException if any + * @throws java.lang.IllegalStateException if the thread was interrupted */ public int getOpenCount(URI uri) throws IOException { + return getCanonicalUriUsage(checkFactory(uri).getCanonicalUri(uri)); + } + + private int getCanonicalUriUsage(URI uri) { RrdEntry ref = null; try { ref = getEntry(uri, false); return Optional.ofNullable(ref).map(e -> e.count).orElse(0); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new RuntimeException("getOpenCount interrupted", e); + throw new IllegalStateException("getOpenCount interrupted", e); } finally { passNext(ACTION.SWAP, ref); } } + /** + * Wait until the pool is empty and return a lock that prevent any additions of new RrdDb references until it's released. + * + * @since 3.7 + * + * @param timeout the time to wait for the write lock + * @param unit the time unit of the timeout argument + * @return a lock to release when operations on this pool are finished. + * @throws InterruptedException if interrupted whole waiting for the lock + */ + public Lock lockEmpty(long timeout, TimeUnit unit) throws InterruptedException { + usageWLock.tryLock(timeout, unit); + try { + usage.acquire(maxCapacity); + } catch (InterruptedException e) { + usageWLock.unlock(); + Thread.currentThread().interrupt(); + throw e; + } + return usageWLock; + } + + private RrdBackendFactory checkFactory(URI uri) { + return defaultFactory.canStore(uri) ? defaultFactory : RrdBackendFactory.findFactory(uri); + } + } diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackend.java b/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackend.java index bf07edc93..67510f1fb 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackend.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackend.java @@ -2,6 +2,8 @@ package org.rrd4j.core; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; /** * Backend to be used to store all RRD bytes in memory. @@ -9,14 +11,17 @@ import java.nio.ByteBuffer; */ public class RrdMemoryBackend extends ByteBufferBackend { - private ByteBuffer dbb = null; + private final AtomicReference refbb; /** *

Constructor for RrdMemoryBackend.

* * @param path a {@link java.lang.String} object. + * @param refbb */ - protected RrdMemoryBackend(String path) { + protected RrdMemoryBackend(String path, AtomicReference refbb) { super(path); + this.refbb = refbb; + Optional.ofNullable(refbb).map(r -> r.get()).ifPresent(this::setByteBuffer); } @Override @@ -24,13 +29,13 @@ public class RrdMemoryBackend extends ByteBufferBackend { if (length < 0 || length > Integer.MAX_VALUE) { throw new IllegalArgumentException("Illegal length: " + length); } - dbb = ByteBuffer.allocate((int) length); - setByteBuffer(dbb); + refbb.set(ByteBuffer.allocate((int) length)); + setByteBuffer(refbb.get()); } @Override public long getLength() throws IOException { - return dbb.capacity(); + return Optional.ofNullable(refbb.get()).map(ByteBuffer::capacity).orElse(0); } /** diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackendFactory.java b/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackendFactory.java index bb97bf5e1..c4c33536e 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackendFactory.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdMemoryBackendFactory.java @@ -2,8 +2,10 @@ package org.rrd4j.core; import java.io.IOException; import java.net.URI; +import java.nio.ByteBuffer; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; /** * Factory class which creates actual {@link org.rrd4j.core.RrdMemoryBackend} objects. Rrd4j's support @@ -19,7 +21,7 @@ import java.util.concurrent.ConcurrentHashMap; @RrdBackendAnnotation(name="MEMORY", shouldValidateHeader=false) public class RrdMemoryBackendFactory extends RrdBackendFactory { - protected final Map backends = new ConcurrentHashMap<>(); + protected final Map> backends = new ConcurrentHashMap<>(); /** * {@inheritDoc} @@ -27,15 +29,8 @@ public class RrdMemoryBackendFactory extends RrdBackendFactory { * Creates RrdMemoryBackend object. */ protected RrdBackend open(String id, boolean readOnly) throws IOException { - RrdMemoryBackend backend; - if (backends.containsKey(id)) { - backend = backends.get(id); - } - else { - backend = new RrdMemoryBackend(id); - backends.put(id, backend); - } - return backend; + AtomicReference refbb = backends.computeIfAbsent(id, i -> new AtomicReference()); + return new RrdMemoryBackend(id, refbb); } @Override @@ -47,6 +42,7 @@ public class RrdMemoryBackendFactory extends RrdBackendFactory { * {@inheritDoc} * * Method to determine if a memory storage with the given ID already exists. + * */ protected boolean exists(String id) { return backends.containsKey(id); diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdNioBackendFactory.java b/apps/jrobin/java/src/org/rrd4j/core/RrdNioBackendFactory.java index 88e4c0e1a..7ab4278ce 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdNioBackendFactory.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdNioBackendFactory.java @@ -86,7 +86,7 @@ public class RrdNioBackendFactory extends RrdFileBackendFactory { * Creates a new RrdNioBackendFactory with default settings. */ public RrdNioBackendFactory() { - this(RrdNioBackendFactory.defaultSyncPeriod, DefaultSyncThreadPool.INSTANCE); + this(RrdNioBackendFactory.defaultSyncPeriod, RrdNioBackendFactory.defaultSyncPeriod > 0 ? DefaultSyncThreadPool.INSTANCE : null); } /** diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdSafeFileBackendFactory.java b/apps/jrobin/java/src/org/rrd4j/core/RrdSafeFileBackendFactory.java index 9830b7241..4310e5d8d 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdSafeFileBackendFactory.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdSafeFileBackendFactory.java @@ -4,6 +4,9 @@ import java.io.IOException; /** * Factory class which creates actual {@link org.rrd4j.core.RrdSafeFileBackend} objects. + *

+ * Because of locking, each RrdDb can be open only once even from within the JVM. So usage + * of the {@link org.rrd4j.core.RrdDbPool} is mandatory with this backend. * */ @RrdBackendAnnotation(name="SAFE", shouldValidateHeader=true, cachingAllowed=false) diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdToolkit.java b/apps/jrobin/java/src/org/rrd4j/core/RrdToolkit.java index 0d2758f93..9bc705284 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/RrdToolkit.java +++ b/apps/jrobin/java/src/org/rrd4j/core/RrdToolkit.java @@ -31,7 +31,6 @@ import org.rrd4j.ConsolFun; * (files which are currently in use). * */ -@SuppressWarnings("deprecation") public class RrdToolkit { private static final String SOURCE_AND_DESTINATION_PATHS_ARE_THE_SAME = "Source and destination paths are the same"; @@ -472,11 +471,8 @@ public class RrdToolkit { if (arcDef.getRows() != newRows) { arcDef.setRows(newRows); rrdDef.setPath(destPath); - RrdDb rrdDest = new RrdDb(rrdDef); - try { + try (RrdDb rrdDest = RrdDb.of(rrdDef)){ rrdSource.copyStateTo(rrdDest); - } finally { - rrdDest.close(); } } } @@ -571,4 +567,3 @@ public class RrdToolkit { } } - diff --git a/apps/jrobin/java/src/org/rrd4j/core/XmlWriter.java b/apps/jrobin/java/src/org/rrd4j/core/XmlWriter.java index 9b0ba28e5..82a56e234 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/XmlWriter.java +++ b/apps/jrobin/java/src/org/rrd4j/core/XmlWriter.java @@ -202,7 +202,7 @@ public class XmlWriter implements AutoCloseable { } private static String escape(String s) { - return s.replaceAll("<", "<").replaceAll(">", ">"); + return s.replace("<", "<").replace(">", ">"); } @Override diff --git a/apps/jrobin/java/src/org/rrd4j/core/jrrd/DataChunk.java b/apps/jrobin/java/src/org/rrd4j/core/jrrd/DataChunk.java index a5c1fa12d..8cfbf247a 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/jrrd/DataChunk.java +++ b/apps/jrobin/java/src/org/rrd4j/core/jrrd/DataChunk.java @@ -2,8 +2,8 @@ package org.rrd4j.core.jrrd; import java.util.Map; -import org.rrd4j.data.LinearInterpolator; import org.rrd4j.data.Plottable; +import org.rrd4j.data.LinearInterpolator; /** * Models a chunk of result data from an RRDatabase. @@ -11,6 +11,7 @@ import org.rrd4j.data.Plottable; * @author Ciaran Treanor * @version $Revision: 1.1 $ */ +@SuppressWarnings("deprecation") public class DataChunk { private static final String NEWLINE = System.getProperty("line.separator"); diff --git a/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeParser.java b/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeParser.java index 52a06706d..d2501caf5 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeParser.java +++ b/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeParser.java @@ -1,11 +1,13 @@ package org.rrd4j.core.timespec; -import org.rrd4j.core.Util; +import java.time.Instant; /** * Class which parses at-style time specification (described in detail on the rrdfetch man page), * used in all RRDTool commands. This code is in most parts just a java port of Tobi's parsetime.c * code. + * + * For years written with two digits, any year before 38 will be post 2000. * */ public class TimeParser { @@ -102,7 +104,8 @@ public class TimeParser { * the scanner state to what it was at entry, and returns without setting anything. */ private void timeOfDay() { - int hour, minute = 0; + int hour = 0; + int minute = 0; /* save token status in case we must abort */ scanner.saveState(); /* first pick out the time of day - we assume a HH (COLON|DOT) MM time */ @@ -165,20 +168,13 @@ public class TimeParser { } private void assignDate(long mday, long mon, long year) { - if (year > 138) { - if (year > 1970) { - year -= 1900; - } - else { - throw new IllegalArgumentException("Invalid year " + year + " (should be either 00-99 or >1900)"); - } + if (year >= 0 && year < 38) { + // 00-37 means post 2000 + year += 2000; } - else if (year >= 0 && year < 38) { - year += 100; /* Allow year 2000-2037 to be specified as */ - } /* 00-37 until the problem of 2038 year will */ - /* arise for unices with 32-bit time_t */ - if (year < 70) { - throw new IllegalArgumentException("Won't handle dates before epoch (01/01/1970), sorry"); + else if (year >= 38 && year <= 99) { + // 38-99 means 1938-1999 + year += 1900; } spec.year = (int) year; spec.month = (int) mon; @@ -186,7 +182,10 @@ public class TimeParser { } private void day() { - long mday = 0, wday, mon, year = spec.year; + long mday = 0; + long wday = 0; + long mon = 0; + long year = spec.year; switch (token.token_id) { case TimeToken.YESTERDAY: spec.day--; @@ -244,7 +243,7 @@ public class TimeParser { token = scanner.nextToken(); break; } - if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */ + if (mon > 19000101 && mon < 24000101) { /*works between 1900 and 2400 */ year = mon / 10000; mday = mon % 100; mon = (mon / 100) % 100; @@ -292,7 +291,7 @@ public class TimeParser { * @return Object representing parsed date/time. */ public TimeSpec parse() { - long now = Util.getTime(); + long now = Instant.now().getEpochSecond(); int hr = 0; /* this MUST be initialized to zero for midnight/noon/teatime */ /* establish the default time reference */ diff --git a/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeScanner.java b/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeScanner.java index f0297b7c5..f3ae8c7d7 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeScanner.java +++ b/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeScanner.java @@ -7,11 +7,11 @@ class TimeScanner { private TimeToken token, token_save; static final TimeToken[] WORDS = { - new TimeToken("midnight", TimeToken.MIDNIGHT), /* 00:00:00 of today or tomorrow */ - new TimeToken("noon", TimeToken.NOON), /* 12:00:00 of today or tomorrow */ - new TimeToken("teatime", TimeToken.TEATIME), /* 16:00:00 of today or tomorrow */ - new TimeToken("am", TimeToken.AM), /* morning times for 0-12 clock */ - new TimeToken("pm", TimeToken.PM), /* evening times for 0-12 clock */ + new TimeToken("midnight", TimeToken.MIDNIGHT), /* 00:00:00 of today or tomorrow */ + new TimeToken("noon", TimeToken.NOON), /* 12:00:00 of today or tomorrow */ + new TimeToken("teatime", TimeToken.TEATIME), /* 16:00:00 of today or tomorrow */ + new TimeToken("am", TimeToken.AM), /* morning times for 0-12 clock */ + new TimeToken("pm", TimeToken.PM), /* evening times for 0-12 clock */ new TimeToken("tomorrow", TimeToken.TOMORROW), new TimeToken("yesterday", TimeToken.YESTERDAY), new TimeToken("today", TimeToken.TODAY), @@ -62,33 +62,33 @@ class TimeScanner { new TimeToken(null, 0) /*** SENTINEL ***/ }; - static TimeToken[] MULTIPLIERS = { - new TimeToken("second", TimeToken.SECONDS), /* seconds multiplier */ - new TimeToken("seconds", TimeToken.SECONDS), /* (pluralized) */ - new TimeToken("sec", TimeToken.SECONDS), /* (generic) */ + static final TimeToken[] MULTIPLIERS = { + new TimeToken("second", TimeToken.SECONDS), /* seconds multiplier */ + new TimeToken("seconds", TimeToken.SECONDS), /* (pluralized) */ + new TimeToken("sec", TimeToken.SECONDS), /* (generic) */ new TimeToken("s", TimeToken.SECONDS), /* (short generic) */ - new TimeToken("minute", TimeToken.MINUTES), /* minutes multiplier */ - new TimeToken("minutes", TimeToken.MINUTES), /* (pluralized) */ - new TimeToken("min", TimeToken.MINUTES), /* (generic) */ - new TimeToken("m", TimeToken.MONTHS_MINUTES), /* (short generic) */ - new TimeToken("hour", TimeToken.HOURS), /* hours ... */ - new TimeToken("hours", TimeToken.HOURS), /* (pluralized) */ - new TimeToken("hr", TimeToken.HOURS), /* (generic) */ - new TimeToken("h", TimeToken.HOURS), /* (short generic) */ - new TimeToken("day", TimeToken.DAYS), /* days ... */ + new TimeToken("minute", TimeToken.MINUTES), /* minutes multiplier */ + new TimeToken("minutes", TimeToken.MINUTES), /* (pluralized) */ + new TimeToken("min", TimeToken.MINUTES), /* (generic) */ + new TimeToken("m", TimeToken.MONTHS_MINUTES), /* (short generic) */ + new TimeToken("hour", TimeToken.HOURS), /* hours ... */ + new TimeToken("hours", TimeToken.HOURS), /* (pluralized) */ + new TimeToken("hr", TimeToken.HOURS), /* (generic) */ + new TimeToken("h", TimeToken.HOURS), /* (short generic) */ + new TimeToken("day", TimeToken.DAYS), /* days ... */ new TimeToken("days", TimeToken.DAYS), /* (pluralized) */ - new TimeToken("d", TimeToken.DAYS), /* (short generic) */ - new TimeToken("week", TimeToken.WEEKS), /* week ... */ - new TimeToken("weeks", TimeToken.WEEKS), /* (pluralized) */ - new TimeToken("wk", TimeToken.WEEKS), /* (generic) */ - new TimeToken("w", TimeToken.WEEKS), /* (short generic) */ - new TimeToken("month", TimeToken.MONTHS), /* week ... */ + new TimeToken("d", TimeToken.DAYS), /* (short generic) */ + new TimeToken("week", TimeToken.WEEKS), /* week ... */ + new TimeToken("weeks", TimeToken.WEEKS), /* (pluralized) */ + new TimeToken("wk", TimeToken.WEEKS), /* (generic) */ + new TimeToken("w", TimeToken.WEEKS), /* (short generic) */ + new TimeToken("month", TimeToken.MONTHS), /* week ... */ new TimeToken("months", TimeToken.MONTHS), /* (pluralized) */ - new TimeToken("mon", TimeToken.MONTHS), /* (generic) */ - new TimeToken("year", TimeToken.YEARS), /* year ... */ - new TimeToken("years", TimeToken.YEARS), /* (pluralized) */ - new TimeToken("yr", TimeToken.YEARS), /* (generic) */ - new TimeToken("y", TimeToken.YEARS), /* (short generic) */ + new TimeToken("mon", TimeToken.MONTHS), /* (generic) */ + new TimeToken("year", TimeToken.YEARS), /* year ... */ + new TimeToken("years", TimeToken.YEARS), /* (pluralized) */ + new TimeToken("yr", TimeToken.YEARS), /* (generic) */ + new TimeToken("y", TimeToken.YEARS), /* (short generic) */ new TimeToken(null, 0) /*** SENTINEL ***/ }; diff --git a/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeSpec.java b/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeSpec.java index dcc6e960a..af705a0ee 100644 --- a/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeSpec.java +++ b/apps/jrobin/java/src/org/rrd4j/core/timespec/TimeSpec.java @@ -1,7 +1,5 @@ package org.rrd4j.core.timespec; -import org.rrd4j.core.Util; - import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -33,7 +31,7 @@ public class TimeSpec { void localtime(long timestamp) { GregorianCalendar date = new GregorianCalendar(); date.setTime(new Date(timestamp * 1000L)); - year = date.get(Calendar.YEAR) - 1900; + year = date.get(Calendar.YEAR); month = date.get(Calendar.MONTH); day = date.get(Calendar.DAY_OF_MONTH); hour = date.get(Calendar.HOUR_OF_DAY); @@ -46,7 +44,7 @@ public class TimeSpec { GregorianCalendar gc; // absolute time, this is easy if (type == TYPE_ABSOLUTE) { - gc = new GregorianCalendar(year + 1900, month, day, hour, min, sec); + gc = new GregorianCalendar(year, month, day, hour, min, sec); } // relative time, we need a context to evaluate it else if (context != null && context.type == TYPE_ABSOLUTE) { @@ -77,7 +75,7 @@ public class TimeSpec { * @return Timestamp (in seconds, no milliseconds) */ public long getTimestamp() { - return Util.getTimestamp(getTime()); + return getTime().toInstant().getEpochSecond(); } String dump() { @@ -133,7 +131,7 @@ public class TimeSpec { public static long[] getTimestamps(TimeSpec spec1, TimeSpec spec2) { Calendar[] gcs = getTimes(spec1, spec2); return new long[] { - Util.getTimestamp(gcs[0]), Util.getTimestamp(gcs[1]) + gcs[0].toInstant().getEpochSecond(), gcs[1].toInstant().getEpochSecond() }; } } diff --git a/apps/jrobin/java/src/org/rrd4j/data/CubicSplineInterpolator.java b/apps/jrobin/java/src/org/rrd4j/data/CubicSplineInterpolator.java index 94d851615..3327dde1b 100644 --- a/apps/jrobin/java/src/org/rrd4j/data/CubicSplineInterpolator.java +++ b/apps/jrobin/java/src/org/rrd4j/data/CubicSplineInterpolator.java @@ -13,6 +13,7 @@ import java.util.Date; * WARNING: So far, this class cannot handle NaN datasource values * (an exception will be thrown by the constructor). Future releases might change this. */ +@SuppressWarnings("deprecation") public class CubicSplineInterpolator extends Plottable { private double[] x; private double[] y; diff --git a/apps/jrobin/java/src/org/rrd4j/data/DataProcessor.java b/apps/jrobin/java/src/org/rrd4j/data/DataProcessor.java index 2871926d5..dec613e5b 100644 --- a/apps/jrobin/java/src/org/rrd4j/data/DataProcessor.java +++ b/apps/jrobin/java/src/org/rrd4j/data/DataProcessor.java @@ -1,6 +1,22 @@ package org.rrd4j.data; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.time.Instant; +import java.time.temporal.TemporalAmount; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + import org.rrd4j.ConsolFun; +import org.rrd4j.core.DataHolder; import org.rrd4j.core.FetchData; import org.rrd4j.core.FetchRequest; import org.rrd4j.core.RrdBackendFactory; @@ -8,18 +24,6 @@ import org.rrd4j.core.RrdDb; import org.rrd4j.core.RrdDbPool; import org.rrd4j.core.Util; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Date; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TimeZone; - /** *

Class which should be used for all calculations based on the data fetched from RRD files. This class * supports ordinary DEF datasources (defined in RRD files), CDEF datasources (RPN expressions evaluation), @@ -43,28 +47,24 @@ import java.util.TimeZone; * System.out.println(dp.dump()); * */ -public class DataProcessor { +public class DataProcessor implements DataHolder { /** - * Constant representing the default number of pixels on a Rrd4j graph (will be used if - * no other value is specified with {@link #setStep(long) setStep()} method. + * Not used any more. */ + @Deprecated public static final int DEFAULT_PIXEL_COUNT = 600; /** Constant DEFAULT_PERCENTILE=95.0 */ public static final double DEFAULT_PERCENTILE = 95.0; // % - private int pixelCount = DEFAULT_PIXEL_COUNT; + private int pixelCount = 0; - /** - * Constant that defines the default {@link RrdDbPool} usage policy. Defaults to false - * (i.e. the pool will not be used to fetch data from RRD files) - */ - public static final boolean DEFAULT_POOL_USAGE_POLICY = false; private boolean poolUsed = DEFAULT_POOL_USAGE_POLICY; private RrdDbPool pool = null; - private final long tStart; - private long tEnd, timestamps[]; + private long tStart; + private long tEnd; + private long timestamps[]; private long lastRrdArchiveUpdateTime = 0; // this will be adjusted later private long step = 0; @@ -123,11 +123,24 @@ public class DataProcessor { this.tz = gc1.getTimeZone(); } + /** + * Creates new DataProcessor object for the given time duration. The given duration will be + * substracted from current time. + * + * @param d duration to substract. + */ + public DataProcessor(TemporalAmount d) { + Instant now = Instant.now(); + this.tEnd = now.getEpochSecond(); + this.tStart = now.minus(d).getEpochSecond(); + } + /** * Returns boolean value representing {@link org.rrd4j.core.RrdDbPool RrdDbPool} usage policy. * * @return true, if the pool will be used internally to fetch data from RRD files, false otherwise. */ + @Override public boolean isPoolUsed() { return poolUsed; } @@ -137,10 +150,12 @@ public class DataProcessor { * * @param poolUsed true, if the pool should be used to fetch data from RRD files, false otherwise. */ + @Override public void setPoolUsed(boolean poolUsed) { this.poolUsed = poolUsed; } + @Override public RrdDbPool getPool() { return pool; } @@ -150,6 +165,7 @@ public class DataProcessor { * set to true, the default {@link RrdDbPool#getInstance()} will be used. * @param pool an optional pool to use. */ + @Override public void setPool(RrdDbPool pool) { this.pool = pool; } @@ -163,9 +179,6 @@ public class DataProcessor { * and similar methods. In other words, aggregated values will not change once you decide to change * the dimension of your graph.

* - * The default number of pixels is defined by constant {@link #DEFAULT_PIXEL_COUNT} - * and can be changed with a {@link #setPixelCount(int)} method. - * * @param pixelCount The number of pixels. If you process RRD data in order to display it on the graph, * this should be the width of your graph. */ @@ -183,30 +196,22 @@ public class DataProcessor { } /** - *

Roughly corresponds to the --step option in RRDTool's graph/xport commands. Here is an explanation borrowed - * from RRDTool:

- *

"By default rrdgraph calculates the width of one pixel in the time - * domain and tries to get data at that resolution from the RRD. With - * this switch you can override this behavior. If you want rrdgraph to - * get data at 1 hour resolution from the RRD, then you can set the - * step to 3600 seconds. Note, that a step smaller than 1 pixel will - * be silently ignored."

- *

I think this option is not that useful, but it's here just for compatibility.

- * @param step Time step at which data should be fetched from RRD files. If this method is not used, - * the step will be equal to the smallest RRD step of all processed RRD files. If no RRD file is processed, - * the step will be roughly equal to the with of one graph pixel (in seconds). + * Once data are fetched, the step value will be used to generate values. If not defined, or set to 0, a optimal + * step will be calculated. + * @param step Default to 0. */ + @Override public void setStep(long step) { this.step = step; } /** * Returns the time step used for data processing. Initially, this method returns zero. - * Once {@link #processData()} is finished, the method will return the real value used for - * all internal computations. Roughly corresponds to the --step option in RRDTool's graph/xport commands. + * Once {@link #processData()} is finished, the method will return the time stamp interval. * * @return Step used for data processing. */ + @Override public long getStep() { return step; } @@ -236,10 +241,12 @@ public class DataProcessor { this.fetchRequestResolution = fetchRequestResolution; } + @Override public TimeZone getTimeZone() { return tz; } + @Override public void setTimeZone(TimeZone tz) { this.tz = tz; } @@ -251,6 +258,7 @@ public class DataProcessor { * value will be calculated from the last update times of processed RRD files. * * @return Ending timestamp in seconds + * @deprecated Uses {@link #getEndTime()} instead. */ public long getEndingTimestamp() { return tEnd; @@ -263,7 +271,7 @@ public class DataProcessor { */ public long[] getTimestamps() { if (timestamps == null) { - throw new IllegalArgumentException("Timestamps not calculated yet"); + throw new IllegalStateException("Timestamps not calculated yet"); } else { return timestamps; @@ -465,8 +473,24 @@ public class DataProcessor { * * @param name source name. * @param plottable class that extends Plottable class and is suited for graphing. + * @deprecated Uses {@link #datasource(String, IPlottable)} instead */ + @Deprecated public void addDatasource(String name, Plottable plottable) { + datasource(name, plottable); + } + + /** + * Adds a custom, {@link org.rrd4j.data.Plottable plottable} datasource (PDEF). + * The datapoints should be made available by a class extending + * {@link org.rrd4j.data.Plottable Plottable} class. + * + * @param name source name. + * @param plottable class that extends Plottable class and is suited for graphing. + * @since 3.7 + */ + @Override + public void datasource(String name, IPlottable plottable) { PDef pDef = new PDef(name, plottable); sources.put(name, pDef); } @@ -492,8 +516,38 @@ public class DataProcessor { * @param name source name. * @param rpnExpression RPN expression containing comma delimited simple and complex * source names, RPN constants, functions and operators. + * @deprecated Uses {@link #datasource(String, String)} instead */ + @Deprecated public void addDatasource(String name, String rpnExpression) { + datasource(name, rpnExpression); + } + + /** + *

Adds complex source (CDEF). + * Complex sources are evaluated using the supplied RPN expression.

+ * Complex source name can be used: + *
    + *
  • To specify sources for line, area and stack plots.
  • + *
  • To define other complex sources.
  • + *
+ * + * The supported RPN functions, operators and constants are detailed at + * RRD4J's wiki. + *

+ * Rrd4j does not force you to specify at least one simple source name as RRDTool. + *

+ * For more details on RPN see RRDTool's + * + * rrdgraph man page. + * + * @param name source name. + * @param rpnExpression RPN expression containing comma delimited simple and complex + * source names, RPN constants, functions and operators. + * @since 3.7 + */ + @Override + public void datasource(String name, String rpnExpression) { CDef cDef = new CDef(name, rpnExpression); sources.put(name, cDef); } @@ -533,7 +587,7 @@ public class DataProcessor { /** * Creates a datasource that performs a variable calculation on an - * another named datasource to yield a single combined timestampe/value. + * another named datasource to yield a single combined timestamp/value. * * Requires that the other datasource has already been defined; otherwise, it'll * end up with no data @@ -542,8 +596,28 @@ public class DataProcessor { * @param defName - the datasource from which to extract the percentile. Must be a previously * defined virtual datasource * @param var - a new instance of a Variable used to do the calculation + * @deprecated Uses {@link #datasource(String, String, Variable)} instead. */ + @Deprecated public void addDatasource(String name, String defName, Variable var) { + datasource(name, defName, var); + } + + /** + * Creates a datasource that performs a variable calculation on an + * another named datasource to yield a single combined timestamp/value. + * + * Requires that the other datasource has already been defined; otherwise, it'll + * end up with no data + * + * @param name - the new virtual datasource name + * @param defName - the datasource from which to extract the percentile. Must be a previously + * defined virtual datasource + * @param var - a new instance of a Variable used to do the calculation + * @since 3.7 + */ + @Override + public void datasource(String name, String defName, Variable var) { VDef sDef = new VDef(name, defName, var); sources.put(name, sDef); } @@ -560,9 +634,52 @@ public class DataProcessor { * @param file Path to RRD file. * @param dsName Datasource name defined in the RRD file. * @param consolFunc Consolidation function that will be used to extract data from the RRD + * @deprecated Uses {@link #datasource(String, String, String, ConsolFun)} instead. */ + @Deprecated public void addDatasource(String name, String file, String dsName, ConsolFun consolFunc) { - Def def = new Def(name, file, dsName, consolFunc); + datasource(name, file, dsName, consolFunc); + } + + /** + *

Adds simple datasource (DEF). Simple source name + * can be used:

+ *
    + *
  • To specify sources for line, area and stack plots.
  • + *
  • To define complex sources + *
+ * + * @param name source name. + * @param file Path to RRD file. + * @param dsName Datasource name defined in the RRD file. + * @param consolFunc Consolidation function that will be used to extract data from the RRD + * @since 3.7 + */ + @Override + public void datasource(String name, String file, String dsName, ConsolFun consolFunc) { + RrdBackendFactory factory = RrdBackendFactory.getDefaultFactory(); + Def def = new Def(name, factory.getUri(file), dsName, consolFunc, factory); + sources.put(name, def); + } + + /** + *

Adds simple datasource (DEF). Simple source name + * can be used:

+ *
    + *
  • To specify sources for line, area and stack plots.
  • + *
  • To define complex sources + *
+ * + * @param name source name. + * @param rrdUri URI to RRD file. + * @param dsName Datasource name defined in the RRD file. + * @param consolFunc Consolidation function that will be used to extract data from the RRD + * @since 3.7 + */ + @Override + public void datasource(String name, URI rrdUri, String dsName, + ConsolFun consolFunc) { + Def def = new Def(name, rrdUri, dsName, consolFunc, RrdBackendFactory.findFactory(rrdUri)); sources.put(name, def); } @@ -580,11 +697,12 @@ public class DataProcessor { * file ("AVERAGE", "MIN", "MAX" or "LAST" - these string constants are conveniently defined * in the {@link org.rrd4j.ConsolFun ConsolFun} class). * @param backend Name of the RrdBackendFactory that should be used for this RrdDb. - * @deprecated uses {@link #addDatasource(String, String, String, ConsolFun, RrdBackendFactory)} instead + * @deprecated uses {@link #datasource(String, String, String, ConsolFun, RrdBackendFactory)} instead */ @Deprecated public void addDatasource(String name, String file, String dsName, ConsolFun consolFunc, String backend) { - Def def = new Def(name, file, dsName, consolFunc, RrdBackendFactory.getFactory(backend)); + RrdBackendFactory factory = RrdBackendFactory.getFactory(backend); + Def def = new Def(name, factory.getUri(file), dsName, consolFunc, factory); sources.put(name, def); } @@ -602,9 +720,55 @@ public class DataProcessor { * file ("AVERAGE", "MIN", "MAX" or "LAST" - these string constants are conveniently defined * in the {@link org.rrd4j.ConsolFun ConsolFun} class). * @param backend Name of the RrdBackendFactory that should be used for this RrdDb. + * @deprecated uses {@link #datasource(String, String, String, ConsolFun, RrdBackendFactory)} instead */ + @Deprecated public void addDatasource(String name, String file, String dsName, ConsolFun consolFunc, RrdBackendFactory backend) { - Def def = new Def(name, file, dsName, consolFunc, backend); + datasource(name, file, dsName, consolFunc, backend); + } + + /** + *

Adds simple source (DEF). Source name can be used:

+ *
    + *
  • To specify sources for line, area and stack plots.
  • + *
  • To define complex sources + *
+ * + * @param name Source name. + * @param file Path to RRD file. + * @param dsName Data source name defined in the RRD file. + * @param consolFunc Consolidation function that will be used to extract data from the RRD + * file ("AVERAGE", "MIN", "MAX" or "LAST" - these string constants are conveniently defined + * in the {@link org.rrd4j.ConsolFun ConsolFun} class). + * @param backend Name of the RrdBackendFactory that should be used for this RrdDb. + * @since 3.7 + */ + @Override + public void datasource(String name, String file, String dsName, ConsolFun consolFunc, RrdBackendFactory backend) { + Def def = new Def(name, backend.getUri(file), dsName, consolFunc, backend); + sources.put(name, def); + } + + + /** + *

Adds simple source (DEF). Source name can be used:

+ *
    + *
  • To specify sources for line, area and stack plots.
  • + *
  • To define complex sources + *
+ * + * @param name Source name. + * @param uri URI to RRD file. + * @param dsName Data source name defined in the RRD file. + * @param consolFunc Consolidation function that will be used to extract data from the RRD + * file ("AVERAGE", "MIN", "MAX" or "LAST" - these string constants are conveniently defined + * in the {@link org.rrd4j.ConsolFun ConsolFun} class). + * @param backend Name of the RrdBackendFactory that should be used for this RrdDb. + * @since 3.7 + */ + @Override + public void datasource(String name, URI uri, String dsName, ConsolFun consolFunc, RrdBackendFactory backend) { + Def def = new Def(name, uri, dsName, consolFunc, backend); sources.put(name, def); } @@ -614,8 +778,23 @@ public class DataProcessor { * * @param name Source name. * @param fetchData Fetched data containing values for the given source name. + * @deprecated Uses {@link #datasource(String, FetchData)} instead. */ + @Deprecated public void addDatasource(String name, FetchData fetchData) { + datasource(name, fetchData); + } + + /** + * Adds DEF datasource with datasource values already available in the FetchData object. This method is + * used internally by Rrd4j and probably has no purpose outside of it. + * + * @param name Source name. + * @param fetchData Fetched data containing values for the given source name. + * @since 3.7 + */ + @Override + public void datasource(String name, FetchData fetchData) { Def def = new Def(name, fetchData); sources.put(name, def); } @@ -628,12 +807,30 @@ public class DataProcessor { * @param name Source name. * @param dsName Source name in the fetch data. * @param fetchData Fetched data containing values for the given source name. + * @deprecated Uses {@link #datasource(String, String, FetchData)} instead. */ + @Deprecated public void addDatasource(String name, String dsName, FetchData fetchData) { Def def = new Def(name, dsName, fetchData); sources.put(name, def); } + /** + * Adds DEF datasource with datasource values already available in the FetchData object. This method is + * used internally by Rrd4j and probably has no purpose outside of it. + * The values will be extracted from dsName in fetchData. + * + * @param name Source name. + * @param dsName Source name in the fetch data. + * @param fetchData Fetched data containing values for the given source name. + * @since 3.7 + */ + @Override + public void datasource(String name, String dsName, FetchData fetchData) { + Def def = new Def(name, dsName, fetchData); + sources.put(name, def); + } + ///////////////////////////////////////////////////////////////// // CALCULATIONS ///////////////////////////////////////////////////////////////// @@ -711,7 +908,8 @@ public class DataProcessor { } /** - * Calculates timestamps which correspond to individual pixels on the graph. + * Calculates timestamps which correspond to individual pixels on the graph. It also + * set the timestampsPerPixel value. * * @param pixelCount Graph width * @return Array of timestamps @@ -772,35 +970,60 @@ public class DataProcessor { // PRIVATE METHODS private void extractDefs() { - List defList = new ArrayList(); - for (Source source : sources.values()) { - if (source instanceof Def) { - defList.add((Def) source); - } - } - defSources = defList.toArray(new Def[defList.size()]); + defSources = sources.values().stream().filter(s -> s instanceof Def).toArray(Def[]::new); } private void fetchRrdData() throws IOException { long tEndFixed = (tEnd == 0) ? Util.getTime() : tEnd; - for (int i = 0; i < defSources.length; i++) { - if (!defSources[i].isLoaded()) { - // not fetched yet - Set dsNames = new HashSet(); - dsNames.add(defSources[i].getDsName()); - // look for all other datasources with the same path and the same consolidation function - for (int j = i + 1; j < defSources.length; j++) { - if (defSources[i].isCompatibleWith(defSources[j])) { - dsNames.add(defSources[j].getDsName()); + RrdDb[] batchRrd = new RrdDb[defSources.length]; + Map openRrd = new HashMap<>(defSources.length); + Set newDb = new HashSet<>(defSources.length); + try { + // Storing of the RrdDb in a array to batch open/close, useful if a pool + // is used. + int d = 0; + for (Def def: defSources) { + URI curi = def.getCanonicalUri(); + batchRrd[d++] = openRrd.computeIfAbsent(curi, uri -> { + if (! def.isLoaded()) { + RrdBackendFactory backend = def.getBackend(); + try { + RrdDb rrdDb = RrdDb.getBuilder().setPath(curi).setBackendFactory(backend).readOnly().setPool(pool).setUsePool(poolUsed).build(); + newDb.add(rrdDb); + return rrdDb; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } else { + return def.getRrdDb(); } + }); + } + for (int i = 0; i < defSources.length; i++) { + if (batchRrd[i] == null) { + // The rrdDb failed to open, skip it + continue; } - // now we have everything - try (RrdDb rrd = getRrd(defSources[i])){ - lastRrdArchiveUpdateTime = Math.max(lastRrdArchiveUpdateTime, rrd.getLastArchiveUpdateTime()); - FetchRequest req = rrd.createFetchRequest(defSources[i].getConsolFun(), - tStart, tEndFixed, fetchRequestResolution); + if (!defSources[i].isLoaded()) { + // not fetched yet + Set dsNames = new HashSet(); + dsNames.add(defSources[i].getDsName()); + // look for all other datasources with the same path and the same consolidation function + for (int j = i + 1; j < defSources.length; j++) { + if (defSources[i].isCompatibleWith(defSources[j])) { + dsNames.add(defSources[j].getDsName()); + } + } + // now we have everything + lastRrdArchiveUpdateTime = Math.max( + lastRrdArchiveUpdateTime, + batchRrd[i].getLastArchiveUpdateTime()); + FetchRequest req = batchRrd[i].createFetchRequest( + defSources[i].getConsolFun(), tStart, tEndFixed, + fetchRequestResolution); req.setFilter(dsNames); FetchData data = req.fetchData(); + assert data != null; defSources[i].setFetchData(data); for (int j = i + 1; j < defSources.length; j++) { if (defSources[i].isCompatibleWith(defSources[j])) { @@ -809,6 +1032,15 @@ public class DataProcessor { } } } + } catch (UncheckedIOException ex){ + throw ex.getCause(); + } finally { + newDb.forEach(t -> { + try { + t.close(); + } catch (IOException e) { + } + }); } } @@ -831,7 +1063,8 @@ public class DataProcessor { private void chooseOptimalStep() { long newStep = Long.MAX_VALUE; for (Def defSource : defSources) { - long fetchStep = defSource.getFetchStep(), tryStep = fetchStep; + long fetchStep = defSource.getFetchStep(); + long tryStep = fetchStep; if (step > 0) { tryStep = Math.min(newStep, (((step - 1) / fetchStep) + 1) * fetchStep); } @@ -841,9 +1074,13 @@ public class DataProcessor { // step resolved from a RRD file step = newStep; } - else { - // choose step based on the number of pixels (useful for plottable datasources) + else if (pixelCount != 0) { + // Only calculated sources. But requested in a graph. So use the graph + // width as an hint step = Math.max((tEnd - tStart) / pixelCount, 1); + } else if (step <= 0) { + // If step was not given, just 1 + step = 1; } } @@ -884,12 +1121,6 @@ public class DataProcessor { } } - private RrdDb getRrd(Def def) throws IOException { - String path = def.getPath(); - RrdBackendFactory backend = def.getBackend(); - return RrdDb.getBuilder().setPath(path).setBackendFactory(backend).readOnly().setUsePool(poolUsed).setPool(pool).build(); - } - private static String format(String s, int length) { StringBuilder b = new StringBuilder(s); for (int i = 0; i < length - s.length(); i++) { @@ -898,4 +1129,46 @@ public class DataProcessor { return b.toString(); } + /** + * + * @since 3.7 + */ + @Override + public void setEndTime(long time) { + this.tEnd = time; + } + + /** + * + * @since 3.7 + */ + @Override + public long getEndTime() { + return tEnd; + } + + @Override + public void setStartTime(long time) { + this.tStart = time; + } + + /** + * + * @since 3.7 + */ + @Override + public long getStartTime() { + return tStart; + } + + /** + * + * @since 3.7 + */ + @Override + public void setTimeSpan(long startTime, long endTime) { + this.tStart = startTime; + this.tEnd = endTime; + } + } diff --git a/apps/jrobin/java/src/org/rrd4j/data/Def.java b/apps/jrobin/java/src/org/rrd4j/data/Def.java index 8123343d0..fab7ff81e 100644 --- a/apps/jrobin/java/src/org/rrd4j/data/Def.java +++ b/apps/jrobin/java/src/org/rrd4j/data/Def.java @@ -1,14 +1,15 @@ package org.rrd4j.data; +import java.io.IOException; +import java.net.URI; + import org.rrd4j.ConsolFun; import org.rrd4j.core.FetchData; import org.rrd4j.core.RrdBackendFactory; -import org.rrd4j.core.Util; - -import java.io.IOException; +import org.rrd4j.core.RrdDb; class Def extends Source { - private final String path; + private final URI rrdUri; private final String dsName; private final RrdBackendFactory backend; private final ConsolFun consolFun; @@ -20,34 +21,26 @@ class Def extends Source { Def(String name, String dsName, FetchData fetchData) { this(name, - fetchData.getRequest().getParentDb().getPath(), + fetchData.getRequest().getParentDb().getCanonicalUri(), dsName, fetchData.getRequest().getConsolFun(), fetchData.getRequest().getParentDb().getRrdBackend().getFactory() ); this.fetchData = fetchData; } - Def(String name, String path, String dsName, ConsolFun consolFunc) { - this(name, path, dsName, consolFunc, null); - } - - Def(String name, String path, String dsName, ConsolFun consolFunc, RrdBackendFactory backend) { + Def(String name, URI rrdUri, String dsName, ConsolFun consolFunc, RrdBackendFactory backend) { super(name); - this.path = path; + this.rrdUri = backend.getCanonicalUri(rrdUri); this.dsName = dsName; this.consolFun = consolFunc; this.backend = backend; } - String getPath() { - return path; + URI getCanonicalUri() throws IOException { + return rrdUri; } - String getCanonicalPath() throws IOException { - return Util.getCanonicalPath(path); - } - - String getDsName() { + String getDsName() { return dsName; } @@ -60,12 +53,16 @@ class Def extends Source { } boolean isCompatibleWith(Def def) throws IOException { - return getCanonicalPath().equals(def.getCanonicalPath()) && + return getCanonicalUri().equals(def.getCanonicalUri()) && getConsolFun() == def.consolFun && ((backend == null && def.backend == null) || (backend != null && def.backend != null && backend.equals(def.backend))); } + RrdDb getRrdDb() { + return fetchData.getRequest().getParentDb(); + } + void setFetchData(FetchData fetchData) { this.fetchData = fetchData; } diff --git a/apps/jrobin/java/src/org/rrd4j/data/IPlottable.java b/apps/jrobin/java/src/org/rrd4j/data/IPlottable.java new file mode 100644 index 000000000..9f657fbab --- /dev/null +++ b/apps/jrobin/java/src/org/rrd4j/data/IPlottable.java @@ -0,0 +1,20 @@ +package org.rrd4j.data; + +/** + * Interface to be used for custom datasources. + * + *

If you wish to use a custom datasource in a graph, you should create a class implementing this interface + * that represents that datasource, and then pass this class on to the RrdGraphDef.

+ * @since 3.7 + */ +@FunctionalInterface +public interface IPlottable { + /** + * Retrieves datapoint value based on a given timestamp. + * Use this method if you only have one series of data in this class. + * + * @param timestamp Timestamp in seconds for the datapoint. + * @return Double value of the datapoint. + */ + public double getValue(long timestamp); +} diff --git a/apps/jrobin/java/src/org/rrd4j/data/LinearInterpolator.java b/apps/jrobin/java/src/org/rrd4j/data/LinearInterpolator.java index 53eb26268..76a4561e2 100644 --- a/apps/jrobin/java/src/org/rrd4j/data/LinearInterpolator.java +++ b/apps/jrobin/java/src/org/rrd4j/data/LinearInterpolator.java @@ -15,6 +15,7 @@ import java.util.Date; * Interpolation method handles NaN datasource * values gracefully.

*/ +@SuppressWarnings("deprecation") public class LinearInterpolator extends Plottable { /** diff --git a/apps/jrobin/java/src/org/rrd4j/data/Normalizer.java b/apps/jrobin/java/src/org/rrd4j/data/Normalizer.java index 4ed014faa..afc8bbac6 100644 --- a/apps/jrobin/java/src/org/rrd4j/data/Normalizer.java +++ b/apps/jrobin/java/src/org/rrd4j/data/Normalizer.java @@ -64,4 +64,3 @@ class Normalizer { return values; } } - diff --git a/apps/jrobin/java/src/org/rrd4j/data/PDef.java b/apps/jrobin/java/src/org/rrd4j/data/PDef.java index cc215b220..34ec87ebf 100644 --- a/apps/jrobin/java/src/org/rrd4j/data/PDef.java +++ b/apps/jrobin/java/src/org/rrd4j/data/PDef.java @@ -1,11 +1,11 @@ package org.rrd4j.data; class PDef extends Source implements NonRrdSource { - private final Plottable plottable; + private final IPlottable plottable; - PDef(String name, Plottable plottable) { + PDef(String name, IPlottable plottable2) { super(name); - this.plottable = plottable; + this.plottable = plottable2; } /** {@inheritDoc} */ diff --git a/apps/jrobin/java/src/org/rrd4j/data/Plottable.java b/apps/jrobin/java/src/org/rrd4j/data/Plottable.java index f8fb3a9c6..e1011db64 100644 --- a/apps/jrobin/java/src/org/rrd4j/data/Plottable.java +++ b/apps/jrobin/java/src/org/rrd4j/data/Plottable.java @@ -5,8 +5,10 @@ package org.rrd4j.data; * *

If you wish to use a custom datasource in a graph, you should create a class implementing this interface * that represents that datasource, and then pass this class on to the RrdGraphDef.

+ * @deprecated use implementations of {@link IPlottable} instead */ -public abstract class Plottable { +@Deprecated +public abstract class Plottable implements IPlottable { /** * Retrieves datapoint value based on a given timestamp. * Use this method if you only have one series of data in this class. @@ -14,7 +16,5 @@ public abstract class Plottable { * @param timestamp Timestamp in seconds for the datapoint. * @return Double value of the datapoint. */ - public double getValue(long timestamp) { - return Double.NaN; - } + public abstract double getValue(long timestamp); } diff --git a/apps/jrobin/java/src/org/rrd4j/data/Variable.java b/apps/jrobin/java/src/org/rrd4j/data/Variable.java index 2668032cf..d6d6aea40 100644 --- a/apps/jrobin/java/src/org/rrd4j/data/Variable.java +++ b/apps/jrobin/java/src/org/rrd4j/data/Variable.java @@ -26,6 +26,10 @@ public abstract class Variable { this.value = value; this.timestamp = timestamp; } + @Override + public String toString() { + return "Value [value=" + value + ", timestamp=" + timestamp + "]"; + } }; public static final Value INVALIDVALUE = new Value(0, Double.NaN); @@ -33,7 +37,7 @@ public abstract class Variable { private Value val = null; /** - * Used to calculate the needed value from a source, this method call fill. + * Used to calculate the needed value from a source, this method call the abstract method {@link #fill(long[], double[], long, long)}. * @param s * @param start * @param end @@ -44,8 +48,8 @@ public abstract class Variable { int last = -1; // Iterate over array, stop then end cursor reach start or when both start and end has been found // It also stop if cursor cross other side boundary - for(int i = 0, j = s.timestamps.length - 1 ; ( last == -1 && j > first ) || ( first == -1 && ( last == -1 || i < last ) ) ; i++, j--) { - if(first == -1) { + for (int i = 0, j = s.timestamps.length - 1 ; i < s.timestamps.length && j >= 0 ; i++, j--) { + if (first == -1) { long leftdown = Math.max(s.timestamps[i] - step, start); long rightdown = Math.min(s.timestamps[i], end); if(rightdown > leftdown) { @@ -53,34 +57,35 @@ public abstract class Variable { } } - if(last == -1) { + if (last == -1) { long leftup = Math.max(s.timestamps[j] - step, start); long rightup = Math.min(s.timestamps[j], end); - if(rightup > leftup ) { + if (rightup > leftup ) { last = j; } } + if ((( last != -1 || j <= first ) && ( first != -1 || ( last != -1 && i >= last )))) { + break; + } } - if( first == -1 || last == -1) { - throw new RuntimeException("Invalid range"); - } - if(s instanceof VDef) { + if (first == -1 || last == -1) { + val = INVALIDVALUE; + } else if (s instanceof VDef) { // Already a variable, just check if it fits Value v = ((VDef) s).getValue(); // No time stamp, or not time stamped value, keep it - if(v.timestamp == 0) { + if (v.timestamp == 0) { val = v; } else { - if(v.timestamp < end && v.timestamp > start) { + if (v.timestamp < end && v.timestamp > start) { val = v; } else { val = new Value(0, Double.NaN); } } - } - else { + } else { long[] timestamps = new long[ last - first + 1]; System.arraycopy(s.timestamps, first, timestamps, 0, timestamps.length); double[] values = new double[ last - first + 1]; @@ -115,8 +120,8 @@ public abstract class Variable { public static class FIRST extends Variable { @Override protected Value fill(long[] timestamps, double[] values, long start, long end) { - for(int i = 0; i < values.length; i++) { - if( timestamps[i] > start && timestamps[i] < end && ! Double.isNaN(values[i])) { + for (int i = 0; i < values.length; i++) { + if (timestamps[i] > start && timestamps[i] < end && ! Double.isNaN(values[i])) { return new Value(timestamps[i], values[i]); } } @@ -131,8 +136,8 @@ public abstract class Variable { public static class LAST extends Variable { @Override protected Value fill(long[] timestamps, double[] values, long start, long end) { - for(int i = values.length - 1 ; i >=0 ; i--) { - if( ! Double.isNaN(values[i]) ) { + for (int i = values.length - 1 ; i >=0 ; i--) { + if (! Double.isNaN(values[i]) ) { return new Value(timestamps[i], values[i]); } } @@ -149,11 +154,11 @@ public abstract class Variable { protected Value fill(long[] timestamps, double[] values, long start, long end) { long timestamp = 0; double value = Double.NaN; - for(int i = values.length -1 ; i >=0 ; i--) { - if(! Double.isNaN(values[i]) && Double.isNaN(value)) { + for (int i = values.length -1 ; i >=0 ; i--) { + if (! Double.isNaN(values[i]) && Double.isNaN(value)) { timestamp = timestamps[i]; value = values[i]; - } else if( ! Double.isNaN(values[i]) && value > values[i]) { + } else if ( ! Double.isNaN(values[i]) && value > values[i]) { timestamp = timestamps[i]; value = values[i]; } @@ -171,11 +176,11 @@ public abstract class Variable { protected Value fill(long[] timestamps, double[] values, long start, long end) { long timestamp = 0; double value = Double.NaN; - for(int i = values.length -1 ; i >=0 ; i--) { - if(! Double.isNaN(values[i]) && Double.isNaN(value)) { + for (int i = values.length -1 ; i >=0 ; i--) { + if (! Double.isNaN(values[i]) && Double.isNaN(value)) { timestamp = timestamps[i]; value = values[i]; - } else if(!Double.isNaN(values[i]) && value < values[i]) { + } else if (!Double.isNaN(values[i]) && value < values[i]) { timestamp = timestamps[i]; value = values[i]; } @@ -193,7 +198,7 @@ public abstract class Variable { protected Value fill(long[] timestamps, double[] values, long start, long end) { double value = Double.NaN; - for(double tempVal: values) { + for (double tempVal: values) { value = Util.sum(value, tempVal); } return new Value(0, value * (timestamps[1] - timestamps[0]) ); @@ -217,8 +222,7 @@ public abstract class Variable { } if (! Double.isNaN(value) && count > 0) { value = value / count; - } - else { + } else { value = Double.NaN; } return new Value(0, value); @@ -237,21 +241,20 @@ public abstract class Variable { double M = 0.0; double S = 0.0; // See Knuth TAOCP vol 2, 3rd edition, page 232 and http://www.johndcook.com/standard_deviation.html - for(double cursVal: values) { - if(Double.isNaN(cursVal)) + for (double cursVal: values) { + if (Double.isNaN(cursVal)) continue; count++; - if(count == 1) { + if (count == 1) { M = cursVal; S = 0; - } - else { + } else { double dM = cursVal - M; M += dM/count; S += dM * (cursVal - M); } } - if(count > 1) { + if (count > 1) { value = Math.sqrt( S/(count - 1) ); } return new Value(0, value); @@ -303,9 +306,9 @@ public abstract class Variable { static final class ComparPercentElemen implements Comparator, Serializable { @Override public final int compare(PercentElem arg0, PercentElem arg1) { - if(Double.isNaN(arg0.value) && Double.isNaN(arg1.value)) + if (Double.isNaN(arg0.value) && Double.isNaN(arg1.value)) return Long.signum(arg0.timestamp - arg1.timestamp); - else if(Double.isNaN(arg0.value)) + else if (Double.isNaN(arg0.value)) return -1; else if (Double.isNaN(arg1.value)) return +1; @@ -344,12 +347,12 @@ public abstract class Variable { protected Value fill(long[] timestamps, double[] values, long start, long end) { // valuesSet will be a set with NaN packet at the start SortedSet valuesSet = new TreeSet(new ComparPercentElemen()); - for(int i = 0 ; i < values.length ; i++) { + for (int i = 0 ; i < values.length ; i++) { valuesSet.add(new PercentElem(i, timestamps[i], values[i])); } //If not with nan, just drop all nan (inferior to min value) - if( ! withNaN) { + if (! withNaN) { valuesSet = valuesSet.tailSet(new PercentElem(0, 0, Double.NEGATIVE_INFINITY )); } @@ -435,7 +438,7 @@ public abstract class Variable { double lslslope; double lslint; - for(int i = 0; i < values.length; i++) { + for (int i = 0; i < values.length; i++) { double value = values[i]; if (!Double.isNaN(value)) { @@ -449,7 +452,7 @@ public abstract class Variable { lslstep++; } double divisor = (SUMx * SUMx - cnt * SUMxx); - if(cnt > 0 && divisor != 0) { + if (cnt > 0 && divisor != 0) { /* Bestfit line by linear least squares method */ lslslope = (SUMx * SUMy - cnt * SUMxy) / divisor; lslint = (SUMy - lslslope * SUMx) / cnt; @@ -478,7 +481,7 @@ public abstract class Variable { double SUMyy = 0.0; double lslcorrel; - for(int i = 0; i < values.length; i++) { + for (int i = 0; i < values.length; i++) { double value = values[i]; if (!Double.isNaN(value)) { @@ -492,7 +495,7 @@ public abstract class Variable { } lslstep++; } - if(cnt > 0) { + if (cnt > 0) { /* Bestfit line by linear least squares method */ lslcorrel = (SUMxy - (SUMx * SUMy) / cnt) / diff --git a/apps/jrobin/java/src/org/rrd4j/graph/CDef.java b/apps/jrobin/java/src/org/rrd4j/graph/CDef.java index 380d78b98..0810e36be 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/CDef.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/CDef.java @@ -11,6 +11,6 @@ class CDef extends Source { } void requestData(DataProcessor dproc) { - dproc.addDatasource(name, rpnExpression); + dproc.datasource(name, rpnExpression); } } diff --git a/apps/jrobin/java/src/org/rrd4j/graph/Def.java b/apps/jrobin/java/src/org/rrd4j/graph/Def.java index c5a2bef53..5fdf36ba9 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/Def.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/Def.java @@ -1,42 +1,29 @@ package org.rrd4j.graph; import org.rrd4j.data.DataProcessor; + +import java.net.URI; + import org.rrd4j.ConsolFun; import org.rrd4j.core.RrdBackendFactory; class Def extends Source { - private final String rrdPath, dsName; + + private final URI rrdUri; + private final String dsName; private final RrdBackendFactory backend; private final ConsolFun consolFun; - Def(String name, String rrdPath, String dsName, ConsolFun consolFun) { - this(name, rrdPath, dsName, consolFun, (RrdBackendFactory)null); - } - - @Deprecated - Def(String name, String rrdPath, String dsName, ConsolFun consolFun, String backend) { + Def(String name, URI rrdUri, String dsName, ConsolFun consolFun, RrdBackendFactory backend) { super(name); - this.rrdPath = rrdPath; - this.dsName = dsName; - this.consolFun = consolFun; - this.backend = RrdBackendFactory.getFactory(backend); - } - - Def(String name, String rrdPath, String dsName, ConsolFun consolFun, RrdBackendFactory backend) { - super(name); - this.rrdPath = rrdPath; + this.rrdUri = rrdUri; this.dsName = dsName; this.consolFun = consolFun; this.backend = backend; } void requestData(DataProcessor dproc) { - if (backend == null) { - dproc.addDatasource(name, rrdPath, dsName, consolFun); - } - else { - dproc.addDatasource(name, rrdPath, dsName, consolFun, backend); - } + dproc.datasource(name, rrdUri, dsName, consolFun, backend); } } diff --git a/apps/jrobin/java/src/org/rrd4j/graph/PDef.java b/apps/jrobin/java/src/org/rrd4j/graph/PDef.java index 88fe3b0dd..930f34684 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/PDef.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/PDef.java @@ -1,17 +1,17 @@ package org.rrd4j.graph; import org.rrd4j.data.DataProcessor; -import org.rrd4j.data.Plottable; +import org.rrd4j.data.IPlottable; class PDef extends Source { - private Plottable plottable; + private IPlottable plottable; - PDef(String name, Plottable plottable) { + PDef(String name, IPlottable plottable) { super(name); this.plottable = plottable; } void requestData(DataProcessor dproc) { - dproc.addDatasource(name, plottable); + dproc.datasource(name, plottable); } } diff --git a/apps/jrobin/java/src/org/rrd4j/graph/PrintText.java b/apps/jrobin/java/src/org/rrd4j/graph/PrintText.java index f247d6872..07ca2baa3 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/PrintText.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/PrintText.java @@ -45,7 +45,7 @@ class PrintText extends CommentText { resolvedText = String.format(l, resolvedText, c); } catch (Exception e) { throw new RuntimeException("can't format '" + resolvedText + "'", e); - } + } } else { resolvedText = "-"; diff --git a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java index 058774d16..9b985f1f2 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java @@ -23,13 +23,18 @@ import org.rrd4j.graph.DownSampler.DataSet; */ public class RrdGraph implements RrdGraphConstants { private static final double[] SENSIBLE_VALUES = { - 1000.0, 900.0, 800.0, 750.0, 700.0, 600.0, 500.0, 400.0, 300.0, 250.0, 200.0, 125.0, 100.0, - 90.0, 80.0, 75.0, 70.0, 60.0, 50.0, 40.0, 30.0, 25.0, 20.0, 10.0, - 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.5, 3.0, 2.5, 2.0, 1.8, 1.5, 1.2, 1.0, + 1000.0, 900.0, 800.0, 750.0, 700.0, + 600.0, 500.0, 400.0, 300.0, 250.0, + 200.0, 125.0, 100.0, 90.0, 80.0, + 75.0, 70.0, 60.0, 50.0, 40.0, 30.0, + 25.0, 20.0, 10.0, 9.0, 8.0, + 7.0, 6.0, 5.0, 4.0, 3.5, 3.0, + 2.5, 2.0, 1.8, 1.5, 1.2, 1.0, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1 }; - private static final char[] SYMBOLS = {'a', 'f', 'p', 'n', 'µ', 'm', ' ', 'k', 'M', 'G', 'T', 'P', 'E'}; + private static final int SYMBOLS_CENTER = 8; + private static final char[] SYMBOLS = {'y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', ' ', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}; final RrdGraphDef gdef; final ImageParameters im; @@ -122,13 +127,14 @@ public class RrdGraph implements RrdGraphConstants { fetchData(); resolveTextElements(); if (gdef.shouldPlot() && !lazy) { + initializeLimits(); calculatePlotValues(); findMinMaxValues(); identifySiUnit(); expandValueRange(); removeOutOfRangeRules(); removeOutOfRangeSpans(); - initializeLimits(); + mapper = new Mapper(this); placeLegends(); createImageWorker(); drawBackground(); @@ -436,8 +442,6 @@ public class RrdGraph implements RrdGraphConstants { im.yorigin = PADDING_TOP + im.ysize; } - mapper = new Mapper(this); - if (!gdef.onlyGraph && gdef.title != null) { im.yorigin += getFontHeight(FONTTAG_TITLE) + PADDING_TITLE; } @@ -575,7 +579,6 @@ public class RrdGraph implements RrdGraphConstants { im.unitsexponent = gdef.unitsExponent; im.base = gdef.base; if (!gdef.logarithmic) { - int symbcenter = 6; double digits; if (im.unitsexponent != Integer.MAX_VALUE) { digits = Math.floor(im.unitsexponent / 3.0); @@ -584,8 +587,8 @@ public class RrdGraph implements RrdGraphConstants { digits = Math.floor(Math.log(Math.max(Math.abs(im.minval), Math.abs(im.maxval))) / Math.log(im.base)); } im.magfact = Math.pow(im.base, digits); - if (((digits + symbcenter) < SYMBOLS.length) && ((digits + symbcenter) >= 0)) { - im.symbol = SYMBOLS[(int) digits + symbcenter]; + if (((digits + SYMBOLS_CENTER) < SYMBOLS.length) && ((digits + SYMBOLS_CENTER) >= 0)) { + im.symbol = SYMBOLS[(int) digits + SYMBOLS_CENTER]; } else { im.symbol = '?'; @@ -655,7 +658,11 @@ public class RrdGraph implements RrdGraphConstants { private void fetchData() throws IOException { dproc = new DataProcessor(gdef.startTime, gdef.endTime); - dproc.setPoolUsed(gdef.poolUsed); + dproc.setPixelCount(im.xsize); + if (gdef.poolUsed) { + dproc.setPoolUsed(gdef.poolUsed); + dproc.setPool(gdef.getPool()); + } dproc.setTimeZone(gdef.tz); if (gdef.step > 0) { dproc.setStep(gdef.step); diff --git a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraphConstants.java b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraphConstants.java index c27f7adea..7e0da6349 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraphConstants.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraphConstants.java @@ -1,13 +1,39 @@ package org.rrd4j.graph; -import java.awt.*; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.Stroke; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URL; import java.util.Calendar; import java.util.Locale; +import java.util.Optional; +import java.util.Properties; +import java.util.function.Function; /** * Class to represent various constants used for graphing. No methods are specified. + *

+ * The fonts settings can be changed use some on the following properties, sorted by increased priority. + *

    + *
  1. org.rrd4j.fonts.properties
  2. + *
  3. org.rrd4j.fonts.properties.url
  4. + *
  5. org.rrd4j.font.plain
  6. + *
  7. org.rrd4j.font.bold
  8. + *
  9. org.rrd4j.font.plain.url
  10. + *
  11. org.rrd4j.font.bold.url
  12. + *
+ * + * If either org.rrd4j.fonts.properties or org.rrd4j.fonts.properties.url is used, the file provided contains any other property of lower priority . + * The last four properties defines directly the plain or bold font. All properties URL related (org.rrd4j.fonts.url, org.rrd4j.font.plain.url and + * org.rrd4j.font.bold.url) download data from an URL. They are useful when those data are provided by the file system, defined by the OS. + * The others search for the data in the classpath. So it's easy to provided font-pack as a jar that's put before RRD44J's jar. + *

+ * The default settings uses org.rrd4j.fonts.properties looking for the file /rrd4jfonts.properties in the classpath. */ public interface RrdGraphConstants { /** @@ -265,10 +291,70 @@ public interface RrdGraphConstants { double DEFAULT_BASE = 1000; /** - * Font constructor, to use embedded fonts + * The file that contains font configuration searched in the class path. The default value is /rrd4jfonts.properties + */ + public static final String PROPERTYFONTSPROPERTIES = "org.rrd4j.fonts.properties"; + /** + * A possible URL to a configuration file. + */ + public static final String PROPERTYFONTSURL = "org.rrd4j.fonts.properties.url"; + /** + * The name of the plain font, used to define the {@link #DEFAULT_SMALL_FONT} and the {@link GATOR_FONT}. To be found in the classpath. + */ + public static final String PROPERTYFONTPLAIN = "org.rrd4j.font.plain"; + /** + * The name of the bold font, used to define the {@link #DEFAULT_LARGE_FONT}. To be found in the classpath. + */ + public static final String PROPERTYFONTBOLD = "org.rrd4j.font.bold"; + /** + * An URL to the plain font, used to define the {@link #DEFAULT_SMALL_FONT} and the {@link GATOR_FONT}. + */ + public static final String PROPERTYFONTPLAINURL = "org.rrd4j.font.plain.url"; + /** + * An URL to the bold font, used to define the {@link #DEFAULT_LARGE_FONT}. + */ + public static final String PROPERTYFONTBOLDURL = "org.rrd4j.font.bold.url"; + + /** + * Font constructor, to use embedded fonts. Not really useful outside internal use for RRD4J. */ static class FontConstructor { + private static final Properties fileProps = new Properties(); + static { + refreshConf(); + } private FontConstructor() {} + + /** + * Used for tests + */ + static void refreshConf() { + fileProps.clear(); + Optional.ofNullable(System.getProperty(PROPERTYFONTSPROPERTIES, "/rrd4jfonts.properties")) + .filter(s -> ! s.isEmpty()) + .map(RrdGraphConstants.class::getResourceAsStream) + .ifPresent(t -> { + try { + fileProps.load(t); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + Optional.ofNullable(System.getProperty(PROPERTYFONTSURL)) + .filter(s -> ! s.isEmpty()) + .ifPresent(t -> { + try { + fileProps.load(new URL(t).openStream()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + for (String prop: new String[] {PROPERTYFONTPLAIN, PROPERTYFONTBOLD, PROPERTYFONTPLAINURL, PROPERTYFONTBOLDURL}) { + Optional.ofNullable(System.getProperty(prop)) + .filter(s -> ! s.isEmpty()) + .ifPresent(s -> fileProps.put(prop, s)); + } + } /** * Return the default RRD4J's default font for the given strength @@ -278,14 +364,22 @@ public interface RrdGraphConstants { */ public static Font getFont(int type, int size) { /* - String fontPath; - if (type == Font.BOLD) - fontPath = "/DejaVuSansMono-Bold.ttf"; - else - fontPath = "/DejaVuSansMono.ttf"; - - try (InputStream fontstream = RrdGraphConstants.class.getResourceAsStream(fontPath)) { - return Font.createFont(Font.TRUETYPE_FONT, fontstream).deriveFont(type == Font.BOLD ? Font.BOLD : Font.PLAIN, size); + Function fontStream = null; + String fontPath = fileProps.getProperty(type == Font.BOLD ? PROPERTYFONTBOLDURL : PROPERTYFONTPLAINURL); + if (fontPath!= null) { + fontStream = s -> { + try { + return new URL(s).openStream(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + } else { + fontPath = fileProps.getProperty(type == Font.BOLD ? PROPERTYFONTBOLD : PROPERTYFONTPLAIN); + fontStream = RrdGraphConstants.class::getResourceAsStream; + } + try (InputStream fontstream = fontStream.apply(fontPath)) { + return Font.createFont(Font.TRUETYPE_FONT, fontstream).deriveFont((float)size); } catch (FontFormatException | IOException e) { throw new RuntimeException(e); } diff --git a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraphDef.java b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraphDef.java index edebe4c53..4efbf2ac6 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraphDef.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraphDef.java @@ -7,7 +7,10 @@ import java.awt.Stroke; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; +import java.net.URI; import java.net.URL; +import java.time.Instant; +import java.time.temporal.TemporalAmount; import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -17,11 +20,13 @@ import java.util.TimeZone; import javax.imageio.ImageIO; import org.rrd4j.ConsolFun; +import org.rrd4j.core.DataHolder; import org.rrd4j.core.FetchData; import org.rrd4j.core.RrdBackendFactory; +import org.rrd4j.core.RrdDbPool; import org.rrd4j.core.Util; import org.rrd4j.data.DataProcessor; -import org.rrd4j.data.Plottable; +import org.rrd4j.data.IPlottable; import org.rrd4j.data.Variable; /** @@ -52,7 +57,7 @@ import org.rrd4j.data.Variable; * without forcing a newline, you can use the special tag \J at the end of * the string to disable the auto justification.

*/ -public class RrdGraphDef implements RrdGraphConstants { +public class RrdGraphDef implements RrdGraphConstants, DataHolder { /** *

Implementations of this class can be used to generate image than can be @@ -96,7 +101,8 @@ public class RrdGraphDef implements RrdGraphConstants { } } - boolean poolUsed = false; // ok + boolean poolUsed = DEFAULT_POOL_USAGE_POLICY; + private RrdDbPool pool = null; boolean antiAliasing = false; // ok boolean textAntiAliasing = false; // ok String filename = RrdGraphConstants.IN_MEMORY_IMAGE; // ok @@ -173,17 +179,47 @@ public class RrdGraphDef implements RrdGraphConstants { /** * Creates RrdGraphDef object and sets default time span (default ending time is 'now', * default starting time is 'end-1day'. + * @deprecated Uses default value that will be probably overriden. */ + @Deprecated public RrdGraphDef() { setTimeSpan(Util.getTimestamps(DEFAULT_START, DEFAULT_END)); } + /** + * Creates RrdGraphDef object. + * @since 3.7 + */ + public RrdGraphDef(long t1, long t2) { + if ((t1 < t2 && t1 > 0 && t2 > 0) || (t1 > 0 && t2 == 0)) { + this.startTime = t1; + this.endTime = t2; + } + else { + throw new IllegalArgumentException("Invalid timestamps specified: " + t1 + ", " + t2); + } + } + + /** + * Creates new DataProcessor object for the given time duration. The given duration will be + * substracted from current time. + * + * @param d duration to substract. + * @since 3.7 + */ + public RrdGraphDef(TemporalAmount d) { + Instant now = Instant.now(); + this.endTime = now.getEpochSecond(); + this.startTime = now.minus(d).getEpochSecond(); + } + /** * Sets the time when the graph should begin. Time in seconds since epoch * (1970-01-01) is required. Negative numbers are relative to the current time. * * @param time Starting time for the graph in seconds since epoch */ + @Override public void setStartTime(long time) { this.startTime = time; if (time <= 0) { @@ -197,6 +233,7 @@ public class RrdGraphDef implements RrdGraphConstants { * * @param time Ending time for the graph in seconds since epoch */ + @Override public void setEndTime(long time) { this.endTime = time; if (time <= 0) { @@ -211,6 +248,7 @@ public class RrdGraphDef implements RrdGraphConstants { * @param startTime Starting time in seconds since epoch * @param endTime Ending time in seconds since epoch */ + @Override public void setTimeSpan(long startTime, long endTime) { setStartTime(startTime); setEndTime(endTime); @@ -234,10 +272,36 @@ public class RrdGraphDef implements RrdGraphConstants { * * @param poolUsed true, if RrdDbPool class should be used. False otherwise. */ + @Override public void setPoolUsed(boolean poolUsed) { this.poolUsed = poolUsed; } + /** + * @since 3.7 + */ + @Override + public boolean isPoolUsed() { + return poolUsed; + } + + /** + * @since 3.7 + */ + @Override + public RrdDbPool getPool() { + return pool; + } + + /** + * @since 3.7 + */ + @Override + public void setPool(RrdDbPool pool) { + this.poolUsed = true; + this.pool = pool; + } + /** * Sets the name of the graph to generate. Since Rrd4j outputs GIFs, PNGs, * and JPEGs it's recommended that the filename end in either .gif, @@ -781,6 +845,7 @@ public class RrdGraphDef implements RrdGraphConstants { * * @param step Desired time step (don't use this method if you don't know what you're doing). */ + @Override public void setStep(long step) { this.step = step; } @@ -821,7 +886,7 @@ public class RrdGraphDef implements RrdGraphConstants { * font is selected. * * @param smallFont Default font for graphing. Use only monospaced fonts. - * @deprecated Use {@link Variable} based method instead. + * @deprecated Use {@link FontTag} based method instead. */ @Deprecated public void setSmallFont(final Font smallFont) { @@ -832,7 +897,7 @@ public class RrdGraphDef implements RrdGraphConstants { * Sets title font. * * @param largeFont Font to be used for graph title. - * @deprecated Use {@link Variable} based method instead. + * @deprecated Use {@link FontTag} based method instead. */ @Deprecated public void setLargeFont(final Font largeFont) { @@ -948,8 +1013,27 @@ public class RrdGraphDef implements RrdGraphConstants { * @param dsName Datasource name in the specified RRD file * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) */ + @Override public void datasource(String name, String rrdPath, String dsName, ConsolFun consolFun) { - sources.add(new Def(name, rrdPath, dsName, consolFun)); + RrdBackendFactory factory = RrdBackendFactory.getDefaultFactory(); + sources.add(new Def(name, factory.getUri(rrdPath), dsName, consolFun, factory)); + } + + /** + * Defines virtual datasource. This datasource can then be used + * in other methods like {@link #datasource(String, String)} or + * {@link #gprint(String, ConsolFun, String)}. + * + * @param name Source name + * @param rrdUri URI to RRD file + * @param dsName Datasource name in the specified RRD file + * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) + * @since 3.7 + */ + @Override + public void datasource(String name, URI rrdUri, String dsName, + ConsolFun consolFun) { + sources.add(new Def(name, rrdUri, dsName, consolFun, RrdBackendFactory.findFactory(rrdUri))); } /** @@ -967,7 +1051,8 @@ public class RrdGraphDef implements RrdGraphConstants { */ @Deprecated public void datasource(String name, String rrdPath, String dsName, ConsolFun consolFun, String backend) { - sources.add(new Def(name, rrdPath, dsName, consolFun, RrdBackendFactory.getFactory(backend))); + RrdBackendFactory factory = RrdBackendFactory.getFactory(backend); + sources.add(new Def(name, factory.getUri(rrdPath), dsName, consolFun, factory)); } /** @@ -981,8 +1066,27 @@ public class RrdGraphDef implements RrdGraphConstants { * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) * @param backend Backend to be used while fetching data from a RRD file. */ + @Override public void datasource(String name, String rrdPath, String dsName, ConsolFun consolFun, RrdBackendFactory backend) { - sources.add(new Def(name, rrdPath, dsName, consolFun, backend)); + sources.add(new Def(name, backend.getUri(rrdPath), dsName, consolFun, backend)); + } + + /** + * Defines virtual datasource. This datasource can then be used + * in other methods like {@link #datasource(String, String)} or + * {@link #gprint(String, ConsolFun, String)}. + * + * @param name Source name + * @param rrdUri Path to RRD file + * @param dsName Datasource name in the specified RRD file + * @param consolFun Consolidation function (AVERAGE, MIN, MAX, LAST) + * @param backend Backend to be used while fetching data from a RRD file. + * @since 3.7 + */ + @Override + public void datasource(String name, URI rrdUri, String dsName, + ConsolFun consolFun, RrdBackendFactory backend) { + sources.add(new Def(name, rrdUri, dsName, consolFun, backend)); } /** @@ -992,6 +1096,7 @@ public class RrdGraphDef implements RrdGraphConstants { * @param name Source name * @param rpnExpression RPN expression. */ + @Override public void datasource(String name, String rpnExpression) { sources.add(new CDef(name, rpnExpression)); } @@ -1010,6 +1115,19 @@ public class RrdGraphDef implements RrdGraphConstants { datasource(name, defName, consolFun.getVariable()); } + /** + * Creates a datasource that performs a variable calculation on an + * another named datasource to yield a single combined timestamp/value. + * + * Requires that the other datasource has already been defined; otherwise, it'll + * end up with no data + * + * @param name - the new virtual datasource name + * @param defName - the datasource from which to extract the percentile. Must be a previously + * defined virtual datasource + * @param var - a new instance of a Variable used to do the calculation + */ + @Override public void datasource(String name, String defName, Variable var) { sources.add(new VDef(name, defName, var)); } @@ -1020,8 +1138,10 @@ public class RrdGraphDef implements RrdGraphConstants { * * @param name Source name. * @param plottable Plottable object. + * @since 3.7 */ - public void datasource(String name, Plottable plottable) { + @Override + public void datasource(String name, IPlottable plottable) { sources.add(new PDef(name, plottable)); } @@ -1032,6 +1152,7 @@ public class RrdGraphDef implements RrdGraphConstants { * @param name Source name. * @param fetchData FetchData object. */ + @Override public void datasource(String name, FetchData fetchData) { sources.add(new TDef(name, name, fetchData)); } @@ -1045,6 +1166,7 @@ public class RrdGraphDef implements RrdGraphConstants { * @param dsName Source name in fetchData. * @param fetchData FetchData object. */ + @Override public void datasource(String name, String dsName, FetchData fetchData) { sources.add(new TDef(name, dsName, fetchData)); } @@ -1723,10 +1845,19 @@ public class RrdGraphDef implements RrdGraphConstants { * * @param tz the time zone to set */ + @Override public void setTimeZone(TimeZone tz) { this.tz = tz; } + /** + * @since 3.7 + */ + @Override + public TimeZone getTimeZone() { + return this.tz; + } + /** * Set the Stroke used to draw grid * @@ -1784,4 +1915,28 @@ public class RrdGraphDef implements RrdGraphConstants { return colors[element.ordinal()]; } + /** + * @since 3.7 + */ + @Override + public long getEndTime() { + return this.endTime; + } + + /** + * @since 3.7 + */ + @Override + public long getStartTime() { + return this.startTime; + } + + /** + * @since 3.7 + */ + @Override + public long getStep() { + return this.step; + } + } diff --git a/apps/jrobin/java/src/org/rrd4j/graph/TDef.java b/apps/jrobin/java/src/org/rrd4j/graph/TDef.java index dcddb8694..0f197c40a 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/TDef.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/TDef.java @@ -16,7 +16,7 @@ class TDef extends Source { @Override void requestData(DataProcessor dproc) { - dproc.addDatasource(name, dsName, fetchData); + dproc.datasource(name, dsName, fetchData); } } diff --git a/apps/jrobin/java/src/org/rrd4j/graph/VDef.java b/apps/jrobin/java/src/org/rrd4j/graph/VDef.java index 6161020a9..332380f6f 100644 --- a/apps/jrobin/java/src/org/rrd4j/graph/VDef.java +++ b/apps/jrobin/java/src/org/rrd4j/graph/VDef.java @@ -14,7 +14,7 @@ class VDef extends Source { } void requestData(DataProcessor dproc) { - dproc.addDatasource(name, defName, var); + dproc.datasource(name, defName, var); } }