This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-metrics.git
commit 510579bc1e277801fa306ed6dca3d253a7279f5d Author: Chetan Mehrotra <[email protected]> AuthorDate: Thu Jan 7 04:20:39 2016 +0000 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 --- pom.xml | 130 ++++++ .../java/org/apache/sling/metrics/Counter.java | 49 +++ .../java/org/apache/sling/metrics/Counting.java | 32 ++ .../java/org/apache/sling/metrics/Histogram.java | 32 ++ src/main/java/org/apache/sling/metrics/Meter.java | 37 ++ src/main/java/org/apache/sling/metrics/Metric.java | 35 ++ .../org/apache/sling/metrics/MetricsService.java | 79 ++++ .../java/org/apache/sling/metrics/NoopMetric.java | 94 ++++ src/main/java/org/apache/sling/metrics/Timer.java | 58 +++ .../apache/sling/metrics/internal/CounterImpl.java | 65 +++ .../sling/metrics/internal/HistogramImpl.java | 50 +++ .../apache/sling/metrics/internal/MeterImpl.java | 54 +++ .../metrics/internal/MetricWebConsolePlugin.java | 475 +++++++++++++++++++++ .../sling/metrics/internal/MetricsServiceImpl.java | 199 +++++++++ .../apache/sling/metrics/internal/TimerImpl.java | 77 ++++ .../org/apache/sling/metrics/package-info.java | 31 ++ .../sling/metrics/internal/MetricServiceTest.java | 120 ++++++ .../sling/metrics/internal/MetricWrapperTest.java | 132 ++++++ 18 files changed, 1749 insertions(+) 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; + } + } +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
