SLING-4080 - API to capture/measure application-level metrics
Moving to trunk from sandbox
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1723456 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5d13870
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>26</version>
+ </parent>
+
+ <artifactId>org.apache.sling.metrics</artifactId>
+ <packaging>bundle</packaging>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <name>Apache Sling Metrics</name>
+ <description>
+ Integrates Metric library http://metrics.dropwizard.io/ with Sling
+
+ Refer to http://sling.apache.org/documentation/bundles/log-tracers.html
+ </description>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/metrics</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/metrics</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/metrics</url>
+ </scm>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <version>3.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.osgi</artifactId>
+ <version>2.2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>4.3.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.3.1</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.inventory</artifactId>
+ <version>1.0.2</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.osgi-mock</artifactId>
+ <version>1.3.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>1.10.19</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/metrics/Counter.java b/src/main/java/org/apache/sling/metrics/Counter.java
new file mode 100644
index 0000000..b828aa5
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/Counter.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics;
+
+import aQute.bnd.annotation.ProviderType;
+
+@ProviderType
+public interface Counter extends Counting, Metric{
+ /**
+ * Increment the counter by one.
+ */
+ void inc();
+
+ /**
+ * Decrement the counter by one.
+ */
+ void dec();
+
+ /**
+ * Increment the counter by {@code n}.
+ *
+ * @param n the amount by which the counter will be increased
+ */
+ void inc(long n);
+
+ /**
+ * Decrement the counter by {@code n}.
+ *
+ * @param n the amount by which the counter will be decreased
+ */
+ void dec(long n);
+}
diff --git a/src/main/java/org/apache/sling/metrics/Counting.java b/src/main/java/org/apache/sling/metrics/Counting.java
new file mode 100644
index 0000000..309decd
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/Counting.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics;
+
+import aQute.bnd.annotation.ProviderType;
+
+@ProviderType
+public interface Counting {
+ /**
+ * Returns the current count.
+ *
+ * @return the current count
+ */
+ long getCount();
+}
diff --git a/src/main/java/org/apache/sling/metrics/Histogram.java b/src/main/java/org/apache/sling/metrics/Histogram.java
new file mode 100644
index 0000000..37938f5
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/Histogram.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics;
+
+import aQute.bnd.annotation.ProviderType;
+
+@ProviderType
+public interface Histogram extends Counting, Metric {
+ /**
+ * Adds a recorded value.
+ *
+ * @param value the length of the value
+ */
+ void update(long value);
+}
diff --git a/src/main/java/org/apache/sling/metrics/Meter.java b/src/main/java/org/apache/sling/metrics/Meter.java
new file mode 100644
index 0000000..a4914d7
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/Meter.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics;
+
+import aQute.bnd.annotation.ProviderType;
+
+@ProviderType
+public interface Meter extends Counting, Metric{
+ /**
+ * Mark the occurrence of an event.
+ */
+ void mark();
+
+ /**
+ * Mark the occurrence of a given number of events.
+ *
+ * @param n the number of events
+ */
+ void mark(long n);
+}
diff --git a/src/main/java/org/apache/sling/metrics/Metric.java b/src/main/java/org/apache/sling/metrics/Metric.java
new file mode 100644
index 0000000..4952017
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/Metric.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics;
+
+import aQute.bnd.annotation.ProviderType;
+
+@ProviderType
+public interface Metric {
+ /**
+ * Adapts the Metric to the specified type.
+ *
+ * @param <A> The type to which this metric is to be adapted.
+ * @param type Class object for the type to which this metric is to be adapted.
+ * @return The object, of the specified type, to which this metric has been adapted
+ * or null if this metric cannot be adapted to the specified type.
+ */
+ <A> A adaptTo(Class<A> type);
+}
diff --git a/src/main/java/org/apache/sling/metrics/MetricsService.java b/src/main/java/org/apache/sling/metrics/MetricsService.java
new file mode 100644
index 0000000..8c51d4b
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/MetricsService.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics;
+
+import aQute.bnd.annotation.ProviderType;
+
+@ProviderType
+public interface MetricsService {
+ MetricsService NOOP = new MetricsService() {
+ @Override
+ public Timer timer(String name) {
+ return NoopMetric.INSTANCE;
+ }
+
+ @Override
+ public Histogram histogram(String name) {
+ return NoopMetric.INSTANCE;
+ }
+
+ @Override
+ public Counter counter(String name) {
+ return NoopMetric.INSTANCE;
+ }
+
+ @Override
+ public Meter meter(String name) {
+ return NoopMetric.INSTANCE;
+ }
+ };
+
+ /**
+ * Creates a new {@link Timer} and registers it under the given name.
+ *
+ * @param name the name of the metric
+ * @return a new {@link Timer}
+ */
+ Timer timer(String name);
+
+ /**
+ * Creates a new {@link Histogram} and registers it under the given name.
+ *
+ * @param name the name of the metric
+ * @return a new {@link Histogram}
+ */
+ Histogram histogram(String name);
+
+ /**
+ * Creates a new {@link Counter} and registers it under the given name.
+ *
+ * @param name the name of the metric
+ * @return a new {@link Counter}
+ */
+ Counter counter(String name);
+
+ /**
+ * Creates a new {@link Meter} and registers it under the given name.
+ *
+ * @param name the name of the metric
+ * @return a new {@link Meter}
+ */
+ Meter meter(String name);
+}
diff --git a/src/main/java/org/apache/sling/metrics/NoopMetric.java b/src/main/java/org/apache/sling/metrics/NoopMetric.java
new file mode 100644
index 0000000..210fb3c
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/NoopMetric.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics;
+
+import java.util.concurrent.TimeUnit;
+
+final class NoopMetric implements Counter, Histogram, Timer, Meter{
+ public static final NoopMetric INSTANCE = new NoopMetric();
+ @Override
+ public long getCount() {
+ return 0;
+ }
+
+ @Override
+ public void inc() {
+
+ }
+
+ @Override
+ public void dec() {
+
+ }
+
+ @Override
+ public void inc(long n) {
+
+ }
+
+ @Override
+ public void dec(long n) {
+
+ }
+
+ @Override
+ public void mark() {
+
+ }
+
+ @Override
+ public void mark(long n) {
+
+ }
+
+ @Override
+ public void update(long duration, TimeUnit unit) {
+
+ }
+
+ @Override
+ public Context time() {
+ return NoopContext.INSTANCE;
+ }
+
+ @Override
+ public void update(long value) {
+
+ }
+
+ @Override
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ return null;
+ }
+
+ private static final class NoopContext implements Context {
+ public static final NoopContext INSTANCE = new NoopContext();
+
+ @Override
+ public long stop() {
+ return 0;
+ }
+
+ @Override
+ public void close() {
+
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/metrics/Timer.java b/src/main/java/org/apache/sling/metrics/Timer.java
new file mode 100644
index 0000000..602a0cb
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/Timer.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics;
+
+import java.io.Closeable;
+import java.util.concurrent.TimeUnit;
+
+import aQute.bnd.annotation.ProviderType;
+
+@ProviderType
+public interface Timer extends Counting, Metric{
+ /**
+ * A timing context.
+ *
+ * @see Timer#time()
+ */
+ interface Context extends Closeable {
+ /**
+ * Updates the timer with the difference between current and start time. Call to this method will
+ * not reset the start time. Multiple calls result in multiple updates.
+ * @return the elapsed time in nanoseconds
+ */
+ long stop();
+ }
+
+ /**
+ * Adds a recorded duration.
+ *
+ * @param duration the length of the duration
+ * @param unit the scale unit of {@code duration}
+ */
+ void update(long duration, TimeUnit unit);
+
+ /**
+ * Returns a new {@link Context}.
+ *
+ * @return a new {@link Context}
+ * @see Context
+ */
+ Context time();
+}
diff --git a/src/main/java/org/apache/sling/metrics/internal/CounterImpl.java b/src/main/java/org/apache/sling/metrics/internal/CounterImpl.java
new file mode 100644
index 0000000..0137b71
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/internal/CounterImpl.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics.internal;
+
+
+import org.apache.sling.metrics.Counter;
+
+final class CounterImpl implements Counter {
+ private final com.codahale.metrics.Counter counter;
+
+ CounterImpl(com.codahale.metrics.Counter counter) {
+ this.counter = counter;
+ }
+
+ @Override
+ public void inc() {
+ counter.inc();
+ }
+
+ @Override
+ public void dec() {
+ counter.dec();
+ }
+
+ @Override
+ public void inc(long n) {
+ counter.inc(n);
+ }
+
+ @Override
+ public void dec(long n) {
+ counter.dec(n);
+ }
+
+ @Override
+ public long getCount() {
+ return counter.getCount();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adaptTo(Class<A> type) {
+ if (type == com.codahale.metrics.Counter.class){
+ return (A) counter;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/sling/metrics/internal/HistogramImpl.java b/src/main/java/org/apache/sling/metrics/internal/HistogramImpl.java
new file mode 100644
index 0000000..15117e9
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/internal/HistogramImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics.internal;
+
+
+import org.apache.sling.metrics.Histogram;
+
+final class HistogramImpl implements Histogram {
+ private final com.codahale.metrics.Histogram histogram;
+
+ HistogramImpl(com.codahale.metrics.Histogram histogram) {
+ this.histogram = histogram;
+ }
+
+ @Override
+ public void update(long value) {
+ histogram.update(value);
+ }
+
+ @Override
+ public long getCount() {
+ return histogram.getCount();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adaptTo(Class<A> type) {
+ if (type == com.codahale.metrics.Histogram.class){
+ return (A) histogram;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/sling/metrics/internal/MeterImpl.java b/src/main/java/org/apache/sling/metrics/internal/MeterImpl.java
new file mode 100644
index 0000000..719d356
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/internal/MeterImpl.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics.internal;
+
+import org.apache.sling.metrics.Meter;
+
+final class MeterImpl implements Meter {
+ private final com.codahale.metrics.Meter meter;
+
+ MeterImpl(com.codahale.metrics.Meter meter) {
+ this.meter = meter;
+ }
+
+ @Override
+ public void mark() {
+ meter.mark();
+ }
+
+ @Override
+ public void mark(long n) {
+ meter.mark(n);
+ }
+
+ @Override
+ public long getCount() {
+ return meter.getCount();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adaptTo(Class<A> type) {
+ if (type == com.codahale.metrics.Meter.class){
+ return (A)meter;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/sling/metrics/internal/MetricWebConsolePlugin.java b/src/main/java/org/apache/sling/metrics/internal/MetricWebConsolePlugin.java
new file mode 100644
index 0000000..180acca
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/internal/MetricWebConsolePlugin.java
@@ -0,0 +1,475 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics.internal;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.codahale.metrics.ConsoleReporter;
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
+import org.apache.commons.io.output.WriterOutputStream;
+import org.apache.felix.inventory.Format;
+import org.apache.felix.inventory.InventoryPrinter;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Service(value = {InventoryPrinter.class, Servlet.class})
+@Properties({
+ @Property(name = "felix.webconsole.label", value = "slingmetrics"),
+ @Property(name = "felix.webconsole.title", value = "Metrics"),
+ @Property(name = "felix.webconsole.category", value = "Sling"),
+ @Property(name = InventoryPrinter.FORMAT, value = {"TEXT" }),
+ @Property(name = InventoryPrinter.NAME, value = "slingmetrics"),
+ @Property(name = InventoryPrinter.TITLE, value = "Sling Metrics"),
+ @Property(name = InventoryPrinter.WEBCONSOLE, boolValue = true)
+})
+public class MetricWebConsolePlugin extends HttpServlet implements
+ InventoryPrinter, ServiceTrackerCustomizer<MetricRegistry, MetricRegistry>{
+ /**
+ * Service property name which stores the MetricRegistry name as a given OSGi
+ * ServiceRegistry might have multiple instances of MetricRegistry
+ */
+ public static final String METRIC_REGISTRY_NAME = "name";
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private BundleContext context;
+ private ServiceTracker<MetricRegistry, MetricRegistry> tracker;
+ private ConcurrentMap<ServiceReference, MetricRegistry> registries
+ = new ConcurrentHashMap<ServiceReference, MetricRegistry>();
+
+ private TimeUnit rateUnit = TimeUnit.SECONDS;
+ private TimeUnit durationUnit = TimeUnit.MILLISECONDS;
+ private Map<String, TimeUnit> specificDurationUnits = Collections.emptyMap();
+ private Map<String, TimeUnit> specificRateUnits = Collections.emptyMap();
+ private MetricTimeUnits timeUnit;
+
+ @Activate
+ private void activate(BundleContext context){
+ this.context = context;
+ this.timeUnit = new MetricTimeUnits(rateUnit, durationUnit, specificRateUnits, specificDurationUnits);
+ tracker = new ServiceTracker<MetricRegistry, MetricRegistry>(context, MetricRegistry.class, this);
+ tracker.open();
+ }
+
+ @Deactivate
+ private void deactivate(BundleContext context){
+ tracker.close();
+ }
+
+ //~--------------------------------------------< InventoryPrinter >
+
+ @Override
+ public void print(PrintWriter printWriter, Format format, boolean isZip) {
+ if (format == Format.TEXT) {
+ MetricRegistry registry = getConsolidatedRegistry();
+ ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
+ .outputTo(new PrintStream(new WriterOutputStream(printWriter)))
+ .build();
+ reporter.report();
+ reporter.close();
+ }
+ }
+
+
+ //~---------------------------------------------< ServiceTracker >
+
+ @Override
+ public MetricRegistry addingService(ServiceReference<MetricRegistry> serviceReference) {
+ MetricRegistry registry = context.getService(serviceReference);
+ registries.put(serviceReference, registry);
+ return registry;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference<MetricRegistry> serviceReference, MetricRegistry registry) {
+ registries.put(serviceReference, registry);
+ }
+
+ @Override
+ public void removedService(ServiceReference<MetricRegistry> serviceReference, MetricRegistry registry) {
+ registries.remove(serviceReference);
+ }
+
+ //~----------------------------------------------< Servlet >
+
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
+ final PrintWriter pw = resp.getWriter();
+ MetricRegistry registry = getConsolidatedRegistry();
+
+ appendMetricStatus(pw, registry);
+ addCounterDetails(pw, registry.getCounters());
+ addGaugeDetails(pw, registry.getGauges());
+ addMeterDetails(pw, registry.getMeters());
+ addTimerDetails(pw, registry.getTimers());
+ addHistogramDetails(pw, registry.getHistograms());
+ }
+
+ private static void appendMetricStatus(PrintWriter pw, MetricRegistry registry) {
+ pw.printf(
+ "<p class='statline'>Metrics: %d gauges, %d timers, %d meters, %d counters, %d histograms</p>%n",
+ registry.getGauges().size(),
+ registry.getTimers().size(),
+ registry.getMeters().size(),
+ registry.getCounters().size(),
+ registry.getHistograms().size());
+ }
+
+ private void addMeterDetails(PrintWriter pw, SortedMap<String, Meter> meters) {
+ if (meters.isEmpty()) {
+ return;
+ }
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Meters</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Count</th>");
+ pw.println("<th class='header'>Mean Rate</th>");
+ pw.println("<th class='header'>OneMinuteRate</th>");
+ pw.println("<th class='header'>FiveMinuteRate</th>");
+ pw.println("<th class='header'>FifteenMinuteRate</ th>");
+ pw.println("<th>RateUnit</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Meter> e : meters.entrySet()) {
+ Meter m = e.getValue();
+ String name = e.getKey();
+
+ double rateFactor = timeUnit.rateFor(name).toSeconds(1);
+ String rateUnit = "events/" + calculateRateUnit(timeUnit.rateFor(name));
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%d</td>", m.getCount());
+ pw.printf("<td>%f</td>", m.getMeanRate() * rateFactor);
+ pw.printf("<td>%f</td>", m.getOneMinuteRate() * rateFactor);
+ pw.printf("<td>%f</td>", m.getFiveMinuteRate() * rateFactor);
+ pw.printf("<td>%f</td>", m.getFifteenMinuteRate() * rateFactor);
+ pw.printf("<td>%s</td>", rateUnit);
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+ private void addTimerDetails(PrintWriter pw, SortedMap<String, Timer> timers) {
+ if (timers.isEmpty()) {
+ return;
+ }
+
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Timers</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Count</th>");
+ pw.println("<th class='header'>Mean Rate</th>");
+ pw.println("<th class='header'>1 min rate</th>");
+ pw.println("<th class='header'>5 mins rate</th>");
+ pw.println("<th class='header'>15 mins rate</th>");
+ pw.println("<th class='header'>50%</th>");
+ pw.println("<th class='header'>Min</th>");
+ pw.println("<th class='header'>Max</th>");
+ pw.println("<th class='header'>Mean</th>");
+ pw.println("<th class='header'>StdDev</th>");
+ pw.println("<th class='header'>75%</th>");
+ pw.println("<th class='header'>95%</th>");
+ pw.println("<th class='header'>98%</th>");
+ pw.println("<th class='header'>99%</th>");
+ pw.println("<th class='header'>999%</th>");
+ pw.println("<th>Rate Unit</th>");
+ pw.println("<th>Duration Unit</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Timer> e : timers.entrySet()) {
+ Timer t = e.getValue();
+ Snapshot s = t.getSnapshot();
+ String name = e.getKey();
+
+ double rateFactor = timeUnit.rateFor(name).toSeconds(1);
+ String rateUnit = "events/" + calculateRateUnit(timeUnit.rateFor(name));
+
+ double durationFactor = 1.0 / timeUnit.durationFor(name).toNanos(1);
+ String durationUnit = timeUnit.durationFor(name).toString().toLowerCase(Locale.US);
+
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%d</td>", t.getCount());
+ pw.printf("<td>%f</td>", t.getMeanRate() * rateFactor);
+ pw.printf("<td>%f</td>", t.getOneMinuteRate() * rateFactor);
+ pw.printf("<td>%f</td>", t.getFiveMinuteRate() * rateFactor);
+ pw.printf("<td>%f</td>", t.getFifteenMinuteRate() * rateFactor);
+
+ pw.printf("<td>%f</td>", s.getMedian() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMin() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMax() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMean() * durationFactor);
+ pw.printf("<td>%f</td>", s.getStdDev() * durationFactor);
+
+ pw.printf("<td>%f</td>", s.get75thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get95thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get98thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get99thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get999thPercentile() * durationFactor);
+
+ pw.printf("<td>%s</td>", rateUnit);
+ pw.printf("<td>%s</td>", durationUnit);
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+ private void addHistogramDetails(PrintWriter pw, SortedMap<String, Histogram> histograms) {
+ if (histograms.isEmpty()) {
+ return;
+ }
+
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Histograms</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Count</th>");
+ pw.println("<th class='header'>50%</th>");
+ pw.println("<th class='header'>Min</th>");
+ pw.println("<th class='header'>Max</th>");
+ pw.println("<th class='header'>Mean</th>");
+ pw.println("<th class='header'>StdDev</th>");
+ pw.println("<th class='header'>75%</th>");
+ pw.println("<th class='header'>95%</th>");
+ pw.println("<th class='header'>98%</th>");
+ pw.println("<th class='header'>99%</th>");
+ pw.println("<th class='header'>999%</th>");
+ pw.println("<th>Duration Unit</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Histogram> e : histograms.entrySet()) {
+ Histogram h = e.getValue();
+ Snapshot s = h.getSnapshot();
+ String name = e.getKey();
+
+ double durationFactor = 1.0 / timeUnit.durationFor(name).toNanos(1);
+ String durationUnit = timeUnit.durationFor(name).toString().toLowerCase(Locale.US);
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%d</td>", h.getCount());
+ pw.printf("<td>%f</td>", s.getMedian() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMin() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMax() * durationFactor);
+ pw.printf("<td>%f</td>", s.getMean() * durationFactor);
+ pw.printf("<td>%f</td>", s.getStdDev() * durationFactor);
+
+ pw.printf("<td>%f</td>", s.get75thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get95thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get98thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get99thPercentile() * durationFactor);
+ pw.printf("<td>%f</td>", s.get999thPercentile() * durationFactor);
+
+ pw.printf("<td>%s</td>", durationUnit);
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+ private void addCounterDetails(PrintWriter pw, SortedMap<String, Counter> counters) {
+ if (counters.isEmpty()) {
+ return;
+ }
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Counters</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Count</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Counter> e : counters.entrySet()) {
+ Counter c = e.getValue();
+ String name = e.getKey();
+
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%d</td>", c.getCount());
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+ private void addGaugeDetails(PrintWriter pw, SortedMap<String, Gauge> gauges) {
+ if (gauges.isEmpty()) {
+ return;
+ }
+
+ pw.println("<br>");
+ pw.println("<div class='table'>");
+ pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Guages</div>");
+ pw.println("<table class='nicetable'>");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th class='header'>Name</th>");
+ pw.println("<th class='header'>Value</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+ pw.println("<tbody>");
+
+ String rowClass = "odd";
+ for (Map.Entry<String, Gauge> e : gauges.entrySet()) {
+ Gauge g = e.getValue();
+ String name = e.getKey();
+
+ pw.printf("<tr class='%s ui-state-default'>%n", rowClass);
+
+ pw.printf("<td>%s</td>", name);
+ pw.printf("<td>%s</td>", g.getValue());
+
+ pw.println("</tr>");
+ rowClass = "odd".equals(rowClass) ? "even" : "odd";
+ }
+
+ pw.println("</tbody>");
+ pw.println("</table>");
+ pw.println("</div>");
+ }
+
+
+ //~----------------------------------------------< internal >
+
+ private MetricRegistry getConsolidatedRegistry() {
+ MetricRegistry registry = new MetricRegistry();
+ for (Map.Entry<ServiceReference, MetricRegistry> registryEntry : registries.entrySet()){
+ String metricRegistryName = (String) registryEntry.getKey().getProperty(METRIC_REGISTRY_NAME);
+ for (Map.Entry<String, Metric> metricEntry : registryEntry.getValue().getMetrics().entrySet()){
+ String metricName = metricEntry.getKey();
+ try{
+ if (metricRegistryName != null){
+ metricName = metricRegistryName + ":" + metricName;
+ }
+ registry.register(metricName, metricEntry.getValue());
+ }catch (IllegalArgumentException ex){
+ log.warn("Duplicate Metric name found {}", metricName, ex);
+ }
+ }
+ }
+ return registry;
+ }
+
+ private static String calculateRateUnit(TimeUnit unit) {
+ final String s = unit.toString().toLowerCase(Locale.US);
+ return s.substring(0, s.length() - 1);
+ }
+
+ private static class MetricTimeUnits {
+ private final TimeUnit defaultRate;
+ private final TimeUnit defaultDuration;
+ private final Map<String, TimeUnit> rateOverrides;
+ private final Map<String, TimeUnit> durationOverrides;
+
+ MetricTimeUnits(TimeUnit defaultRate,
+ TimeUnit defaultDuration,
+ Map<String, TimeUnit> rateOverrides,
+ Map<String, TimeUnit> durationOverrides) {
+ this.defaultRate = defaultRate;
+ this.defaultDuration = defaultDuration;
+ this.rateOverrides = rateOverrides;
+ this.durationOverrides = durationOverrides;
+ }
+
+ public TimeUnit durationFor(String name) {
+ return durationOverrides.containsKey(name) ? durationOverrides.get(name) : defaultDuration;
+ }
+
+ public TimeUnit rateFor(String name) {
+ return rateOverrides.containsKey(name) ? rateOverrides.get(name) : defaultRate;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/metrics/internal/MetricsServiceImpl.java b/src/main/java/org/apache/sling/metrics/internal/MetricsServiceImpl.java
new file mode 100644
index 0000000..dea3ea2
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/internal/MetricsServiceImpl.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics.internal;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.management.MBeanServer;
+
+import com.codahale.metrics.JmxReporter;
+import com.codahale.metrics.MetricRegistry;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.sling.metrics.Counter;
+import org.apache.sling.metrics.Histogram;
+import org.apache.sling.metrics.Meter;
+import org.apache.sling.metrics.Metric;
+import org.apache.sling.metrics.MetricsService;
+import org.apache.sling.metrics.Timer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+@Component
+public class MetricsServiceImpl implements MetricsService{
+ private final List<ServiceRegistration> regs = new ArrayList<ServiceRegistration>();
+ private final ConcurrentMap<String, Metric> metrics = new ConcurrentHashMap<String, Metric>();
+ private final MetricRegistry registry = new MetricRegistry();
+
+ @Reference
+ private MBeanServer server;
+
+ private JmxReporter reporter;
+
+ @Activate
+ private void activate(BundleContext context, Map<String, Object> config) {
+ //TODO Domain name should be based on calling bundle
+ //For that we can register ServiceFactory and make use of calling
+ //bundle symbolic name to determine the mapping
+
+ reporter = JmxReporter.forRegistry(registry)
+ .inDomain("org.apache.sling")
+ .registerWith(server)
+ .build();
+
+ final Dictionary<String, String> svcProps = new Hashtable<String, String>();
+ svcProps.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Metrics Service");
+ svcProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+ regs.add(context.registerService(MetricsService.class.getName(), this, svcProps));
+
+ final Dictionary<String, String> regProps = new Hashtable<String, String>();
+ regProps.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Metrics Registry");
+ regProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+ regProps.put("name", "sling");
+ regs.add(context.registerService(MetricRegistry.class.getName(), registry, regProps));
+ }
+
+ @Deactivate
+ private void deactivate() throws IOException {
+ for (ServiceRegistration reg : regs) {
+ reg.unregister();
+ }
+ regs.clear();
+
+ metrics.clear();
+
+ if (reporter != null) {
+ reporter.close();
+ }
+ }
+
+ @Override
+ public Timer timer(String name) {
+ return getOrAdd(name, MetricBuilder.TIMERS);
+ }
+
+ @Override
+ public Histogram histogram(String name) {
+ return getOrAdd(name, MetricBuilder.HISTOGRAMS);
+ }
+
+ @Override
+ public Counter counter(String name) {
+ return getOrAdd(name, MetricBuilder.COUNTERS);
+ }
+
+ @Override
+ public Meter meter(String name) {
+ return getOrAdd(name, MetricBuilder.METERS);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends Metric> T getOrAdd(String name, MetricBuilder<T> builder) {
+ final Metric metric = metrics.get(name);
+ if (builder.isInstance(metric)) {
+ return (T) metric;
+ } else if (metric == null) {
+ try {
+ return register(name, builder.newMetric(registry, name));
+ } catch (IllegalArgumentException e) {
+ final Metric added = metrics.get(name);
+ if (builder.isInstance(added)) {
+ return (T) added;
+ }
+ }
+ }
+ throw new IllegalArgumentException(name + " is already used for a different type of metric");
+ }
+
+ private <T extends Metric> T register(String name, T metric) throws IllegalArgumentException {
+ final Metric existing = metrics.putIfAbsent(name, metric);
+ if (existing != null) {
+ throw new IllegalArgumentException("A metric named " + name + " already exists");
+ }
+ return metric;
+ }
+
+ /**
+ * A quick and easy way of capturing the notion of default metrics.
+ */
+ private interface MetricBuilder<T extends Metric> {
+ MetricBuilder<Counter> COUNTERS = new MetricBuilder<Counter>() {
+ @Override
+ public Counter newMetric(MetricRegistry registry, String name) {
+ return new CounterImpl(registry.counter(name));
+ }
+
+ @Override
+ public boolean isInstance(Metric metric) {
+ return Counter.class.isInstance(metric);
+ }
+ };
+
+ MetricBuilder<Histogram> HISTOGRAMS = new MetricBuilder<Histogram>() {
+ @Override
+ public Histogram newMetric(MetricRegistry registry, String name) {
+ return new HistogramImpl(registry.histogram(name));
+ }
+
+ @Override
+ public boolean isInstance(Metric metric) {
+ return Histogram.class.isInstance(metric);
+ }
+ };
+
+ MetricBuilder<Meter> METERS = new MetricBuilder<Meter>() {
+ @Override
+ public Meter newMetric(MetricRegistry registry, String name) {
+ return new MeterImpl(registry.meter(name));
+ }
+
+ @Override
+ public boolean isInstance(Metric metric) {
+ return Meter.class.isInstance(metric);
+ }
+ };
+
+ MetricBuilder<Timer> TIMERS = new MetricBuilder<Timer>() {
+ @Override
+ public Timer newMetric(MetricRegistry registry, String name) {
+ return new TimerImpl(registry.timer(name));
+ }
+
+ @Override
+ public boolean isInstance(Metric metric) {
+ return Timer.class.isInstance(metric);
+ }
+ };
+
+ T newMetric(MetricRegistry registry, String name);
+
+ boolean isInstance(Metric metric);
+ }
+}
diff --git a/src/main/java/org/apache/sling/metrics/internal/TimerImpl.java b/src/main/java/org/apache/sling/metrics/internal/TimerImpl.java
new file mode 100644
index 0000000..54fd39c
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/internal/TimerImpl.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics.internal;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sling.metrics.Timer;
+
+
+final class TimerImpl implements Timer {
+ private final com.codahale.metrics.Timer timer;
+
+ TimerImpl(com.codahale.metrics.Timer timer) {
+ this.timer = timer;
+ }
+
+ @Override
+ public void update(long duration, TimeUnit unit) {
+ timer.update(duration, unit);
+ }
+
+ @Override
+ public Context time() {
+ return new ContextImpl(timer.time());
+ }
+
+ @Override
+ public long getCount() {
+ return timer.getCount();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adaptTo(Class<A> type) {
+ if (type == com.codahale.metrics.Timer.class) {
+ return (A) timer;
+ }
+ return null;
+ }
+
+ private static final class ContextImpl implements Context {
+ private final com.codahale.metrics.Timer.Context context;
+
+ private ContextImpl(com.codahale.metrics.Timer.Context context) {
+ this.context = context;
+ }
+
+ public long stop() {
+ return context.stop();
+ }
+
+ /**
+ * Equivalent to calling {@link #stop()}.
+ */
+ @Override
+ public void close() {
+ stop();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/metrics/package-info.java b/src/main/java/org/apache/sling/metrics/package-info.java
new file mode 100644
index 0000000..9b4ae51
--- /dev/null
+++ b/src/main/java/org/apache/sling/metrics/package-info.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Provides configuration support for the Logback based logging in Sling
+ *
+ * @version 1.0
+ */
+@Version("1.0")
+@Export(optional = "provide:=true")
+package org.apache.sling.metrics;
+
+import aQute.bnd.annotation.Export;
+import aQute.bnd.annotation.Version;
+
diff --git a/src/test/java/org/apache/sling/metrics/internal/MetricServiceTest.java b/src/test/java/org/apache/sling/metrics/internal/MetricServiceTest.java
new file mode 100644
index 0000000..e62daad
--- /dev/null
+++ b/src/test/java/org/apache/sling/metrics/internal/MetricServiceTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics.internal;
+
+import java.lang.management.ManagementFactory;
+import java.util.Collections;
+
+import javax.management.MBeanServer;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.sling.metrics.Counter;
+import org.apache.sling.metrics.Histogram;
+import org.apache.sling.metrics.Meter;
+import org.apache.sling.metrics.MetricsService;
+import org.apache.sling.metrics.Timer;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class MetricServiceTest {
+ @Rule
+ public final OsgiContext context = new OsgiContext();
+
+ private MetricsServiceImpl service = new MetricsServiceImpl();
+
+ @After
+ public void registerMBeanServer() {
+ context.registerService(MBeanServer.class, ManagementFactory.getPlatformMBeanServer());
+ }
+
+ @Test
+ public void defaultSetup() throws Exception{
+ activate();
+
+ assertNotNull(context.getService(MetricRegistry.class));
+ assertNotNull(context.getService(MetricsService.class));
+
+ MockOsgi.deactivate(service);
+
+ assertNull(context.getService(MetricRegistry.class));
+ assertNull(context.getService(MetricsService.class));
+ }
+
+ @Test
+ public void meter() throws Exception{
+ activate();
+ Meter meter = service.meter("test");
+
+ assertNotNull(meter);
+ assertTrue(getRegistry().getMeters().containsKey("test"));
+
+ assertSame(meter, service.meter("test"));
+ }
+
+ @Test
+ public void counter() throws Exception{
+ activate();
+ Counter counter = service.counter("test");
+
+ assertNotNull(counter);
+ assertTrue(getRegistry().getCounters().containsKey("test"));
+
+ assertSame(counter, service.counter("test"));
+ }
+
+ @Test
+ public void timer() throws Exception{
+ activate();
+ Timer timer = service.timer("test");
+
+ assertNotNull(timer);
+ assertTrue(getRegistry().getTimers().containsKey("test"));
+
+ assertSame(timer, service.timer("test"));
+ }
+
+ @Test
+ public void histogram() throws Exception{
+ activate();
+ Histogram histo = service.histogram("test");
+
+ assertNotNull(histo);
+ assertTrue(getRegistry().getHistograms().containsKey("test"));
+
+ assertSame(histo, service.histogram("test"));
+ }
+
+ private MetricRegistry getRegistry(){
+ return context.getService(MetricRegistry.class);
+ }
+
+ private void activate() {
+ MockOsgi.activate(service, context.bundleContext(), Collections.<String, Object>emptyMap());
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/metrics/internal/MetricWrapperTest.java b/src/test/java/org/apache/sling/metrics/internal/MetricWrapperTest.java
new file mode 100644
index 0000000..898626c
--- /dev/null
+++ b/src/test/java/org/apache/sling/metrics/internal/MetricWrapperTest.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sling.metrics.internal;
+
+import java.util.concurrent.TimeUnit;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+public class MetricWrapperTest {
+ private MetricRegistry registry = new MetricRegistry();
+
+ @Test
+ public void counter() throws Exception {
+ Counter counter = registry.counter("test");
+ CounterImpl counterStats = new CounterImpl(counter);
+
+ counterStats.inc();
+ assertEquals(1, counterStats.getCount());
+ assertEquals(1, counter.getCount());
+ assertEquals(1, counterStats.getCount());
+
+ counterStats.inc();
+ counterStats.inc();
+ assertEquals(3, counterStats.getCount());
+
+ counterStats.dec();
+ assertEquals(2, counterStats.getCount());
+ assertEquals(2, counter.getCount());
+
+ counterStats.inc(7);
+ assertEquals(9, counterStats.getCount());
+ assertEquals(9, counter.getCount());
+
+ counterStats.dec(5);
+ assertEquals(4, counterStats.getCount());
+ assertEquals(4, counter.getCount());
+
+ assertSame(counter, counterStats.adaptTo(Counter.class));
+ }
+
+ @Test
+ public void meter() throws Exception {
+ Meter meter = registry.meter("test");
+ MeterImpl meterStats = new MeterImpl(meter);
+
+ meterStats.mark();
+ assertEquals(1, meterStats.getCount());
+ assertEquals(1, meter.getCount());
+
+ meterStats.mark(5);
+ assertEquals(6, meterStats.getCount());
+ assertEquals(6, meter.getCount());
+ assertSame(meter, meterStats.adaptTo(Meter.class));
+ }
+
+ @Test
+ public void timer() throws Exception {
+ Timer time = registry.timer("test");
+ TimerImpl timerStats = new TimerImpl(time);
+
+ timerStats.update(100, TimeUnit.SECONDS);
+ assertEquals(1, time.getCount());
+ assertEquals(TimeUnit.SECONDS.toNanos(100), time.getSnapshot().getMax());
+
+ timerStats.update(100, TimeUnit.SECONDS);
+ assertEquals(2, timerStats.getCount());
+
+ assertSame(time, timerStats.adaptTo(Timer.class));
+ }
+
+ @Test
+ public void histogram() throws Exception {
+ Histogram histo = registry.histogram("test");
+ HistogramImpl histoStats = new HistogramImpl(histo);
+
+ histoStats.update(100);
+ assertEquals(1, histo.getCount());
+ assertEquals(1, histoStats.getCount());
+ assertEquals(100, histo.getSnapshot().getMax());
+
+ assertSame(histo, histoStats.adaptTo(Histogram.class));
+ }
+
+ @Test
+ public void timerContext() throws Exception{
+ VirtualClock clock = new VirtualClock();
+ Timer time = new Timer(new ExponentiallyDecayingReservoir(), clock);
+
+ TimerImpl timerStats = new TimerImpl(time);
+ org.apache.sling.metrics.Timer.Context context = timerStats.time();
+
+ clock.tick = TimeUnit.SECONDS.toNanos(314);
+ context.close();
+
+ assertEquals(1, time.getCount());
+ assertEquals(TimeUnit.SECONDS.toNanos(314), time.getSnapshot().getMax());
+ }
+
+ private static class VirtualClock extends com.codahale.metrics.Clock {
+ long tick;
+ @Override
+ public long getTick() {
+ return tick;
+ }
+ }
+}