Fill in more of the implementation of MetricCollector and Metric
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/aefd910b Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/aefd910b Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/aefd910b Branch: refs/heads/master Commit: aefd910b41577bc26a04f6c020a4ccff6354097b Parents: 42c0d1e Author: Howard M. Lewis Ship <[email protected]> Authored: Sat Apr 20 19:42:45 2013 +0100 Committer: Howard M. Lewis Ship <[email protected]> Committed: Sat Apr 20 19:42:45 2013 +0100 ---------------------------------------------------------------------- .../services/metrics/MetricCollectorImpl.java | 296 ++++++++++++++- .../tapestry5/ioc/services/metrics/Metric.java | 15 +- .../ioc/services/metrics/MetricCollector.java | 10 +- 3 files changed, 311 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aefd910b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/metrics/MetricCollectorImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/metrics/MetricCollectorImpl.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/metrics/MetricCollectorImpl.java index 7a2ee47..087ea39 100644 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/metrics/MetricCollectorImpl.java +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/services/metrics/MetricCollectorImpl.java @@ -1,19 +1,309 @@ package org.apache.tapestry5.ioc.internal.services.metrics; +import com.google.common.util.concurrent.AtomicDouble; +import org.apache.tapestry5.func.F; +import org.apache.tapestry5.ioc.annotations.PostInjection; +import org.apache.tapestry5.ioc.annotations.Symbol; +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; +import org.apache.tapestry5.ioc.internal.util.InternalUtils; +import org.apache.tapestry5.ioc.internal.util.LockSupport; +import org.apache.tapestry5.ioc.modules.MetricsSymbols; +import org.apache.tapestry5.ioc.services.cron.IntervalSchedule; +import org.apache.tapestry5.ioc.services.cron.PeriodicExecutor; import org.apache.tapestry5.ioc.services.metrics.Metric; import org.apache.tapestry5.ioc.services.metrics.MetricCollector; +import org.apache.tapestry5.ioc.util.ExceptionUtils; +import org.rrd4j.core.*; +import org.slf4j.Logger; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; -public class MetricCollectorImpl implements MetricCollector +public class MetricCollectorImpl extends LockSupport implements MetricCollector, Runnable { + private final boolean inMemory; + + private final RrdBackendFactory factory; + + private final Logger logger; + + private final String dbDir; + + private final List<Runnable> updates = CollectionFactory.newThreadSafeList(); + + public static final int HEARTBEAT = 10; // seconds + + public static final String DS_NAME = "data"; + + private class MetricImpl extends LockSupport implements Metric, Comparable<MetricImpl>, Runnable + { + private final MetricImpl parent; + + private final Map<String, Metric> children = new HashMap<String, Metric>(); + + private final String name; + + private final String path; + + private final Type type; + + private final Units units; + + private final RrdDb db; + + // TODO: May want to initialize this from stored data for Type.TOTAL + + private final AtomicDouble accumulator = new AtomicDouble(); + + MetricImpl(MetricImpl parent, String name, Type type, Units units) + { + this.name = name; + assert InternalUtils.isNonBlank(name); + assert type != null; + assert units != null; + + this.parent = parent; + + // Parent may be null for a root Metric + this.path = parent == null ? name : parent.getPath() + "/" + name; + + this.type = type; + this.units = units; + + try + { + this.db = createDb(); + } catch (IOException ex) + { + throw new RuntimeException(String.format("Unable to create RrdDb for '%s': %s", + path, + ExceptionUtils.toMessage(ex)), ex); + } + } + + private RrdDb createDb() throws IOException + { + + if (inMemory) + { + return new RrdDb(createDef(path), factory); + } + + // TODO: If we want to support other options, such as Mongo or Berkley, we'll need + // to abstract the RRDb factory a bit further! + + String filePath = dbDir + "/" + path + ".rrdb"; + + File dbFile = new File(filePath); + dbFile.getParentFile().mkdirs(); + + if (dbFile.exists()) + { + return new RrdDb(filePath, factory); + } + + return new RrdDb(createDef(filePath), factory); + } + + private RrdDef createDef(String filePath) + { + return null; + } + + public int compareTo(MetricImpl o) + { + return this.name.compareTo(o.name); + } + + public Metric createChild(String name) + { + assert InternalUtils.isNonBlank(name); + + try + { + acquireReadLock(); + + Metric child = children.get(name); + + if (child == null) + { + try + { + upgradeReadLockToWriteLock(); + + // Could be a race ... + + child = children.get(name); + + if (child == null) + { + child = new MetricImpl(this, name, type, units); + + children.put(name, child); + } + } finally + { + downgradeWriteLockToReadLock(); + } + } + + return child; + + } finally + { + releaseReadLock(); + } + } + + public String getPath() + { + return path; + } + + public Type getType() + { + return type; + } + + public Units getUnits() + { + return units; + } + + public void increment() + { + store(1); + } + + public void store(double value) + { + accumulator.addAndGet(value); + + if (parent != null) + { + parent.store(value); + } + } + + public List<Metric> getChildren() + { + try + { + acquireReadLock(); + + F.flow(children.values()).sort().toList(); + + } finally + { + releaseReadLock(); + } + } + + public void run() + { + try + { + + db.createSample().setValue(DS_NAME, accumulator.getAndSet(0)).update(); + } catch (IOException ex) + { + logger.error(String.format("Unable to update database for metric '%s': %s", + path, + ExceptionUtils.toMessage(ex)), ex); + } + } + } + + private final Map<String, Metric> rootMetrics = new HashMap<String, Metric>(); + + public MetricCollectorImpl(Logger logger, @Symbol(MetricsSymbols.RRD_DB_DIR) String dbDir) + { + this.logger = logger; + this.dbDir = dbDir; + + inMemory = dbDir.equals(""); + + factory = inMemory ? new RrdMemoryBackendFactory() : new RrdNioBackendFactory(); + + logger.info(String.format("Collecting metrics %s.", + inMemory ? "in memory" : (" to" + dbDir)); + } + + public Metric createRootMetric(String name, Metric.Type type, Metric.Units units) { - return null; //To change body of implemented methods use File | Settings | File Templates. + + try + { + acquireReadLock(); + + Metric result = rootMetrics.get(name); + + if (result == null) + { + try + { + upgradeReadLockToWriteLock(); + + // There's a window where another thread may create the metric instead. + + result = rootMetrics.get(name); + + // But in the normal case, that won't happen and this thread has the exclusive + // write lock to create and cache the new metric. + if (result == null) + { + + result = new MetricImpl(null, name, type, units); + + rootMetrics.put(name, result); + } + } finally + { + downgradeWriteLockToReadLock(); + } + } + + if (result.getType() != type || result.getUnits() != units) + { + throw new IllegalArgumentException(String.format("Metric %s already exists and is type %s, units %s.", + result.getPath(), + result.getType().name(), + result.getUnits().name())); + } + + return result; + + } finally + { + releaseReadLock(); + } + + } + + /** + * Invoked every few seconds to make all the active metrics update their dbs. + */ + public void run() + { + for (Runnable r : updates) + { + r.run(); + } + } + + @PostInjection + public void activatePeriodicUpdates(PeriodicExecutor executor) + { + executor.addJob(new IntervalSchedule(HEARTBEAT * 1000), + "UpdateMetrics", this); } public List<Metric> getRootMetrics() { - return null; //To change body of implemented methods use File | Settings | File Templates. + return F.flow(rootMetrics.values()).sort().toList(); } } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aefd910b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/Metric.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/Metric.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/Metric.java index 287094e..83f82da 100644 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/Metric.java +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/Metric.java @@ -1,15 +1,16 @@ package org.apache.tapestry5.ioc.services.metrics; -import org.apache.xpath.operations.String; - import java.util.List; +/** + * @since 5.4 + */ public interface Metric { enum Type { /** - * Storing a value accum;ulates into an ever increasing total. Example: + * Storing a value accumulates into an ever increasing total. Example: * number of burgers served. */ TOTAL, @@ -38,7 +39,7 @@ public interface Metric /** * Placeholder for all other kinds of units, such as "pages viewed" or "messages processed". */ - UNIT + COUNTER } /** @@ -63,12 +64,14 @@ public interface Metric Units getUnits(); /** - * Stores a value of 1; useful when the metric's type is {@linkplain Units#UNIT unit}. + * Stores a value of 1; useful when the metric's type is {@link Units#COUNTER}. */ void increment(); /** - * Stores a value into the current time interval. + * Stores a value into the current time interval. This may be called multiple times during + * a time interval, and the values will accumulate. In addition, {@link #store(double)} + * propagates the value up to the parent metrics, if any, all the way up to the root metric. * * @param value */ http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/aefd910b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/MetricCollector.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/MetricCollector.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/MetricCollector.java index d95192b..e5c168f 100644 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/MetricCollector.java +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/metrics/MetricCollector.java @@ -2,17 +2,25 @@ package org.apache.tapestry5.ioc.services.metrics; import java.util.List; +/** + * Central hub for creating or obtaining {@link Metric}s. + * + * @since 5.4 + */ public interface MetricCollector { /** * Creates a root metric, or returns an existing one. * * @param name + * name of the metric to create * @param type + * used when creating the metric * @param units + * used when creating the metric * @return the metric * @throws IllegalMonitorStateException - * if an existing metric is present, but does + * if an existing root metric with that name is present, but does * not have the matching type and units. */ Metric createRootMetric(String name, Metric.Type type, Metric.Units units);
