[dropwizard-metrics] 01/04: New upstream version 3.2.4

Christopher Stuart Hoskin mans0954 at moszumanska.debian.org
Sat Aug 12 08:08:47 UTC 2017


This is an automated email from the git hooks/post-receive script.

mans0954 pushed a commit to branch master
in repository dropwizard-metrics.

commit 5018b52ea33494d757675d2938399ca47c5e4d65
Author: Christopher Hoskin <mans0954 at debian.org>
Date:   Sat Aug 12 07:42:46 2017 +0100

    New upstream version 3.2.4
---
 .gitignore                                         |   3 +
 .travis.yml                                        |   1 -
 README.md                                          |   1 +
 docs/list_contributors.rb                          |  16 ++
 docs/pom.xml                                       |  76 +++++
 docs/source/_themes/metrics/layout.html            |   3 +
 docs/source/about/contributors.rst                 | 118 ++++++--
 docs/source/about/release-notes.rst                | 181 ++++++++++++
 docs/source/conf.py                                |   4 +-
 docs/source/getting-started.rst                    |   1 +
 docs/source/manual/core.rst                        |  41 ++-
 docs/source/manual/graphite.rst                    |   2 +-
 docs/source/manual/log4j.rst                       |  16 ++
 docs/source/manual/third-party.rst                 |  59 +++-
 metrics-annotation/pom.xml                         |   2 +-
 metrics-benchmarks/pom.xml                         |   2 +-
 .../metrics/benchmarks/ReservoirBenchmark.java     |  37 ++-
 .../SlidingTimeWindowReservoirsBenchmark.java      |  80 ++++++
 metrics-core/pom.xml                               |   2 +-
 .../metrics/ChunkedAssociativeLongArray.java       | 310 +++++++++++++++++++++
 .../java/com/codahale/metrics/ConsoleReporter.java | 134 ++++++---
 .../main/java/com/codahale/metrics/Counter.java    |   4 +-
 .../java/com/codahale/metrics/CsvFileProvider.java |  12 +
 .../java/com/codahale/metrics/CsvReporter.java     |  54 +++-
 .../src/main/java/com/codahale/metrics/EWMA.java   |   2 +-
 .../metrics/ExponentiallyDecayingReservoir.java    |  28 +-
 .../codahale/metrics/FixedNameCsvFileProvider.java |  22 ++
 .../main/java/com/codahale/metrics/Histogram.java  |   4 +-
 .../com/codahale/metrics/JmxAttributeGauge.java    |  13 +-
 .../java/com/codahale/metrics/JmxReporter.java     |   9 +-
 .../com/codahale/metrics/LongAdderAdapter.java     |  18 ++
 .../java/com/codahale/metrics/LongAdderProxy.java  | 108 +++++++
 .../src/main/java/com/codahale/metrics/Meter.java  |   2 +-
 .../java/com/codahale/metrics/MetricAttribute.java |  33 +++
 .../java/com/codahale/metrics/MetricRegistry.java  | 118 +++++++-
 .../com/codahale/metrics/ScheduledReporter.java    | 156 ++++++++---
 .../codahale/metrics/SharedMetricRegistries.java   |  63 +++++
 .../java/com/codahale/metrics/Slf4jReporter.java   |  52 +++-
 .../metrics/SlidingTimeWindowArrayReservoir.java   | 100 +++++++
 .../metrics/SlidingTimeWindowReservoir.java        |  12 +-
 .../main/java/com/codahale/metrics/Striped64.java  |  75 +----
 .../codahale/metrics/ThreadLocalRandomProxy.java   |  50 ++++
 .../src/main/java/com/codahale/metrics/Timer.java  |  15 +
 .../com/codahale/metrics/UniformReservoir.java     |   6 +-
 .../java/com/codahale/metrics/UniformSnapshot.java |   2 +-
 .../metrics/ChunkedAssociativeLongArrayTest.java   |  71 +++++
 .../com/codahale/metrics/ConsoleReporterTest.java  | 173 +++++++++++-
 .../java/com/codahale/metrics/CounterTest.java     |  16 ++
 .../java/com/codahale/metrics/CsvReporterTest.java |  45 +--
 .../ExponentiallyDecayingReservoirTest.java        | 141 +++++++++-
 .../metrics/FixedNameCsvFileProviderTest.java      |  38 +++
 .../metrics/InstrumentedExecutorServiceTest.java   |   7 +-
 .../InstrumentedScheduledExecutorServiceTest.java  |   6 +-
 .../codahale/metrics/JmxAttributeGaugeTest.java    |  99 +++++--
 .../java/com/codahale/metrics/JmxReporterTest.java |   9 +-
 .../com/codahale/metrics/MetricRegistryTest.java   |  90 +++++-
 .../codahale/metrics/ScheduledReporterTest.java    | 163 +++++++++--
 .../metrics/SharedMetricRegistriesTest.java        |  44 ++-
 .../com/codahale/metrics/Slf4jReporterTest.java    |  26 +-
 .../SlidingTimeWindowArrayReservoirTest.java       | 150 ++++++++++
 .../metrics/SlidingTimeWindowReservoirTest.java    |  79 +++++-
 .../test/java/com/codahale/metrics/TimerTest.java  |  20 ++
 .../com/codahale/metrics/UniformSnapshotTest.java  |  65 ++---
 metrics-ehcache/pom.xml                            |   2 +-
 metrics-ganglia/pom.xml                            |   2 +-
 .../codahale/metrics/ganglia/GangliaReporter.java  | 137 ++++++---
 .../metrics/ganglia/GangliaReporterTest.java       |  38 +++
 metrics-graphite/pom.xml                           |   2 +-
 .../com/codahale/metrics/graphite/Graphite.java    |  25 +-
 .../metrics/graphite/GraphiteRabbitMQ.java         |  12 +-
 .../metrics/graphite/GraphiteReporter.java         | 188 +++++++++----
 .../metrics/graphite/GraphiteSanitize.java         |  16 ++
 .../com/codahale/metrics/graphite/GraphiteUDP.java |  38 ++-
 .../codahale/metrics/graphite/PickledGraphite.java |   4 +-
 .../metrics/graphite/GraphiteReporterTest.java     | 161 ++++++++++-
 .../metrics/graphite/GraphiteSanitizeTest.java     |  27 ++
 .../codahale/metrics/graphite/GraphiteTest.java    |   4 +-
 .../codahale/metrics/graphite/GraphiteUDPTest.java |  43 +++
 metrics-healthchecks/pom.xml                       |   2 +-
 .../metrics/health/AsyncHealthCheckDecorator.java  |  60 ++++
 .../com/codahale/metrics/health/HealthCheck.java   | 165 ++++++++++-
 .../metrics/health/HealthCheckRegistry.java        | 182 +++++++++++-
 .../health/HealthCheckRegistryListener.java        |  26 ++
 .../health/SharedHealthCheckRegistries.java        |  64 ++++-
 .../codahale/metrics/health/annotation/Async.java  |  64 +++++
 .../health/AsyncHealthCheckDecoratorTest.java      | 224 +++++++++++++++
 .../metrics/health/HealthCheckRegistryTest.java    | 137 +++++++--
 .../codahale/metrics/health/HealthCheckTest.java   |  91 +++++-
 .../health/SharedHealthCheckRegistriesTest.java    |  97 +++++++
 .../metrics/health/SharedMetricRegistriesTest.java |  54 ----
 metrics-httpasyncclient/pom.xml                    |   8 +-
 metrics-httpclient/pom.xml                         |   4 +-
 .../httpclient/HttpClientMetricNameStrategies.java |  19 +-
 .../HttpClientMetricNameStrategiesTest.java        |  39 +++
 {metrics-graphite => metrics-jcache}/pom.xml       |  23 +-
 .../codahale/metrics/jcache/JCacheGaugeSet.java    |  86 ++++++
 .../src/test/java/JCacheGaugeSetTest.java          |  86 ++++++
 metrics-jcache/src/test/resources/ehcache.xml      |  16 ++
 metrics-jcstress/README.md                         |  17 ++
 metrics-jcstress/findbugs-exclude.xml              |   8 +
 {metrics-benchmarks => metrics-jcstress}/pom.xml   |  78 ++++--
 ...lidingTimeWindowArrayReservoirTrimReadTest.java |  67 +++++
 ...gTimeWindowArrayReservoirWriteReadAllocate.java |  45 +++
 ...idingTimeWindowArrayReservoirWriteReadTest.java |  45 +++
 metrics-jdbi/pom.xml                               |   2 +-
 metrics-jersey/pom.xml                             |   2 +-
 metrics-jersey2/pom.xml                            |   2 +-
 ...trumentedResourceMethodApplicationListener.java | 128 ++++++---
 ...MetricsExceptionMeteredPerClassJerseyTest.java} |  67 ++---
 .../jersey2/SingletonMetricsJerseyTest.java        |  16 +-
 .../SingletonMetricsMeteredPerClassJerseyTest.java |  66 +++++
 .../SingletonMetricsTimedPerClassJerseyTest.java   |  66 +++++
 .../jersey2/resources/InstrumentedResource.java    |   5 +
 ...trumentedResourceExceptionMeteredPerClass.java} |  25 +-
 .../InstrumentedResourceMeteredPerClass.java       |  26 ++
 .../InstrumentedResourceTimedPerClass.java         |  26 ++
 .../jersey2/resources/InstrumentedSubResource.java |  18 ++
 ...mentedSubResourceExceptionMeteredPerClass.java} |  21 +-
 .../InstrumentedSubResourceMeteredPerClass.java    |  18 ++
 .../InstrumentedSubResourceTimedPerClass.java      |  18 ++
 metrics-jetty8/pom.xml                             |   2 +-
 metrics-jetty9-legacy/pom.xml                      |   2 +-
 metrics-jetty9/pom.xml                             |   2 +-
 .../jetty9/InstrumentedConnectionFactory.java      |  22 ++
 .../metrics/jetty9/InstrumentedHandler.java        |   4 +-
 .../jetty9/InstrumentedQueuedThreadPool.java       |  30 +-
 .../metrics/jetty9/InstrumentedHandlerTest.java    | 179 +++++++++---
 .../jetty9/InstrumentedQueuedThreadPoolTest.java   |  49 ++++
 metrics-json/pom.xml                               |   2 +-
 .../codahale/metrics/json/HealthCheckModule.java   |  13 +-
 .../com/codahale/metrics/json/MetricsModule.java   |   2 +-
 .../metrics/json/HealthCheckModuleTest.java        |  40 +++
 .../codahale/metrics/json/MetricsModuleTest.java   |   2 +-
 metrics-jvm/pom.xml                                |   2 +-
 .../codahale/metrics/jvm/MemoryUsageGaugeSet.java  |  10 +
 .../java/com/codahale/metrics/jvm/ThreadDump.java  |   2 +-
 .../metrics/jvm/MemoryUsageGaugeSetTest.java       |  17 ++
 .../com/codahale/metrics/jvm/ThreadDumpTest.java   |   2 +-
 metrics-log4j/pom.xml                              |   2 +-
 metrics-log4j2/pom.xml                             |  17 +-
 .../metrics/log4j2/InstrumentedAppender.java       |  40 ++-
 .../log4j2/InstrumentedAppenderConfigTest.java     |  65 +++++
 .../src/test/resources/log4j2-testconfig.xml       |  11 +
 metrics-logback/pom.xml                            |   4 +-
 metrics-servlet/pom.xml                            |   2 +-
 .../servlet/AbstractInstrumentedFilter.java        |  70 ++++-
 metrics-servlets/pom.xml                           |   7 +-
 .../codahale/metrics/servlets/AdminServlet.java    |  18 +-
 .../metrics/servlets/CpuProfileServlet.java        |  81 ++++++
 .../metrics/servlets/HealthCheckServlet.java       |   6 +
 .../codahale/metrics/servlets/MetricsServlet.java  |   2 +-
 .../metrics/servlets/AdminServletTest.java         |   2 +
 .../metrics/servlets/CpuProfileServletTest.java    |  43 +++
 .../MetricsServletContextListenerTest.java         |   4 +-
 .../metrics/servlets/MetricsServletTest.java       |   8 +-
 pom.xml                                            |  29 +-
 156 files changed, 6147 insertions(+), 847 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0336509..6b9fbe5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@ bin
 *.ipr
 *.iws
 .metadata
+jcstress.*
+metrics-jcstress/results/
+TODO.md
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index a5a6cbf..16c7e43 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,6 @@ install: echo "I trust Maven."
 script: mvn verify
 
 jdk:
-  - oraclejdk7
   - oraclejdk8
 
 notifications:
diff --git a/README.md b/README.md
index 659854a..7378e9b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
 Metrics [![Build Status](https://secure.travis-ci.org/dropwizard/metrics.png)](http://travis-ci.org/dropwizard/metrics)
+[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.dropwizard.metrics/metrics-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.dropwizard.metrics/metrics-core/)
 =======
 
 *Capturing JVM- and application-level metrics. So you know what's going on.*
diff --git a/docs/list_contributors.rb b/docs/list_contributors.rb
new file mode 100755
index 0000000..b59d24f
--- /dev/null
+++ b/docs/list_contributors.rb
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+require 'octokit'
+
+Octokit.configure do |c|
+  # Provide an Access Token to prevent running into the hourly rate-limit
+  # see https://help.github.com/articles/creating-an-access-token-for-command-line-use
+  c.access_token = ENV['GITHUB_TOKEN'] || ''
+  c.auto_paginate = true
+end
+
+contributors = Octokit.contributors('dropwizard/metrics')
+contributors.each do |c|
+  user = Octokit.user(c.login)
+  name = if user.name.nil? then user.login else user.name end
+  puts "* `#{name} <#{user.html_url}>`_"
+end
diff --git a/docs/pom.xml b/docs/pom.xml
new file mode 100644
index 0000000..1525744
--- /dev/null
+++ b/docs/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>io.dropwizard.metrics</groupId>
+        <artifactId>metrics-parent</artifactId>
+        <version>3.2.4</version>
+    </parent>
+
+    <artifactId>docs</artifactId>
+    <name>Metrics Documentation</name>
+
+    <properties>
+        <jar.skipIfEmpty>true</jar.skipIfEmpty>
+        <mpir.skip>true</mpir.skip>
+        <maven.deploy.skip>true</maven.deploy.skip>
+    </properties>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${basedir}/source</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <version>1.9.1</version>
+                <executions>
+                    <execution>
+                        <id>parse-version</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>parse-version</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>process-resources</id>
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>resources</goal>
+                        </goals>
+                        <configuration>
+                            <delimiters>
+                                <delimiter>@</delimiter>
+                            </delimiters>
+                            <outputDirectory>${project.build.directory}/source</outputDirectory>
+                            <useDefaultDelimiters>false</useDefaultDelimiters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>sphinx-maven-plugin</artifactId>
+                <configuration>
+                    <sourceDirectory>${project.build.directory}/source</sourceDirectory>
+                </configuration>
+            </plugin>
+        </plugins>
+    </reporting>
+</project>
diff --git a/docs/source/_themes/metrics/layout.html b/docs/source/_themes/metrics/layout.html
index 7734475..ab56388 100644
--- a/docs/source/_themes/metrics/layout.html
+++ b/docs/source/_themes/metrics/layout.html
@@ -115,6 +115,7 @@
         </div>
         <hr/>
         <footer>
+            <p style="float: left">
             {%- if show_copyright %}
             {%- if hasdoc('copyright') %}
             {% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a>
@@ -130,6 +131,8 @@
             {% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>
             {{ sphinx_version }}.{% endtrans %}
             {%- endif %}
+            </p>
+            <p style="float: right">Dropwizard Metrics v{{ release }}</p>
         </footer>
     </div>
 </body>
diff --git a/docs/source/about/contributors.rst b/docs/source/about/contributors.rst
index 50453eb..0a62d1b 100644
--- a/docs/source/about/contributors.rst
+++ b/docs/source/about/contributors.rst
@@ -7,77 +7,145 @@ Contributors
 Many, many thanks to:
 
 * `Alan Woodward <https://github.com/romseygeek>`_
-* `Alex Lambert <https://github.com/bifflabs>`_
+* `Alexander Reelsen <https://github.com/spinscale>`_
+* `Alex Lambert <https://github.com/alambert>`_
+* `Anil V <https://github.com/avaitla>`_
+* `Anthony Dahanne <https://github.com/anthonydahanne>`_
+* `Antonin Stefanutti <https://github.com/astefanutti>`_
+* `Artem Prigoda <https://github.com/arteam>`_
+* `Bartosz Krasiński <https://github.com/krasinski>`_
+* `Bart Prokop <https://github.com/bartprokop>`_
 * `Basil James Whitehouse III <https://github.com/basil3whitehouse>`_
 * `Benjamin Gehrels <https://github.com/BGehrels>`_
-* `Bernardo Gomez Palacio <https://github.com/berngp>`_
-* `Brian Ehmann <https://github.com/codelotus>`_
+* `Ben Tatham <https://github.com/bentatham>`_
+* `Bogdan Storozhuk <https://github.com/storozhukBM>`_
+* `Brenden Matthews <https://github.com/brndnmtthws>`_
+* `Brian  <https://github.com/codelotus>`_
 * `Brian Roberts <https://github.com/flicken>`_
 * `Bruce Mitchener <https://github.com/waywardmonkeys>`_
-* `C. Scott Andreas <https://github.com/cscotta>`_
+* `cburroughs <https://github.com/cburroughs>`_
 * `ceetav <https://github.com/ceetav>`_
 * `Charles Care <https://github.com/ccare>`_
 * `Chris Birchall <https://github.com/cb372>`_
-* `Chris Burroughs <https://github.com/cburroughs>`_
+* `Christopher Gray <https://github.com/chrisgray>`_
 * `Christopher Swenson <https://github.com/swenson>`_
-* `Ciamac Moallemi <https://github.com/ciamac>`_
-* `Cliff Moon <https://github.com/cliffmoon>`_
-* `Collin VanDyck <https://github.com/collinvandyck>`_
+* `ciamac <https://github.com/ciamac>`_
+* `Coda Hale <https://github.com/codahale>`_
+* `Collin Van Dyck <https://github.com/collinvandyck>`_
+* `Corentin Chary <https://github.com/iksaif>`_
+* `C. Scott Andreas <https://github.com/cscotta>`_
 * `Dag Liodden <https://github.com/daggerrz>`_
 * `Dale Wijnand <https://github.com/dwijnand>`_
 * `Dan Brown <https://github.com/jdanbrown>`_
-* `Dan Everton <https://github.com/wotifgroup>`_
+* `Dan Everton <https://github.com/deverton>`_
+* `Daniel James <https://github.com/dwhjames>`_
 * `Dan Revel <https://github.com/nopolabs>`_
+* `David M. Karr <https://github.com/davidmichaelkarr>`_
+* `David Schlosnagle <https://github.com/schlosna>`_
 * `David Sutherland <https://github.com/djsutho>`_
-* `Diwaker Gupta <https://github.com/maginatics>`_
+* `Diwaker Gupta <https://github.com/diwakergupta>`_
 * `Drew Stephens <https://github.com/dinomite>`_
+* `Eduard Martinescu <https://github.com/Arvoreen>`_
 * `Edwin Shin <https://github.com/eddies>`_
-* `Eric Daigneault <https://github.com/Newtopian>`_
+* `Erik van Oosten <https://github.com/erikvanoosten>`_
 * `Evan Jones <https://github.com/evanj>`_
+* `Fabrizio Cannizzo <https://github.com/smartrics>`_
 * `François Beausoleil <https://github.com/francois>`_
+* `Gabor Arki <https://github.com/arkigabor>`_
+* `George Spalding <https://github.com/georgespalding>`_
 * `Gerolf Seitz <https://github.com/gseitz>`_
+* `gilbode <https://github.com/gilbode>`_
 * `Greg Bowyer <https://github.com/GregBowyer>`_
+* `Gunnar Ahlberg <https://github.com/gunnarahlberg>`_
+* `Henri Tremblay <https://github.com/henri-tremblay>`_
+* `ho3rexqj <https://github.com/ho3rexqj>`_
+* `Hussein Elsayed <https://github.com/husseincoder>`_
+* `Ian Strachan <https://github.com/ianestrachan>`_
+* `Istvan Meszaros <https://github.com/IstvanM>`_
+* `Ivan Dyedov <https://github.com/idyedov>`_
 * `Jackson Davis <https://github.com/jcdavis>`_
+* `James Burkhart <https://github.com/fourk>`_
 * `James Casey <https://github.com/jamesc>`_
 * `Jan-Helge Bergesen <https://github.com/jhberges>`_
+* `Janne Sinivirta <https://github.com/vertti>`_
 * `Jason A. Beranek <https://github.com/jasonberanek>`_
 * `Jason Slagle <https://github.com/jmslagle>`_
-* `JD Maturen <https://github.com/sku>`_
+* `Jason Whitlark <https://github.com/jwhitlark>`_
 * `Jeff Hodges <https://github.com/jmhodges>`_
+* `Jeff Klukas <https://github.com/jklukas>`_
+* `Jeff Wartes <https://github.com/randomstatistic>`_
+* `Jens Schauder <https://github.com/schauder>`_
 * `Jesper Blomquist <https://github.com/jebl01>`_
 * `Jesse Eichar <https://github.com/jesseeichar>`_
-* `John Ewart <https://github.com/johnewart>`_
+* `Jochen Schalanda <https://github.com/joschi>`_
+* `Joe Ellis <https://github.com/ellisjoe>`_
+* `Joel Takvorian <https://github.com/jotak>`_
+* `John-John Tedro <https://github.com/udoprog>`_
 * `John Wang <https://github.com/javasoze>`_
+* `Jordan Focht <https://github.com/jfocht>`_
+* `Juha Syrjälä <https://github.com/jsyrjala>`_
+* `Julio Lopez <https://github.com/julio-maginatics>`_
 * `Justin Plock <https://github.com/jplock>`_
 * `Kevin Clark <https://github.com/kevinclark>`_
+* `Kevin Menard <https://github.com/nirvdrum>`_
+* `Kevin Yeh <https://github.com/kyeah>`_
+* `konnik <https://github.com/konnik>`_
+* `Larry Shatzer, Jr. <https://github.com/larrys>`_
+* `Luke Amdor <https://github.com/rubbish>`_
 * `Mahesh Tiyyagura <https://github.com/tmahesh>`_
+* `Mark Menard <https://github.com/MarkMenard>`_
+* `Marlon Bernardes <https://github.com/marlonbernardes>`_
+* `Mårten Gustafson <https://github.com/chids>`_
+* `Martin Jöhren <https://github.com/matlockx>`_
 * `Martin Traverso <https://github.com/martint>`_
+* `Matheus Cabral <https://github.com/mcgois>`_
 * `Matt Abrams <https://github.com/abramsm>`_
-* `Matt Ryall <https://github.com/mattryall>`_
 * `Matthew Gilliard <https://github.com/mjg123>`_
 * `Matthew O'Connor <https://github.com/oconnor0>`_
-* `Mathijs Vogelzang <https://github.com/mathijs81>`_
-* `Mårten Gustafson <https://github.com/chids>`_
+* `Matt Veitas <https://github.com/mveitas>`_
 * `Michał Minicki <https://github.com/martel>`_
+* `Miikka Koskinen <https://github.com/miikka>`_
 * `Neil Prosser <https://github.com/neilprosser>`_
+* `Nick Babcock <https://github.com/nickbabcock>`_
 * `Nick Telford <https://github.com/nicktelford>`_
-* `Niklas Konstenius <https://github.com/konnik>`_
 * `Norbert Potocki <https://github.com/norbertpotocki>`_
 * `Pablo Fernandez <https://github.com/fernandezpablo85>`_
-* `Paul Bloch <https://github.com/pbloch>`_
+* `Patryk Najda <https://github.com/patrox>`_
 * `Paul Brown <https://github.com/prb>`_
 * `Paul Doran <https://github.com/dorzey>`_
-* `Paul Sandwald <https://github.com/pcsanwald>`_
+* `Paul Sanwald <https://github.com/pcsanwald>`_
+* `Philipp Hauer <https://github.com/phauer>`_
+* `Raman Gupta <https://github.com/rocketraman>`_
 * `Realbot <https://github.com/realbot>`_
 * `Robby Walker <https://github.com/robbywalker>`_
-* `Ryan Kennedy <https://github.com/ryankennedy>`_
-* `Ryan W Tenney <https://github.com/ryantenney>`_
+* `Ron Klein <https://github.com/kleinron>`_
+* `Ryan Campbell <https://github.com/recampbell>`_
+* `Ryan McCrone <https://github.com/rwmccro>`_
+* `Ryan Tenney <https://github.com/ryantenney>`_
+* `saadmufti <https://github.com/saadmufti>`_
 * `Sam Perman <https://github.com/samperman>`_
+* `Samy Dindane <https://github.com/Dinduks>`_
 * `Sean Laurent <https://github.com/organicveggie>`_
-* `Shaneal Manek <https://github.com/smanek>`_
+* `Sebastian Lövdahl <https://github.com/slovdahl>`_
+* `Sergey Nazarov <https://github.com/phearnot>`_
+* `Silvia Mandalà <https://github.com/simad>`_
+* `sofax <https://github.com/sofax>`_
+* `Steve Fosdal <https://github.com/sfosdal>`_
 * `Steven Schlansker <https://github.com/stevenschlansker>`_
-* `Stewart Allen <https://github.com/stewartoallen>`_
-* `Thomas Dudziak <https://github.com/tomdz>`_
+* `stockmaj <https://github.com/stockmaj>`_
+* `Stuart Gunter <https://github.com/stuartgunter>`_
+* `Thomas Cashman <https://github.com/tomcashman>`_
+* `Tobias Bieniek <https://github.com/Turbo87>`_
 * `Tobias Lidskog <https://github.com/tobli>`_
-* `Yang Ye <https://github.com/yeyangever>`_
+* `Tom Akehurst <https://github.com/tomakehurst>`_
+* `Tomasz Guzik <https://github.com/tguzik>`_
+* `Tomasz Nurkiewicz <https://github.com/nurkiewicz>`_
+* `Tom Golden <https://github.com/TomRK1089>`_
+* `tvleminckx <https://github.com/tvleminckx>`_
+* `v-garki <https://github.com/v-garki>`_
+* `Vladimir Bukhtoyarov <https://github.com/vladimir-bukhtoyarov>`_
+* `Volker Fritzsch <https://github.com/volker>`_
+* `Wolfgang Hoschek <https://github.com/whoschek>`_
 * `Wolfgang Schell <https://github.com/jetztgradnet>`_
+* `yeyangever <https://github.com/yeyangever>`_
+* `Zach A. Thomas <https://github.com/zathomas>`_
diff --git a/docs/source/about/release-notes.rst b/docs/source/about/release-notes.rst
index 5157cdd..7d96638 100644
--- a/docs/source/about/release-notes.rst
+++ b/docs/source/about/release-notes.rst
@@ -4,6 +4,187 @@
 Release Notes
 #############
 
+.. _rel-3.2.3:
+
+v3.2.3: Jun 28 2017
+===================
+
+* Improve ``ScheduledReporter`` ``convertDurations`` precision `#1115 <https://github.com/dropwizard/metrics/pull/1115>`_
+* Suppress all kinds of Throwables raised by ``report()`` `#1128 <https://github.com/dropwizard/metrics/pull/1128>`_
+* ``ExponentiallyDecayingReservoir`` was giving incorrect values in the snapshot if the inactive period was too long `#1135 <https://github.com/dropwizard/metrics/pull/1135>`_
+* Ability to get default metrics registry without an exception `#1140 <https://github.com/dropwizard/metrics/pull/1140>`_
+* Ability to get default health check registry without an exception `#1152 <https://github.com/dropwizard/metrics/pull/1152>`_
+* ``SlidingTimeWindowArrayReservoir`` as a fast alternative of ``SlidingTimeWindowReservoir`` `#1139 <https://github.com/dropwizard/metrics/pull/1139>`_
+* Avoid a NPE in toString of ``HealthCheck.Result`` `#1141 <https://github.com/dropwizard/metrics/pull/1141>`_
+
+.. _rel-3.1.5:
+
+v3.1.5: Jun 2 2017
+===================
+
+* More robust lookup of ``ThreadLocal`` and ``LongAdder`` on JDK6 (e.g. WebLogic) `#1136 <https://github.com/dropwizard/metrics/pull/1136>`_
+
+.. _rel-3.2.2:
+
+v3.2.2: Mar 20 2017
+===================
+
+* Fix creating a uniform snapshot from a collection `#1111 <https://github.com/dropwizard/metrics/pull/1111>`_
+* Register metrics defined at Resource level `#1105 <https://github.com/dropwizard/metrics/pull/1105>`_
+
+.. _rel-3.2.1:
+
+v3.2.1: Mar 10 2017
+===================
+
+* Support for shutting down the health check registry. `#1084 <https://github.com/dropwizard/metrics/pull/1084>`_
+* Added support for the default shared health check registry name #1095 `#1095 <https://github.com/dropwizard/metrics/pull/1095>`_
+* SharedMetricRegistries are now thread-safe. `#1094 <https://github.com/dropwizard/metrics/pull/1095>`_
+* The size of the snapshot of a histogram is reported via JMX. `#1102 <https://github.com/dropwizard/metrics/pull/1102>`_
+* Don't ignore the counter attribute for reporters. `#1090 <https://github.com/dropwizard/metrics/pull/1090>`_
+* Added support for disabling attributes in ConsoleReporter. `#1092 <https://github.com/dropwizard/metrics/pull/1092>`_
+* Rollbacked GraphiteSanitize to replacing whitespaces. `#1099 <https://github.com/dropwizard/metrics/pull/1099>`_
+
+.. _rel-3.1.4:
+
+v3.1.4: Mar 10 2017
+===================
+
+* Fix accidentally broken Graphite UDP reporter `#1100 <https://github.com/dropwizard/metrics/pull/1100>`_
+
+.. _rel-3.2.0:
+
+v3.2.0: Feb 24 2017
+===================
+
+* `GraphiteReporter` opens a new TCP connection when sending metrics instead of maintaining a persisted connection. `#1047 <https://github.com/dropwizard/metrics/pull/1047>`_
+* `GraphiteReporter` retries DNS lookups in case of a lookup failure. `#1064 <https://github.com/dropwizard/metrics/pull/1064>`_
+* `ScheduledReporter` suppresses all kind of exceptions raised by the `report` method. `#1049 <https://github.com/dropwizard/metrics/pull/1049>`_
+* JDK's `ThreadLocalRandom` is now used by default. `#1052 <https://github.com/dropwizard/metrics/pull/1052>`_
+* JDK's `LongAdder` is now used by default. `#1055 <https://github.com/dropwizard/metrics/pull/1055>`_
+* Fixed a race condition bug in `ExponentiallyDecayingReservoir`. `#1033 <https://github.com/dropwizard/metrics/pull/1033>`_
+* Fixed a long overflow bug in `SlidingTimeWindowReservoir`. `#1063 <https://github.com/dropwizard/metrics/pull/1063>`_
+* `AdminServlet` supports CPU profiling. `#927 <https://github.com/dropwizard/metrics/pull/927>`_
+* `GraphiteReporter` sanitizes metrics. `#938 <https://github.com/dropwizard/metrics/pull/938>`_
+* Support for publishing `BigInteger` and `BigDecimal` metrics in `GraphiteReporter`. `#933 <https://github.com/dropwizard/metrics/pull/933>`_
+* Support for publishing boolean metrics in `GraphiteReporter`. `#905 <https://github.com/dropwizard/metrics/pull/905>`_
+* Added support for overriding the format of floating numbers in `GraphiteReporter`. `#1073 <https://github.com/dropwizard/metrics/pull/1073>`_
+* Added support for disabling reporting of metric attributes. `#1048 <https://github.com/dropwizard/metrics/pull/1048>`_
+* Reporters are more user friendly for managed environments like GAE or JEE. `#1018 <https://github.com/dropwizard/metrics/pull/1018>`_
+* Support for setting a custom initial delay for reporters. `#999 <https://github.com/dropwizard/metrics/pull/999>`_
+* Support for custom details in a result of a health check. `#663 <https://github.com/dropwizard/metrics/pull/663>`_
+* Added a listener for health checks. `#1068 <https://github.com/dropwizard/metrics/pull/1068>`_
+* Support for asynchronous health checks `#1077 <https://github.com/dropwizard/metrics/pull/1077>`_
+* Health checks are reported as unhealthy on exceptions. `#783 <https://github.com/dropwizard/metrics/pull/783>`_
+* Allow setting a custom prefix for Jetty's `InstrumentedQueuedThreadPool`. `#947 <https://github.com/dropwizard/metrics/pull/947>`_
+* Allow setting custom prefix for Jetty's `QueuedThreadPool`. `#908 <https://github.com/dropwizard/metrics/pull/908>`_
+* Added support for Jetty 9.3 and higher. `#1038 <https://github.com/dropwizard/metrics/pull/1038>`_
+* Fixed instrumentation of Jetty9 async servlets. `#1074 <https://github.com/dropwizard/metrics/pull/1074>`_
+* Added support for JCache/JSR 107 metrics. `#1010 <https://github.com/dropwizard/metrics/pull/1010>`_
+* Added thread-safe getters for metrics with custom instantiations. `#1023 <https://github.com/dropwizard/metrics/pull/1023>`_
+* Added an overload of `Timer#time` that takes a `Runnable`. `#989 <https://github.com/dropwizard/metrics/pull/989>`_
+* Support extracting the request URI from wrapped requests in `HttpClientMetricNameStrategies`. `#947 <https://github.com/dropwizard/metrics/pull/947>`_
+* Support for the log4j2 xml-based config. `#900 <https://github.com/dropwizard/metrics/pull/900>`_
+* Internal `Striped64` doesn't depend on `sun.misc.Unsafe` anymore. `#966 <https://github.com/dropwizard/metrics/pull/966>`_
+* Optimized creation of `UniformSnapshot`. `#970 <https://github.com/dropwizard/metrics/pull/970>`_
+* Added a memory pool gauge to the JVM memory usage metrics. `#786 <https://github.com/dropwizard/metrics/pull/786>`_
+* Added support for async servlets for `metric-servlet`. `#796 <https://github.com/dropwizard/metrics/pull/796>`_
+* Opt-in default shared metric registry. `#801 <https://github.com/dropwizard/metrics/pull/801>`_
+* Added support for patterns in MBean object names `#809 <https://github.com/dropwizard/metrics/pull/809>`_
+* Allow a pluggable strategy for the name of the CSV files for `CsvReporter`. `#882 <https://github.com/dropwizard/metrics/pull/882>`_
+* Upgraded to slf4j 1.22
+* Upgraded to Jackson 2.6.6
+* Upgraded to amqp-client 3.6.6
+* Upgraded to httpclient 4.5.2
+* Upgraded to log4j2 2.3
+* Upgraded to logback 1.1.10
+
+.. _rel-3.1.3:
+
+v3.1.3: Feb 24 2017
+===================
+
+* `GraphiteReporter` opens a new TCP connection when sending metrics instead of maintaining a persisted connection. `#1036 <https://github.com/dropwizard/metrics/pull/1036>`_
+* `GraphiteReporter` retries DNS lookups in case of a lookup failure. `#1064 <https://github.com/dropwizard/metrics/pull/1064>`_
+* `ScheduledReporter` suppresses all kind of exceptions raised by the `report` method. `#1040 <https://github.com/dropwizard/metrics/pull/1040>`_
+* JDK's `ThreadLocalRandom` is now used by default. `#1052 <https://github.com/dropwizard/metrics/pull/1052>`_
+* JDK's `LongAdder` is now used by default. `#1055 <https://github.com/dropwizard/metrics/pull/1055>`_
+* Fixed a race condition bug in `ExponentiallyDecayingReservoir`. `#1046 <https://github.com/dropwizard/metrics/pull/1046>`_
+* Fixed a long overflow bug in `SlidingTimeWindowReservoir`. `#1072 <https://github.com/dropwizard/metrics/pull/1072>`_
+
+
+.. _rel-3.1.0:
+
+v3.1.0: Sen 10 2014
+===================
+
+https://groups.google.com/forum/#!topic/metrics-user/zwzHnMBcAX4
+
+* Upgrade to Jetty 9.1 (metrics-jetty9, Jetty 9.0 module renamed to metrics-jetty9-legacy)
+* Add log4j2 support (metrics-log4j2)
+* Upgrade to Jersey2 (metrics-jersey2)
+* Add httpasyncclient support (metrics-httpasyncclient)
+* Changed maven groupId to io.dropwizard.metrics
+* Enable Java8 builds on Travis, fix javadocs and disable some doclinting
+* Fixing some compilation warnings about missing generics and varargs invocation
+* Instrumentation for java.util.concurrent classes
+* ExponentiallyDecayingReservoir: quantiles weighting
+* Loosen type requirements for JmxAttributeGauge constructor
+* SlidingWindowReservoir - ArrayOutOfBoundsException thrown if # of Reservoir examples exceeds Integer max value
+* Classloader metrics
+* Add an instrumented ScheduledExecutorService
+* Fix race condition in InstrumentedThreadFactoryTest
+* Correct comparison of System.nanoTime in SlidingTimeWindowReservoir
+* Add SharedHealthCheckRegistries class
+* Migrate benchmarks from Caliper to JMH
+* New annotations: @CachedGauge, @Counted, @Metric
+* Support for annotations on classes and constructors
+* Allow @Metric on methods and parameters
+* Add @Inherited and @Documented on all type annotations
+* Adapted ehcache integration to latest ehcache version 2.8.3
+* Upgrade to HttpClient 4.3
+* InstrumentedHandler: Remove duplicate calls to requests.update(...)
+* New metric 'utilization-max' to track thread usage out of max pool size in jetty
+* Replaced Jetty-specific Request with Servlet API interfaces
+* Jetty 8: Avoid NPE if InstrumentedQueuedThreadPool gauges are read too early
+* Jetty 8: Call updateResponses onComplete of ContinuationListener
+* Allow specifying a custom prefix Jetty 9 InstrumentedHandler
+* MetricsModule is serializing wrong minute rates for timers
+* MeterSerializer.serialize had m1_rate and m15_rate transposed
+* Add CachedThreadStatesGaugeSet
+* Monitor count of deadlock threads
+* Prevent exceptions from ThreadDumpServlet on Google AppEngine
+* Upgrade to logback 1.1.1
+* Allow InstrumentedAppender use in logback.xml
+* Use getClass() in place of AbstractInstrumentedFilter.class in generated metric names
+* Update MetricsServlet with support for JSONP as alternative to CORS
+* Specify the base name of the metrics as a filter init-param for the metrics captured in the AbstractInstrumentedFilter
+* Add option to provide MetricFilter to MetricsServlet
+* AdminServlet generates link to pretty printed healthchecks
+* MetricsServlet.ContextListener doesn't initialize the context correctly
+* Every reporter implements Reporter interface to indicate that is a Reporter
+* Added support for passing a ScheduledExecutorService to ScheduledReporters
+* Improve the ScheduledReporter#stop method
+* Ensure ScheduledReporters get unique thread pools.
+* Suppress runtime exceptions thrown from ScheduledReporter#report
+* Ability to inject a factory of ObjectName
+* Lazy fetch of PlatformMBeanServer
+* JMX Reporter throws exception when metric name contains an asterisk
+* onTimerRemoved in JmxListener calls registered.add
+* Support for mBean servers that rewrite the supplied ObjectName upon registration
+* Graphite reporter does not notify when Graphite/Carbon server is unreachable
+* Persistent connections to Graphite
+* Graphite constructor accepts host/port
+* Graphtie Pickle sender
+* Graphite UDP sender
+* Graphite AMQP sender
+* Add a threshold/minimum value to report before converting results to 0
+* Report to multiple gmetric instances
+* Escape slahes on ganglia metric names
+* Upgrade slf4j to 1.7.6
+* Enhancement for logging level option on Slf4jReporter
+
+
 .. _rel-3.0.1:
 
 v3.0.1: Jul 23 2013
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 4acdc45..49c301b 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -48,9 +48,9 @@ copyright = u'2010-2014, Coda Hale, Yammer Inc.'
 # built documents.
 #
 # The short X.Y version.
-version = '3.1'
+version = '@parsedVersion.majorVersion at .@parsedVersion.minorVersion@'
 # The full version, including alpha/beta/rc tags.
-release = '3.1.0'
+release = '@project.version@'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst
index 760b98c..9576aaa 100644
--- a/docs/source/getting-started.rst
+++ b/docs/source/getting-started.rst
@@ -44,6 +44,7 @@ rate, meters also track 1-, 5-, and 15-minute moving averages.
 
 .. code-block:: java
 
+    private final MetricRegistry metrics = new MetricRegistry();
     private final Meter requests = metrics.meter("requests");
 
     public void handleRequest(Request request, Response response) {
diff --git a/docs/source/manual/core.rst b/docs/source/manual/core.rst
index f29914d..4fb0f1b 100644
--- a/docs/source/manual/core.rst
+++ b/docs/source/manual/core.rst
@@ -21,20 +21,26 @@ Metric Registries
 =================
 
 The starting point for Metrics is the ``MetricRegistry`` class, which is a collection of all the
-metrics for your application (or a subset of your application). If your application is running
-alongside other applications in a single JVM instance (e.g., multiple WARs deployed to an
-application server), you should use per-application ``MetricRegistry`` instances with different
-names.
+metrics for your application (or a subset of your application).
+
+Generally you only need one ``MetricRegistry`` instance per application, although you may choose
+to use more if you want to organize your metrics in particular reporting groups.
+
+Global named registries can also be shared through the static ``SharedMetricRegistries`` class. This
+allows the same registry to be used in different sections of code without explicitly passing a ``MetricRegistry``
+instance around.
+
+Like all Metrics classes, ``SharedMetricRegistries`` is fully thread-safe.
 
 .. _man-core-names:
 
 Metric Names
 ============
 
-Each metric has a unique *name*, which is a simple dotted name, like ``com.example.Queue.size``.
-This flexibility allows you to encode a wide variety of context directly into a metric's name. If
-you have two instances of ``com.example.Queue``, you can give them more specific:
-``com.example.Queue.requests.size`` vs. ``com.example.Queue.responses.size``, for example.
+Each metric is associated with a ``MetricRegistry``, and has a unique *name* within that registry. This is a simple
+dotted name, like ``com.example.Queue.size``. This flexibility allows you to encode a wide variety of
+context directly into a metric's name. If you have two instances of ``com.example.Queue``, you can give
+them more specific: ``com.example.Queue.requests.size`` vs. ``com.example.Queue.responses.size``, for example.
 
 ``MetricRegistry`` has a set of static helper methods for easily creating names:
 
@@ -71,7 +77,7 @@ return the number of evictions from the cache.
 JMX Gauges
 ----------
 
-Given that many third-party library often expose metrics only via JMX, Metrics provides the
+Given that many third-party libraries often expose metrics only via JMX, Metrics provides the
 ``JmxAttributeGauge`` class, which takes the object name of a JMX MBean and the name of an attribute
 and produces a gauge implementation which returns the value of that attribute:
 
@@ -112,7 +118,10 @@ This gauge returns the ratio of cache hits to misses using a meter and a timer.
 Cached Gauges
 -------------
 
-A cached gauge allows for a more efficient reporting of values which are expensive to calculate:
+A cached gauge allows for a more efficient reporting of values which are expensive
+to calculate. The value is cached for the period specified in the constructor. The "getValue()"
+method called by the client only returns the cached value. The protected "loadValue()" method
+is only called internally to reload the cache value.
 
 .. code-block:: java
 
@@ -245,6 +254,18 @@ representative of the past ``N`` seconds (or other time period).
     high-frequency process can require a significant amount of memory. Because it records every
     measurement, it's also the slowest reservoir type.
 
+.. hint::
+
+    Try to use our new optimised version of ``SlidingTimeWindowReservoir`` called ``SlidingTimeWindowArrayReservoir``.
+    It brings much lower memory overhead. Also it's allocation/free patterns are different,
+    so GC overhead is 60x-80x lower then ``SlidingTimeWindowReservoir``. Now ``SlidingTimeWindowArrayReservoir`` is
+    comparable with ``ExponentiallyDecayingReservoir`` in terms GC overhead and performance.
+    As for required memory, ``SlidingTimeWindowArrayReservoir`` takes ~128 bits per stored measurement and you can simply
+    calculate required amount of heap.
+
+    Example: 10K measurements / sec with reservoir storing time of 1 minute will take
+    10000 * 60 * 128 / 8 = 9600000 bytes ~ 9 megabytes
+
 .. _man-core-meters:
 
 Meters
diff --git a/docs/source/manual/graphite.rst b/docs/source/manual/graphite.rst
index a1a68d7..3a94402 100644
--- a/docs/source/manual/graphite.rst
+++ b/docs/source/manual/graphite.rst
@@ -24,7 +24,7 @@ If you prefer to write metrics in batches using pickle, you can use the ``Pickle
 
 .. code-block:: java
 
-    final Graphite pickledGraphite = new PickledGraphite(new InetSocketAddress("graphite.example.com", 2004));
+    final PickledGraphite pickledGraphite = new PickledGraphite(new InetSocketAddress("graphite.example.com", 2004));
     final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry)
                                                       .prefixedWith("web1.example.com")
                                                       .convertRatesTo(TimeUnit.SECONDS)
diff --git a/docs/source/manual/log4j.rst b/docs/source/manual/log4j.rst
index 68cdcc4..5c8863c 100644
--- a/docs/source/manual/log4j.rst
+++ b/docs/source/manual/log4j.rst
@@ -33,3 +33,19 @@ For log4j 2.x:
     Configuration config = context.getConfiguration();
     config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).addAppender(appender, level, filter);
     context.updateLoggers(config);
+
+You can also use standard log4j2 configuration, via plugin support:
+
+.. code-block:: xml
+
+    <?xml version="1.0" encoding="UTF-8"?>
+    <Configuration status="INFO" name="log4j2-config" packages="com.codahale.metrics.log4j2">
+    <Appenders>
+        <MetricsAppender name="metrics" registryName="shared-metrics-registry"/>
+    </Appenders>
+    <Loggers>
+        <Root level="INFO">
+            <AppenderRef ref="metrics" />
+        </Root>
+    </Loggers>
+    </Configuration>
diff --git a/docs/source/manual/third-party.rst b/docs/source/manual/third-party.rst
index e72ebfb..0511ed1 100644
--- a/docs/source/manual/third-party.rst
+++ b/docs/source/manual/third-party.rst
@@ -7,20 +7,55 @@ Third Party Libraries
 If you're looking to integrate with something not provided by the main Metrics libraries, check out
 the many third-party libraries which extend Metrics:
 
-* `metrics-librato <https://github.com/librato/metrics-librato>`_ provides a reporter for `Librato Metrics <https://metrics.librato.com/>`_, a scalable metric collection, aggregation, monitoring, and alerting service.
-* `metrics-spring <https://github.com/ryantenney/metrics-spring>`_ provides integration with Spring
-* `sematext-metrics-reporter <https://github.com/sematext/sematext-metrics-reporter>`_ provides a reporter for `SPM <http://sematext.com/spm/index.html>`_.
-* `wicket-metrics <https://github.com/NitorCreations/wicket-metrics>`_ provides easy integration for your `Wicket <http://wicket.apache.org/>`_ application.
+Instrumented Libraries
+~~~~~~~~~~~~~~~~~~~~~~
+
+* `camel-metrics <https://github.com/InitiumIo/camel-metrics>`_ provides component for your `Apache Camel <https://camel.apache.org/>`_ route.
+* `hdrhistogram-metrics-reservoir <https://bitbucket.org/marshallpierce/hdrhistogram-metrics-reservoir>`_ provides a Histogram reservoir backed by `HdrHistogram <http://hdrhistogram.org/>`_.
+* `jersey2-metrics <https://bitbucket.org/marshallpierce/jersey2-metrics>`_ provides integration with `Jersey 2 <https://jersey.java.net/>`_.
+* `jersey-metrics-filter <https://github.com/palominolabs/jersey-metrics-filter>`_ provides integration with Jersey 1.
+* `metrics-aspectj <https://github.com/astefanutti/metrics-aspectj>`_ provides integration with `AspectJ <http://eclipse.org/aspectj/>`_.
+* `metrics-cdi <https://github.com/astefanutti/metrics-cdi>`_ provides integration with `CDI <http://www.cdi-spec.org/>`_ environments,
 * `metrics-guice <https://github.com/palominolabs/metrics-guice>`_ provides integration with `Guice <https://code.google.com/p/google-guice/>`_.
-* `metrics-scala <https://github.com/erikvanoosten/metrics-scala>`_ provides an API optimized for Scala.
+* `metrics-guice-servlet <https://github.com/palominolabs/metrics-guice-servlet>`_ provides `Guice Servlet <https://github.com/google/guice/wiki/Servlets>`_ integration with AdminServlet.
+* `metrics-okhttp <https://github.com/raskasa/metrics-okhttp>`_ provides integration with `OkHttp <http://square.github.io/okhttp>`_.
+* `metrics-play <https://github.com/kenshoo/metrics-play>`_ provides an integration with the `Play Framework <https://www.playframework.com/>`_.
+* `metrics-spring <https://github.com/ryantenney/metrics-spring>`_ provides integration with `Spring <http://spring.io/>`_.
+* `wicket-metrics <https://github.com/NitorCreations/wicket-metrics>`_ provides easy integration for your `Wicket <http://wicket.apache.org/>`_ application.
+
+Language Wrappers
+~~~~~~~~~~~~~~~~~
+
 * `metrics-clojure <https://github.com/sjl/metrics-clojure>`_ provides an API optimized for Clojure.
-* `metrics-cassandra <https://github.com/brndnmtthws/metrics-cassandra>`_ provides a reporter for `Apache Cassandra <https://cassandra.apache.org/>`_.
+* `metrics-scala <https://github.com/erikvanoosten/metrics-scala>`_ provides an API optimized for Scala.
+
+Reporters
+~~~~~~~~~
+
+* `finagle-metrics <https://github.com/rlazoti/finagle-metrics>`_ provides a reporter for a `Finagle <https://twitter.github.io/finagle/>`_ service.
+* `kafka-dropwizard-metrics <https://github.com/SimpleFinance/kafka-dropwizard-reporter>`_ allows Kafka producers, consumers, and streaming applications to register their built-in metrics with a Dropwizard Metrics registry.
 * `MetricCatcher <https://github.com/addthis/MetricCatcher>`_ Turns JSON over UDP into Metrics so that non-jvm languages can know what's going on too.
-* `metrics-reporter-config <https://github.com/addthis/metrics-reporter-config>`_ DropWizard-eqsue YAML configuration of reporters.
+* `metrics-cassandra <https://github.com/brndnmtthws/metrics-cassandra>`_ provides a reporter for `Apache Cassandra <https://cassandra.apache.org/>`_.
+* `metrics-circonus <https://github.com/circonus-labs/metrics-circonus>`_ provides a registry and reporter for sending metrics (including full histograms) to `Circonus <https://www.circonus.com/>`_.
+* `metrics-datadog <https://github.com/coursera/metrics-datadog>`_ provides a reporter to send data to `Datadog <http://www.datadoghq.com/>`_.
 * `metrics-elasticsearch-reporter <https://github.com/elasticsearch/elasticsearch-metrics-reporter-java>`_ provides a reporter for `elasticsearch <http://www.elasticsearch.org/>`_
-* `metrics-statsd <https://github.com/ReadyTalk/metrics-statsd>`_ provides a Metrics 2.x and 3.x reporter for `StatsD <https://github.com/etsy/statsd/>`_
-* `metrics-datadog <https://github.com/vistarmedia/metrics-datadog>`_ provides a reporter to send data to `Datadog <http://www.datadoghq.com/>`_
+* `metrics-hadoop-metrics2-reporter <https://github.com/joshelser/dropwizard-hadoop-metrics2>`_ provides a reporter for `Hadoop Metrics2 <https://hadoop.apache.org/docs/r2.7.2/api/org/apache/hadoop/metrics2/package-summary.html>`_.
+* `metrics-hawkular <https://github.com/hawkular/hawkular-dropwizard-reporter>`_ provides a reporter for `Hawkular Metrics <http://www.hawkular.org/>`_.
 * `metrics-influxdb <https://github.com/novaquark/metrics-influxdb>`_ provides a reporter which announces measurements to `InfluxDB <http://influxdb.org/>`_
-* `metrics-cdi <https://github.com/astefanutti/metrics-cdi>`_ provides integration with `CDI <http://www.cdi-spec.org/>`_ environments
-* `metrics-aspectj <https://github.com/astefanutti/metrics-aspectj>`_ provides integration with `AspectJ <http://eclipse.org/aspectj/>`_
-* `camel-metrics <https://github.com/InitiumIo/camel-metrics>`_ provides component for your `Apache Camel <https://camel.apache.org/>`_ route
+* `metrics-instrumental <https://github.com/egineering-llc/metrics-instrumental>`_ provides a reporter to send data to `Instrumental <http://instrumentalapp.com/>`_.
+* `metrics-kafka <https://github.com/hengyunabc/metrics-kafka>`_ provides a reporter for `Kafka <http://kafka.apache.org/>`_.
+* `metrics-librato <https://github.com/librato/metrics-librato>`_ provides a reporter for `Librato Metrics <https://metrics.librato.com/>`_, a scalable metric collection, aggregation, monitoring, and alerting service.
+* `metrics-mongodb-reporter <https://github.com/aparnachaudhary/mongodb-metrics-reporter>`_ provides a reporter for `MongoDB <https://www.mongodb.org/>`_.
+* `metrics-munin-reporter <https://github.com/slashidea/metrics-munin-reporter>`_ provides a reporter for `Munin <http://munin-monitoring.org/>`_
+* `metrics-new-relic <https://github.com/palominolabs/metrics-new-relic>`_ provides a reporter which sends data to `New Relic <http://newrelic.com/>`_.
+* `metrics-reporter-config <https://github.com/addthis/metrics-reporter-config>`_ DropWizard-esque YAML configuration of reporters.
+* `metrics-signalfx <https://github.com/signalfx/signalfx-java>`_ provides a reporter to send data to `SignalFx <http://www.signalfx.com/>`_.
+* `metrics-spark-reporter <https://github.com/ippontech/metrics-spark-reporter>`_ provides a reporter for `Apache Spark Streaming <https://spark.apache.org/streaming/>`_.
+* `metrics-splunk <https://github.com/zenmoto/metrics-splunk>`_ provides a reporter for `Splunk <http://www.splunk.com/>`_.
+* `metrics-statsd <https://github.com/ReadyTalk/metrics-statsd>`_ provides a Metrics 2.x and 3.x reporter for `StatsD <https://github.com/etsy/statsd/>`_
+* `metrics-zabbiz <https://github.com/hengyunabc/metrics-zabbix>`_ provides a reporter for `Zabbix <http://www.zabbix.com/>`_.
+* `sematext-metrics-reporter <https://github.com/sematext/sematext-metrics-reporter>`_ provides a reporter for `SPM <http://sematext.com/spm/index.html>`_.
+
+Advansed metrics implementations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* `rolling-metrics <https://github.com/vladimir-bukhtoyarov/rolling-metrics>`_ provides a collection of advanced metrics with rolling time window semantic, such as Rolling-Counter, Hit-Ratio, Top and Reservoir backed by HdrHistogram.
diff --git a/metrics-annotation/pom.xml b/metrics-annotation/pom.xml
index 7ad0c43..4b093b3 100644
--- a/metrics-annotation/pom.xml
+++ b/metrics-annotation/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-annotation</artifactId>
diff --git a/metrics-benchmarks/pom.xml b/metrics-benchmarks/pom.xml
index 045649d..6441948 100644
--- a/metrics-benchmarks/pom.xml
+++ b/metrics-benchmarks/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-benchmarks</artifactId>
diff --git a/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/ReservoirBenchmark.java b/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/ReservoirBenchmark.java
index 5d47355..5d2e788 100644
--- a/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/ReservoirBenchmark.java
+++ b/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/ReservoirBenchmark.java
@@ -1,18 +1,20 @@
 package com.codahale.metrics.benchmarks;
 
 import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.SlidingTimeWindowArrayReservoir;
 import com.codahale.metrics.SlidingTimeWindowReservoir;
 import com.codahale.metrics.SlidingWindowReservoir;
 import com.codahale.metrics.UniformReservoir;
-
 import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
-
+import org.openjdk.jmh.profile.GCProfiler;
 import org.openjdk.jmh.runner.Runner;
 import org.openjdk.jmh.runner.RunnerException;
 import org.openjdk.jmh.runner.options.Options;
 import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
 
 import java.util.concurrent.TimeUnit;
 
@@ -22,7 +24,8 @@ public class ReservoirBenchmark {
     private final UniformReservoir uniform = new UniformReservoir();
     private final ExponentiallyDecayingReservoir exponential = new ExponentiallyDecayingReservoir();
     private final SlidingWindowReservoir sliding = new SlidingWindowReservoir(1000);
-    private final SlidingTimeWindowReservoir slidingTime = new SlidingTimeWindowReservoir(1, TimeUnit.SECONDS);
+    private final SlidingTimeWindowReservoir slidingTime = new SlidingTimeWindowReservoir(200, TimeUnit.MILLISECONDS);
+    private final SlidingTimeWindowArrayReservoir arrTime = new SlidingTimeWindowArrayReservoir(200, TimeUnit.MILLISECONDS);
 
     // It's intentionally not declared as final to avoid constant folding
     private long nextValue = 0xFBFBABBA;
@@ -34,17 +37,23 @@ public class ReservoirBenchmark {
     }
 
     @Benchmark
+    public Object perfSlidingTimeWindowArrayReservoir() {
+        arrTime.update(nextValue);
+        return arrTime;
+    }
+
+    @Benchmark
     public Object perfExponentiallyDecayingReservoir() {
         exponential.update(nextValue);
         return exponential;
     }
-    
+
     @Benchmark
     public Object perfSlidingWindowReservoir() {
         sliding.update(nextValue);
         return sliding;
     }
-    
+
     @Benchmark
     public Object perfSlidingTimeWindowReservoir() {
         slidingTime.update(nextValue);
@@ -53,14 +62,18 @@ public class ReservoirBenchmark {
 
     public static void main(String[] args) throws RunnerException {
         Options opt = new OptionsBuilder()
-                .include(".*" + ReservoirBenchmark.class.getSimpleName() + ".*")
-                .warmupIterations(3)
-                .measurementIterations(5)
-                .threads(4)
-                .forks(1)
-                .build();
+            .include(".*" + ReservoirBenchmark.class.getSimpleName() + ".*")
+            .warmupIterations(10)
+            .measurementIterations(10)
+            .addProfiler(GCProfiler.class)
+            .measurementTime(TimeValue.seconds(3))
+            .timeUnit(TimeUnit.MICROSECONDS)
+            .mode(Mode.AverageTime)
+            .threads(4)
+            .forks(1)
+            .build();
 
         new Runner(opt).run();
     }
-    
+
 }
diff --git a/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/SlidingTimeWindowReservoirsBenchmark.java b/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/SlidingTimeWindowReservoirsBenchmark.java
new file mode 100644
index 0000000..a21904b
--- /dev/null
+++ b/metrics-benchmarks/src/main/java/com/codahale/metrics/benchmarks/SlidingTimeWindowReservoirsBenchmark.java
@@ -0,0 +1,80 @@
+package com.codahale.metrics.benchmarks;
+
+import com.codahale.metrics.SlidingTimeWindowArrayReservoir;
+import com.codahale.metrics.SlidingTimeWindowReservoir;
+import com.codahale.metrics.Snapshot;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.profile.GCProfiler;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author bstorozhuk
+ */
+ at State(Scope.Benchmark)
+public class SlidingTimeWindowReservoirsBenchmark {
+    private final SlidingTimeWindowReservoir slidingTime = new SlidingTimeWindowReservoir(200, TimeUnit.MILLISECONDS);
+    private final SlidingTimeWindowArrayReservoir arrTime = new SlidingTimeWindowArrayReservoir(200, TimeUnit.MILLISECONDS);
+
+    // It's intentionally not declared as final to avoid constant folding
+    private long nextValue = 0xFBFBABBA;
+
+    @Benchmark
+    @Group("slidingTime")
+    @GroupThreads(3)
+    public Object slidingTimeAddMeasurement() {
+        slidingTime.update(nextValue);
+        return slidingTime;
+    }
+
+    @Benchmark
+    @Group("slidingTime")
+    @GroupThreads(1)
+    public Object slidingTimeRead() {
+        Snapshot snapshot = slidingTime.getSnapshot();
+        return snapshot;
+    }
+
+    @Benchmark
+    @Group("arrTime")
+    @GroupThreads(3)
+    public Object arrTimeAddMeasurement() {
+        arrTime.update(nextValue);
+        return slidingTime;
+    }
+
+    @Benchmark
+    @Group("arrTime")
+    @GroupThreads(1)
+    public Object arrTimeRead() {
+        Snapshot snapshot = arrTime.getSnapshot();
+        return snapshot;
+    }
+
+    public static void main(String[] args) throws RunnerException {
+        Options opt = new OptionsBuilder()
+            .include(".*" + SlidingTimeWindowReservoirsBenchmark.class.getSimpleName() + ".*")
+            .warmupIterations(10)
+            .measurementIterations(10)
+            .addProfiler(GCProfiler.class)
+            .measurementTime(TimeValue.seconds(3))
+            .timeUnit(TimeUnit.MICROSECONDS)
+            .mode(Mode.AverageTime)
+            .forks(1)
+            .build();
+
+        new Runner(opt).run();
+    }
+}
+
diff --git a/metrics-core/pom.xml b/metrics-core/pom.xml
index 59b9d87..35f63c2 100644
--- a/metrics-core/pom.xml
+++ b/metrics-core/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-core</artifactId>
diff --git a/metrics-core/src/main/java/com/codahale/metrics/ChunkedAssociativeLongArray.java b/metrics-core/src/main/java/com/codahale/metrics/ChunkedAssociativeLongArray.java
new file mode 100644
index 0000000..8caf4e0
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/ChunkedAssociativeLongArray.java
@@ -0,0 +1,310 @@
+package com.codahale.metrics;
+
+import static java.lang.System.arraycopy;
+import static java.util.Arrays.binarySearch;
+
+import java.lang.ref.SoftReference;
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+class ChunkedAssociativeLongArray {
+    private static final long[] EMPTY = new long[0];
+    private static final int DEFAULT_CHUNK_SIZE = 512;
+    private static final int MAX_CACHE_SIZE = 128;
+
+    private final int defaultChunkSize;
+    /*
+     * We use this ArrayDeque as cache to store chunks that are expired and removed from main data structure.
+     * Then instead of allocating new Chunk immediately we are trying to poll one from this deque.
+     * So if you have constant or slowly changing load ChunkedAssociativeLongArray will never
+     * throw away old chunks or allocate new ones which makes this data structure almost garbage free.
+     */
+    private final ArrayDeque<SoftReference<Chunk>> chunksCache = new ArrayDeque<SoftReference<Chunk>>();
+
+    /*
+     * Why LinkedList if we are creating fast data structure with low GC overhead?
+     *
+     * First of all LinkedList here has relatively small size countOfStoredMeasurements / DEFAULT_CHUNK_SIZE.
+     * And we are heavily rely on LinkedList implementation because:
+     * 1. Now we deleting chunks from both sides of the list  in trim(long startKey, long endKey)
+     * 2. Deleting from and inserting chunks into the middle in clear(long startKey, long endKey)
+     *
+     * LinkedList gives us O(1) complexity for all this operations and that is not the case with ArrayList.
+     */
+    private final LinkedList<Chunk> chunks = new LinkedList<Chunk>();
+
+    ChunkedAssociativeLongArray() {
+        this(DEFAULT_CHUNK_SIZE);
+    }
+
+    ChunkedAssociativeLongArray(int chunkSize) {
+        this.defaultChunkSize = chunkSize;
+    }
+
+    private Chunk allocateChunk() {
+        while (true) {
+            final SoftReference<Chunk> chunkRef = chunksCache.pollLast();
+            if (chunkRef == null) {
+                return new Chunk(defaultChunkSize);
+            }
+            final Chunk chunk = chunkRef.get();
+            if (chunk != null) {
+                chunk.cursor = 0;
+                chunk.startIndex = 0;
+                chunk.chunkSize = chunk.keys.length;
+                return chunk;
+            }
+        }
+    }
+
+    private void freeChunk(Chunk chunk) {
+        if (chunksCache.size() < MAX_CACHE_SIZE) {
+            chunksCache.add(new SoftReference<Chunk>(chunk));
+        }
+    }
+
+    synchronized boolean put(long key, long value) {
+        Chunk activeChunk = chunks.peekLast();
+
+        if (activeChunk == null) { // lazy chunk creation
+            activeChunk = allocateChunk();
+            chunks.add(activeChunk);
+
+        } else {
+            if (activeChunk.cursor != 0 && activeChunk.keys[activeChunk.cursor - 1] > key) {
+                return false; // key should be the same as last inserted or bigger
+            }
+            boolean isFull = activeChunk.cursor - activeChunk.startIndex == activeChunk.chunkSize;
+            if (isFull) {
+                activeChunk = allocateChunk();
+                chunks.add(activeChunk);
+            }
+        }
+
+        activeChunk.append(key, value);
+        return true;
+    }
+
+    synchronized long[] values() {
+        int valuesSize = size();
+        if (valuesSize == 0) {
+            return EMPTY;
+        }
+
+        long[] values = new long[valuesSize];
+        int valuesIndex = 0;
+        for (Chunk copySourceChunk : chunks) {
+            int length = copySourceChunk.cursor - copySourceChunk.startIndex;
+            int itemsToCopy = Math.min(valuesSize - valuesIndex, length);
+            arraycopy(copySourceChunk.values, copySourceChunk.startIndex, values, valuesIndex, itemsToCopy);
+            valuesIndex += length;
+        }
+        return values;
+    }
+
+    synchronized int size() {
+        int result = 0;
+        for (Chunk chunk : chunks) {
+            result += chunk.cursor - chunk.startIndex;
+        }
+        return result;
+    }
+
+    synchronized String out() {
+        Iterator<Chunk> fromTailIterator = chunks.iterator();
+        StringBuilder builder = new StringBuilder();
+        while (fromTailIterator.hasNext()) {
+            Chunk copySourceChunk = fromTailIterator.next();
+            builder.append('[');
+            for (int i = copySourceChunk.startIndex; i < copySourceChunk.cursor; i++) {
+                long key = copySourceChunk.keys[i];
+                long value = copySourceChunk.values[i];
+                builder.append('(').append(key).append(": ").append(value).append(')').append(' ');
+            }
+            builder.append(']');
+            if (fromTailIterator.hasNext()) {
+                builder.append("->");
+            }
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Try to trim all beyond specified boundaries.
+     * All items that are less then startKey or greater/equals then endKey
+     *
+     * @param startKey
+     * @param endKey
+     */
+    synchronized void trim(long startKey, long endKey) {
+        /*
+         * [3, 4, 5, 9] -> [10, 13, 14, 15] -> [21, 24, 29, 30] -> [31] :: start layout
+         *       |5______________________________23|                    :: trim(5, 23)
+         *       [5, 9] -> [10, 13, 14, 15] -> [21]                     :: result layout
+         */
+        ListIterator<Chunk> fromHeadIterator = chunks.listIterator(chunks.size());
+        while (fromHeadIterator.hasPrevious()) {
+            Chunk currentHead = fromHeadIterator.previous();
+            if (isFirstElementIsEmptyOrGreaterEqualThanKey(currentHead, endKey)) {
+                freeChunk(currentHead);
+                fromHeadIterator.remove();
+            } else {
+                int newEndIndex = findFirstIndexOfGreaterEqualElements(
+                    currentHead.keys, currentHead.startIndex, currentHead.cursor, endKey
+                );
+                currentHead.cursor = newEndIndex;
+                break;
+            }
+        }
+
+        ListIterator<Chunk> fromTailIterator = chunks.listIterator();
+        while (fromTailIterator.hasNext()) {
+            Chunk currentTail = fromTailIterator.next();
+            if (isLastElementIsLessThanKey(currentTail, startKey)) {
+                freeChunk(currentTail);
+                fromTailIterator.remove();
+            } else {
+                int newStartIndex = findFirstIndexOfGreaterEqualElements(
+                    currentTail.keys, currentTail.startIndex, currentTail.cursor, startKey
+                );
+                if (currentTail.startIndex != newStartIndex) {
+                    currentTail.startIndex = newStartIndex;
+                    currentTail.chunkSize = currentTail.cursor - currentTail.startIndex;
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * Clear all in specified boundaries.
+     * Remove all items between startKey(inclusive) and endKey(exclusive)
+     *
+     * @param startKey
+     * @param endKey
+     */
+    synchronized void clear(long startKey, long endKey) {
+        /*
+         * [3, 4, 5, 9] -> [10, 13, 14, 15] -> [21, 24, 29, 30] -> [31] :: start layout
+         *       |5______________________________23|                    :: clear(5, 23)
+         * [3, 4]               ->                 [24, 29, 30] -> [31] :: result layout
+         */
+        ListIterator<Chunk> fromHeadIterator = chunks.listIterator(chunks.size());
+        while (fromHeadIterator.hasPrevious()) {
+            Chunk currentTail = fromHeadIterator.previous();
+            if (!isFirstElementIsEmptyOrGreaterEqualThanKey(currentTail, endKey)) {
+                Chunk afterTailChunk = splitChunkOnTwoSeparateChunks(currentTail, endKey);
+                if (afterTailChunk != null) {
+                    fromHeadIterator.add(afterTailChunk);
+                    break;
+                }
+            }
+        }
+
+        // now we should remove specified gap [startKey, endKey]
+        while (fromHeadIterator.hasPrevious()) {
+            Chunk afterGapHead = fromHeadIterator.previous();
+            if (isFirstElementIsEmptyOrGreaterEqualThanKey(afterGapHead, startKey)) {
+                freeChunk(afterGapHead);
+                fromHeadIterator.remove();
+            } else {
+                int newEndIndex = findFirstIndexOfGreaterEqualElements(
+                    afterGapHead.keys, afterGapHead.startIndex, afterGapHead.cursor, startKey
+                );
+                if (newEndIndex == afterGapHead.startIndex) {
+                    break;
+                }
+                if (afterGapHead.cursor != newEndIndex) {
+                    afterGapHead.cursor = newEndIndex;
+                    afterGapHead.chunkSize = afterGapHead.cursor - afterGapHead.startIndex;
+                    break;
+                }
+            }
+        }
+    }
+
+    synchronized void clear() {
+        chunks.clear();
+    }
+
+    private Chunk splitChunkOnTwoSeparateChunks(Chunk chunk, long key) {
+        /*
+         * [1, 2, 3, 4, 5, 6, 7, 8] :: beforeSplit
+         * |s--------chunk-------e|
+         *
+         *  splitChunkOnTwoSeparateChunks(chunk, 5)
+         *
+         * [1, 2, 3, 4, 5, 6, 7, 8] :: afterSplit
+         * |s--tail--e||s--head--e|
+         */
+        int splitIndex = findFirstIndexOfGreaterEqualElements(
+            chunk.keys, chunk.startIndex, chunk.cursor, key
+        );
+        if (splitIndex == chunk.startIndex || splitIndex == chunk.cursor) {
+            return null;
+        }
+        int newTailSize = splitIndex - chunk.startIndex;
+        Chunk newTail = new Chunk(chunk.keys, chunk.values, chunk.startIndex, splitIndex, newTailSize);
+        chunk.startIndex = splitIndex;
+        chunk.chunkSize = chunk.chunkSize - newTailSize;
+        return newTail;
+    }
+
+    private boolean isFirstElementIsEmptyOrGreaterEqualThanKey(Chunk chunk, long key) {
+        return chunk.cursor == chunk.startIndex
+            || chunk.keys[chunk.startIndex] >= key;
+    }
+
+    private boolean isLastElementIsLessThanKey(Chunk chunk, long key) {
+        return chunk.cursor == chunk.startIndex
+            || chunk.keys[chunk.cursor - 1] < key;
+    }
+
+
+    private int findFirstIndexOfGreaterEqualElements(long[] array, int startIndex, int endIndex, long minKey) {
+        if (endIndex == startIndex || array[startIndex] >= minKey) {
+            return startIndex;
+        }
+        int searchIndex = binarySearch(array, startIndex, endIndex, minKey);
+        int realIndex;
+        if (searchIndex < 0) {
+            realIndex = -(searchIndex + 1);
+        } else {
+            realIndex = searchIndex;
+        }
+        return realIndex;
+    }
+
+    private static class Chunk {
+
+        private final long[] keys;
+        private final long[] values;
+
+        private int chunkSize; // can differ from keys.length after half clear()
+        private int startIndex = 0;
+        private int cursor = 0;
+
+        private Chunk(int chunkSize) {
+            this.chunkSize = chunkSize;
+            this.keys = new long[chunkSize];
+            this.values = new long[chunkSize];
+        }
+
+        private Chunk(final long[] keys, final long[] values,
+                      final int startIndex, final int cursor, final int chunkSize) {
+            this.keys = keys;
+            this.values = values;
+            this.startIndex = startIndex;
+            this.cursor = cursor;
+            this.chunkSize = chunkSize;
+        }
+
+        private void append(long key, long value) {
+            keys[cursor] = key;
+            values[cursor] = value;
+            cursor++;
+        }
+    }
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/ConsoleReporter.java b/metrics-core/src/main/java/com/codahale/metrics/ConsoleReporter.java
index 5274569..e61095c 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/ConsoleReporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/ConsoleReporter.java
@@ -3,6 +3,7 @@ package com.codahale.metrics;
 import java.io.PrintStream;
 import java.text.DateFormat;
 import java.util.*;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -33,6 +34,9 @@ public class ConsoleReporter extends ScheduledReporter {
         private TimeUnit rateUnit;
         private TimeUnit durationUnit;
         private MetricFilter filter;
+        private ScheduledExecutorService executor;
+        private boolean shutdownExecutorOnStop;
+        private Set<MetricAttribute> disabledMetricAttributes;
 
         private Builder(MetricRegistry registry) {
             this.registry = registry;
@@ -43,6 +47,35 @@ public class ConsoleReporter extends ScheduledReporter {
             this.rateUnit = TimeUnit.SECONDS;
             this.durationUnit = TimeUnit.MILLISECONDS;
             this.filter = MetricFilter.ALL;
+            this.executor = null;
+            this.shutdownExecutorOnStop = true;
+            disabledMetricAttributes = Collections.emptySet();
+        }
+
+        /**
+         * Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
+         * Default value is true.
+         * Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.
+         *
+         * @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
+         * @return {@code this}
+         */
+        public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
+            this.shutdownExecutorOnStop = shutdownExecutorOnStop;
+            return this;
+        }
+
+        /**
+         * Specifies the executor to use while scheduling reporting of metrics.
+         * Default value is null.
+         * Null value leads to executor will be auto created on start.
+         *
+         * @param executor the executor to use while scheduling reporting of metrics.
+         * @return {@code this}
+         */
+        public Builder scheduleOn(ScheduledExecutorService executor) {
+            this.executor = executor;
+            return this;
         }
 
         /**
@@ -123,6 +156,18 @@ public class ConsoleReporter extends ScheduledReporter {
         }
 
         /**
+         * Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15").
+         * See {@link MetricAttribute}.
+         *
+         * @param disabledMetricAttributes a {@link MetricFilter}
+         * @return {@code this}
+         */
+        public Builder disabledMetricAttributes(Set<MetricAttribute> disabledMetricAttributes) {
+            this.disabledMetricAttributes = disabledMetricAttributes;
+            return this;
+        }
+
+        /**
          * Builds a {@link ConsoleReporter} with the given properties.
          *
          * @return a {@link ConsoleReporter}
@@ -135,7 +180,10 @@ public class ConsoleReporter extends ScheduledReporter {
                                        timeZone,
                                        rateUnit,
                                        durationUnit,
-                                       filter);
+                                       filter,
+                                       executor,
+                                       shutdownExecutorOnStop,
+                                       disabledMetricAttributes);
         }
     }
 
@@ -153,8 +201,11 @@ public class ConsoleReporter extends ScheduledReporter {
                             TimeZone timeZone,
                             TimeUnit rateUnit,
                             TimeUnit durationUnit,
-                            MetricFilter filter) {
-        super(registry, "console-reporter", filter, rateUnit, durationUnit);
+                            MetricFilter filter,
+                            ScheduledExecutorService executor,
+                            boolean shutdownExecutorOnStop,
+                            Set<MetricAttribute> disabledMetricAttributes) {
+        super(registry, "console-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop, disabledMetricAttributes);
         this.output = output;
         this.locale = locale;
         this.clock = clock;
@@ -224,11 +275,11 @@ public class ConsoleReporter extends ScheduledReporter {
     }
 
     private void printMeter(Meter meter) {
-        output.printf(locale, "             count = %d%n", meter.getCount());
-        output.printf(locale, "         mean rate = %2.2f events/%s%n", convertRate(meter.getMeanRate()), getRateUnit());
-        output.printf(locale, "     1-minute rate = %2.2f events/%s%n", convertRate(meter.getOneMinuteRate()), getRateUnit());
-        output.printf(locale, "     5-minute rate = %2.2f events/%s%n", convertRate(meter.getFiveMinuteRate()), getRateUnit());
-        output.printf(locale, "    15-minute rate = %2.2f events/%s%n", convertRate(meter.getFifteenMinuteRate()), getRateUnit());
+        printIfEnabled(MetricAttribute.COUNT, String.format(locale, "             count = %d", meter.getCount()));
+        printIfEnabled(MetricAttribute.MEAN_RATE, String.format(locale, "         mean rate = %2.2f events/%s", convertRate(meter.getMeanRate()), getRateUnit()));
+        printIfEnabled(MetricAttribute.M1_RATE, String.format(locale, "     1-minute rate = %2.2f events/%s", convertRate(meter.getOneMinuteRate()), getRateUnit()));
+        printIfEnabled(MetricAttribute.M5_RATE, String.format(locale, "     5-minute rate = %2.2f events/%s", convertRate(meter.getFiveMinuteRate()), getRateUnit()));
+        printIfEnabled(MetricAttribute.M15_RATE, String.format(locale, "    15-minute rate = %2.2f events/%s", convertRate(meter.getFifteenMinuteRate()), getRateUnit()));
     }
 
     private void printCounter(Map.Entry<String, Counter> entry) {
@@ -240,38 +291,38 @@ public class ConsoleReporter extends ScheduledReporter {
     }
 
     private void printHistogram(Histogram histogram) {
-        output.printf(locale, "             count = %d%n", histogram.getCount());
+        printIfEnabled(MetricAttribute.COUNT, String.format(locale, "             count = %d", histogram.getCount()));
         Snapshot snapshot = histogram.getSnapshot();
-        output.printf(locale, "               min = %d%n", snapshot.getMin());
-        output.printf(locale, "               max = %d%n", snapshot.getMax());
-        output.printf(locale, "              mean = %2.2f%n", snapshot.getMean());
-        output.printf(locale, "            stddev = %2.2f%n", snapshot.getStdDev());
-        output.printf(locale, "            median = %2.2f%n", snapshot.getMedian());
-        output.printf(locale, "              75%% <= %2.2f%n", snapshot.get75thPercentile());
-        output.printf(locale, "              95%% <= %2.2f%n", snapshot.get95thPercentile());
-        output.printf(locale, "              98%% <= %2.2f%n", snapshot.get98thPercentile());
-        output.printf(locale, "              99%% <= %2.2f%n", snapshot.get99thPercentile());
-        output.printf(locale, "            99.9%% <= %2.2f%n", snapshot.get999thPercentile());
+        printIfEnabled(MetricAttribute.MIN, String.format(locale, "               min = %d", snapshot.getMin()));
+        printIfEnabled(MetricAttribute.MAX, String.format(locale, "               max = %d", snapshot.getMax()));
+        printIfEnabled(MetricAttribute.MEAN, String.format(locale, "              mean = %2.2f", snapshot.getMean()));
+        printIfEnabled(MetricAttribute.STDDEV, String.format(locale, "            stddev = %2.2f", snapshot.getStdDev()));
+        printIfEnabled(MetricAttribute.P50, String.format(locale, "            median = %2.2f", snapshot.getMedian()));
+        printIfEnabled(MetricAttribute.P75, String.format(locale, "              75%% <= %2.2f", snapshot.get75thPercentile()));
+        printIfEnabled(MetricAttribute.P95, String.format(locale, "              95%% <= %2.2f", snapshot.get95thPercentile()));
+        printIfEnabled(MetricAttribute.P98, String.format(locale, "              98%% <= %2.2f", snapshot.get98thPercentile()));
+        printIfEnabled(MetricAttribute.P99, String.format(locale, "              99%% <= %2.2f", snapshot.get99thPercentile()));
+        printIfEnabled(MetricAttribute.P999, String.format(locale, "            99.9%% <= %2.2f", snapshot.get999thPercentile()));
     }
 
     private void printTimer(Timer timer) {
         final Snapshot snapshot = timer.getSnapshot();
-        output.printf(locale, "             count = %d%n", timer.getCount());
-        output.printf(locale, "         mean rate = %2.2f calls/%s%n", convertRate(timer.getMeanRate()), getRateUnit());
-        output.printf(locale, "     1-minute rate = %2.2f calls/%s%n", convertRate(timer.getOneMinuteRate()), getRateUnit());
-        output.printf(locale, "     5-minute rate = %2.2f calls/%s%n", convertRate(timer.getFiveMinuteRate()), getRateUnit());
-        output.printf(locale, "    15-minute rate = %2.2f calls/%s%n", convertRate(timer.getFifteenMinuteRate()), getRateUnit());
-
-        output.printf(locale, "               min = %2.2f %s%n", convertDuration(snapshot.getMin()), getDurationUnit());
-        output.printf(locale, "               max = %2.2f %s%n", convertDuration(snapshot.getMax()), getDurationUnit());
-        output.printf(locale, "              mean = %2.2f %s%n", convertDuration(snapshot.getMean()), getDurationUnit());
-        output.printf(locale, "            stddev = %2.2f %s%n", convertDuration(snapshot.getStdDev()), getDurationUnit());
-        output.printf(locale, "            median = %2.2f %s%n", convertDuration(snapshot.getMedian()), getDurationUnit());
-        output.printf(locale, "              75%% <= %2.2f %s%n", convertDuration(snapshot.get75thPercentile()), getDurationUnit());
-        output.printf(locale, "              95%% <= %2.2f %s%n", convertDuration(snapshot.get95thPercentile()), getDurationUnit());
-        output.printf(locale, "              98%% <= %2.2f %s%n", convertDuration(snapshot.get98thPercentile()), getDurationUnit());
-        output.printf(locale, "              99%% <= %2.2f %s%n", convertDuration(snapshot.get99thPercentile()), getDurationUnit());
-        output.printf(locale, "            99.9%% <= %2.2f %s%n", convertDuration(snapshot.get999thPercentile()), getDurationUnit());
+        printIfEnabled(MetricAttribute.COUNT, String.format(locale, "             count = %d", timer.getCount()));
+        printIfEnabled(MetricAttribute.MEAN_RATE, String.format(locale, "         mean rate = %2.2f calls/%s", convertRate(timer.getMeanRate()), getRateUnit()));
+        printIfEnabled(MetricAttribute.M1_RATE, String.format(locale, "     1-minute rate = %2.2f calls/%s", convertRate(timer.getOneMinuteRate()), getRateUnit()));
+        printIfEnabled(MetricAttribute.M5_RATE, String.format(locale, "     5-minute rate = %2.2f calls/%s", convertRate(timer.getFiveMinuteRate()), getRateUnit()));
+        printIfEnabled(MetricAttribute.M15_RATE, String.format(locale, "    15-minute rate = %2.2f calls/%s", convertRate(timer.getFifteenMinuteRate()), getRateUnit()));
+
+        printIfEnabled(MetricAttribute.MIN, String.format(locale, "               min = %2.2f %s", convertDuration(snapshot.getMin()), getDurationUnit()));
+        printIfEnabled(MetricAttribute.MAX, String.format(locale, "               max = %2.2f %s", convertDuration(snapshot.getMax()), getDurationUnit()));
+        printIfEnabled(MetricAttribute.MEAN, String.format(locale, "              mean = %2.2f %s", convertDuration(snapshot.getMean()), getDurationUnit()));
+        printIfEnabled(MetricAttribute.STDDEV, String.format(locale, "            stddev = %2.2f %s", convertDuration(snapshot.getStdDev()), getDurationUnit()));
+        printIfEnabled(MetricAttribute.P50, String.format(locale, "            median = %2.2f %s", convertDuration(snapshot.getMedian()), getDurationUnit()));
+        printIfEnabled(MetricAttribute.P75, String.format(locale, "              75%% <= %2.2f %s", convertDuration(snapshot.get75thPercentile()), getDurationUnit()));
+        printIfEnabled(MetricAttribute.P95, String.format(locale, "              95%% <= %2.2f %s", convertDuration(snapshot.get95thPercentile()), getDurationUnit()));
+        printIfEnabled(MetricAttribute.P98, String.format(locale, "              98%% <= %2.2f %s", convertDuration(snapshot.get98thPercentile()), getDurationUnit()));
+        printIfEnabled(MetricAttribute.P99, String.format(locale, "              99%% <= %2.2f %s", convertDuration(snapshot.get99thPercentile()), getDurationUnit()));
+        printIfEnabled(MetricAttribute.P999, String.format(locale, "            99.9%% <= %2.2f %s", convertDuration(snapshot.get999thPercentile()), getDurationUnit()));
     }
 
     private void printWithBanner(String s, char c) {
@@ -282,4 +333,17 @@ public class ConsoleReporter extends ScheduledReporter {
         }
         output.println();
     }
+
+    /**
+     * Print only if the attribute is enabled
+     * @param type Metric attribute
+     * @param status Status to be logged
+     */
+    private void printIfEnabled(MetricAttribute type, String status) {
+        if(getDisabledMetricAttributes().contains(type)) {
+            return;
+        }
+
+        output.println(status);
+    }
 }
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Counter.java b/metrics-core/src/main/java/com/codahale/metrics/Counter.java
index 632d127..9aa6ba7 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Counter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Counter.java
@@ -4,10 +4,10 @@ package com.codahale.metrics;
  * An incrementing and decrementing counter metric.
  */
 public class Counter implements Metric, Counting {
-    private final LongAdder count;
+    private final LongAdderAdapter count;
 
     public Counter() {
-        this.count = new LongAdder();
+        this.count = LongAdderProxy.create();
     }
 
     /**
diff --git a/metrics-core/src/main/java/com/codahale/metrics/CsvFileProvider.java b/metrics-core/src/main/java/com/codahale/metrics/CsvFileProvider.java
new file mode 100644
index 0000000..3797030
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/CsvFileProvider.java
@@ -0,0 +1,12 @@
+package com.codahale.metrics;
+
+import java.io.File;
+
+/**
+ * This interface allows a pluggable implementation of what file names
+ * the {@link CsvReporter} will write to.
+ */
+public interface CsvFileProvider {
+
+    File getFile(File directory, String metricName);
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/CsvReporter.java b/metrics-core/src/main/java/com/codahale/metrics/CsvReporter.java
index 4319399..e3e6bc1 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/CsvReporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/CsvReporter.java
@@ -8,6 +8,7 @@ import java.nio.charset.Charset;
 import java.util.Locale;
 import java.util.Map;
 import java.util.SortedMap;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -35,6 +36,9 @@ public class CsvReporter extends ScheduledReporter {
         private TimeUnit durationUnit;
         private Clock clock;
         private MetricFilter filter;
+        private ScheduledExecutorService executor;
+        private boolean shutdownExecutorOnStop;
+        private CsvFileProvider csvFileProvider;
 
         private Builder(MetricRegistry registry) {
             this.registry = registry;
@@ -43,6 +47,35 @@ public class CsvReporter extends ScheduledReporter {
             this.durationUnit = TimeUnit.MILLISECONDS;
             this.clock = Clock.defaultClock();
             this.filter = MetricFilter.ALL;
+            this.executor = null;
+            this.shutdownExecutorOnStop = true;
+            this.csvFileProvider = new FixedNameCsvFileProvider();
+        }
+
+        /**
+         * Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
+         * Default value is true.
+         * Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.
+         *
+         * @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
+         * @return {@code this}
+         */
+        public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
+            this.shutdownExecutorOnStop = shutdownExecutorOnStop;
+            return this;
+        }
+
+        /**
+         * Specifies the executor to use while scheduling reporting of metrics.
+         * Default value is null.
+         * Null value leads to executor will be auto created on start.
+         *
+         * @param executor the executor to use while scheduling reporting of metrics.
+         * @return {@code this}
+         */
+        public Builder scheduleOn(ScheduledExecutorService executor) {
+            this.executor = executor;
+            return this;
         }
 
         /**
@@ -100,6 +133,11 @@ public class CsvReporter extends ScheduledReporter {
             return this;
         }
 
+        public Builder withCsvFileProvider(CsvFileProvider csvFileProvider) {
+            this.csvFileProvider = csvFileProvider;
+            return this;
+        }
+
         /**
          * Builds a {@link CsvReporter} with the given properties, writing {@code .csv} files to the
          * given directory.
@@ -114,7 +152,10 @@ public class CsvReporter extends ScheduledReporter {
                                    rateUnit,
                                    durationUnit,
                                    clock,
-                                   filter);
+                                   filter,
+                                   executor,
+                                   shutdownExecutorOnStop,
+                                   csvFileProvider);
         }
     }
 
@@ -124,6 +165,7 @@ public class CsvReporter extends ScheduledReporter {
     private final File directory;
     private final Locale locale;
     private final Clock clock;
+    private final CsvFileProvider csvFileProvider;
 
     private CsvReporter(MetricRegistry registry,
                         File directory,
@@ -131,11 +173,15 @@ public class CsvReporter extends ScheduledReporter {
                         TimeUnit rateUnit,
                         TimeUnit durationUnit,
                         Clock clock,
-                        MetricFilter filter) {
-        super(registry, "csv-reporter", filter, rateUnit, durationUnit);
+                        MetricFilter filter,
+                        ScheduledExecutorService executor,
+                        boolean shutdownExecutorOnStop,
+                        CsvFileProvider csvFileProvider) {
+        super(registry, "csv-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop);
         this.directory = directory;
         this.locale = locale;
         this.clock = clock;
+        this.csvFileProvider = csvFileProvider;
     }
 
     @Override
@@ -236,7 +282,7 @@ public class CsvReporter extends ScheduledReporter {
 
     private void report(long timestamp, String name, String header, String line, Object... values) {
         try {
-            final File file = new File(directory, sanitize(name) + ".csv");
+            final File file = csvFileProvider.getFile(directory, name);
             final boolean fileAlreadyExists = file.exists();
             if (fileAlreadyExists || file.createNewFile()) {
                 final PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file,true), UTF_8));
diff --git a/metrics-core/src/main/java/com/codahale/metrics/EWMA.java b/metrics-core/src/main/java/com/codahale/metrics/EWMA.java
index 0e82fe4..7738180 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/EWMA.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/EWMA.java
@@ -26,7 +26,7 @@ public class EWMA {
     private volatile boolean initialized = false;
     private volatile double rate = 0.0;
 
-    private final LongAdder uncounted = new LongAdder();
+    private final LongAdderAdapter uncounted = LongAdderProxy.create();
     private final double alpha, interval;
 
     /**
diff --git a/metrics-core/src/main/java/com/codahale/metrics/ExponentiallyDecayingReservoir.java b/metrics-core/src/main/java/com/codahale/metrics/ExponentiallyDecayingReservoir.java
index 7ff16de..4a539f7 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/ExponentiallyDecayingReservoir.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/ExponentiallyDecayingReservoir.java
@@ -95,7 +95,7 @@ public class ExponentiallyDecayingReservoir implements Reservoir {
         try {
             final double itemWeight = weight(timestamp - startTime);
             final WeightedSample sample = new WeightedSample(value, itemWeight);
-            final double priority = itemWeight / ThreadLocalRandom.current().nextDouble();
+            final double priority = itemWeight / ThreadLocalRandomProxy.current().nextDouble();
             
             final long newCount = count.incrementAndGet();
             if (newCount <= size) {
@@ -124,6 +124,7 @@ public class ExponentiallyDecayingReservoir implements Reservoir {
 
     @Override
     public Snapshot getSnapshot() {
+        rescaleIfNeeded();
         lockForRegularUsage();
         try {
             return new WeightedSnapshot(values.values());
@@ -159,25 +160,28 @@ public class ExponentiallyDecayingReservoir implements Reservoir {
      * a linear pass over whatever data structure is being used."
      */
     private void rescale(long now, long next) {
-        if (nextScaleTime.compareAndSet(next, now + RESCALE_THRESHOLD)) {
-            lockForRescale();
-            try {
+        lockForRescale();
+        try {
+            if (nextScaleTime.compareAndSet(next, now + RESCALE_THRESHOLD)) {
                 final long oldStartTime = startTime;
                 this.startTime = currentTimeInSeconds();
                 final double scalingFactor = exp(-alpha * (startTime - oldStartTime));
-
-                final ArrayList<Double> keys = new ArrayList<Double>(values.keySet());
-                for (Double key : keys) {
-                    final WeightedSample sample = values.remove(key);
-                    final WeightedSample newSample = new WeightedSample(sample.value, sample.weight * scalingFactor);
-                    values.put(key * scalingFactor, newSample);
+                if (Double.compare(scalingFactor, 0) == 0) {
+                    values.clear();
+                } else {
+                    final ArrayList<Double> keys = new ArrayList<Double>(values.keySet());
+                    for (Double key : keys) {
+                        final WeightedSample sample = values.remove(key);
+                        final WeightedSample newSample = new WeightedSample(sample.value, sample.weight * scalingFactor);
+                        values.put(key * scalingFactor, newSample);
+                    }
                 }
 
                 // make sure the counter is in sync with the number of stored samples.
                 count.set(values.size());
-            } finally {
-                unlockForRescale();
             }
+        } finally {
+            unlockForRescale();
         }
     }
 
diff --git a/metrics-core/src/main/java/com/codahale/metrics/FixedNameCsvFileProvider.java b/metrics-core/src/main/java/com/codahale/metrics/FixedNameCsvFileProvider.java
new file mode 100644
index 0000000..eaa8538
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/FixedNameCsvFileProvider.java
@@ -0,0 +1,22 @@
+package com.codahale.metrics;
+
+import java.io.File;
+
+/**
+ * This implementation of the {@link CsvFileProvider} will always return the same name
+ * for the same metric. This means the CSV file will grow indefinitely.
+ */
+public class FixedNameCsvFileProvider implements CsvFileProvider {
+
+    @Override
+    public File getFile(File directory, String metricName) {
+        return new File(directory, sanitize(metricName) + ".csv");
+    }
+
+    protected String sanitize(String metricName) {
+        //Forward slash character is definitely illegal in both Windows and Linux
+        //https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+        final String sanitizedName = metricName.replaceFirst("^/","").replaceAll("/",".");
+        return sanitizedName;
+    }
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Histogram.java b/metrics-core/src/main/java/com/codahale/metrics/Histogram.java
index c3a199f..48f877b 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Histogram.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Histogram.java
@@ -8,7 +8,7 @@ package com.codahale.metrics;
  */
 public class Histogram implements Metric, Sampling, Counting {
     private final Reservoir reservoir;
-    private final LongAdder count;
+    private final LongAdderAdapter count;
 
     /**
      * Creates a new {@link Histogram} with the given reservoir.
@@ -17,7 +17,7 @@ public class Histogram implements Metric, Sampling, Counting {
      */
     public Histogram(Reservoir reservoir) {
         this.reservoir = reservoir;
-        this.count = new LongAdder();
+        this.count = LongAdderProxy.create();
     }
 
     /**
diff --git a/metrics-core/src/main/java/com/codahale/metrics/JmxAttributeGauge.java b/metrics-core/src/main/java/com/codahale/metrics/JmxAttributeGauge.java
index 800c932..b49fa16 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/JmxAttributeGauge.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/JmxAttributeGauge.java
@@ -5,6 +5,7 @@ import javax.management.JMException;
 import javax.management.MBeanServerConnection;
 import javax.management.ObjectName;
 import java.lang.management.ManagementFactory;
+import java.util.Set;
 
 /**
  * A {@link Gauge} implementation which queries an {@link MBeanServerConnection} for an attribute of an object.
@@ -40,11 +41,21 @@ public class JmxAttributeGauge implements Gauge<Object> {
     @Override
     public Object getValue() {
         try {
-            return mBeanServerConn.getAttribute(objectName, attributeName);
+            return mBeanServerConn.getAttribute(getObjectName(), attributeName);
         } catch (IOException e) {
             return null;
         } catch (JMException e) {
             return null;
         }
     }
+
+    private ObjectName getObjectName() throws IOException {
+        if (objectName.isPattern()) {
+            Set<ObjectName> foundNames = mBeanServerConn.queryNames(objectName, null);
+            if (foundNames.size() == 1) {
+                return foundNames.iterator().next();
+            }
+        }
+        return objectName;
+    }
 }
diff --git a/metrics-core/src/main/java/com/codahale/metrics/JmxReporter.java b/metrics-core/src/main/java/com/codahale/metrics/JmxReporter.java
index c444e9f..2be42f4 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/JmxReporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/JmxReporter.java
@@ -4,7 +4,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.management.*;
-
 import java.io.Closeable;
 import java.lang.management.ManagementFactory;
 import java.util.Collections;
@@ -237,6 +236,8 @@ public class JmxReporter implements Reporter, Closeable {
         double get999thPercentile();
 
         long[] values();
+
+        long getSnapshotSize();
     }
     // CHECKSTYLE:ON
 
@@ -313,6 +314,10 @@ public class JmxReporter implements Reporter, Closeable {
         public long[] values() {
             return metric.getSnapshot().getValues();
         }
+
+        public long getSnapshotSize() {
+            return metric.getSnapshot().size();
+        }
     }
 
     //CHECKSTYLE:OFF
@@ -341,7 +346,7 @@ public class JmxReporter implements Reporter, Closeable {
             super(objectName);
             this.metric = metric;
             this.rateFactor = rateUnit.toSeconds(1);
-            this.rateUnit = "events/" + calculateRateUnit(rateUnit);
+            this.rateUnit = ("events/" + calculateRateUnit(rateUnit)).intern();
         }
 
         @Override
diff --git a/metrics-core/src/main/java/com/codahale/metrics/LongAdderAdapter.java b/metrics-core/src/main/java/com/codahale/metrics/LongAdderAdapter.java
new file mode 100644
index 0000000..d38febd
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/LongAdderAdapter.java
@@ -0,0 +1,18 @@
+package com.codahale.metrics;
+
+/**
+ * Interface which exposes the LongAdder functionality. Allows different
+ * LongAdder implementations to coexist together.
+ */
+interface LongAdderAdapter {
+
+    void add(long x);
+
+    long sum();
+
+    void increment();
+
+    void decrement();
+
+    long sumThenReset();
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/LongAdderProxy.java b/metrics-core/src/main/java/com/codahale/metrics/LongAdderProxy.java
new file mode 100644
index 0000000..bec8ab9
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/LongAdderProxy.java
@@ -0,0 +1,108 @@
+package com.codahale.metrics;
+
+/**
+ * Proxy for creating long adders depending on the runtime. By default it tries to
+ * the JDK's implementation and fallbacks to the internal one if the JDK doesn't provide
+ * any. The JDK's LongAdder and the internal one don't have a common interface, therefore
+ * we adapten them to {@link InternalLongAdderProvider}, which serves as a common interface for
+ * long adders.
+ */
+class LongAdderProxy {
+
+    private interface Provider {
+        LongAdderAdapter get();
+    }
+
+    /**
+     * To avoid NoClassDefFoundError during loading {@link LongAdderProxy}
+     */
+    private static class JdkProvider implements Provider {
+
+        @Override
+        public LongAdderAdapter get() {
+            return new LongAdderAdapter() {
+                private final java.util.concurrent.atomic.LongAdder longAdder =
+                        new java.util.concurrent.atomic.LongAdder();
+
+                @Override
+                public void add(long x) {
+                    longAdder.add(x);
+                }
+
+                @Override
+                public long sum() {
+                    return longAdder.sum();
+                }
+
+                @Override
+                public void increment() {
+                    longAdder.increment();
+                }
+
+                @Override
+                public void decrement() {
+                    longAdder.decrement();
+                }
+
+                @Override
+                public long sumThenReset() {
+                    return longAdder.sumThenReset();
+                }
+            };
+        }
+    }
+
+    /**
+     * Backed by the internal LongAdder
+     */
+    private static class InternalLongAdderProvider implements Provider {
+
+        @Override
+        public LongAdderAdapter get() {
+            return new LongAdderAdapter() {
+                private final LongAdder longAdder = new LongAdder();
+
+                @Override
+                public void add(long x) {
+                    longAdder.add(x);
+                }
+
+                @Override
+                public long sum() {
+                    return longAdder.sum();
+                }
+
+                @Override
+                public void increment() {
+                    longAdder.increment();
+                }
+
+                @Override
+                public void decrement() {
+                    longAdder.decrement();
+                }
+
+                @Override
+                public long sumThenReset() {
+                    return longAdder.sumThenReset();
+                }
+            };
+        }
+
+    }
+
+    private static final Provider INSTANCE = getLongAdderProvider();
+    private static Provider getLongAdderProvider() {
+        try {
+            final JdkProvider jdkProvider = new JdkProvider();
+            jdkProvider.get(); // To trigger a possible `NoClassDefFoundError` exception
+            return jdkProvider;
+        } catch (Throwable e) {
+            return new InternalLongAdderProvider();
+        }
+    }
+
+    public static LongAdderAdapter create() {
+        return INSTANCE.get();
+    }
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Meter.java b/metrics-core/src/main/java/com/codahale/metrics/Meter.java
index b956066..1f8b80b 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Meter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Meter.java
@@ -16,7 +16,7 @@ public class Meter implements Metered {
     private final EWMA m5Rate = EWMA.fiveMinuteEWMA();
     private final EWMA m15Rate = EWMA.fifteenMinuteEWMA();
 
-    private final LongAdder count = new LongAdder();
+    private final LongAdderAdapter count = LongAdderProxy.create();
     private final long startTime;
     private final AtomicLong lastTick;
     private final Clock clock;
diff --git a/metrics-core/src/main/java/com/codahale/metrics/MetricAttribute.java b/metrics-core/src/main/java/com/codahale/metrics/MetricAttribute.java
new file mode 100644
index 0000000..24a6834
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/MetricAttribute.java
@@ -0,0 +1,33 @@
+package com.codahale.metrics;
+
+/**
+ * Represents attributes of metrics which can be reported.
+ */
+public enum MetricAttribute {
+
+    MAX("max"),
+    MEAN("mean"),
+    MIN("min"),
+    STDDEV("stddev"),
+    P50("p50"),
+    P75("p75"),
+    P95("p95"),
+    P98("p98"),
+    P99("p99"),
+    P999("p999"),
+    COUNT("count"),
+    M1_RATE("m1_rate"),
+    M5_RATE("m5_rate"),
+    M15_RATE("m15_rate"),
+    MEAN_RATE("mean_rate");
+
+    private final String code;
+
+    MetricAttribute(String code) {
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/MetricRegistry.java b/metrics-core/src/main/java/com/codahale/metrics/MetricRegistry.java
index 6751995..8662954 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/MetricRegistry.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/MetricRegistry.java
@@ -116,6 +116,27 @@ public class MetricRegistry implements MetricSet {
     }
 
     /**
+     * Return the {@link Counter} registered under this name; or create and register
+     * a new {@link Counter} using the provided MetricSupplier if none is registered.
+     *
+     * @param name the name of the metric
+     * @param supplier a MetricSupplier that can be used to manufacture a counter.
+     * @return a new or pre-existing {@link Counter}
+     */
+    public Counter counter(String name, final MetricSupplier<Counter> supplier) {
+        return getOrAdd(name, new MetricBuilder<Counter>() {
+            @Override
+            public Counter newMetric() {
+                return supplier.newMetric();
+            }
+            @Override
+            public boolean isInstance(Metric metric) {
+                return Counter.class.isInstance(metric);
+            }
+        });
+    }
+
+    /**
      * Return the {@link Histogram} registered under this name; or create and register 
      * a new {@link Histogram} if none is registered.
      *
@@ -127,7 +148,28 @@ public class MetricRegistry implements MetricSet {
     }
 
     /**
-     * Return the {@link Meter} registered under this name; or create and register 
+     * Return the {@link Histogram} registered under this name; or create and register
+     * a new {@link Histogram} using the provided MetricSupplier if none is registered.
+     *
+     * @param name the name of the metric
+     * @param supplier a MetricSupplier that can be used to manufacture a histogram
+     * @return a new or pre-existing {@link Histogram}
+     */
+    public Histogram histogram(String name, final MetricSupplier<Histogram> supplier) {
+      return getOrAdd(name, new MetricBuilder<Histogram>() {
+        @Override
+        public Histogram newMetric() {
+          return supplier.newMetric();
+        }
+        @Override
+        public boolean isInstance(Metric metric) {
+          return Histogram.class.isInstance(metric);
+        }
+      });
+    }
+
+    /**
+     * Return the {@link Meter} registered under this name; or create and register
      * a new {@link Meter} if none is registered.
      *
      * @param name the name of the metric
@@ -138,7 +180,28 @@ public class MetricRegistry implements MetricSet {
     }
 
     /**
-     * Return the {@link Timer} registered under this name; or create and register 
+     * Return the {@link Meter} registered under this name; or create and register
+     * a new {@link Meter} using the provided MetricSupplier if none is registered.
+     *
+     * @param name the name of the metric
+     * @param supplier a MetricSupplier that can be used to manufacture a Meter
+     * @return a new or pre-existing {@link Meter}
+     */
+    public Meter meter(String name, final MetricSupplier<Meter> supplier) {
+        return getOrAdd(name, new MetricBuilder<Meter>() {
+            @Override
+            public Meter newMetric() {
+                return supplier.newMetric();
+            }
+            @Override
+            public boolean isInstance(Metric metric) {
+                return Meter.class.isInstance(metric);
+            }
+        });
+    }
+
+    /**
+     * Return the {@link Timer} registered under this name; or create and register
      * a new {@link Timer} if none is registered.
      *
      * @param name the name of the metric
@@ -149,11 +212,54 @@ public class MetricRegistry implements MetricSet {
     }
 
     /**
-     * Removes the metric with the given name.
+     * Return the {@link Timer} registered under this name; or create and register
+     * a new {@link Timer} using the provided MetricSupplier if none is registered.
+     *
+     * @param name the name of the metric
+     * @param supplier a MetricSupplier that can be used to manufacture a Timer
+     * @return a new or pre-existing {@link Timer}
+     */
+    public Timer timer(String name, final MetricSupplier<Timer> supplier) {
+        return getOrAdd(name, new MetricBuilder<Timer>() {
+            @Override
+            public Timer newMetric() {
+                return supplier.newMetric();
+            }
+            @Override
+            public boolean isInstance(Metric metric) {
+                return Timer.class.isInstance(metric);
+            }
+        });
+    }
+
+    /**
+     * Return the {@link Gauge} registered under this name; or create and register
+     * a new {@link Gauge} using the provided MetricSupplier if none is registered.
      *
      * @param name the name of the metric
-     * @return whether or not the metric was removed
+     * @param supplier a MetricSupplier that can be used to manufacture a Gauge
+     * @return a new or pre-existing {@link Gauge}
      */
+    public Gauge gauge(String name, final MetricSupplier<Gauge> supplier) {
+        return getOrAdd(name, new MetricBuilder<Gauge>() {
+            @Override
+            public Gauge newMetric() {
+                return supplier.newMetric();
+            }
+            @Override
+            public boolean isInstance(Metric metric) {
+                return Gauge.class.isInstance(metric);
+            }
+        });
+    }
+
+
+        /**
+         * Removes the metric with the given name.
+         *
+         * @param name the name of the metric
+         * @return whether or not the metric was removed
+         */
     public boolean remove(String name) {
         final Metric metric = metrics.remove(name);
         if (metric != null) {
@@ -396,6 +502,10 @@ public class MetricRegistry implements MetricSet {
         return Collections.unmodifiableMap(metrics);
     }
 
+    public interface MetricSupplier<T extends Metric> {
+      T newMetric();
+    }
+
     /**
      * A quick and easy way of capturing the notion of default metrics.
      */
diff --git a/metrics-core/src/main/java/com/codahale/metrics/ScheduledReporter.java b/metrics-core/src/main/java/com/codahale/metrics/ScheduledReporter.java
index 8ddcd31..37750b0 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/ScheduledReporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/ScheduledReporter.java
@@ -1,17 +1,22 @@
 package com.codahale.metrics;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.Closeable;
+import java.util.Collections;
 import java.util.Locale;
+import java.util.Set;
 import java.util.SortedMap;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 /**
  * The abstract base class for all scheduled reporters (i.e., reporters which process a registry's
  * metrics periodically).
@@ -54,10 +59,13 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
 
     private final MetricRegistry registry;
     private final ScheduledExecutorService executor;
+    private final boolean shutdownExecutorOnStop;
+    private final Set<MetricAttribute> disabledMetricAttributes;
+    private ScheduledFuture<?> scheduledFuture;
     private final MetricFilter filter;
-    private final double durationFactor;
+    private final long durationFactor;
     private final String durationUnit;
-    private final double rateFactor;
+    private final long rateFactor;
     private final String rateUnit;
 
     /**
@@ -67,7 +75,7 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
      *                 reporter will report
      * @param name     the reporter's name
      * @param filter   the filter for which metrics to report
-     * @param rateUnit a unit of time 
+     * @param rateUnit a unit of time
      * @param durationUnit a unit of time
      */
     protected ScheduledReporter(MetricRegistry registry,
@@ -75,10 +83,9 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
                                 MetricFilter filter,
                                 TimeUnit rateUnit,
                                 TimeUnit durationUnit) {
-		this(registry, name, filter, rateUnit, durationUnit,
-                Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory(name + '-' + FACTORY_ID.incrementAndGet())));
+		this(registry, name, filter, rateUnit, durationUnit, createDefaultExecutor(name));
     }
-	
+
     /**
      * Creates a new {@link ScheduledReporter} instance.
      *
@@ -94,13 +101,48 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
                                 TimeUnit rateUnit,
                                 TimeUnit durationUnit,
                                 ScheduledExecutorService executor) {
+        this(registry, name, filter, rateUnit, durationUnit, executor, true);
+    }
+
+    /**
+     * Creates a new {@link ScheduledReporter} instance.
+     *
+     * @param registry the {@link com.codahale.metrics.MetricRegistry} containing the metrics this
+     *                 reporter will report
+     * @param name     the reporter's name
+     * @param filter   the filter for which metrics to report
+     * @param executor the executor to use while scheduling reporting of metrics.
+     * @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
+     */
+    protected ScheduledReporter(MetricRegistry registry,
+                                String name,
+                                MetricFilter filter,
+                                TimeUnit rateUnit,
+                                TimeUnit durationUnit,
+                                ScheduledExecutorService executor,
+                                boolean shutdownExecutorOnStop) {
+       this(registry, name, filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop,
+               Collections.<MetricAttribute>emptySet());
+    }
+
+    protected ScheduledReporter(MetricRegistry registry,
+                                String name,
+                                MetricFilter filter,
+                                TimeUnit rateUnit,
+                                TimeUnit durationUnit,
+                                ScheduledExecutorService executor,
+                                boolean shutdownExecutorOnStop,
+                                Set<MetricAttribute> disabledMetricAttributes) {
         this.registry = registry;
         this.filter = filter;
-        this.executor = executor;
+        this.executor = executor == null? createDefaultExecutor(name) : executor;
+        this.shutdownExecutorOnStop = shutdownExecutorOnStop;
         this.rateFactor = rateUnit.toSeconds(1);
         this.rateUnit = calculateRateUnit(rateUnit);
-        this.durationFactor = 1.0 / durationUnit.toNanos(1);
+        this.durationFactor = durationUnit.toNanos(1);
         this.durationUnit = durationUnit.toString().toLowerCase(Locale.US);
+        this.disabledMetricAttributes = disabledMetricAttributes != null ? disabledMetricAttributes :
+                Collections.<MetricAttribute>emptySet();
     }
 
     /**
@@ -110,39 +152,70 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
      * @param unit   the unit for {@code period}
      */
     public void start(long period, TimeUnit unit) {
-        executor.scheduleAtFixedRate(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    report();
-                } catch (RuntimeException ex) {
-                    LOG.error("RuntimeException thrown from {}#report. Exception was suppressed.", ScheduledReporter.this.getClass().getSimpleName(), ex);
-                }
-            }
-        }, period, period, unit);
+       start(period, period, unit);
     }
 
     /**
-     * Stops the reporter and shuts down its thread of execution.
+     * Starts the reporter polling at the given period.
+     *
+     * @param initialDelay the time to delay the first execution
+     * @param period       the amount of time between polls
+     * @param unit         the unit for {@code period}
+     */
+    synchronized public void start(long initialDelay, long period, TimeUnit unit) {
+      if (this.scheduledFuture != null) {
+          throw new IllegalArgumentException("Reporter already started");
+      }
+
+      this.scheduledFuture = executor.scheduleAtFixedRate(new Runnable() {
+         @Override
+         public void run() {
+             try {
+                 report();
+             } catch (Throwable ex) {
+                 LOG.error("Exception thrown from {}#report. Exception was suppressed.", ScheduledReporter.this.getClass().getSimpleName(), ex);
+             }
+         }
+      }, initialDelay, period, unit);
+    }
+
+    /**
+     * Stops the reporter and if shutdownExecutorOnStop is true then shuts down its thread of execution.
      *
      * Uses the shutdown pattern from http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html
      */
     public void stop() {
-        executor.shutdown(); // Disable new tasks from being submitted
-        try {
-            // Wait a while for existing tasks to terminate
-            if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
-                executor.shutdownNow(); // Cancel currently executing tasks
-                // Wait a while for tasks to respond to being cancelled
+        if (shutdownExecutorOnStop) {
+            executor.shutdown(); // Disable new tasks from being submitted
+            try {
+                // Wait a while for existing tasks to terminate
                 if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
-                    System.err.println(getClass().getSimpleName() + ": ScheduledExecutorService did not terminate");
+                    executor.shutdownNow(); // Cancel currently executing tasks
+                    // Wait a while for tasks to respond to being cancelled
+                    if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
+                        System.err.println(getClass().getSimpleName() + ": ScheduledExecutorService did not terminate");
+                    }
+                }
+            } catch (InterruptedException ie) {
+                // (Re-)Cancel if current thread also interrupted
+                executor.shutdownNow();
+                // Preserve interrupt status
+                Thread.currentThread().interrupt();
+            }
+        } else {
+            // The external manager(like JEE container) responsible for lifecycle of executor
+            synchronized (this) {
+                if (this.scheduledFuture == null) {
+                    // was never started
+                    return;
+                }
+                if (this.scheduledFuture.isCancelled()) {
+                    // already cancelled
+                    return;
                 }
+                // just cancel the scheduledFuture and exit
+                this.scheduledFuture.cancel(false);
             }
-        } catch (InterruptedException ie) {
-            // (Re-)Cancel if current thread also interrupted
-            executor.shutdownNow();
-            // Preserve interrupt status
-            Thread.currentThread().interrupt();
         }
     }
 
@@ -191,15 +264,28 @@ public abstract class ScheduledReporter implements Closeable, Reporter {
     }
 
     protected double convertDuration(double duration) {
-        return duration * durationFactor;
+        return duration / durationFactor;
     }
 
     protected double convertRate(double rate) {
         return rate * rateFactor;
     }
 
+    protected boolean isShutdownExecutorOnStop() {
+        return shutdownExecutorOnStop;
+    }
+
+    protected Set<MetricAttribute> getDisabledMetricAttributes() {
+        return disabledMetricAttributes;
+    }
+
     private String calculateRateUnit(TimeUnit unit) {
         final String s = unit.toString().toLowerCase(Locale.US);
         return s.substring(0, s.length() - 1);
     }
+
+    private static ScheduledExecutorService createDefaultExecutor(String name) {
+        return Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory(name + '-' + FACTORY_ID.incrementAndGet()));
+    }
+
 }
diff --git a/metrics-core/src/main/java/com/codahale/metrics/SharedMetricRegistries.java b/metrics-core/src/main/java/com/codahale/metrics/SharedMetricRegistries.java
index 6c8812b..2fe00ae 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/SharedMetricRegistries.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/SharedMetricRegistries.java
@@ -3,6 +3,7 @@ package com.codahale.metrics;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * A map of shared, named metric registries.
@@ -11,6 +12,13 @@ public class SharedMetricRegistries {
     private static final ConcurrentMap<String, MetricRegistry> REGISTRIES =
             new ConcurrentHashMap<String, MetricRegistry>();
 
+    private static AtomicReference<String> defaultRegistryName = new AtomicReference<String>();
+
+    /* Visible for testing */
+    static void setDefaultRegistryName(AtomicReference<String> defaultRegistryName) {
+        SharedMetricRegistries.defaultRegistryName = defaultRegistryName;
+    }
+
     private SharedMetricRegistries() { /* singleton */ }
 
     public static void clear() {
@@ -41,4 +49,59 @@ public class SharedMetricRegistries {
         }
         return existing;
     }
+
+    /**
+     * Creates a new registry and sets it as the default one under the provided name.
+     *
+     * @param name the registry name
+     * @return the default registry
+     * @throws IllegalStateException if the name has already been set
+     */
+    public synchronized static MetricRegistry setDefault(String name) {
+        final MetricRegistry registry = getOrCreate(name);
+        return setDefault(name, registry);
+    }
+
+    /**
+     * Sets the provided registry as the default one under the provided name
+     *
+     * @param name           the default registry name
+     * @param metricRegistry the default registry
+     * @throws IllegalStateException if the default registry has already been set
+     */
+    public static MetricRegistry setDefault(String name, MetricRegistry metricRegistry) {
+        if (defaultRegistryName.compareAndSet(null, name)) {
+            add(name, metricRegistry);
+            return metricRegistry;
+        }
+        throw new IllegalStateException("Default metric registry name is already set.");
+    }
+
+    /**
+     * Gets the name of the default registry, if it has been set
+     *
+     * @return the default registry
+     * @throws IllegalStateException if the default has not been set
+     */
+    public static MetricRegistry getDefault() {
+        MetricRegistry metricRegistry = tryGetDefault();
+        if (metricRegistry == null) {
+            throw new IllegalStateException("Default registry name has not been set.");
+        }
+        return metricRegistry;
+    }
+
+    /**
+     * Same as {@link #getDefault()} except returns null when the default registry has not been set.
+     *
+     * @return the default registry or null
+     */
+    public static MetricRegistry tryGetDefault() {
+        final String name = defaultRegistryName.get();
+        if (name != null) {
+            return getOrCreate(name);
+        } else {
+            return null;
+        }
+    }
 }
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Slf4jReporter.java b/metrics-core/src/main/java/com/codahale/metrics/Slf4jReporter.java
index 7e06025..317517c 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Slf4jReporter.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Slf4jReporter.java
@@ -6,6 +6,7 @@ import org.slf4j.Marker;
 
 import java.util.Map.Entry;
 import java.util.SortedMap;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -41,6 +42,8 @@ public class Slf4jReporter extends ScheduledReporter {
         private TimeUnit rateUnit;
         private TimeUnit durationUnit;
         private MetricFilter filter;
+        private ScheduledExecutorService executor;
+        private boolean shutdownExecutorOnStop;
 
         private Builder(MetricRegistry registry) {
             this.registry = registry;
@@ -51,6 +54,34 @@ public class Slf4jReporter extends ScheduledReporter {
             this.durationUnit = TimeUnit.MILLISECONDS;
             this.filter = MetricFilter.ALL;
             this.loggingLevel = LoggingLevel.INFO;
+            this.executor = null;
+            this.shutdownExecutorOnStop = true;
+        }
+
+        /**
+         * Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
+         * Default value is true.
+         * Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.
+         *
+         * @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
+         * @return {@code this}
+         */
+        public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
+            this.shutdownExecutorOnStop = shutdownExecutorOnStop;
+            return this;
+        }
+
+        /**
+         * Specifies the executor to use while scheduling reporting of metrics.
+         * Default value is null.
+         * Null value leads to executor will be auto created on start.
+         *
+         * @param executor the executor to use while scheduling reporting of metrics.
+         * @return {@code this}
+         */
+        public Builder scheduleOn(ScheduledExecutorService executor) {
+            this.executor = executor;
+            return this;
         }
 
         /**
@@ -155,7 +186,7 @@ public class Slf4jReporter extends ScheduledReporter {
                     loggerProxy = new DebugLoggerProxy(logger);
                     break;
             }
-            return new Slf4jReporter(registry, loggerProxy, marker, prefix, rateUnit, durationUnit, filter);
+            return new Slf4jReporter(registry, loggerProxy, marker, prefix, rateUnit, durationUnit, filter, executor, shutdownExecutorOnStop);
         }
     }
 
@@ -169,8 +200,10 @@ public class Slf4jReporter extends ScheduledReporter {
                           String prefix,
                           TimeUnit rateUnit,
                           TimeUnit durationUnit,
-                          MetricFilter filter) {
-        super(registry, "logger-reporter", filter, rateUnit, durationUnit);
+                          MetricFilter filter,
+                          ScheduledExecutorService executor,
+                          boolean shutdownExecutorOnStop) {
+        super(registry, "logger-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop);
         this.loggerProxy = loggerProxy;
         this.marker = marker;
         this.prefix = prefix;
@@ -208,9 +241,10 @@ public class Slf4jReporter extends ScheduledReporter {
     private void logTimer(String name, Timer timer) {
         final Snapshot snapshot = timer.getSnapshot();
         loggerProxy.log(marker,
-                "type=TIMER, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, " +
+                "type={}, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, " +
                         "p75={}, p95={}, p98={}, p99={}, p999={}, mean_rate={}, m1={}, m5={}, " +
                         "m15={}, rate_unit={}, duration_unit={}",
+                "TIMER",
                 prefix(name),
                 timer.getCount(),
                 convertDuration(snapshot.getMin()),
@@ -233,7 +267,8 @@ public class Slf4jReporter extends ScheduledReporter {
 
     private void logMeter(String name, Meter meter) {
         loggerProxy.log(marker,
-                "type=METER, name={}, count={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}",
+                "type={}, name={}, count={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}",
+                "METER",
                 prefix(name),
                 meter.getCount(),
                 convertRate(meter.getMeanRate()),
@@ -246,8 +281,9 @@ public class Slf4jReporter extends ScheduledReporter {
     private void logHistogram(String name, Histogram histogram) {
         final Snapshot snapshot = histogram.getSnapshot();
         loggerProxy.log(marker,
-                "type=HISTOGRAM, name={}, count={}, min={}, max={}, mean={}, stddev={}, " +
+                "type={}, name={}, count={}, min={}, max={}, mean={}, stddev={}, " +
                         "median={}, p75={}, p95={}, p98={}, p99={}, p999={}",
+                "HISTOGRAM",
                 prefix(name),
                 histogram.getCount(),
                 snapshot.getMin(),
@@ -263,11 +299,11 @@ public class Slf4jReporter extends ScheduledReporter {
     }
 
     private void logCounter(String name, Counter counter) {
-        loggerProxy.log(marker, "type=COUNTER, name={}, count={}", prefix(name), counter.getCount());
+        loggerProxy.log(marker, "type={}, name={}, count={}", "COUNTER", prefix(name), counter.getCount());
     }
 
     private void logGauge(String name, Gauge gauge) {
-        loggerProxy.log(marker, "type=GAUGE, name={}, value={}", prefix(name), gauge.getValue());
+        loggerProxy.log(marker, "type={}, name={}, value={}", "GAUGE", prefix(name), gauge.getValue());
     }
 
     @Override
diff --git a/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoir.java b/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoir.java
new file mode 100644
index 0000000..cb47a44
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoir.java
@@ -0,0 +1,100 @@
+package com.codahale.metrics;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A {@link Reservoir} implementation backed by a sliding window that stores only the measurements made
+ * in the last {@code N} seconds (or other time unit).
+ */
+public class SlidingTimeWindowArrayReservoir implements Reservoir {
+    // allow for this many duplicate ticks before overwriting measurements
+    private static final long COLLISION_BUFFER = 256L;
+    // only trim on updating once every N
+    private static final long TRIM_THRESHOLD = 256L;
+    private static final long CLEAR_BUFFER = TimeUnit.HOURS.toNanos(1) * COLLISION_BUFFER;
+
+    private final Clock clock;
+    private final ChunkedAssociativeLongArray measurements;
+    private final long window;
+    private final AtomicLong lastTick;
+    private final AtomicLong count;
+    private final long startTick;
+
+    /**
+     * Creates a new {@link SlidingTimeWindowArrayReservoir} with the given window of time.
+     *
+     * @param window     the window of time
+     * @param windowUnit the unit of {@code window}
+     */
+    public SlidingTimeWindowArrayReservoir(long window, TimeUnit windowUnit) {
+        this(window, windowUnit, Clock.defaultClock());
+    }
+
+    /**
+     * Creates a new {@link SlidingTimeWindowArrayReservoir} with the given clock and window of time.
+     *
+     * @param window     the window of time
+     * @param windowUnit the unit of {@code window}
+     * @param clock      the {@link Clock} to use
+     */
+    public SlidingTimeWindowArrayReservoir(long window, TimeUnit windowUnit, Clock clock) {
+        this.startTick = clock.getTick();
+        this.clock = clock;
+        this.measurements = new ChunkedAssociativeLongArray();
+        this.window = windowUnit.toNanos(window) * COLLISION_BUFFER;
+        this.lastTick = new AtomicLong((clock.getTick() - startTick) * COLLISION_BUFFER);
+        this.count = new AtomicLong();
+    }
+
+    @Override
+    public int size() {
+        trim();
+        return measurements.size();
+    }
+
+    @Override
+    public void update(long value) {
+        long newTick;
+        do {
+            if (count.incrementAndGet() % TRIM_THRESHOLD == 0L) {
+                trim();
+            }
+            long lastTick = this.lastTick.get();
+            newTick = getTick();
+            boolean longOverflow = newTick < lastTick;
+            if (longOverflow) {
+                measurements.clear();
+            }
+        } while (!measurements.put(newTick, value));
+    }
+
+    @Override
+    public Snapshot getSnapshot() {
+        trim();
+        return new UniformSnapshot(measurements.values());
+    }
+
+    private long getTick() {
+        for (; ; ) {
+            final long oldTick = lastTick.get();
+            final long tick = (clock.getTick() - startTick) * COLLISION_BUFFER;
+            // ensure the tick is strictly incrementing even if there are duplicate ticks
+            final long newTick = tick - oldTick > 0L ? tick : oldTick + 1L;
+            if (lastTick.compareAndSet(oldTick, newTick)) {
+                return newTick;
+            }
+        }
+    }
+
+    void trim() {
+        final long now = getTick();
+        final long windowStart = now - window;
+        final long windowEnd = now + CLEAR_BUFFER;
+        if (windowStart < windowEnd) {
+            measurements.trim(windowStart, windowEnd);
+        } else {
+            measurements.clear(windowEnd, windowStart);
+        }
+    }
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowReservoir.java b/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowReservoir.java
index c07a394..e1a9d09 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowReservoir.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/SlidingTimeWindowReservoir.java
@@ -13,6 +13,8 @@ public class SlidingTimeWindowReservoir implements Reservoir {
     private static final int COLLISION_BUFFER = 256;
     // only trim on updating once every N
     private static final int TRIM_THRESHOLD = 256;
+    // offsets the front of the time window for the purposes of clearing the buffer in trim
+    private static final long CLEAR_BUFFER = TimeUnit.HOURS.toNanos(1) * COLLISION_BUFFER;
 
     private final Clock clock;
     private final ConcurrentSkipListMap<Long, Long> measurements;
@@ -78,6 +80,14 @@ public class SlidingTimeWindowReservoir implements Reservoir {
     }
 
     private void trim() {
-        measurements.headMap(getTick() - window).clear();
+        final long now = getTick();
+        final long windowStart = now - window;
+        final long windowEnd = now + CLEAR_BUFFER;
+        if (windowStart < windowEnd) {
+            measurements.headMap(windowStart).clear();
+            measurements.tailMap(windowEnd).clear();
+        } else {
+            measurements.subMap(windowEnd, windowStart).clear();
+        }
     }
 }
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Striped64.java b/metrics-core/src/main/java/com/codahale/metrics/Striped64.java
index 73a5d67..3652d95 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Striped64.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Striped64.java
@@ -9,6 +9,8 @@
 package com.codahale.metrics;
 
 import java.util.Random;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
 
 // CHECKSTYLE:OFF
 /**
@@ -98,23 +100,10 @@ abstract class Striped64 extends Number {
         }
 
         final boolean cas(long cmp, long val) {
-            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
+            return valueUpdater.compareAndSet(this, cmp, val);
         }
 
-        // Unsafe mechanics
-        private static final sun.misc.Unsafe UNSAFE;
-        private static final long valueOffset;
-
-        static {
-            try {
-                UNSAFE = getUnsafe();
-                Class<?> ak = Cell.class;
-                valueOffset = UNSAFE.objectFieldOffset
-                        (ak.getDeclaredField("value"));
-            } catch (Exception e) {
-                throw new Error(e);
-            }
-        }
+        private static final AtomicLongFieldUpdater<Cell> valueUpdater = AtomicLongFieldUpdater.newUpdater(Cell.class, "value");
 
     }
 
@@ -141,6 +130,9 @@ abstract class Striped64 extends Number {
         }
     }
 
+    static final AtomicLongFieldUpdater<Striped64> baseUpdater = AtomicLongFieldUpdater.newUpdater(Striped64.class, "base");
+    static final AtomicIntegerFieldUpdater<Striped64> busyUpdater = AtomicIntegerFieldUpdater.newUpdater(Striped64.class, "busy");
+
     /**
      * Static per-thread hash codes. Shared across all instances to reduce ThreadLocal pollution and
      * because adjustments due to collisions in one table are likely to be appropriate for others.
@@ -178,14 +170,14 @@ abstract class Striped64 extends Number {
      * CASes the base field.
      */
     final boolean casBase(long cmp, long val) {
-        return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val);
+        return baseUpdater.compareAndSet(this, cmp, val);
     }
 
     /**
      * CASes the busy field from 0 to 1 to acquire lock.
      */
     final boolean casBusy() {
-        return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1);
+        return busyUpdater.compareAndSet(this, 0, 1);
     }
 
     /**
@@ -301,54 +293,5 @@ abstract class Striped64 extends Number {
         }
     }
 
-    // Unsafe mechanics
-    private static final sun.misc.Unsafe UNSAFE;
-    private static final long baseOffset;
-    private static final long busyOffset;
-
-    static {
-        try {
-            UNSAFE = getUnsafe();
-            Class<?> sk = Striped64.class;
-            baseOffset = UNSAFE.objectFieldOffset
-                    (sk.getDeclaredField("base"));
-            busyOffset = UNSAFE.objectFieldOffset
-                    (sk.getDeclaredField("busy"));
-        } catch (Exception e) {
-            throw new Error(e);
-        }
-    }
-
-    /**
-     * Returns a sun.misc.Unsafe.  Suitable for use in a 3rd party package. Replace with a simple
-     * call to Unsafe.getUnsafe when integrating into a jdk.
-     *
-     * @return a sun.misc.Unsafe
-     */
-    private static sun.misc.Unsafe getUnsafe() {
-        try {
-            return sun.misc.Unsafe.getUnsafe();
-        } catch (SecurityException ignored) {
-
-        }
-        try {
-            return java.security.AccessController.doPrivileged
-                    (new java.security.PrivilegedExceptionAction<sun.misc.Unsafe>() {
-                        public sun.misc.Unsafe run() throws Exception {
-                            Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
-                            for (java.lang.reflect.Field f : k.getDeclaredFields()) {
-                                f.setAccessible(true);
-                                Object x = f.get(null);
-                                if (k.isInstance(x))
-                                    return k.cast(x);
-                            }
-                            throw new NoSuchFieldError("the Unsafe");
-                        }
-                    });
-        } catch (java.security.PrivilegedActionException e) {
-            throw new RuntimeException("Could not initialize intrinsics",
-                                       e.getCause());
-        }
-    }
 }
 // CHECKSTYLE:ON
diff --git a/metrics-core/src/main/java/com/codahale/metrics/ThreadLocalRandomProxy.java b/metrics-core/src/main/java/com/codahale/metrics/ThreadLocalRandomProxy.java
new file mode 100644
index 0000000..08dc9f0
--- /dev/null
+++ b/metrics-core/src/main/java/com/codahale/metrics/ThreadLocalRandomProxy.java
@@ -0,0 +1,50 @@
+package com.codahale.metrics;
+
+import java.util.Random;
+
+/**
+ * Proxy for creating thread local {@link Random} instances depending on the runtime.
+ * By default it tries to use the JDK's implementation and fallbacks to the internal
+ * one if the JDK doesn't provide any.
+ */
+class ThreadLocalRandomProxy {
+
+    private interface Provider {
+        Random current();
+    }
+
+    /**
+     * To avoid NoClassDefFoundError during loading {@link ThreadLocalRandomProxy}
+     */
+    private static class JdkProvider implements Provider {
+
+        @Override
+        public Random current() {
+            return java.util.concurrent.ThreadLocalRandom.current();
+        }
+    }
+
+    private static class InternalProvider implements Provider {
+
+        @Override
+        public Random current() {
+            return ThreadLocalRandom.current();
+        }
+    }
+
+    private static final Provider INSTANCE = getThreadLocalProvider();
+    private static Provider getThreadLocalProvider() {
+        try {
+            final JdkProvider jdkProvider = new JdkProvider();
+            jdkProvider.current(); //  To make sure that ThreadLocalRandom actually exists in the JDK
+            return jdkProvider;
+        } catch (Throwable e) {
+            return new InternalProvider();
+        }
+    }
+
+    public static Random current() {
+        return INSTANCE.current();
+    }
+
+}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/Timer.java b/metrics-core/src/main/java/com/codahale/metrics/Timer.java
index 29ef9c8..e49841a 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/Timer.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/Timer.java
@@ -105,6 +105,21 @@ public class Timer implements Metered, Sampling {
     }
 
     /**
+     * Times and records the duration of event.
+     *
+     * @param event a {@link Runnable} whose {@link Runnable#run()} method implements a process
+     *              whose duration should be timed
+     */
+    public void time(Runnable event) {
+        final long startTime = clock.getTick();
+        try {
+            event.run();
+        } finally {
+            update(clock.getTick() - startTime);
+        }
+    }
+
+    /**
      * Returns a new {@link Context}.
      *
      * @return a new {@link Context}
diff --git a/metrics-core/src/main/java/com/codahale/metrics/UniformReservoir.java b/metrics-core/src/main/java/com/codahale/metrics/UniformReservoir.java
index d981253..8b461fa 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/UniformReservoir.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/UniformReservoir.java
@@ -70,7 +70,7 @@ public class UniformReservoir implements Reservoir {
     private static long nextLong(long n) {
         long bits, val;
         do {
-            bits = ThreadLocalRandom.current().nextLong() & (~(1L << BITS_PER_LONG));
+            bits = ThreadLocalRandomProxy.current().nextLong() & (~(1L << BITS_PER_LONG));
             val = bits % n;
         } while (bits - val + (n - 1) < 0L);
         return val;
@@ -79,9 +79,9 @@ public class UniformReservoir implements Reservoir {
     @Override
     public Snapshot getSnapshot() {
         final int s = size();
-        final List<Long> copy = new ArrayList<Long>(s);
+        long[] copy = new long[s];
         for (int i = 0; i < s; i++) {
-            copy.add(values.get(i));
+            copy[i] = values.get(i);  
         }
         return new UniformSnapshot(copy);
     }
diff --git a/metrics-core/src/main/java/com/codahale/metrics/UniformSnapshot.java b/metrics-core/src/main/java/com/codahale/metrics/UniformSnapshot.java
index 000199f..16c5e1e 100644
--- a/metrics-core/src/main/java/com/codahale/metrics/UniformSnapshot.java
+++ b/metrics-core/src/main/java/com/codahale/metrics/UniformSnapshot.java
@@ -34,7 +34,7 @@ public class UniformSnapshot extends Snapshot {
     /**
      * Create a new {@link Snapshot} with the given values.
      *
-     * @param values    an unordered set of values in the reservoir
+     * @param values    an unordered set of values in the reservoir that can be used by this class directly
      */
     public UniformSnapshot(long[] values) {
         this.values = Arrays.copyOf(values, values.length);
diff --git a/metrics-core/src/test/java/com/codahale/metrics/ChunkedAssociativeLongArrayTest.java b/metrics-core/src/test/java/com/codahale/metrics/ChunkedAssociativeLongArrayTest.java
new file mode 100644
index 0000000..b83a7e1
--- /dev/null
+++ b/metrics-core/src/test/java/com/codahale/metrics/ChunkedAssociativeLongArrayTest.java
@@ -0,0 +1,71 @@
+package com.codahale.metrics;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+import org.junit.Test;
+
+public class ChunkedAssociativeLongArrayTest {
+
+    @Test
+    public void testClear() {
+        ChunkedAssociativeLongArray array = new ChunkedAssociativeLongArray(3);
+        array.put(-3, 3);
+        array.put(-2, 1);
+        array.put(0, 5);
+        array.put(3, 0);
+        array.put(9, 8);
+        array.put(15, 0);
+        array.put(19, 5);
+        array.put(21, 5);
+        array.put(34, -9);
+        array.put(109, 5);
+
+        then(array.out())
+            .isEqualTo("[(-3: 3) (-2: 1) (0: 5) ]->[(3: 0) (9: 8) (15: 0) ]->[(19: 5) (21: 5) (34: -9) ]->[(109: 5) ]");
+        then(array.values())
+            .isEqualTo(new long[]{3, 1, 5, 0, 8, 0, 5, 5, -9, 5});
+        then(array.size())
+            .isEqualTo(10);
+
+        array.clear(-2, 20);
+        then(array.out())
+            .isEqualTo("[(-3: 3) ]->[(21: 5) (34: -9) ]->[(109: 5) ]");
+        then(array.values())
+            .isEqualTo(new long[]{3, 5, -9, 5});
+        then(array.size())
+            .isEqualTo(4);
+    }
+
+
+    @Test
+    public void testTrim() {
+        ChunkedAssociativeLongArray array = new ChunkedAssociativeLongArray(3);
+        array.put(-3, 3);
+        array.put(-2, 1);
+        array.put(0, 5);
+        array.put(3, 0);
+        array.put(9, 8);
+        array.put(15, 0);
+        array.put(19, 5);
+        array.put(21, 5);
+        array.put(34, -9);
+        array.put(109, 5);
+
+        then(array.out())
+            .isEqualTo("[(-3: 3) (-2: 1) (0: 5) ]->[(3: 0) (9: 8) (15: 0) ]->[(19: 5) (21: 5) (34: -9) ]->[(109: 5) ]");
+        then(array.values())
+            .isEqualTo(new long[]{3, 1, 5, 0, 8, 0, 5, 5, -9, 5});
+        then(array.size())
+            .isEqualTo(10);
+
+        array.trim(-2, 20);
+
+        then(array.out())
+            .isEqualTo("[(-2: 1) (0: 5) ]->[(3: 0) (9: 8) (15: 0) ]->[(19: 5) ]");
+        then(array.values())
+            .isEqualTo(new long[]{1, 5, 0, 8, 0, 5});
+        then(array.size())
+            .isEqualTo(6);
+
+    }
+}
\ No newline at end of file
diff --git a/metrics-core/src/test/java/com/codahale/metrics/ConsoleReporterTest.java b/metrics-core/src/test/java/com/codahale/metrics/ConsoleReporterTest.java
index d93b0a7..caaa00b 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/ConsoleReporterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/ConsoleReporterTest.java
@@ -7,9 +7,11 @@ import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.io.UnsupportedEncodingException;
 import java.util.Locale;
-import java.util.SortedMap;
 import java.util.TimeZone;
+import java.util.SortedMap;
 import java.util.TreeMap;
+import java.util.EnumSet;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -17,15 +19,18 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class ConsoleReporterTest {
+    private final Locale locale = Locale.US;
+    private final TimeZone timeZone = TimeZone.getTimeZone("PST");
+
     private final MetricRegistry registry = mock(MetricRegistry.class);
     private final Clock clock = mock(Clock.class);
     private final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
     private final PrintStream output = new PrintStream(bytes);
     private final ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
                                                             .outputTo(output)
-                                                            .formattedFor(Locale.US)
+                                                            .formattedFor(locale)
                                                             .withClock(clock)
-                                                            .formattedFor(TimeZone.getTimeZone("PST"))
+                                                            .formattedFor(timeZone)
                                                             .convertRatesTo(TimeUnit.SECONDS)
                                                             .convertDurationsTo(TimeUnit.MILLISECONDS)
                                                             .filter(MetricFilter.ALL)
@@ -216,6 +221,168 @@ public class ConsoleReporterTest {
                 ));
     }
 
+    @Test
+    public void reportMeterWithDisabledAttributes() throws Exception {
+        Set<MetricAttribute> disabledMetricAttributes = EnumSet.of(MetricAttribute.M15_RATE, MetricAttribute.M5_RATE, MetricAttribute.COUNT);
+
+        final ConsoleReporter customReporter = ConsoleReporter.forRegistry(registry)
+                .outputTo(output)
+                .formattedFor(locale)
+                .withClock(clock)
+                .formattedFor(timeZone)
+                .convertRatesTo(TimeUnit.SECONDS)
+                .convertDurationsTo(TimeUnit.MILLISECONDS)
+                .filter(MetricFilter.ALL)
+                .disabledMetricAttributes(disabledMetricAttributes)
+            .build();
+
+        final Meter meter = mock(Meter.class);
+        when(meter.getCount()).thenReturn(1L);
+        when(meter.getMeanRate()).thenReturn(2.0);
+        when(meter.getOneMinuteRate()).thenReturn(3.0);
+        when(meter.getFiveMinuteRate()).thenReturn(4.0);
+        when(meter.getFifteenMinuteRate()).thenReturn(5.0);
+
+        customReporter.report(this.<Gauge>map(),
+                this.<Counter>map(),
+                this.<Histogram>map(),
+                map("test.meter", meter),
+                this.<Timer>map());
+
+        assertThat(consoleOutput())
+                .isEqualTo(lines(
+                        "3/17/13 6:04:36 PM =============================================================",
+                        "",
+                        "-- Meters ----------------------------------------------------------------------",
+                        "test.meter",
+                        "         mean rate = 2.00 events/second",
+                        "     1-minute rate = 3.00 events/second",
+                        "",
+                        ""
+                ));
+    }
+
+    @Test
+    public void reportTimerWithDisabledAttributes() throws Exception {
+        Set<MetricAttribute> disabledMetricAttributes = EnumSet.of(MetricAttribute.P50, MetricAttribute.P999, MetricAttribute.M5_RATE, MetricAttribute.MAX);
+
+        final ConsoleReporter customReporter = ConsoleReporter.forRegistry(registry)
+                .outputTo(output)
+                .formattedFor(locale)
+                .withClock(clock)
+                .formattedFor(timeZone)
+                .convertRatesTo(TimeUnit.SECONDS)
+                .convertDurationsTo(TimeUnit.MILLISECONDS)
+                .filter(MetricFilter.ALL)
+                .disabledMetricAttributes(disabledMetricAttributes)
+                .build();
+
+        final Timer timer = mock(Timer.class);
+        when(timer.getCount()).thenReturn(1L);
+        when(timer.getMeanRate()).thenReturn(2.0);
+        when(timer.getOneMinuteRate()).thenReturn(3.0);
+        when(timer.getFiveMinuteRate()).thenReturn(4.0);
+        when(timer.getFifteenMinuteRate()).thenReturn(5.0);
+
+        final Snapshot snapshot = mock(Snapshot.class);
+        when(snapshot.getMax()).thenReturn(TimeUnit.MILLISECONDS.toNanos(100));
+        when(snapshot.getMean()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(200));
+        when(snapshot.getMin()).thenReturn(TimeUnit.MILLISECONDS.toNanos(300));
+        when(snapshot.getStdDev()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(400));
+        when(snapshot.getMedian()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(500));
+        when(snapshot.get75thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(600));
+        when(snapshot.get95thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(700));
+        when(snapshot.get98thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(800));
+        when(snapshot.get99thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(900));
+        when(snapshot.get999thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS
+                .toNanos(1000));
+
+        when(timer.getSnapshot()).thenReturn(snapshot);
+
+        customReporter.report(this.<Gauge>map(),
+                this.<Counter>map(),
+                this.<Histogram>map(),
+                this.<Meter>map(),
+                map("test.another.timer", timer));
+
+        assertThat(consoleOutput())
+                .isEqualTo(lines(
+                        "3/17/13 6:04:36 PM =============================================================",
+                        "",
+                        "-- Timers ----------------------------------------------------------------------",
+                        "test.another.timer",
+                        "             count = 1",
+                        "         mean rate = 2.00 calls/second",
+                        "     1-minute rate = 3.00 calls/second",
+                        "    15-minute rate = 5.00 calls/second",
+                        "               min = 300.00 milliseconds",
+                        "              mean = 200.00 milliseconds",
+                        "            stddev = 400.00 milliseconds",
+                        "              75% <= 600.00 milliseconds",
+                        "              95% <= 700.00 milliseconds",
+                        "              98% <= 800.00 milliseconds",
+                        "              99% <= 900.00 milliseconds",
+                        "",
+                        ""
+                ));
+    }
+
+    @Test
+    public void reportHistogramWithDisabledAttributes() throws Exception {
+        Set<MetricAttribute> disabledMetricAttributes = EnumSet.of(MetricAttribute.MIN, MetricAttribute.MAX, MetricAttribute.STDDEV, MetricAttribute.P95);
+
+        final ConsoleReporter customReporter = ConsoleReporter.forRegistry(registry)
+                .outputTo(output)
+                .formattedFor(locale)
+                .withClock(clock)
+                .formattedFor(timeZone)
+                .convertRatesTo(TimeUnit.SECONDS)
+                .convertDurationsTo(TimeUnit.MILLISECONDS)
+                .filter(MetricFilter.ALL)
+                .disabledMetricAttributes(disabledMetricAttributes)
+                .build();
+
+        final Histogram histogram = mock(Histogram.class);
+        when(histogram.getCount()).thenReturn(1L);
+
+        final Snapshot snapshot = mock(Snapshot.class);
+        when(snapshot.getMax()).thenReturn(2L);
+        when(snapshot.getMean()).thenReturn(3.0);
+        when(snapshot.getMin()).thenReturn(4L);
+        when(snapshot.getStdDev()).thenReturn(5.0);
+        when(snapshot.getMedian()).thenReturn(6.0);
+        when(snapshot.get75thPercentile()).thenReturn(7.0);
+        when(snapshot.get95thPercentile()).thenReturn(8.0);
+        when(snapshot.get98thPercentile()).thenReturn(9.0);
+        when(snapshot.get99thPercentile()).thenReturn(10.0);
+        when(snapshot.get999thPercentile()).thenReturn(11.0);
+
+        when(histogram.getSnapshot()).thenReturn(snapshot);
+
+        customReporter.report(this.<Gauge>map(),
+                this.<Counter>map(),
+                map("test.histogram", histogram),
+                this.<Meter>map(),
+                this.<Timer>map());
+
+        assertThat(consoleOutput())
+                .isEqualTo(lines(
+                        "3/17/13 6:04:36 PM =============================================================",
+                        "",
+                        "-- Histograms ------------------------------------------------------------------",
+                        "test.histogram",
+                        "             count = 1",
+                        "              mean = 3.00",
+                        "            median = 6.00",
+                        "              75% <= 7.00",
+                        "              98% <= 9.00",
+                        "              99% <= 10.00",
+                        "            99.9% <= 11.00",
+                        "",
+                        ""
+                ));
+    }
+
     private String lines(String... lines) {
         final StringBuilder builder = new StringBuilder();
         for (String line : lines) {
diff --git a/metrics-core/src/test/java/com/codahale/metrics/CounterTest.java b/metrics-core/src/test/java/com/codahale/metrics/CounterTest.java
index 113645e..f2a3f46 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/CounterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/CounterTest.java
@@ -44,4 +44,20 @@ public class CounterTest {
         assertThat(counter.getCount())
                 .isEqualTo(-12);
     }
+
+    @Test
+    public void incrementByNegativeDelta() throws Exception {
+        counter.inc(-12);
+
+        assertThat(counter.getCount())
+                .isEqualTo(-12);
+    }
+
+    @Test
+    public void decrementByNegativeDelta() throws Exception {
+        counter.dec(-12);
+
+        assertThat(counter.getCount())
+                .isEqualTo(12);
+    }
 }
diff --git a/metrics-core/src/test/java/com/codahale/metrics/CsvReporterTest.java b/metrics-core/src/test/java/com/codahale/metrics/CsvReporterTest.java
index 0a9e178..ae40c2f 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/CsvReporterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/CsvReporterTest.java
@@ -5,16 +5,16 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
-import java.io.*;
-import java.nio.CharBuffer;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.util.Locale;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 public class CsvReporterTest {
     @Rule public final TemporaryFolder folder = new TemporaryFolder();
@@ -166,6 +166,27 @@ public class CsvReporterTest {
                 ));
     }
 
+    @Test
+    public void testCsvFileProviderIsUsed() {
+        CsvFileProvider fileProvider = mock(CsvFileProvider.class);
+        when(fileProvider.getFile(dataDirectory, "gauge")).thenReturn(new File(dataDirectory, "guage.csv"));
+
+        CsvReporter reporter = CsvReporter.forRegistry(registry)
+                .withCsvFileProvider(fileProvider)
+                .build(dataDirectory);
+
+        final Gauge gauge = mock(Gauge.class);
+        when(gauge.getValue()).thenReturn(1);
+
+        reporter.report(map("gauge", gauge),
+                this.<Counter>map(),
+                this.<Histogram>map(),
+                this.<Meter>map(),
+                this.<Timer>map());
+
+        verify(fileProvider).getFile(dataDirectory, "gauge");
+    }
+
     private String csv(String... lines) {
         final StringBuilder builder = new StringBuilder();
         for (String line : lines) {
@@ -175,21 +196,7 @@ public class CsvReporterTest {
     }
 
     private String fileContents(String filename) throws IOException {
-        final StringBuilder builder = new StringBuilder();
-        final FileInputStream input = new FileInputStream(new File(dataDirectory, filename));
-        try {
-            final InputStreamReader reader = new InputStreamReader(input);
-            final BufferedReader bufferedReader = new BufferedReader(reader);
-            final CharBuffer buf = CharBuffer.allocate(1024);
-            while (bufferedReader.read(buf) != -1) {
-                buf.flip();
-                builder.append(buf);
-                buf.clear();
-            }
-        } finally {
-            input.close();
-        }
-        return builder.toString();
+        return new String(Files.readAllBytes(new File(dataDirectory, filename).toPath()));
     }
 
     private <T> SortedMap<String, T> map() {
diff --git a/metrics-core/src/test/java/com/codahale/metrics/ExponentiallyDecayingReservoirTest.java b/metrics-core/src/test/java/com/codahale/metrics/ExponentiallyDecayingReservoirTest.java
index 953bbce..f6ee786 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/ExponentiallyDecayingReservoirTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/ExponentiallyDecayingReservoirTest.java
@@ -1,7 +1,10 @@
 package com.codahale.metrics;
 
 import org.junit.Test;
+
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -84,7 +87,7 @@ public class ExponentiallyDecayingReservoirTest {
         clock.addHours(15);
         reservoir.update(2000);
         assertThat(reservoir.getSnapshot().size())
-                .isEqualTo(2);
+                .isEqualTo(1);
         assertAllValuesBetween(reservoir, 1000, 3000);
 
 
@@ -99,6 +102,142 @@ public class ExponentiallyDecayingReservoirTest {
     }
 
     @Test
+    public void longPeriodsOfInactivity_fetchShouldResample() {
+        final ManualClock clock = new ManualClock();
+        final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(10,
+                0.015,
+                clock);
+
+        // add 1000 values at a rate of 10 values/second
+        for (int i = 0; i < 1000; i++) {
+            reservoir.update(1000 + i);
+            clock.addMillis(100);
+        }
+        assertThat(reservoir.getSnapshot().size())
+                .isEqualTo(10);
+        assertAllValuesBetween(reservoir, 1000, 2000);
+
+        // wait for 15 hours and add another value.
+        // this should trigger a rescale. Note that the number of samples will be reduced to 2
+        // because of the very small scaling factor that will make all existing priorities equal to
+        // zero after rescale.
+        clock.addHours(20);
+
+        Snapshot snapshot = reservoir.getSnapshot();
+        assertThat(snapshot.getMax()).isEqualTo(0);
+        assertThat(snapshot.getMean()).isEqualTo(0);
+        assertThat(snapshot.getMedian()).isEqualTo(0);
+        assertThat(snapshot.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void emptyReservoirSnapshot_shouldReturnZeroForAllValues() {
+        final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(100, 0.015,
+                new ManualClock());
+
+        Snapshot snapshot = reservoir.getSnapshot();
+        assertThat(snapshot.getMax()).isEqualTo(0);
+        assertThat(snapshot.getMean()).isEqualTo(0);
+        assertThat(snapshot.getMedian()).isEqualTo(0);
+        assertThat(snapshot.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void multipleUpdatesAfterlongPeriodsOfInactivityShouldNotCorruptSamplingState () throws Exception {
+        // This test illustrates the potential race condition in rescale that
+        // can lead to a corrupt state.  Note that while this test uses updates
+        // exclusively to trigger the race condition, two concurrent updates
+        // may be made much more likely to trigger this behavior if executed
+        // while another thread is constructing a snapshot of the reservoir;
+        // that thread then holds the read lock when the two competing updates
+        // are executed and the race condition's window is substantially
+        // expanded.
+
+        // Run the test several times.
+        for (int attempt=0; attempt < 10; attempt++) {
+            final ManualClock clock = new ManualClock();
+            final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(10,
+                    0.015,
+                    clock);
+
+            // Various atomics used to communicate between this thread and the
+            // thread created below.
+            final AtomicBoolean running = new AtomicBoolean(true);
+            final AtomicInteger threadUpdates = new AtomicInteger(0);
+            final AtomicInteger testUpdates = new AtomicInteger(0);
+
+            final Thread thread = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    int previous = 0;
+                    while (running.get()) {
+                        // Wait for the test thread to update it's counter
+                        // before updaing the reservoir.
+                        int next;
+                        while (previous >= (next = testUpdates.get()))
+                            ; // spin lock
+
+                        previous = next;
+
+                        // Update the reservoir.  This needs to occur at the
+                        // same time as the test thread's update.
+                        reservoir.update(1000);
+
+                        // Signal the main thread; allows the next update
+                        // attempt to begin.
+                        threadUpdates.incrementAndGet();
+                    }
+                }
+            });
+
+            thread.start();
+
+            int sum = 0;
+            int previous = -1;
+            for (int i = 0; i < 100; i++) {
+                // Wait for 24 hours before attempting the next concurrent
+                // update.  The delay here needs to be sufficiently long to
+                // overflow if an update attempt is allowed to add a value to
+                // the reservoir without rescaling.  Note that:
+                // e(alpha*(15*60*60)) =~ 10^351 >> Double.MAX_VALUE =~ 1.8*10^308.
+                clock.addHours(15);
+
+                // Signal the other thread; asynchronously updates the reservoir.
+                testUpdates.incrementAndGet();
+
+                // Delay a variable length of time.  Without a delay here this
+                // thread is heavily favored and the race condition is almost
+                // never observed.
+                for (int j = 0; j < i; j++)
+                    sum += j;
+
+                // Competing reservoir update.
+                reservoir.update(1000);
+
+                // Wait for the other thread to finish it's update.
+                int next;
+                while (previous >= (next = threadUpdates.get()))
+                    ; // spin lock
+
+                previous = next;
+            }
+
+            // Terminate the thread.
+            running.set(false);
+            testUpdates.incrementAndGet();
+            thread.join();
+
+            // Test failures will result in normWeights that are not finite;
+            // checking the mean value here is sufficient.
+            assertThat(reservoir.getSnapshot().getMean()).isBetween(0.0, Double.MAX_VALUE);
+
+            // Check the value of sum; should prevent the JVM from optimizing
+            // out the delay loop entirely.
+            assertThat(sum).isEqualTo(161700);
+        }
+    }
+
+    @Test
     public void spotLift() {
         final ManualClock clock = new ManualClock();
         final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1000,
diff --git a/metrics-core/src/test/java/com/codahale/metrics/FixedNameCsvFileProviderTest.java b/metrics-core/src/test/java/com/codahale/metrics/FixedNameCsvFileProviderTest.java
new file mode 100644
index 0000000..d9a05a5
--- /dev/null
+++ b/metrics-core/src/test/java/com/codahale/metrics/FixedNameCsvFileProviderTest.java
@@ -0,0 +1,38 @@
+package com.codahale.metrics;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class FixedNameCsvFileProviderTest {
+    @Rule
+    public final TemporaryFolder folder = new TemporaryFolder();
+
+    private File dataDirectory;
+
+    @Before
+    public void setUp() throws Exception {
+        this.dataDirectory = folder.newFolder();
+    }
+
+    @Test
+    public void testGetFile() {
+        FixedNameCsvFileProvider provider = new FixedNameCsvFileProvider();
+        File file = provider.getFile(dataDirectory, "test");
+        assertThat(file.getParentFile()).isEqualTo(dataDirectory);
+        assertThat(file.getName()).isEqualTo("test.csv");
+    }
+
+    @Test
+    public void testGetFileSanitize() {
+        FixedNameCsvFileProvider provider = new FixedNameCsvFileProvider();
+        File file = provider.getFile(dataDirectory, "/myfake/uri");
+        assertThat(file.getParentFile()).isEqualTo(dataDirectory);
+        assertThat(file.getName()).isEqualTo("myfake.uri.csv");
+    }
+}
\ No newline at end of file
diff --git a/metrics-core/src/test/java/com/codahale/metrics/InstrumentedExecutorServiceTest.java b/metrics-core/src/test/java/com/codahale/metrics/InstrumentedExecutorServiceTest.java
index 6d657a3..4e8eebd 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/InstrumentedExecutorServiceTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/InstrumentedExecutorServiceTest.java
@@ -2,6 +2,8 @@ package com.codahale.metrics;
 
 import org.junit.After;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -11,6 +13,9 @@ import java.util.concurrent.TimeUnit;
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class InstrumentedExecutorServiceTest {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(InstrumentedExecutorServiceTest.class);
+
     private final ExecutorService executor = Executors.newCachedThreadPool();
     private final MetricRegistry registry = new MetricRegistry();
     private final InstrumentedExecutorService instrumentedExecutorService = new InstrumentedExecutorService(executor, registry, "xs");
@@ -49,7 +54,7 @@ public class InstrumentedExecutorServiceTest {
     public void tearDown() throws Exception {
         instrumentedExecutorService.shutdown();
         if (!instrumentedExecutorService.awaitTermination(2, TimeUnit.SECONDS)) {
-            System.err.println("InstrumentedExecutorService did not terminate.");
+            LOGGER.error("InstrumentedExecutorService did not terminate.");
         }
     }
 
diff --git a/metrics-core/src/test/java/com/codahale/metrics/InstrumentedScheduledExecutorServiceTest.java b/metrics-core/src/test/java/com/codahale/metrics/InstrumentedScheduledExecutorServiceTest.java
index fc2fda0..2061372 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/InstrumentedScheduledExecutorServiceTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/InstrumentedScheduledExecutorServiceTest.java
@@ -2,12 +2,16 @@ package com.codahale.metrics;
 
 import org.junit.After;
 import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.concurrent.*;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class InstrumentedScheduledExecutorServiceTest {
+    private static final Logger LOGGER = LoggerFactory.getLogger(InstrumentedScheduledExecutorServiceTest.class);
+
     private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
     private final MetricRegistry registry = new MetricRegistry();
     private final InstrumentedScheduledExecutorService instrumentedScheduledExecutor = new InstrumentedScheduledExecutorService(scheduledExecutor, registry, "xs");
@@ -256,7 +260,7 @@ public class InstrumentedScheduledExecutorServiceTest {
     public void tearDown() throws Exception {
         instrumentedScheduledExecutor.shutdown();
         if (!instrumentedScheduledExecutor.awaitTermination(2, TimeUnit.SECONDS)) {
-            System.err.println("InstrumentedScheduledExecutorService did not terminate.");
+            LOGGER.error("InstrumentedScheduledExecutorService did not terminate.");
         }
     }
 
diff --git a/metrics-core/src/test/java/com/codahale/metrics/JmxAttributeGaugeTest.java b/metrics-core/src/test/java/com/codahale/metrics/JmxAttributeGaugeTest.java
index 719cd43..02f77f6 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/JmxAttributeGaugeTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/JmxAttributeGaugeTest.java
@@ -1,34 +1,99 @@
 package com.codahale.metrics;
 
-import org.junit.Test;
+import static org.assertj.core.api.Assertions.assertThat;
 
-import javax.management.AttributeNotFoundException;
+import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.management.JMException;
 import javax.management.MBeanServer;
+import javax.management.ObjectInstance;
 import javax.management.ObjectName;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
 
 public class JmxAttributeGaugeTest {
-    private final MBeanServer mBeanServer = mock(MBeanServer.class);
-    private final ObjectName objectName = mock(ObjectName.class);
-    private final JmxAttributeGauge gauge = new JmxAttributeGauge(mBeanServer, objectName, "attr");
-    private final Object value = mock(Object.class);
+
+    private static MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
+
+    private static List<ObjectName> registeredMBeans = new ArrayList<ObjectName>();
+
+    public interface JmxTestMBean {
+        Long getValue();
+    }
+
+    private static class JmxTest implements JmxTestMBean {
+        @Override
+        public Long getValue() {
+            return Long.MAX_VALUE;
+        }
+    }
+
+    @BeforeClass
+    public static void setUp() throws Exception {
+        registerMBean(new ObjectName("JmxAttributeGaugeTest:type=test,name=test1"));
+        registerMBean(new ObjectName("JmxAttributeGaugeTest:type=test,name=test2"));
+    }
+
+    @AfterClass
+    public static void tearDown() {
+        for (ObjectName objectName : registeredMBeans) {
+            try {
+                mBeanServer.unregisterMBean(objectName);
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+    }
 
     @Test
-    public void returnsAJmxAttribute() throws Exception {
-        when(mBeanServer.getAttribute(objectName, "attr")).thenReturn(value);
+    public void returnsJmxAttribute() throws Exception {
+        ObjectName objectName = new ObjectName("java.lang:type=ClassLoading");
+        JmxAttributeGauge gauge = new JmxAttributeGauge(mBeanServer, objectName, "LoadedClassCount");
 
-        assertThat(gauge.getValue())
-                .isEqualTo(value);
+        assertThat(gauge.getValue()).isInstanceOf(Integer.class);
+        assertThat((Integer) gauge.getValue()).isGreaterThan(0);
     }
 
     @Test
-    public void returnsNullIfThereIsAnException() throws Exception {
-        when(mBeanServer.getAttribute(objectName, "attr")).thenThrow(new AttributeNotFoundException());
+    public void returnsNullIfAttributeDoesNotExist() throws Exception {
+        ObjectName objectName = new ObjectName("java.lang:type=ClassLoading");
+        JmxAttributeGauge gauge = new JmxAttributeGauge(mBeanServer, objectName, "DoesNotExist");
 
-        assertThat(gauge.getValue())
-                .isNull();
+        assertThat(gauge.getValue()).isNull();
     }
+
+    @Test
+    public void returnsNullIfMBeanNotFound() throws Exception {
+        ObjectName objectName = new ObjectName("foo.bar:type=NoSuchMBean");
+        JmxAttributeGauge gauge = new JmxAttributeGauge(mBeanServer, objectName, "LoadedClassCount");
+
+        assertThat(gauge.getValue()).isNull();
+    }
+
+    @Test
+    public void returnsAttributeForObjectNamePattern() throws Exception {
+        ObjectName objectName = new ObjectName("JmxAttributeGaugeTest:name=test1,*");
+        JmxAttributeGauge gauge = new JmxAttributeGauge(mBeanServer, objectName, "Value");
+
+        assertThat(gauge.getValue()).isInstanceOf(Long.class);
+        assertThat((Long) gauge.getValue()).isEqualTo(Long.MAX_VALUE);
+    }
+
+    @Test
+    public void returnsNullIfObjectNamePatternAmbiguous() throws Exception {
+        ObjectName objectName = new ObjectName("JmxAttributeGaugeTest:type=test,*");
+        JmxAttributeGauge gauge = new JmxAttributeGauge(mBeanServer, objectName, "Value");
+
+        assertThat(gauge.getValue()).isNull();
+    }
+
+    private static void registerMBean(ObjectName objectName) throws JMException {
+        ObjectInstance objectInstance = mBeanServer.registerMBean(new JmxTest(), objectName);
+        registeredMBeans.add(objectInstance.getObjectName());
+    }
+
 }
diff --git a/metrics-core/src/test/java/com/codahale/metrics/JmxReporterTest.java b/metrics-core/src/test/java/com/codahale/metrics/JmxReporterTest.java
index 0463f83..2ae96af 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/JmxReporterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/JmxReporterTest.java
@@ -54,6 +54,7 @@ public class JmxReporterTest {
         when(hSnapshot.get98thPercentile()).thenReturn(9.0);
         when(hSnapshot.get99thPercentile()).thenReturn(10.0);
         when(hSnapshot.get999thPercentile()).thenReturn(11.0);
+        when(hSnapshot.size()).thenReturn(1);
 
         when(histogram.getSnapshot()).thenReturn(hSnapshot);
 
@@ -80,6 +81,7 @@ public class JmxReporterTest {
         when(tSnapshot.get98thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(800));
         when(tSnapshot.get99thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(900));
         when(tSnapshot.get999thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(1000));
+        when(tSnapshot.size()).thenReturn(1);
 
         when(timer.getSnapshot()).thenReturn(tSnapshot);
 
@@ -152,7 +154,8 @@ public class JmxReporterTest {
                                                        "95thPercentile",
                                                        "98thPercentile",
                                                        "99thPercentile",
-                                                       "999thPercentile");
+                                                       "999thPercentile",
+                                                       "SnapshotSize");
 
         assertThat(values(attributes))
                 .contains(entry("Count", 1L))
@@ -165,7 +168,9 @@ public class JmxReporterTest {
                 .contains(entry("95thPercentile", 8.0))
                 .contains(entry("98thPercentile", 9.0))
                 .contains(entry("99thPercentile", 10.0))
-                .contains(entry("999thPercentile", 11.0));
+                .contains(entry("999thPercentile", 11.0))
+                .contains(entry("SnapshotSize", 1L))
+        ;
     }
 
     @Test
diff --git a/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryTest.java b/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryTest.java
index 5032ae5..f314c19 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/MetricRegistryTest.java
@@ -64,6 +64,24 @@ public class MetricRegistryTest {
     }
 
     @Test
+    public void accessingACustomCounterRegistersAndReusesTheCounter() throws Exception {
+        final MetricRegistry.MetricSupplier<Counter> supplier = new MetricRegistry.MetricSupplier<Counter>() {
+            @Override
+            public Counter newMetric() {
+                return counter;
+            }
+        };
+        final Counter counter1 = registry.counter("thing", supplier);
+        final Counter counter2 = registry.counter("thing", supplier);
+
+        assertThat(counter1)
+                .isSameAs(counter2);
+
+        verify(listener).onCounterAdded("thing", counter1);
+    }
+
+
+    @Test
     public void removingACounterTriggersANotification() throws Exception {
         registry.register("thing", counter);
 
@@ -93,6 +111,23 @@ public class MetricRegistryTest {
     }
 
     @Test
+    public void accessingACustomHistogramRegistersAndReusesIt() throws Exception {
+        final MetricRegistry.MetricSupplier<Histogram> supplier = new MetricRegistry.MetricSupplier<Histogram>() {
+            @Override
+            public Histogram newMetric() {
+                return histogram;
+            }
+        };
+        final Histogram histogram1 = registry.histogram("thing", supplier);
+        final Histogram histogram2 = registry.histogram("thing", supplier);
+
+        assertThat(histogram1)
+                .isSameAs(histogram2);
+
+        verify(listener).onHistogramAdded("thing", histogram1);
+    }
+
+    @Test
     public void removingAHistogramTriggersANotification() throws Exception {
         registry.register("thing", histogram);
 
@@ -122,6 +157,23 @@ public class MetricRegistryTest {
     }
 
     @Test
+    public void accessingACustomMeterRegistersAndReusesIt() throws Exception {
+        final MetricRegistry.MetricSupplier<Meter> supplier = new MetricRegistry.MetricSupplier<Meter>() {
+            @Override
+            public Meter newMetric() {
+                return meter;
+            }
+        };
+        final Meter meter1 = registry.meter("thing", supplier);
+        final Meter meter2 = registry.meter("thing", supplier);
+
+        assertThat(meter1)
+                .isSameAs(meter2);
+
+        verify(listener).onMeterAdded("thing", meter1);
+    }
+
+        @Test
     public void removingAMeterTriggersANotification() throws Exception {
         registry.register("thing", meter);
 
@@ -151,6 +203,24 @@ public class MetricRegistryTest {
     }
 
     @Test
+    public void accessingACustomTimerRegistersAndReusesIt() throws Exception {
+        final MetricRegistry.MetricSupplier<Timer> supplier = new MetricRegistry.MetricSupplier<Timer>() {
+            @Override
+            public Timer newMetric() {
+                return timer;
+            }
+        };
+        final Timer timer1 = registry.timer("thing", supplier);
+        final Timer timer2 = registry.timer("thing", supplier);
+
+        assertThat(timer1)
+                .isSameAs(timer2);
+
+        verify(listener).onTimerAdded("thing", timer1);
+    }
+
+
+    @Test
     public void removingATimerTriggersANotification() throws Exception {
         registry.register("thing", timer);
 
@@ -161,6 +231,24 @@ public class MetricRegistryTest {
     }
 
     @Test
+    public void accessingACustomGaugeRegistersAndReusesIt() throws Exception {
+        final MetricRegistry.MetricSupplier<Gauge> supplier = new MetricRegistry.MetricSupplier<Gauge>() {
+            @Override
+            public Gauge newMetric() {
+                return gauge;
+            }
+        };
+        final Gauge gauge1 = registry.gauge("thing", supplier);
+        final Gauge gauge2 = registry.gauge("thing", supplier);
+
+        assertThat(gauge1)
+                .isSameAs(gauge2);
+
+        verify(listener).onGaugeAdded("thing", gauge1);
+    }
+
+
+    @Test
     public void addingAListenerWithExistingMetricsCatchesItUp() throws Exception {
         registry.register("gauge", gauge);
         registry.register("counter", counter);
@@ -357,7 +445,7 @@ public class MetricRegistryTest {
         };
 
         assertThat(name(g.getClass(), "one", "two"))
-                .isEqualTo("com.codahale.metrics.MetricRegistryTest$5.one.two");
+                .isEqualTo("com.codahale.metrics.MetricRegistryTest$10.one.two");
     }
 
     @Test
diff --git a/metrics-core/src/test/java/com/codahale/metrics/ScheduledReporterTest.java b/metrics-core/src/test/java/com/codahale/metrics/ScheduledReporterTest.java
index f8456c0..880ebc6 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/ScheduledReporterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/ScheduledReporterTest.java
@@ -6,8 +6,12 @@ import org.junit.Test;
 
 import java.util.SortedMap;
 import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.*;
 
 public class ScheduledReporterTest {
@@ -17,23 +21,21 @@ public class ScheduledReporterTest {
     private final Meter meter = mock(Meter.class);
     private final Timer timer = mock(Timer.class);
 
+    private final ScheduledExecutorService mockExecutor = mock(ScheduledExecutorService.class);
+    private final ScheduledExecutorService customExecutor = Executors.newSingleThreadScheduledExecutor();
+    private final ScheduledExecutorService externalExecutor = Executors.newSingleThreadScheduledExecutor();
+
     private final MetricRegistry registry = new MetricRegistry();
     private final ScheduledReporter reporter = spy(
-            new ScheduledReporter(registry,
-                                  "example",
-                                  MetricFilter.ALL,
-                                  TimeUnit.SECONDS,
-                                  TimeUnit.MILLISECONDS) {
-                @Override
-                public void report(SortedMap<String, Gauge> gauges,
-                                   SortedMap<String, Counter> counters,
-                                   SortedMap<String, Histogram> histograms,
-                                   SortedMap<String, Meter> meters,
-                                   SortedMap<String, Timer> timers) {
-                    // nothing doing!
-                }
-            }
+            new DummyReporter(registry, "example", MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS)
+    );
+    private final ScheduledReporter reporterWithNullExecutor = spy(
+            new DummyReporter(registry, "example", MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS, null)
     );
+    private final ScheduledReporter reporterWithCustomMockExecutor = new DummyReporter(registry, "example", MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS, mockExecutor);
+    private final ScheduledReporter reporterWithCustomExecutor = new DummyReporter(registry, "example", MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS, customExecutor);
+    private final DummyReporter reporterWithExternallyManagedExecutor = new DummyReporter(registry, "example", MetricFilter.ALL, TimeUnit.SECONDS, TimeUnit.MILLISECONDS, externalExecutor, false);
+    private final ScheduledReporter[] reporters = new ScheduledReporter[] {reporter, reporterWithCustomExecutor, reporterWithExternallyManagedExecutor};
 
     @Before
     public void setUp() throws Exception {
@@ -42,17 +44,20 @@ public class ScheduledReporterTest {
         registry.register("histogram", histogram);
         registry.register("meter", meter);
         registry.register("timer", timer);
-
-        reporter.start(200, TimeUnit.MILLISECONDS);
     }
 
     @After
     public void tearDown() throws Exception {
+        customExecutor.shutdown();
+        externalExecutor.shutdown();
         reporter.stop();
+        reporterWithNullExecutor.stop();
     }
 
     @Test
     public void pollsPeriodically() throws Exception {
+        reporter.start(200, TimeUnit.MILLISECONDS);
+
         Thread.sleep(500);
         verify(reporter, times(2)).report(
                 map("gauge", gauge),
@@ -63,9 +68,133 @@ public class ScheduledReporterTest {
         );
     }
 
+    @Test
+    public void shouldUsePeriodAsInitialDelayIfNotSpecifiedOtherwise() throws Exception {
+        reporterWithCustomMockExecutor.start(200, TimeUnit.MILLISECONDS);
+
+        verify(mockExecutor, times(1)).scheduleAtFixedRate(
+            any(Runnable.class), eq(200L), eq(200L), eq(TimeUnit.MILLISECONDS)
+        );
+    }
+
+    @Test
+    public void shouldStartWithSpecifiedInitialDelay() throws Exception {
+        reporterWithCustomMockExecutor.start(350, 100, TimeUnit.MILLISECONDS);
+
+        verify(mockExecutor).scheduleAtFixedRate(
+            any(Runnable.class), eq(350L), eq(100L), eq(TimeUnit.MILLISECONDS)
+        );
+    }
+
+    @Test
+    public void shouldAutoCreateExecutorWhenItNull() throws Exception {
+        reporterWithNullExecutor.start(200, TimeUnit.MILLISECONDS);
+
+        Thread.sleep(500);
+        verify(reporterWithNullExecutor, times(2)).report(
+                map("gauge", gauge),
+                map("counter", counter),
+                map("histogram", histogram),
+                map("meter", meter),
+                map("timer", timer)
+        );
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldDisallowToStartReportingMultiple() throws Exception {
+        reporter.start(200, TimeUnit.MILLISECONDS);
+        reporter.start(200, TimeUnit.MILLISECONDS);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldDisallowToStartReportingMultipleTimesOnCustomExecutor() throws Exception {
+        reporterWithCustomExecutor.start(200, TimeUnit.MILLISECONDS);
+        reporterWithCustomExecutor.start(200, TimeUnit.MILLISECONDS);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldDisallowToStartReportingMultipleTimesOnExternallyManagedExecutor() throws Exception {
+        reporterWithExternallyManagedExecutor.start(200, TimeUnit.MILLISECONDS);
+        reporterWithExternallyManagedExecutor.start(200, TimeUnit.MILLISECONDS);
+    }
+
+    @Test
+    public void shouldNotFailOnStopIfReporterWasNotStared() {
+        for (ScheduledReporter reporter : reporters) {
+            reporter.stop();
+        }
+    }
+
+    @Test
+    public void shouldNotFailWhenStoppingMultipleTimes() {
+        for (ScheduledReporter reporter : reporters) {
+            reporter.start(200, TimeUnit.MILLISECONDS);
+            reporter.stop();
+            reporter.stop();
+            reporter.stop();
+        }
+    }
+
+    @Test
+    public void shouldShutdownExecutorOnStopByDefault() {
+        reporterWithCustomExecutor.start(200, TimeUnit.MILLISECONDS);
+        reporterWithCustomExecutor.stop();
+        assertTrue(customExecutor.isTerminated());
+    }
+
+    @Test
+    public void shouldNotShutdownExternallyManagedExecutorOnStop() {
+        reporterWithExternallyManagedExecutor.start(200, TimeUnit.MILLISECONDS);
+        reporterWithExternallyManagedExecutor.stop();
+        assertFalse(mockExecutor.isTerminated());
+        assertFalse(mockExecutor.isShutdown());
+    }
+
+    @Test
+    public void shouldCancelScheduledFutureWhenStoppingWithExternallyManagedExecutor() throws InterruptedException, ExecutionException, TimeoutException {
+        // configure very frequency rate of execution
+        reporterWithExternallyManagedExecutor.start(1, TimeUnit.MILLISECONDS);
+        reporterWithExternallyManagedExecutor.stop();
+        Thread.sleep(100);
+
+        // executionCount should not increase when scheduled future is canceled properly
+        int executionCount = reporterWithExternallyManagedExecutor.executionCount.get();
+        Thread.sleep(500);
+        assertEquals(executionCount, reporterWithExternallyManagedExecutor.executionCount.get());
+    }
+
+    @Test
+    public void shouldConvertDurationToMillisecondsPrecisely() {
+        assertEquals(2.0E-5, reporter.convertDuration(20), 0.0);
+    }
+
     private <T> SortedMap<String, T> map(String name, T value) {
         final SortedMap<String, T> map = new TreeMap<String, T>();
         map.put(name, value);
         return map;
     }
+
+    private static class DummyReporter extends ScheduledReporter {
+
+        private AtomicInteger executionCount = new AtomicInteger();
+
+        public DummyReporter(MetricRegistry registry, String name, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit) {
+            super(registry, name, filter, rateUnit, durationUnit);
+        }
+
+        public DummyReporter(MetricRegistry registry, String name, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit, ScheduledExecutorService executor) {
+            super(registry, name, filter, rateUnit, durationUnit, executor);
+        }
+
+        public DummyReporter(MetricRegistry registry, String name, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit, ScheduledExecutorService executor, boolean shutdownExecutorOnStop) {
+            super(registry, name, filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop);
+        }
+
+        @Override
+        public void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters, SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers) {
+            executionCount.incrementAndGet();
+            // nothing doing!
+        }
+    }
+
 }
diff --git a/metrics-core/src/test/java/com/codahale/metrics/SharedMetricRegistriesTest.java b/metrics-core/src/test/java/com/codahale/metrics/SharedMetricRegistriesTest.java
index 0394cf0..e9637ac 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/SharedMetricRegistriesTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/SharedMetricRegistriesTest.java
@@ -5,14 +5,17 @@ import org.junit.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.concurrent.atomic.AtomicReference;
+
 public class SharedMetricRegistriesTest {
     @Before
     public void setUp() throws Exception {
+        SharedMetricRegistries.setDefaultRegistryName(new AtomicReference<String>());
         SharedMetricRegistries.clear();
     }
 
     @Test
-    public void memoizesRegistriesByName() throws Exception {
+    public void memorizesRegistriesByName() throws Exception {
         final MetricRegistry one = SharedMetricRegistries.getOrCreate("one");
         final MetricRegistry two = SharedMetricRegistries.getOrCreate("one");
 
@@ -51,4 +54,43 @@ public class SharedMetricRegistriesTest {
         assertThat(SharedMetricRegistries.names())
                 .isEmpty();
     }
+
+    @Test
+    public void errorsWhenDefaultUnset() throws Exception {
+        try {
+            SharedMetricRegistries.getDefault();
+        } catch (final Exception e) {
+            assertThat(e).isInstanceOf(IllegalStateException.class);
+            assertThat(e.getMessage()).isEqualTo("Default registry name has not been set.");
+        }
+    }
+
+    @Test
+    public void createsDefaultRegistries() throws Exception {
+        final String defaultName = "default";
+        final MetricRegistry registry = SharedMetricRegistries.setDefault(defaultName);
+        assertThat(registry).isNotNull();
+        assertThat(SharedMetricRegistries.getDefault()).isEqualTo(registry);
+        assertThat(SharedMetricRegistries.getOrCreate(defaultName)).isEqualTo(registry);
+    }
+
+    @Test
+    public void errorsWhenDefaultAlreadySet() throws Exception {
+        try {
+            SharedMetricRegistries.setDefault("foobah");
+            SharedMetricRegistries.setDefault("borg");
+        } catch (final Exception e) {
+            assertThat(e).isInstanceOf(IllegalStateException.class);
+            assertThat(e.getMessage()).isEqualTo("Default metric registry name is already set.");
+        }
+    }
+
+    @Test
+    public void setsDefaultExistingRegistries() throws Exception {
+        final String defaultName = "default";
+        final MetricRegistry registry = new MetricRegistry();
+        assertThat(SharedMetricRegistries.setDefault(defaultName, registry)).isEqualTo(registry);
+        assertThat(SharedMetricRegistries.getDefault()).isEqualTo(registry);
+        assertThat(SharedMetricRegistries.getOrCreate(defaultName)).isEqualTo(registry);
+    }
 }
diff --git a/metrics-core/src/test/java/com/codahale/metrics/Slf4jReporterTest.java b/metrics-core/src/test/java/com/codahale/metrics/Slf4jReporterTest.java
index eeb52bc..c0575f4 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/Slf4jReporterTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/Slf4jReporterTest.java
@@ -42,7 +42,7 @@ public class Slf4jReporterTest {
                 this.<Meter>map(),
                 this.<Timer>map());
 
-        verify(logger).error(marker, "type=GAUGE, name={}, value={}", new Object[]{"gauge", "value"});
+        verify(logger).error(marker, "type={}, name={}, value={}", new Object[]{"GAUGE", "gauge", "value"});
     }
 
     @Test
@@ -57,7 +57,7 @@ public class Slf4jReporterTest {
                 this.<Meter>map(),
                 this.<Timer>map());
 
-        verify(logger).error(marker, "type=COUNTER, name={}, count={}", new Object[]{"test.counter", 100L});
+        verify(logger).error(marker, "type={}, name={}, count={}", new Object[]{"COUNTER", "test.counter", 100L});
     }
 
     @Test
@@ -87,7 +87,8 @@ public class Slf4jReporterTest {
                 this.<Timer>map());
 
         verify(logger).error(marker,
-                "type=HISTOGRAM, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}",
+                "type={}, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}",
+                "HISTOGRAM",
                 "test.histogram",
                 1L,
                 4L,
@@ -119,7 +120,8 @@ public class Slf4jReporterTest {
                 this.<Timer>map());
 
         verify(logger).error(marker,
-                "type=METER, name={}, count={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}",
+                "type={}, name={}, count={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}",
+                "METER",
                 "test.meter",
                 1L,
                 2.0,
@@ -163,7 +165,8 @@ public class Slf4jReporterTest {
                 map("test.another.timer", timer));
 
         verify(logger).error(marker,
-                "type=TIMER, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}, duration_unit={}",
+                "type={}, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}, duration_unit={}",
+                "TIMER",
                 "test.another.timer",
                 1L,
                 300.0,
@@ -193,7 +196,7 @@ public class Slf4jReporterTest {
                 this.<Meter>map(),
                 this.<Timer>map());
 
-        verify(logger).info(marker, "type=GAUGE, name={}, value={}", new Object[]{"prefix.gauge", "value"});
+        verify(logger).info(marker, "type={}, name={}, value={}", new Object[]{"GAUGE", "prefix.gauge", "value"});
     }
 
     @Test
@@ -208,7 +211,7 @@ public class Slf4jReporterTest {
                 this.<Meter>map(),
                 this.<Timer>map());
 
-        verify(logger).info(marker, "type=COUNTER, name={}, count={}", new Object[]{"prefix.test.counter", 100L});
+        verify(logger).info(marker, "type={}, name={}, count={}", new Object[]{"COUNTER", "prefix.test.counter", 100L});
     }
 
     @Test
@@ -238,7 +241,8 @@ public class Slf4jReporterTest {
                 this.<Timer>map());
 
         verify(logger).info(marker,
-                "type=HISTOGRAM, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}",
+                "type={}, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}",
+                "HISTOGRAM",
                 "prefix.test.histogram",
                 1L,
                 4L,
@@ -270,7 +274,8 @@ public class Slf4jReporterTest {
                 this.<Timer>map());
 
         verify(logger).info(marker,
-                "type=METER, name={}, count={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}",
+                "type={}, name={}, count={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}",
+                "METER",
                 "prefix.test.meter",
                 1L,
                 2.0,
@@ -313,7 +318,8 @@ public class Slf4jReporterTest {
                 map("test.another.timer", timer));
 
         verify(logger).info(marker,
-                "type=TIMER, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}, duration_unit={}",
+                "type={}, name={}, count={}, min={}, max={}, mean={}, stddev={}, median={}, p75={}, p95={}, p98={}, p99={}, p999={}, mean_rate={}, m1={}, m5={}, m15={}, rate_unit={}, duration_unit={}",
+                "TIMER",
                 "prefix.test.another.timer",
                 1L,
                 300.0,
diff --git a/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTest.java b/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTest.java
new file mode 100644
index 0000000..9636823
--- /dev/null
+++ b/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTest.java
@@ -0,0 +1,150 @@
+package com.codahale.metrics;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+ at SuppressWarnings("Duplicates")
+public class SlidingTimeWindowArrayReservoirTest {
+
+    @Test
+    public void storesMeasurementsWithDuplicateTicks() throws Exception {
+        final Clock clock = mock(Clock.class);
+        final SlidingTimeWindowArrayReservoir reservoir = new SlidingTimeWindowArrayReservoir(10, NANOSECONDS, clock);
+
+        when(clock.getTick()).thenReturn(20L);
+
+        reservoir.update(1);
+        reservoir.update(2);
+
+        assertThat(reservoir.getSnapshot().getValues())
+            .containsOnly(1, 2);
+    }
+
+    @Test
+    public void boundsMeasurementsToATimeWindow() throws Exception {
+        final Clock clock = mock(Clock.class);
+        final SlidingTimeWindowArrayReservoir reservoir = new SlidingTimeWindowArrayReservoir(10, NANOSECONDS, clock);
+
+        when(clock.getTick()).thenReturn(0L);
+        reservoir.update(1);
+
+        when(clock.getTick()).thenReturn(5L);
+        reservoir.update(2);
+
+        when(clock.getTick()).thenReturn(10L);
+        reservoir.update(3);
+
+        when(clock.getTick()).thenReturn(15L);
+        reservoir.update(4);
+
+        when(clock.getTick()).thenReturn(20L);
+        reservoir.update(5);
+
+        assertThat(reservoir.getSnapshot().getValues())
+            .containsOnly(4, 5);
+    }
+
+    @Test
+    public void comparisonResultsTest() {
+        int cycles = 1000000;
+        long time = (Long.MAX_VALUE / 256) - (long) (cycles * 0.5);
+        ManualClock manualClock = new ManualClock();
+        manualClock.addNanos(time);
+        int window = 300;
+        Random random = new Random(ThreadLocalRandom.current().nextInt());
+
+        SlidingTimeWindowReservoir treeReservoir = new SlidingTimeWindowReservoir(window, NANOSECONDS, manualClock);
+        SlidingTimeWindowArrayReservoir arrayReservoir = new SlidingTimeWindowArrayReservoir(window, NANOSECONDS, manualClock);
+
+        for (int i = 0; i < cycles; i++) {
+            manualClock.addNanos(1);
+            treeReservoir.update(i);
+            arrayReservoir.update(i);
+            if (random.nextDouble() < 0.01) {
+                long[] treeValues = treeReservoir.getSnapshot().getValues();
+                long[] arrValues = arrayReservoir.getSnapshot().getValues();
+                assertThat(arrValues).isEqualTo(treeValues);
+            }
+            if (random.nextDouble() < 0.05) {
+                assertThat(arrayReservoir.size()).isEqualTo(treeReservoir.size());
+            }
+        }
+    }
+
+    @Test
+    public void testGetTickOverflow() {
+        final Random random = new Random(0);
+        final int window = 128;
+        AtomicLong counter = new AtomicLong(0L);
+
+        // Note: 'threshold' defines the number of updates submitted to the reservoir after overflowing
+        for (int threshold : Arrays.asList(0, 1, 2, 127, 128, 129, 255, 256, 257)) {
+
+            // Note: 'updatePerTick' defines the number of updates submitted to the reservoir between each tick
+            for (int updatesPerTick : Arrays.asList(1, 2, 127, 128, 129, 255, 256, 257)) {
+                //logger.info("Executing test: threshold={}, updatesPerTick={}", threshold, updatesPerTick);
+
+                // Set the clock to overflow in (2*window+1)ns
+                final ManualClock clock = new ManualClock();
+                clock.addNanos(Long.MAX_VALUE / 256 - 2 * window - clock.getTick());
+                assertThat(clock.getTick() * 256).isGreaterThan(0);
+
+                // Create the reservoir
+                final SlidingTimeWindowArrayReservoir reservoir = new SlidingTimeWindowArrayReservoir(window, NANOSECONDS, clock);
+                int updatesAfterThreshold = 0;
+                while (true) {
+                    // Update the reservoir
+                    for (int i = 0; i < updatesPerTick; i++) {
+                        long l = counter.incrementAndGet();
+                        reservoir.update(l);
+                    }
+
+                    // Randomly check the reservoir size
+                    if (random.nextDouble() < 0.1) {
+                        assertThat(reservoir.size())
+                            .as("Bad reservoir size with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
+                            .isLessThanOrEqualTo(window * 256);
+                    }
+
+                    // Update the clock
+                    clock.addNanos(1);
+
+                    // If the clock has overflowed start counting updates
+                    if ((clock.getTick() * 256) < 0) {
+                        if (updatesAfterThreshold++ >= threshold) {
+                            break;
+                        }
+                    }
+                }
+
+                // Check the final reservoir size
+                assertThat(reservoir.size())
+                    .as("Bad final reservoir size with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
+                    .isLessThanOrEqualTo(window * 256);
+
+                // Advance the clock far enough to clear the reservoir.  Note that here the window only loosely defines
+                // the reservoir window; when updatesPerTick is greater than 128 the sliding window will always be well
+                // ahead of the current clock time, and advances in getTick while in trim (called randomly above from
+                // size and every 256 updates).  Until the clock "catches up" advancing the clock will have no effect on
+                // the reservoir, and reservoir.size() will merely move the window forward 1/256th of a ns - as such, an
+                // arbitrary increment of 1s here was used instead to advance the clock well beyond any updates recorded
+                // above.
+                clock.addSeconds(1);
+
+                // The reservoir should now be empty
+                assertThat(reservoir.size())
+                    .as("Bad reservoir size after delay with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
+                    .isEqualTo(0);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowReservoirTest.java b/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowReservoirTest.java
index e80e683..d4a0a37 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowReservoirTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/SlidingTimeWindowReservoirTest.java
@@ -2,18 +2,21 @@ package com.codahale.metrics;
 
 import org.junit.Test;
 
+import java.util.Arrays;
+import java.util.Random;
 import java.util.concurrent.TimeUnit;
 
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class SlidingTimeWindowReservoirTest {
-    private final Clock clock = mock(Clock.class);
-    private final SlidingTimeWindowReservoir reservoir = new SlidingTimeWindowReservoir(10, TimeUnit.NANOSECONDS, clock);
-
     @Test
     public void storesMeasurementsWithDuplicateTicks() throws Exception {
+        final Clock clock = mock(Clock.class);
+        final SlidingTimeWindowReservoir reservoir = new SlidingTimeWindowReservoir(10, NANOSECONDS, clock);
+
         when(clock.getTick()).thenReturn(20L);
 
         reservoir.update(1);
@@ -25,6 +28,9 @@ public class SlidingTimeWindowReservoirTest {
 
     @Test
     public void boundsMeasurementsToATimeWindow() throws Exception {
+        final Clock clock = mock(Clock.class);
+        final SlidingTimeWindowReservoir reservoir = new SlidingTimeWindowReservoir(10, NANOSECONDS, clock);
+
         when(clock.getTick()).thenReturn(0L);
         reservoir.update(1);
 
@@ -43,4 +49,69 @@ public class SlidingTimeWindowReservoirTest {
         assertThat(reservoir.getSnapshot().getValues())
                 .containsOnly(4, 5);
     }
-}
+
+    @Test
+    public void testGetTickOverflow () {
+        final Random random = new Random(0);
+        final int window = 128;
+
+        // Note: 'threshold' defines the number of updates submitted to the reservoir after overflowing
+        for (int threshold : Arrays.asList(0, 1, 2, 127, 128, 129, 255, 256, 257)) {
+
+            // Note: 'updatePerTick' defines the number of updates submitted to the reservoir between each tick
+            for (int updatesPerTick : Arrays.asList(1, 2, 127, 128, 129, 255, 256, 257)) {
+                //logger.info("Executing test: threshold={}, updatesPerTick={}", threshold, updatesPerTick);
+
+                // Set the clock to overflow in (2*window+1)ns
+                final ManualClock clock = new ManualClock();
+                clock.addNanos(Long.MAX_VALUE/256 - 2*window - clock.getTick());
+                assertThat(clock.getTick() * 256).isGreaterThan(0);
+
+                // Create the reservoir
+                final SlidingTimeWindowReservoir reservoir = new SlidingTimeWindowReservoir(window, NANOSECONDS, clock);
+
+                int updatesAfterThreshold = 0;
+                while (true) {
+                    // Update the reservoir
+                    for (int i = 0; i < updatesPerTick; i++)
+                        reservoir.update(0);
+
+                    // Randomly check the reservoir size
+                    if (random.nextDouble() < 0.1) {
+                        assertThat(reservoir.size())
+                                .as("Bad reservoir size with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
+                                .isLessThanOrEqualTo(window * 256);
+                    }
+
+                    // Update the clock
+                    clock.addNanos(1);
+
+                    // If the clock has overflowed start counting updates
+                    if ((clock.getTick() * 256) < 0) {
+                        if (updatesAfterThreshold++ >= threshold)
+                            break;
+                    }
+                }
+
+                // Check the final reservoir size
+                assertThat(reservoir.size())
+                        .as("Bad final reservoir size with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
+                        .isLessThanOrEqualTo(window * 256);
+
+                // Advance the clock far enough to clear the reservoir.  Note that here the window only loosely defines
+                // the reservoir window; when updatesPerTick is greater than 128 the sliding window will always be well
+                // ahead of the current clock time, and advances in getTick while in trim (called randomly above from
+                // size and every 256 updates).  Until the clock "catches up" advancing the clock will have no effect on
+                // the reservoir, and reservoir.size() will merely move the window forward 1/256th of a ns - as such, an
+                // arbitrary increment of 1s here was used instead to advance the clock well beyond any updates recorded
+                // above.
+                clock.addSeconds(1);
+
+                // The reservoir should now be empty
+                assertThat(reservoir.size())
+                        .as("Bad reservoir size after delay with: threshold=%d, updatesPerTick=%d", threshold, updatesPerTick)
+                        .isEqualTo(0);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/metrics-core/src/test/java/com/codahale/metrics/TimerTest.java b/metrics-core/src/test/java/com/codahale/metrics/TimerTest.java
index a6a4f26..c321cf6 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/TimerTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/TimerTest.java
@@ -4,6 +4,7 @@ import org.junit.Test;
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.offset;
@@ -70,6 +71,25 @@ public class TimerTest {
     }
 
     @Test
+    public void timesRunnableInstances() throws Exception {
+        final AtomicBoolean called = new AtomicBoolean();
+        timer.time(new Runnable() {
+            @Override
+            public void run() {
+                called.set(true);
+            }
+        });
+
+        assertThat(timer.getCount())
+                .isEqualTo(1);
+
+        assertThat(called.get())
+                .isTrue();
+
+        verify(reservoir).update(50000000);
+    }
+
+    @Test
     public void timesContexts() throws Exception {
         timer.time().stop();
 
diff --git a/metrics-core/src/test/java/com/codahale/metrics/UniformSnapshotTest.java b/metrics-core/src/test/java/com/codahale/metrics/UniformSnapshotTest.java
index a29e49f..46dcc88 100644
--- a/metrics-core/src/test/java/com/codahale/metrics/UniformSnapshotTest.java
+++ b/metrics-core/src/test/java/com/codahale/metrics/UniformSnapshotTest.java
@@ -3,8 +3,10 @@ package com.codahale.metrics;
 import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Random;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -93,35 +95,36 @@ public class UniformSnapshotTest {
     }
 
     @Test
-    public void worksWithUnderestimatedCollections() throws Exception {
-        final List<Long> longs = spy(new ArrayList<Long>());
-        longs.add(5L);
-        longs.add(1L);
-        longs.add(2L);
-        longs.add(3L);
-        longs.add(4L);
-        when(longs.size()).thenReturn(4, 5);
-
-        final Snapshot other = new UniformSnapshot(longs);
-
-        assertThat(other.getValues())
-                .containsOnly(1, 2, 3, 4, 5);
-    }
-
-    @Test
-    public void worksWithOverestimatedCollections() throws Exception {
-        final List<Long> longs = spy(new ArrayList<Long>());
-        longs.add(5L);
-        longs.add(1L);
-        longs.add(2L);
-        longs.add(3L);
-        longs.add(4L);
-        when(longs.size()).thenReturn(6, 5);
-
-        final Snapshot other = new UniformSnapshot(longs);
-
-        assertThat(other.getValues())
-                .containsOnly(1, 2, 3, 4, 5);
+    public void correctlyCreatedFromCollectionWithWeakIterator() throws Exception {
+        final ConcurrentSkipListSet<Long> values = new ConcurrentSkipListSet<Long>();
+
+        // Create a latch to make sure that the background thread has started and
+        // pushed some data to the collection.
+        final CountDownLatch latch = new CountDownLatch(10);
+        final Thread backgroundThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                final Random random = new Random();
+                // Update the collection in the loop to trigger a potential `ArrayOutOfBoundException`
+                // and verify that the snapshot doesn't make assumptions about the size of the iterator.
+                while (!Thread.currentThread().isInterrupted()) {
+                    values.add(random.nextLong());
+                    latch.countDown();
+                }
+            }
+        });
+        backgroundThread.start();
+
+        try {
+            latch.await(5, TimeUnit.SECONDS);
+            assertThat(latch.getCount()).isEqualTo(0);
+
+            // Create a snapshot while the  collection is being updated.
+            final Snapshot snapshot = new UniformSnapshot(values);
+            assertThat(snapshot.getValues().length).isGreaterThanOrEqualTo(10);
+        } finally {
+            backgroundThread.interrupt();
+        }
     }
 
     @Test
diff --git a/metrics-ehcache/pom.xml b/metrics-ehcache/pom.xml
index 2666787..264e487 100644
--- a/metrics-ehcache/pom.xml
+++ b/metrics-ehcache/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-ehcache</artifactId>
diff --git a/metrics-ganglia/pom.xml b/metrics-ganglia/pom.xml
index a4f9302..287c109 100644
--- a/metrics-ganglia/pom.xml
+++ b/metrics-ganglia/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-ganglia</artifactId>
diff --git a/metrics-ganglia/src/main/java/com/codahale/metrics/ganglia/GangliaReporter.java b/metrics-ganglia/src/main/java/com/codahale/metrics/ganglia/GangliaReporter.java
index 5d8b7ce..2e0fd29 100644
--- a/metrics-ganglia/src/main/java/com/codahale/metrics/ganglia/GangliaReporter.java
+++ b/metrics-ganglia/src/main/java/com/codahale/metrics/ganglia/GangliaReporter.java
@@ -1,7 +1,7 @@
 package com.codahale.metrics.ganglia;
 
 import com.codahale.metrics.*;
-
+import com.codahale.metrics.MetricAttribute;
 import info.ganglia.gmetric4j.gmetric.GMetric;
 import info.ganglia.gmetric4j.gmetric.GMetricSlope;
 import info.ganglia.gmetric4j.gmetric.GMetricType;
@@ -10,12 +10,16 @@ import info.ganglia.gmetric4j.gmetric.GangliaException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedMap;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
 import static com.codahale.metrics.MetricRegistry.name;
+import static com.codahale.metrics.MetricAttribute.*;
 
 /**
  * A reporter which announces metric values to a Ganglia cluster.
@@ -49,6 +53,9 @@ public class GangliaReporter extends ScheduledReporter {
         private TimeUnit rateUnit;
         private TimeUnit durationUnit;
         private MetricFilter filter;
+        private ScheduledExecutorService executor;
+        private boolean shutdownExecutorOnStop;
+        private Set<MetricAttribute> disabledMetricAttributes = Collections.emptySet();
 
         private Builder(MetricRegistry registry) {
             this.registry = registry;
@@ -57,6 +64,34 @@ public class GangliaReporter extends ScheduledReporter {
             this.rateUnit = TimeUnit.SECONDS;
             this.durationUnit = TimeUnit.MILLISECONDS;
             this.filter = MetricFilter.ALL;
+            this.executor = null;
+            this.shutdownExecutorOnStop = true;
+        }
+
+        /**
+         * Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
+         * Default value is true.
+         * Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.
+         *
+         * @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
+         * @return {@code this}
+         */
+        public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
+            this.shutdownExecutorOnStop = shutdownExecutorOnStop;
+            return this;
+        }
+
+        /**
+         * Specifies the executor to use while scheduling reporting of metrics.
+         * Default value is null.
+         * Null value leads to executor will be auto created on start.
+         *
+         * @param executor the executor to use while scheduling reporting of metrics.
+         * @return {@code this}
+         */
+        public Builder scheduleOn(ScheduledExecutorService executor) {
+            this.executor = executor;
+            return this;
         }
 
         /**
@@ -126,6 +161,18 @@ public class GangliaReporter extends ScheduledReporter {
         }
 
         /**
+         * Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15").
+         * See {@link MetricAttribute}.
+         *
+         * @param disabledMetricAttributes a {@link MetricFilter}
+         * @return {@code this}
+         */
+        public Builder disabledMetricAttributes(Set<MetricAttribute> disabledMetricAttributes) {
+            this.disabledMetricAttributes = disabledMetricAttributes;
+            return this;
+        }
+
+        /**
          * Builds a {@link GangliaReporter} with the given properties, announcing metrics to the
          * given {@link GMetric} client.
          *
@@ -133,7 +180,8 @@ public class GangliaReporter extends ScheduledReporter {
          * @return a {@link GangliaReporter}
          */
         public GangliaReporter build(GMetric gmetric) {
-            return new GangliaReporter(registry, gmetric, null, prefix, tMax, dMax, rateUnit, durationUnit, filter);
+            return new GangliaReporter(registry, gmetric, null, prefix, tMax, dMax, rateUnit, durationUnit, filter,
+                    executor, shutdownExecutorOnStop, disabledMetricAttributes);
         }
 
         /**
@@ -144,7 +192,8 @@ public class GangliaReporter extends ScheduledReporter {
          * @return a {@link GangliaReporter}
          */
         public GangliaReporter build(GMetric... gmetrics) {
-            return new GangliaReporter(registry, null, gmetrics, prefix, tMax, dMax, rateUnit, durationUnit, filter);
+            return new GangliaReporter(registry, null, gmetrics, prefix, tMax, dMax, rateUnit, durationUnit,
+                    filter, executor, shutdownExecutorOnStop , disabledMetricAttributes);
         }
     }
 
@@ -164,8 +213,12 @@ public class GangliaReporter extends ScheduledReporter {
                             int dMax,
                             TimeUnit rateUnit,
                             TimeUnit durationUnit,
-                            MetricFilter filter) {
-        super(registry, "ganglia-reporter", filter, rateUnit, durationUnit);
+                            MetricFilter filter,
+                            ScheduledExecutorService executor,
+                            boolean shutdownExecutorOnStop,
+                            Set<MetricAttribute> disabledMetricAttributes) {
+        super(registry, "ganglia-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop,
+                disabledMetricAttributes);
         this.gmetric = gmetric;
         this.gmetrics = gmetrics;
         this.prefix = prefix;
@@ -206,29 +259,29 @@ public class GangliaReporter extends ScheduledReporter {
         try {
             final Snapshot snapshot = timer.getSnapshot();
 
-            announce(prefix(sanitizedName, "max"), group, convertDuration(snapshot.getMax()), getDurationUnit());
-            announce(prefix(sanitizedName, "mean"), group, convertDuration(snapshot.getMean()), getDurationUnit());
-            announce(prefix(sanitizedName, "min"), group, convertDuration(snapshot.getMin()), getDurationUnit());
-            announce(prefix(sanitizedName, "stddev"), group, convertDuration(snapshot.getStdDev()), getDurationUnit());
+            announceIfEnabled(MAX, sanitizedName, group, convertDuration(snapshot.getMax()), getDurationUnit());
+            announceIfEnabled(MEAN, sanitizedName, group, convertDuration(snapshot.getMean()), getDurationUnit());
+            announceIfEnabled(MIN, sanitizedName, group, convertDuration(snapshot.getMin()), getDurationUnit());
+            announceIfEnabled(STDDEV, sanitizedName, group, convertDuration(snapshot.getStdDev()), getDurationUnit());
 
-            announce(prefix(sanitizedName, "p50"), group, convertDuration(snapshot.getMedian()), getDurationUnit());
-            announce(prefix(sanitizedName, "p75"),
+            announceIfEnabled(P50, sanitizedName, group, convertDuration(snapshot.getMedian()), getDurationUnit());
+            announceIfEnabled(P75, sanitizedName,
                      group,
                      convertDuration(snapshot.get75thPercentile()),
                      getDurationUnit());
-            announce(prefix(sanitizedName, "p95"),
+            announceIfEnabled(P95, sanitizedName,
                      group,
                      convertDuration(snapshot.get95thPercentile()),
                      getDurationUnit());
-            announce(prefix(sanitizedName, "p98"),
+            announceIfEnabled(P98, sanitizedName,
                      group,
                      convertDuration(snapshot.get98thPercentile()),
                      getDurationUnit());
-            announce(prefix(sanitizedName, "p99"),
+            announceIfEnabled(P99, sanitizedName,
                      group,
                      convertDuration(snapshot.get99thPercentile()),
                      getDurationUnit());
-            announce(prefix(sanitizedName, "p999"),
+            announceIfEnabled(P999, sanitizedName,
                      group,
                      convertDuration(snapshot.get999thPercentile()),
                      getDurationUnit());
@@ -251,11 +304,11 @@ public class GangliaReporter extends ScheduledReporter {
 
     private void reportMetered(String name, Metered meter, String group, String eventName) throws GangliaException {
         final String unit = eventName + '/' + getRateUnit();
-        announce(prefix(name, "count"), group, meter.getCount(), eventName);
-        announce(prefix(name, "m1_rate"), group, convertRate(meter.getOneMinuteRate()), unit);
-        announce(prefix(name, "m5_rate"), group, convertRate(meter.getFiveMinuteRate()), unit);
-        announce(prefix(name, "m15_rate"), group, convertRate(meter.getFifteenMinuteRate()), unit);
-        announce(prefix(name, "mean_rate"), group, convertRate(meter.getMeanRate()), unit);
+        announceIfEnabled(COUNT, name, group, meter.getCount(), eventName);
+        announceIfEnabled(M1_RATE, name, group, convertRate(meter.getOneMinuteRate()), unit);
+        announceIfEnabled(M5_RATE, name, group, convertRate(meter.getFiveMinuteRate()), unit);
+        announceIfEnabled(M15_RATE, name, group, convertRate(meter.getFifteenMinuteRate()), unit);
+        announceIfEnabled(MEAN_RATE, name, group, convertRate(meter.getMeanRate()), unit);
     }
 
     private void reportHistogram(String name, Histogram histogram) {
@@ -264,17 +317,17 @@ public class GangliaReporter extends ScheduledReporter {
         try {
             final Snapshot snapshot = histogram.getSnapshot();
 
-            announce(prefix(sanitizedName, "count"), group, histogram.getCount(), "");
-            announce(prefix(sanitizedName, "max"), group, snapshot.getMax(), "");
-            announce(prefix(sanitizedName, "mean"), group, snapshot.getMean(), "");
-            announce(prefix(sanitizedName, "min"), group, snapshot.getMin(), "");
-            announce(prefix(sanitizedName, "stddev"), group, snapshot.getStdDev(), "");
-            announce(prefix(sanitizedName, "p50"), group, snapshot.getMedian(), "");
-            announce(prefix(sanitizedName, "p75"), group, snapshot.get75thPercentile(), "");
-            announce(prefix(sanitizedName, "p95"), group, snapshot.get95thPercentile(), "");
-            announce(prefix(sanitizedName, "p98"), group, snapshot.get98thPercentile(), "");
-            announce(prefix(sanitizedName, "p99"), group, snapshot.get99thPercentile(), "");
-            announce(prefix(sanitizedName, "p999"), group, snapshot.get999thPercentile(), "");
+            announceIfEnabled(COUNT, sanitizedName, group, histogram.getCount(), "");
+            announceIfEnabled(MAX, sanitizedName, group, snapshot.getMax(), "");
+            announceIfEnabled(MEAN, sanitizedName, group, snapshot.getMean(), "");
+            announceIfEnabled(MIN, sanitizedName, group, snapshot.getMin(), "");
+            announceIfEnabled(STDDEV, sanitizedName, group, snapshot.getStdDev(), "");
+            announceIfEnabled(P50, sanitizedName, group, snapshot.getMedian(), "");
+            announceIfEnabled(P75, sanitizedName, group, snapshot.get75thPercentile(), "");
+            announceIfEnabled(P95, sanitizedName, group, snapshot.get95thPercentile(), "");
+            announceIfEnabled(P98, sanitizedName, group, snapshot.get98thPercentile(), "");
+            announceIfEnabled(P99, sanitizedName, group, snapshot.get99thPercentile(), "");
+            announceIfEnabled(P999, sanitizedName, group, snapshot.get999thPercentile(), "");
         } catch (GangliaException e) {
             LOGGER.warn("Unable to report histogram {}", sanitizedName, e);
         }
@@ -284,7 +337,7 @@ public class GangliaReporter extends ScheduledReporter {
         final String sanitizedName = escapeSlashes(name);
         final String group = group(name);
         try {
-            announce(prefix(sanitizedName, "count"), group, counter.getCount(), "");
+            announce(prefix(sanitizedName, COUNT.getCode()), group, Long.toString(counter.getCount()), GMetricType.DOUBLE, "");
         } catch (GangliaException e) {
             LOGGER.warn("Unable to report counter {}", name, e);
         }
@@ -304,16 +357,26 @@ public class GangliaReporter extends ScheduledReporter {
     }
 
     private static final double MIN_VAL = 1E-300;
-    private void announce(String name, String group, double value, String units) throws GangliaException {
+
+    private void announceIfEnabled(MetricAttribute metricAttribute, String metricName, String group, double value, String units)
+            throws GangliaException {
+        if (getDisabledMetricAttributes().contains(metricAttribute)) {
+            return;
+        }
         final String string = Math.abs(value) < MIN_VAL ? "0" : Double.toString(value);
-        announce(name, group, string, GMetricType.DOUBLE, units);
+        announce(prefix(metricName, metricAttribute.getCode()), group, string, GMetricType.DOUBLE, units);
     }
 
-    private void announce(String name, String group, long value, String units) throws GangliaException {
-        announce(name, group, Long.toString(value), GMetricType.DOUBLE, units);
+    private void announceIfEnabled(MetricAttribute metricAttribute, String metricName, String group, long value, String units)
+            throws GangliaException {
+        if (getDisabledMetricAttributes().contains(metricAttribute)) {
+            return;
+        }
+        announce(prefix(metricName, metricAttribute.getCode()), group, Long.toString(value), GMetricType.DOUBLE, units);
     }
 
-    private void announce(String name, String group, String value, GMetricType type, String units) throws GangliaException {
+    private void announce(String name, String group, String value, GMetricType type, String units)
+            throws GangliaException {
         if (gmetric != null) {
             gmetric.announce(name, value, type, units, GMetricSlope.BOTH, tMax, dMax, group);
         } else {
diff --git a/metrics-ganglia/src/test/java/com/codahale/metrics/ganglia/GangliaReporterTest.java b/metrics-ganglia/src/test/java/com/codahale/metrics/ganglia/GangliaReporterTest.java
index 2f2b727..470bd58 100644
--- a/metrics-ganglia/src/test/java/com/codahale/metrics/ganglia/GangliaReporterTest.java
+++ b/metrics-ganglia/src/test/java/com/codahale/metrics/ganglia/GangliaReporterTest.java
@@ -6,6 +6,7 @@ import info.ganglia.gmetric4j.gmetric.GMetricSlope;
 import info.ganglia.gmetric4j.gmetric.GMetricType;
 import org.junit.Test;
 
+import java.util.EnumSet;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
@@ -247,6 +248,43 @@ public class GangliaReporterTest {
         verifyNoMoreInteractions(ganglia);
     }
 
+    @Test
+    public void disabledMetricAttributes() throws Exception {
+        final Meter meter = mock(Meter.class);
+        final Counter counter = mock(Counter.class);
+
+        when(meter.getCount()).thenReturn(1L);
+        when(meter.getMeanRate()).thenReturn(2.0);
+        when(meter.getOneMinuteRate()).thenReturn(3.0);
+        when(meter.getFiveMinuteRate()).thenReturn(4.0);
+        when(meter.getFifteenMinuteRate()).thenReturn(5.0);
+
+        when(counter.getCount()).thenReturn(1L);
+
+        GangliaReporter reporter = GangliaReporter.forRegistry(registry)
+                .prefixedWith("m")
+                .withTMax(60)
+                .withDMax(0)
+                .convertRatesTo(TimeUnit.SECONDS)
+                .convertDurationsTo(TimeUnit.MILLISECONDS)
+                .filter(MetricFilter.ALL)
+                .disabledMetricAttributes(EnumSet.of(MetricAttribute.COUNT, MetricAttribute.MEAN_RATE, MetricAttribute.M15_RATE))
+                .build(ganglia);
+
+        reporter.report(this.<Gauge>map(),
+                map("test.counter", counter),
+                this.<Histogram>map(),
+                map("test.meter", meter),
+                this.<Timer>map());
+
+        verify(ganglia).announce("m.test.counter.count", "1", GMetricType.DOUBLE, "", GMetricSlope.BOTH, 60, 0,  "test");
+        verify(ganglia).announce("m.test.meter.m1_rate", "3.0", GMetricType.DOUBLE, "events/second", GMetricSlope.BOTH, 60, 0, "test");
+        verify(ganglia).announce("m.test.meter.m5_rate", "4.0", GMetricType.DOUBLE, "events/second", GMetricSlope.BOTH, 60, 0, "test");
+        verifyNoMoreInteractions(ganglia);
+
+        reporter.close();
+    }
+
     private <T> SortedMap<String, T> map() {
         return new TreeMap<String, T>();
     }
diff --git a/metrics-graphite/pom.xml b/metrics-graphite/pom.xml
index f4a5900..b3ac65e 100644
--- a/metrics-graphite/pom.xml
+++ b/metrics-graphite/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-graphite</artifactId>
diff --git a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/Graphite.java b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/Graphite.java
index cf17420..5761a7b 100644
--- a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/Graphite.java
+++ b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/Graphite.java
@@ -1,5 +1,8 @@
 package com.codahale.metrics.graphite;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import javax.net.SocketFactory;
 
 import java.io.*;
@@ -7,13 +10,11 @@ import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.UnknownHostException;
 import java.nio.charset.Charset;
-import java.util.regex.Pattern;
 
 /**
  * A client to a Carbon server via TCP.
  */
 public class Graphite implements GraphiteSender {
-    private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");
     // this may be optimistic about Carbon/Graphite
     private static final Charset UTF_8 = Charset.forName("UTF-8");
 
@@ -27,6 +28,8 @@ public class Graphite implements GraphiteSender {
     private Writer writer;
     private int failures;
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(Graphite.class);
+
     /**
      * Creates a new client which connects to the given address using the default
      * {@link SocketFactory}.
@@ -112,7 +115,12 @@ public class Graphite implements GraphiteSender {
             address = new InetSocketAddress(hostname, port);
         }
         if (address.getAddress() == null) {
-            throw new UnknownHostException(address.getHostName());
+            // retry lookup, just in case the DNS changed
+            address = new InetSocketAddress(address.getHostName(),address.getPort());
+
+            if (address.getAddress() == null) {
+                throw new UnknownHostException(address.getHostName());
+            }
         }
 
         this.socket = socketFactory.createSocket(address.getAddress(), address.getPort());
@@ -159,16 +167,23 @@ public class Graphite implements GraphiteSender {
                 writer.close();
             }
         } catch (IOException ex) {
+            LOGGER.debug("Error closing writer", ex);
+        } finally {
+            this.writer = null;
+        }
+
+        try {
             if (socket != null) {
                 socket.close();
             }
+        } catch (IOException ex) {
+            LOGGER.debug("Error closing socket", ex);
         } finally {
             this.socket = null;
-            this.writer = null;
         }
     }
 
     protected String sanitize(String s) {
-        return WHITESPACE.matcher(s).replaceAll("-");
+        return GraphiteSanitize.sanitize(s);
     }
 }
diff --git a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteRabbitMQ.java b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteRabbitMQ.java
index 08a19c5..93e6c75 100644
--- a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteRabbitMQ.java
+++ b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteRabbitMQ.java
@@ -8,15 +8,13 @@ import com.rabbitmq.client.DefaultSocketConfigurator;
 import java.io.IOException;
 import java.net.Socket;
 import java.nio.charset.Charset;
-import java.util.regex.Pattern;
+import java.util.concurrent.TimeoutException;
 
 /**
  * A rabbit-mq client to a Carbon server.
  */
 public class GraphiteRabbitMQ implements GraphiteSender {
 
-    private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");
-
     private static final Charset UTF_8 = Charset.forName("UTF-8");
 
     private static final Integer DEFAULT_RABBIT_CONNECTION_TIMEOUT_MS = 500;
@@ -115,7 +113,11 @@ public class GraphiteRabbitMQ implements GraphiteSender {
             throw new IllegalStateException("Already connected");
         }
 
-        connection = connectionFactory.newConnection();
+        try {
+            connection = connectionFactory.newConnection();
+        } catch (TimeoutException e) {
+            throw new IllegalStateException(e);
+        }
         channel = connection.createChannel();
     }
 
@@ -161,7 +163,7 @@ public class GraphiteRabbitMQ implements GraphiteSender {
     }
 
     public String sanitize(String s) {
-        return WHITESPACE.matcher(s).replaceAll("-");
+        return GraphiteSanitize.sanitize(s);
     }
 
 }
diff --git a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteReporter.java b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteReporter.java
index 59f8bf7..f11803e 100644
--- a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteReporter.java
+++ b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteReporter.java
@@ -1,15 +1,23 @@
 package com.codahale.metrics.graphite;
 
 import com.codahale.metrics.*;
+import com.codahale.metrics.Timer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedMap;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
+import static com.codahale.metrics.MetricAttribute.*;
+
 /**
  * A reporter which publishes metric values to a Graphite server.
  *
@@ -38,6 +46,9 @@ public class GraphiteReporter extends ScheduledReporter {
         private TimeUnit rateUnit;
         private TimeUnit durationUnit;
         private MetricFilter filter;
+        private ScheduledExecutorService executor;
+        private boolean shutdownExecutorOnStop;
+        private Set<MetricAttribute> disabledMetricAttributes;
 
         private Builder(MetricRegistry registry) {
             this.registry = registry;
@@ -46,6 +57,35 @@ public class GraphiteReporter extends ScheduledReporter {
             this.rateUnit = TimeUnit.SECONDS;
             this.durationUnit = TimeUnit.MILLISECONDS;
             this.filter = MetricFilter.ALL;
+            this.executor = null;
+            this.shutdownExecutorOnStop = true;
+            this.disabledMetricAttributes = Collections.emptySet();
+        }
+
+        /**
+         * Specifies whether or not, the executor (used for reporting) will be stopped with same time with reporter.
+         * Default value is true.
+         * Setting this parameter to false, has the sense in combining with providing external managed executor via {@link #scheduleOn(ScheduledExecutorService)}.
+         *
+         * @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
+         * @return {@code this}
+         */
+        public Builder shutdownExecutorOnStop(boolean shutdownExecutorOnStop) {
+            this.shutdownExecutorOnStop = shutdownExecutorOnStop;
+            return this;
+        }
+
+        /**
+         * Specifies the executor to use while scheduling reporting of metrics.
+         * Default value is null.
+         * Null value leads to executor will be auto created on start.
+         *
+         * @param executor the executor to use while scheduling reporting of metrics.
+         * @return {@code this}
+         */
+        public Builder scheduleOn(ScheduledExecutorService executor) {
+            this.executor = executor;
+            return this;
         }
 
         /**
@@ -104,6 +144,18 @@ public class GraphiteReporter extends ScheduledReporter {
         }
 
         /**
+         * Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15").
+         * See {@link MetricAttribute}.
+         *
+         * @param disabledMetricAttributes a {@link MetricFilter}
+         * @return {@code this}
+         */
+        public Builder disabledMetricAttributes(Set<MetricAttribute> disabledMetricAttributes) {
+            this.disabledMetricAttributes = disabledMetricAttributes;
+            return this;
+        }
+
+        /**
          * Builds a {@link GraphiteReporter} with the given properties, sending metrics using the
          * given {@link GraphiteSender}.
          *
@@ -130,7 +182,10 @@ public class GraphiteReporter extends ScheduledReporter {
                                         prefix,
                                         rateUnit,
                                         durationUnit,
-                                        filter);
+                                        filter,
+                                        executor,
+                                        shutdownExecutorOnStop,
+                    disabledMetricAttributes);
         }
     }
 
@@ -140,14 +195,33 @@ public class GraphiteReporter extends ScheduledReporter {
     private final Clock clock;
     private final String prefix;
 
-    private GraphiteReporter(MetricRegistry registry,
+    /**
+     * Creates a new {@link GraphiteReporter} instance.
+     *
+     * @param registry               the {@link MetricRegistry} containing the metrics this
+     *                               reporter will report
+     * @param graphite               the {@link GraphiteSender} which is responsible for sending metrics to a Carbon server
+     *                               via a transport protocol
+     * @param clock                  the instance of the time. Use {@link Clock#defaultClock()} for the default
+     * @param prefix                 the prefix of all metric names (may be null)
+     * @param rateUnit               the time unit of in which rates will be converted
+     * @param durationUnit           the time unit of in which durations will be converted
+     * @param filter                 the filter for which metrics to report
+     * @param executor               the executor to use while scheduling reporting of metrics (may be null).
+     * @param shutdownExecutorOnStop if true, then executor will be stopped in same time with this reporter
+     */
+    protected GraphiteReporter(MetricRegistry registry,
                              GraphiteSender graphite,
                              Clock clock,
                              String prefix,
                              TimeUnit rateUnit,
                              TimeUnit durationUnit,
-                             MetricFilter filter) {
-        super(registry, "graphite-reporter", filter, rateUnit, durationUnit);
+                             MetricFilter filter,
+                             ScheduledExecutorService executor,
+                             boolean shutdownExecutorOnStop,
+                             Set<MetricAttribute> disabledMetricAttributes) {
+        super(registry, "graphite-reporter", filter, rateUnit, durationUnit, executor, shutdownExecutorOnStop,
+                disabledMetricAttributes);
         this.graphite = graphite;
         this.clock = clock;
         this.prefix = prefix;
@@ -163,9 +237,7 @@ public class GraphiteReporter extends ScheduledReporter {
 
         // oh it'd be lovely to use Java 7 here
         try {
-            if (!graphite.isConnected()) {
-    	          graphite.connect();
-            }
+            graphite.connect();
 
             for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {
                 reportGauge(entry.getKey(), entry.getValue(), timestamp);
@@ -186,10 +258,10 @@ public class GraphiteReporter extends ScheduledReporter {
             for (Map.Entry<String, Timer> entry : timers.entrySet()) {
                 reportTimer(entry.getKey(), entry.getValue(), timestamp);
             }
-
             graphite.flush();
         } catch (IOException e) {
             LOGGER.warn("Unable to report to Graphite", graphite, e);
+        } finally {
             try {
                 graphite.close();
             } catch (IOException e1) {
@@ -213,68 +285,58 @@ public class GraphiteReporter extends ScheduledReporter {
 
     private void reportTimer(String name, Timer timer, long timestamp) throws IOException {
         final Snapshot snapshot = timer.getSnapshot();
-
-        graphite.send(prefix(name, "max"), format(convertDuration(snapshot.getMax())), timestamp);
-        graphite.send(prefix(name, "mean"), format(convertDuration(snapshot.getMean())), timestamp);
-        graphite.send(prefix(name, "min"), format(convertDuration(snapshot.getMin())), timestamp);
-        graphite.send(prefix(name, "stddev"),
-                      format(convertDuration(snapshot.getStdDev())),
-                      timestamp);
-        graphite.send(prefix(name, "p50"),
-                      format(convertDuration(snapshot.getMedian())),
-                      timestamp);
-        graphite.send(prefix(name, "p75"),
-                      format(convertDuration(snapshot.get75thPercentile())),
-                      timestamp);
-        graphite.send(prefix(name, "p95"),
-                      format(convertDuration(snapshot.get95thPercentile())),
-                      timestamp);
-        graphite.send(prefix(name, "p98"),
-                      format(convertDuration(snapshot.get98thPercentile())),
-                      timestamp);
-        graphite.send(prefix(name, "p99"),
-                      format(convertDuration(snapshot.get99thPercentile())),
-                      timestamp);
-        graphite.send(prefix(name, "p999"),
-                      format(convertDuration(snapshot.get999thPercentile())),
-                      timestamp);
-
+        sendIfEnabled(MAX, name, convertDuration(snapshot.getMax()), timestamp);
+        sendIfEnabled(MEAN, name, convertDuration(snapshot.getMean()), timestamp);
+        sendIfEnabled(MIN, name, convertDuration(snapshot.getMin()), timestamp);
+        sendIfEnabled(STDDEV, name, convertDuration(snapshot.getStdDev()), timestamp);
+        sendIfEnabled(P50, name, convertDuration(snapshot.getMedian()), timestamp);
+        sendIfEnabled(P75, name, convertDuration(snapshot.get75thPercentile()), timestamp);
+        sendIfEnabled(P95, name, convertDuration(snapshot.get95thPercentile()), timestamp);
+        sendIfEnabled(P98, name, convertDuration(snapshot.get98thPercentile()), timestamp);
+        sendIfEnabled(P99, name, convertDuration(snapshot.get99thPercentile()), timestamp);
+        sendIfEnabled(P999, name, convertDuration(snapshot.get999thPercentile()), timestamp);
         reportMetered(name, timer, timestamp);
     }
 
     private void reportMetered(String name, Metered meter, long timestamp) throws IOException {
-        graphite.send(prefix(name, "count"), format(meter.getCount()), timestamp);
-        graphite.send(prefix(name, "m1_rate"),
-                      format(convertRate(meter.getOneMinuteRate())),
-                      timestamp);
-        graphite.send(prefix(name, "m5_rate"),
-                      format(convertRate(meter.getFiveMinuteRate())),
-                      timestamp);
-        graphite.send(prefix(name, "m15_rate"),
-                      format(convertRate(meter.getFifteenMinuteRate())),
-                      timestamp);
-        graphite.send(prefix(name, "mean_rate"),
-                      format(convertRate(meter.getMeanRate())),
-                      timestamp);
+        sendIfEnabled(COUNT, name, meter.getCount(), timestamp);
+        sendIfEnabled(M1_RATE, name, convertRate(meter.getOneMinuteRate()), timestamp);
+        sendIfEnabled(M5_RATE, name, convertRate(meter.getFiveMinuteRate()), timestamp);
+        sendIfEnabled(M15_RATE, name, convertRate(meter.getFifteenMinuteRate()), timestamp);
+        sendIfEnabled(MEAN_RATE, name, convertRate(meter.getMeanRate()), timestamp);
     }
 
     private void reportHistogram(String name, Histogram histogram, long timestamp) throws IOException {
         final Snapshot snapshot = histogram.getSnapshot();
-        graphite.send(prefix(name, "count"), format(histogram.getCount()), timestamp);
-        graphite.send(prefix(name, "max"), format(snapshot.getMax()), timestamp);
-        graphite.send(prefix(name, "mean"), format(snapshot.getMean()), timestamp);
-        graphite.send(prefix(name, "min"), format(snapshot.getMin()), timestamp);
-        graphite.send(prefix(name, "stddev"), format(snapshot.getStdDev()), timestamp);
-        graphite.send(prefix(name, "p50"), format(snapshot.getMedian()), timestamp);
-        graphite.send(prefix(name, "p75"), format(snapshot.get75thPercentile()), timestamp);
-        graphite.send(prefix(name, "p95"), format(snapshot.get95thPercentile()), timestamp);
-        graphite.send(prefix(name, "p98"), format(snapshot.get98thPercentile()), timestamp);
-        graphite.send(prefix(name, "p99"), format(snapshot.get99thPercentile()), timestamp);
-        graphite.send(prefix(name, "p999"), format(snapshot.get999thPercentile()), timestamp);
+        sendIfEnabled(COUNT, name, histogram.getCount(), timestamp);
+        sendIfEnabled(MAX, name, snapshot.getMax(), timestamp);
+        sendIfEnabled(MEAN, name, snapshot.getMean(), timestamp);
+        sendIfEnabled(MIN, name, snapshot.getMin(), timestamp);
+        sendIfEnabled(STDDEV, name, snapshot.getStdDev(), timestamp);
+        sendIfEnabled(P50, name, snapshot.getMedian(), timestamp);
+        sendIfEnabled(P75, name, snapshot.get75thPercentile(), timestamp);
+        sendIfEnabled(P95, name, snapshot.get95thPercentile(), timestamp);
+        sendIfEnabled(P98, name, snapshot.get98thPercentile(), timestamp);
+        sendIfEnabled(P99, name, snapshot.get99thPercentile(), timestamp);
+        sendIfEnabled(P999, name, snapshot.get999thPercentile(), timestamp);
+    }
+
+    private void sendIfEnabled(MetricAttribute type, String name, double value, long timestamp) throws IOException {
+        if (getDisabledMetricAttributes().contains(type)){
+            return;
+        }
+        graphite.send(prefix(name, type.getCode()), format(value), timestamp);
+    }
+
+    private void sendIfEnabled(MetricAttribute type, String name, long value, long timestamp) throws IOException {
+        if (getDisabledMetricAttributes().contains(type)){
+            return;
+        }
+        graphite.send(prefix(name, type.getCode()), format(value), timestamp);
     }
 
     private void reportCounter(String name, Counter counter, long timestamp) throws IOException {
-        graphite.send(prefix(name, "count"), format(counter.getCount()), timestamp);
+        graphite.send(prefix(name, COUNT.getCode()), format(counter.getCount()), timestamp);
     }
 
     private void reportGauge(String name, Gauge gauge, long timestamp) throws IOException {
@@ -297,6 +359,12 @@ public class GraphiteReporter extends ScheduledReporter {
             return format(((Integer) o).longValue());
         } else if (o instanceof Long) {
             return format(((Long) o).longValue());
+        } else if (o instanceof BigInteger) {
+            return format(((BigInteger) o).doubleValue());
+        } else if (o instanceof BigDecimal) {
+            return format(((BigDecimal) o).doubleValue());
+        } else if (o instanceof Boolean) {
+            return format(((Boolean) o) ? 1 : 0);
         }
         return null;
     }
@@ -309,7 +377,7 @@ public class GraphiteReporter extends ScheduledReporter {
         return Long.toString(n);
     }
 
-    private String format(double v) {
+    protected String format(double v) {
         // the Carbon plaintext format is pretty underspecified, but it seems like it just wants
         // US-formatted digits
         return String.format(Locale.US, "%2.2f", v);
diff --git a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteSanitize.java b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteSanitize.java
new file mode 100644
index 0000000..d530aab
--- /dev/null
+++ b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteSanitize.java
@@ -0,0 +1,16 @@
+package com.codahale.metrics.graphite;
+
+import java.util.regex.Pattern;
+
+class GraphiteSanitize {
+
+    private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");
+    private static final String DASH = "-";
+
+    /**
+     * Trims the string and replaces all whitespace characters with the provided symbol
+     */
+    static String sanitize(String string) {
+        return WHITESPACE.matcher(string.trim()).replaceAll(DASH);
+    }
+}
diff --git a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteUDP.java b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteUDP.java
index a5f7235..8e44a8e 100644
--- a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteUDP.java
+++ b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/GraphiteUDP.java
@@ -5,15 +5,12 @@ import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
 import java.nio.channels.DatagramChannel;
 import java.nio.charset.Charset;
-import java.util.regex.Pattern;
 
 /**
  * A client to a Carbon server using unconnected UDP
  */
 public class GraphiteUDP implements GraphiteSender {
 
-    private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");
-
     private static final Charset UTF_8 = Charset.forName("UTF-8");
 
     private final String hostname;
@@ -48,15 +45,10 @@ public class GraphiteUDP implements GraphiteSender {
 
     @Override
     public void connect() throws IllegalStateException, IOException {
-        // Only open the channel the first time...
         if (isConnected()) {
             throw new IllegalStateException("Already connected");
         }
 
-        if (datagramChannel != null) {
-            datagramChannel.close();
-        }
-
         // Resolve hostname
         if (hostname != null) {
             address = new InetSocketAddress(hostname, port);
@@ -72,11 +64,6 @@ public class GraphiteUDP implements GraphiteSender {
 
     @Override
     public void send(String name, String value, long timestamp) throws IOException {
-        // Underlying socket can be closed by ICMP
-        if (!isConnected()) {
-            connect();
-        }
-
         try {
             StringBuilder buf = new StringBuilder();
             buf.append(sanitize(name));
@@ -107,11 +94,32 @@ public class GraphiteUDP implements GraphiteSender {
 
     @Override
     public void close() throws IOException {
-        // Leave channel & socket open for next metrics
+        if (datagramChannel != null) {
+            try {
+                datagramChannel.close();
+            } finally {
+                datagramChannel = null;
+            }
+        }
     }
 
     protected String sanitize(String s) {
-        return WHITESPACE.matcher(s).replaceAll("-");
+        return GraphiteSanitize.sanitize(s);
     }
 
+    DatagramChannel getDatagramChannel() {
+        return datagramChannel;
+    }
+
+    void setDatagramChannel(DatagramChannel datagramChannel) {
+        this.datagramChannel = datagramChannel;
+    }
+
+    InetSocketAddress getAddress() {
+        return address;
+    }
+
+    void setAddress(InetSocketAddress address) {
+        this.address = address;
+    }
 }
diff --git a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/PickledGraphite.java b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/PickledGraphite.java
index 1536bec..4d8fa29 100644
--- a/metrics-graphite/src/main/java/com/codahale/metrics/graphite/PickledGraphite.java
+++ b/metrics-graphite/src/main/java/com/codahale/metrics/graphite/PickledGraphite.java
@@ -18,14 +18,12 @@ import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.regex.Pattern;
 
 /**
  * A client to a Carbon server that sends all metrics after they have been pickled in configurable sized batches
  */
 public class PickledGraphite implements GraphiteSender {
 
-    private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");
     private static final Charset UTF_8 = Charset.forName("UTF-8");
 
     private static final Logger LOGGER = LoggerFactory.getLogger(PickledGraphite.class);
@@ -360,7 +358,7 @@ public class PickledGraphite implements GraphiteSender {
     }
 
     protected String sanitize(String s) {
-        return WHITESPACE.matcher(s).replaceAll("-");
+        return GraphiteSanitize.sanitize(s);
     }
 
 }
diff --git a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteReporterTest.java b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteReporterTest.java
index 816067b..a4a43f5 100644
--- a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteReporterTest.java
+++ b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteReporterTest.java
@@ -1,11 +1,16 @@
 package com.codahale.metrics.graphite;
 
 import com.codahale.metrics.*;
+import com.codahale.metrics.Timer;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.InOrder;
 
 import java.net.UnknownHostException;
+import java.util.Locale;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
@@ -23,8 +28,19 @@ public class GraphiteReporterTest {
                                                               .convertRatesTo(TimeUnit.SECONDS)
                                                               .convertDurationsTo(TimeUnit.MILLISECONDS)
                                                               .filter(MetricFilter.ALL)
+                                                              .disabledMetricAttributes(Collections.<MetricAttribute>emptySet())
                                                               .build(graphite);
 
+    private final GraphiteReporter minuteRateReporter = GraphiteReporter
+            .forRegistry(registry)
+            .withClock(clock)
+            .prefixedWith("prefix")
+            .convertRatesTo(TimeUnit.MINUTES)
+            .convertDurationsTo(TimeUnit.MILLISECONDS)
+            .filter(MetricFilter.ALL)
+            .disabledMetricAttributes(Collections.<MetricAttribute>emptySet())
+            .build(graphite);
+
     @Before
     public void setUp() throws Exception {
         when(clock.getTime()).thenReturn(timestamp * 1000);
@@ -39,10 +55,10 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite, never()).send("prefix.gauge", "value", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -56,10 +72,10 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.gauge", "1", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -73,10 +89,10 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.gauge", "1", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -90,10 +106,10 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.gauge", "1", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -107,10 +123,10 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.gauge", "1", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -124,10 +140,10 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.gauge", "1.10", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -141,10 +157,61 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.gauge", "1.10", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
+
+        verifyNoMoreInteractions(graphite);
+    }
+
+    @Test
+    public void reportsDoubleGaugeValuesWithCustomFormat() throws Exception {
+        final GraphiteReporter graphiteReporter = new GraphiteReporter(registry, graphite, clock, "prefix",
+                TimeUnit.SECONDS, TimeUnit.MICROSECONDS, MetricFilter.ALL, null, false,
+                Collections.<MetricAttribute>emptySet()){
+            @Override
+            protected String format(double v) {
+                return String.format(Locale.US, "%4.4f", v);
+            }
+        };
+        graphiteReporter.report(map("gauge", gauge(1.13574)),
+                this.<Counter>map(),
+                this.<Histogram>map(),
+                this.<Meter>map(),
+                this.<Timer>map());
+
+        final InOrder inOrder = inOrder(graphite);
+        inOrder.verify(graphite).connect();
+        inOrder.verify(graphite).send("prefix.gauge", "1.1357", timestamp);
+        inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
+
+        verifyNoMoreInteractions(graphite);
+    }
+
+    @Test
+    public void reportsBooleanGaugeValues() throws Exception {
+        reporter.report(map("gauge", gauge(true)),
+                        this.<Counter>map(),
+                        this.<Histogram>map(),
+                        this.<Meter>map(),
+                        this.<Timer>map());
+
+        reporter.report(map("gauge", gauge(false)),
+                        this.<Counter>map(),
+                        this.<Histogram>map(),
+                        this.<Meter>map(),
+                        this.<Timer>map());
+        final InOrder inOrder = inOrder(graphite);
+        inOrder.verify(graphite).connect();
+        inOrder.verify(graphite).send("prefix.gauge", "1", timestamp);
+        inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
+        inOrder.verify(graphite).connect();
+        inOrder.verify(graphite).send("prefix.gauge", "0", timestamp);
+        inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -161,10 +228,10 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.counter.count", "100", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -195,7 +262,6 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.histogram.count", "1", timestamp);
         inOrder.verify(graphite).send("prefix.histogram.max", "2", timestamp);
@@ -209,6 +275,7 @@ public class GraphiteReporterTest {
         inOrder.verify(graphite).send("prefix.histogram.p99", "10.00", timestamp);
         inOrder.verify(graphite).send("prefix.histogram.p999", "11.00", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -229,7 +296,6 @@ public class GraphiteReporterTest {
                         this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.meter.count", "1", timestamp);
         inOrder.verify(graphite).send("prefix.meter.m1_rate", "2.00", timestamp);
@@ -237,6 +303,35 @@ public class GraphiteReporterTest {
         inOrder.verify(graphite).send("prefix.meter.m15_rate", "4.00", timestamp);
         inOrder.verify(graphite).send("prefix.meter.mean_rate", "5.00", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
+
+        verifyNoMoreInteractions(graphite);
+    }
+
+    @Test
+    public void reportsMetersInMinutes() throws Exception {
+        final Meter meter = mock(Meter.class);
+        when(meter.getCount()).thenReturn(1L);
+        when(meter.getOneMinuteRate()).thenReturn(2.0);
+        when(meter.getFiveMinuteRate()).thenReturn(3.0);
+        when(meter.getFifteenMinuteRate()).thenReturn(4.0);
+        when(meter.getMeanRate()).thenReturn(5.0);
+
+        minuteRateReporter.report(this.<Gauge>map(),
+                this.<Counter>map(),
+                this.<Histogram>map(),
+                this.<Meter>map("meter", meter),
+                this.<Timer>map());
+
+        final InOrder inOrder = inOrder(graphite);
+        inOrder.verify(graphite).connect();
+        inOrder.verify(graphite).send("prefix.meter.count", "1", timestamp);
+        inOrder.verify(graphite).send("prefix.meter.m1_rate", "120.00", timestamp);
+        inOrder.verify(graphite).send("prefix.meter.m5_rate", "180.00", timestamp);
+        inOrder.verify(graphite).send("prefix.meter.m15_rate", "240.00", timestamp);
+        inOrder.verify(graphite).send("prefix.meter.mean_rate", "300.00", timestamp);
+        inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
     }
@@ -272,7 +367,6 @@ public class GraphiteReporterTest {
                         map("timer", timer));
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).send("prefix.timer.max", "100.00", timestamp);
         inOrder.verify(graphite).send("prefix.timer.mean", "200.00", timestamp);
@@ -290,8 +384,11 @@ public class GraphiteReporterTest {
         inOrder.verify(graphite).send("prefix.timer.m15_rate", "5.00", timestamp);
         inOrder.verify(graphite).send("prefix.timer.mean_rate", "2.00", timestamp);
         inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
 
         verifyNoMoreInteractions(graphite);
+
+        reporter.close();
     }
 
     @Test
@@ -304,10 +401,10 @@ public class GraphiteReporterTest {
             this.<Timer>map());
 
         final InOrder inOrder = inOrder(graphite);
-        inOrder.verify(graphite).isConnected();
         inOrder.verify(graphite).connect();
         inOrder.verify(graphite).close();
 
+
         verifyNoMoreInteractions(graphite);
     }
 
@@ -320,6 +417,46 @@ public class GraphiteReporterTest {
         verifyNoMoreInteractions(graphite);
     }
 
+    @Test
+    public void disabledMetricsAttribute() throws Exception {
+        final Meter meter = mock(Meter.class);
+        when(meter.getCount()).thenReturn(1L);
+        when(meter.getOneMinuteRate()).thenReturn(2.0);
+        when(meter.getFiveMinuteRate()).thenReturn(3.0);
+        when(meter.getFifteenMinuteRate()).thenReturn(4.0);
+        when(meter.getMeanRate()).thenReturn(5.0);
+
+        final Counter counter = mock(Counter.class);
+        when(counter.getCount()).thenReturn(11L);
+
+        Set<MetricAttribute> disabledMetricAttributes = EnumSet.of(MetricAttribute.M15_RATE, MetricAttribute.M5_RATE);
+        GraphiteReporter reporterWithdisabledMetricAttributes = GraphiteReporter.forRegistry(registry)
+                .withClock(clock)
+                .prefixedWith("prefix")
+                .convertRatesTo(TimeUnit.SECONDS)
+                .convertDurationsTo(TimeUnit.MILLISECONDS)
+                .filter(MetricFilter.ALL)
+                .disabledMetricAttributes(disabledMetricAttributes)
+                .build(graphite);
+        reporterWithdisabledMetricAttributes.report(this.<Gauge>map(),
+                this.<Counter>map("counter", counter),
+                this.<Histogram>map(),
+                this.<Meter>map("meter", meter),
+                this.<Timer>map());
+
+        final InOrder inOrder = inOrder(graphite);
+        inOrder.verify(graphite).connect();
+        inOrder.verify(graphite).send("prefix.counter.count", "11", timestamp);
+        inOrder.verify(graphite).send("prefix.meter.count", "1", timestamp);
+        inOrder.verify(graphite).send("prefix.meter.m1_rate", "2.00", timestamp);
+        inOrder.verify(graphite).send("prefix.meter.mean_rate", "5.00", timestamp);
+        inOrder.verify(graphite).flush();
+        inOrder.verify(graphite).close();
+
+        verifyNoMoreInteractions(graphite);
+    }
+
+
     private <T> SortedMap<String, T> map() {
         return new TreeMap<String, T>();
     }
diff --git a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteSanitizeTest.java b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteSanitizeTest.java
new file mode 100644
index 0000000..5bd614a
--- /dev/null
+++ b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteSanitizeTest.java
@@ -0,0 +1,27 @@
+package com.codahale.metrics.graphite;
+
+import org.assertj.core.api.SoftAssertions;
+import org.junit.Test;
+
+public class GraphiteSanitizeTest {
+    @Test
+    public void sanitizeGraphiteValues() {
+        SoftAssertions softly = new SoftAssertions();
+
+        softly.assertThat(GraphiteSanitize.sanitize("Foo Bar")).isEqualTo("Foo-Bar");
+        softly.assertThat(GraphiteSanitize.sanitize(" Foo Bar ")).isEqualTo("Foo-Bar");
+        softly.assertThat(GraphiteSanitize.sanitize(" Foo Bar")).isEqualTo("Foo-Bar");
+        softly.assertThat(GraphiteSanitize.sanitize("Foo Bar ")).isEqualTo("Foo-Bar");
+        softly.assertThat(GraphiteSanitize.sanitize("  Foo Bar  ")).isEqualTo("Foo-Bar");
+        softly.assertThat(GraphiteSanitize.sanitize("Foo at Bar")).isEqualTo("Foo at Bar");
+        softly.assertThat(GraphiteSanitize.sanitize("Foó Bar")).isEqualTo("Foó-Bar");
+        softly.assertThat(GraphiteSanitize.sanitize("||ó/.")).isEqualTo("||ó/.");
+        softly.assertThat(GraphiteSanitize.sanitize("${Foo:Bar:baz}")).isEqualTo("${Foo:Bar:baz}");
+        softly.assertThat(GraphiteSanitize.sanitize("St. Foo's of Bar")).isEqualTo("St.-Foo's-of-Bar");
+        softly.assertThat(GraphiteSanitize.sanitize("(Foo and (Bar and (Baz)))")).isEqualTo("(Foo-and-(Bar-and-(Baz)))");
+        softly.assertThat(GraphiteSanitize.sanitize("Foo.bar.baz")).isEqualTo("Foo.bar.baz");
+        softly.assertThat(GraphiteSanitize.sanitize("FooBar")).isEqualTo("FooBar");
+
+        softly.assertAll();
+    }
+}
diff --git a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteTest.java b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteTest.java
index 771bbd2..b3337ce 100644
--- a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteTest.java
+++ b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteTest.java
@@ -103,7 +103,7 @@ public class GraphiteTest {
         graphite.connect();
         graphite.close();
 
-        verify(socket).close();
+        verify(socket, times(2)).close();
     }
 
     @Test
@@ -154,7 +154,7 @@ public class GraphiteTest {
 
     @Test
     public void notifiesIfGraphiteIsUnavailable() throws Exception {
-        final String unavailableHost = "unknown-host-10el6m7yg56ge7dm.com";
+        final String unavailableHost = "unknown-host-10el6m7yg56ge7dmcom";
         InetSocketAddress unavailableAddress = new InetSocketAddress(unavailableHost, 1234);
         Graphite unavailableGraphite = new Graphite(unavailableAddress, socketFactory);
 
diff --git a/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteUDPTest.java b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteUDPTest.java
new file mode 100644
index 0000000..695adc1
--- /dev/null
+++ b/metrics-graphite/src/test/java/com/codahale/metrics/graphite/GraphiteUDPTest.java
@@ -0,0 +1,43 @@
+package com.codahale.metrics.graphite;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+
+public class GraphiteUDPTest {
+
+    private final String host = "example.com";
+    private final int port = 1234;
+
+    private GraphiteUDP graphiteUDP;
+
+    @Test
+    public void connects() throws Exception {
+        graphiteUDP = new GraphiteUDP(host, port);
+        graphiteUDP.connect();
+
+        assertThat(graphiteUDP.getDatagramChannel()).isNotNull();
+        assertThat(graphiteUDP.getAddress()).isEqualTo(new InetSocketAddress(host, port));
+
+        graphiteUDP.close();
+    }
+
+    @Test
+    public void writesValue() throws Exception {
+        graphiteUDP = new GraphiteUDP(host, port);
+        DatagramChannel mockDatagramChannel = Mockito.mock(DatagramChannel.class);
+        graphiteUDP.setDatagramChannel(mockDatagramChannel);
+        graphiteUDP.setAddress(new InetSocketAddress(host, port));
+
+        graphiteUDP.send("name woo", "value", 100);
+        verify(mockDatagramChannel).send(ByteBuffer.wrap("name-woo value 100\n".getBytes("UTF-8")),
+                new InetSocketAddress(host, port));
+    }
+
+}
\ No newline at end of file
diff --git a/metrics-healthchecks/pom.xml b/metrics-healthchecks/pom.xml
index 460d266..bde58ee 100644
--- a/metrics-healthchecks/pom.xml
+++ b/metrics-healthchecks/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-healthchecks</artifactId>
diff --git a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/AsyncHealthCheckDecorator.java b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/AsyncHealthCheckDecorator.java
new file mode 100644
index 0000000..aef57de
--- /dev/null
+++ b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/AsyncHealthCheckDecorator.java
@@ -0,0 +1,60 @@
+package com.codahale.metrics.health;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+
+import com.codahale.metrics.health.annotation.Async;
+
+/**
+ * A health check decorator to manage asynchronous executions.
+ */
+class AsyncHealthCheckDecorator extends HealthCheck implements Runnable {
+    private static final String NO_RESULT_YET_MESSAGE = "Waiting for first asynchronous check result.";
+    private final HealthCheck healthCheck;
+    private final ScheduledFuture<?> future;
+    private volatile Result result;
+
+    AsyncHealthCheckDecorator(HealthCheck healthCheck, ScheduledExecutorService executorService) {
+        check(healthCheck != null, "healthCheck cannot be null");
+        check(executorService != null, "executorService cannot be null");
+        Async async = (Async) healthCheck.getClass().getAnnotation(Async.class);
+        check(async != null, "healthCheck must contain Async annotation");
+        check(async.period() > 0, "period cannot be less than or equal to zero");
+        check(async.initialDelay() >= 0, "initialDelay cannot be less than zero");
+
+        this.healthCheck = healthCheck;
+        result = Async.InitialState.HEALTHY.equals(async.initialState()) ? Result.healthy(NO_RESULT_YET_MESSAGE) :
+                Result.unhealthy(NO_RESULT_YET_MESSAGE);
+        if (Async.ScheduleType.FIXED_RATE.equals(async.scheduleType())) {
+            future = executorService.scheduleAtFixedRate(this, async.initialDelay(), async.period(), async.unit());
+        } else {
+            future = executorService.scheduleWithFixedDelay(this, async.initialDelay(), async.period(), async.unit());
+        }
+
+    }
+
+    @Override
+    public void run() {
+        result = healthCheck.execute();
+    }
+
+    @Override
+    protected Result check() throws Exception {
+        return result;
+    }
+
+    boolean tearDown() {
+        return future.cancel(true);
+    }
+
+    HealthCheck getHealthCheck() {
+        return healthCheck;
+    }
+
+    private void check(boolean expression, String message) {
+        if (!expression) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+}
diff --git a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheck.java b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheck.java
index e33ec03..f2baa81 100644
--- a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheck.java
+++ b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheck.java
@@ -1,15 +1,22 @@
 package com.codahale.metrics.health;
 
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 /**
  * A health check for a component of your application.
  */
 public abstract class HealthCheck {
     /**
-     * The result of a {@link HealthCheck} being run. It can be healthy (with an optional message)
-     * or unhealthy (with either an error message or a thrown exception).
+     * The result of a {@link HealthCheck} being run. It can be healthy (with an optional message and optional details)
+     * or unhealthy (with either an error message or a thrown exception and optional details).
      */
     public static class Result {
-        private static final Result HEALTHY = new Result(true, null, null);
+        private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
         private static final int PRIME = 31;
 
         /**
@@ -18,7 +25,7 @@ public abstract class HealthCheck {
          * @return a healthy {@link Result} with no additional message
          */
         public static Result healthy() {
-            return HEALTHY;
+            return new Result(true, null, null);
         }
 
         /**
@@ -73,20 +80,42 @@ public abstract class HealthCheck {
          * Returns an unhealthy {@link Result} with the given error.
          *
          * @param error an exception thrown during the health check
-         * @return an unhealthy {@link Result} with the given error
+         * @return an unhealthy {@link Result} with the given {@code error}
          */
         public static Result unhealthy(Throwable error) {
             return new Result(false, error.getMessage(), error);
         }
 
+
+		/**
+         * Returns a new {@link ResultBuilder}
+         *
+         * @return the {@link ResultBuilder}
+         */
+        public static ResultBuilder builder() {
+            return new ResultBuilder();
+        }
+
         private final boolean healthy;
         private final String message;
         private final Throwable error;
+        private final Map<String, Object> details;
+        private final String timestamp;
 
         private Result(boolean isHealthy, String message, Throwable error) {
+            this(isHealthy, message, error, null);
+        }
+
+        private Result(ResultBuilder builder) {
+            this(builder.healthy, builder.message, builder.error, builder.details);
+        }
+
+        private Result(boolean isHealthy, String message, Throwable error, Map<String, Object> details) {
             this.healthy = isHealthy;
             this.message = message;
             this.error = error;
+            this.details = details == null ? null : Collections.unmodifiableMap(details);
+            timestamp = new SimpleDateFormat(DATE_FORMAT_PATTERN).format(new Date());
         }
 
         /**
@@ -118,14 +147,31 @@ public abstract class HealthCheck {
             return error;
         }
 
+        /**
+         * Returns the timestamp when the result was created.
+         * @return a formatted timestamp
+         */
+        public String getTimestamp() {
+            return timestamp;
+        }
+
+        public Map<String, Object> getDetails() {
+            return details;
+        }
+
         @Override
         public boolean equals(Object o) {
-            if (this == o) { return true; }
-            if (o == null || getClass() != o.getClass()) { return false; }
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
             final Result result = (Result) o;
             return healthy == result.healthy &&
                     !(error != null ? !error.equals(result.error) : result.error != null) &&
-                    !(message != null ? !message.equals(result.message) : result.message != null);
+                    !(message != null ? !message.equals(result.message) : result.message != null) &&
+                    !(timestamp != null ? !timestamp.equals(result.timestamp) : result.timestamp != null);
         }
 
         @Override
@@ -133,6 +179,7 @@ public abstract class HealthCheck {
             int result = (healthy ? 1 : 0);
             result = PRIME * result + (message != null ? message.hashCode() : 0);
             result = PRIME * result + (error != null ? error.hashCode() : 0);
+            result = PRIME * result + (timestamp != null ? timestamp.hashCode() : 0);
             return result;
         }
 
@@ -146,12 +193,114 @@ public abstract class HealthCheck {
             if (error != null) {
                 builder.append(", error=").append(error);
             }
+            builder.append(", timestamp=").append(timestamp);
+            if (details != null) {
+                Iterator<Map.Entry<String, Object>> it = details.entrySet().iterator();
+                while (it.hasNext()) {
+                    builder.append(", ");
+                    Map.Entry<String, Object> e = it.next();
+                    builder.append(e.getKey())
+                            .append("=")
+                            .append(String.valueOf(e.getValue()));
+                }
+            }
             builder.append('}');
             return builder.toString();
         }
     }
 
     /**
+     * This a convenient builder for an {@link HealthCheck.Result}. It can be health (with optional message and detail)
+     * or unhealthy (with optional message, error and detail)
+     */
+    public static class ResultBuilder {
+        private boolean healthy;
+        private String message;
+        private Throwable error;
+        private Map<String, Object> details;
+
+        protected ResultBuilder() {
+            this.healthy = true;
+            this.details = new LinkedHashMap<String, Object>();
+        }
+
+        /**
+         * Configure an healthy result
+         *
+         * @return
+         */
+        public ResultBuilder healthy() {
+            this.healthy = true;
+            return this;
+        }
+
+        /**
+         * Configure an unhealthy result
+         *
+         * @return
+         */
+        public ResultBuilder unhealthy() {
+            this.healthy = false;
+            return this;
+        }
+
+        /**
+         * Configure an unhealthy result with an {@code error}
+         *
+         * @param error the error
+         * @return
+         */
+        public ResultBuilder unhealthy(Throwable error) {
+            this.error = error;
+            return this.unhealthy().withMessage(error.getMessage());
+        }
+
+        /**
+         * Set an optional message
+         *
+         * @param message an informative message
+         * @return this builder with the given {@code message}
+         */
+        public ResultBuilder withMessage(String message) {
+            this.message = message;
+            return this;
+        }
+
+        /**
+         * Set an optional formatted message
+         * <p/>
+         * Message formatting follows the same rules as {@link String#format(String, Object...)}.
+         *
+         * @param message a message format
+         * @param args    the arguments apply to the message format
+         * @return this builder with the given formatted {@code message}
+         * @see String#format(String, Object...)
+         */
+        public ResultBuilder withMessage(String message, Object... args) {
+            return withMessage(String.format(message, args));
+        }
+
+        /**
+         * Add an optional detail
+         *
+         * @param key  a key for this detail
+         * @param data an object representing the detail data
+         * @return this builder with the given detail added
+         */
+        public ResultBuilder withDetail(String key, Object data) {
+            if (this.details == null) {
+                this.details = new LinkedHashMap<String, Object>();
+            }
+            this.details.put(key, data);
+            return this;
+        }
+
+        public Result build() {
+            return new Result(this);
+        }
+    }
+
+    /**
      * Perform a check of the application component.
      *
      * @return if the component is healthy, a healthy {@link Result}; otherwise, an unhealthy {@link
diff --git a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckRegistry.java b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckRegistry.java
index 459cfc2..754832d 100644
--- a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckRegistry.java
+++ b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckRegistry.java
@@ -1,26 +1,95 @@
 package com.codahale.metrics.health;
 
+import static com.codahale.metrics.health.HealthCheck.Result;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
-import java.util.concurrent.*;
-
-import static com.codahale.metrics.health.HealthCheck.Result;
+import com.codahale.metrics.health.annotation.Async;
 
 /**
  * A registry for health checks.
  */
 public class HealthCheckRegistry {
     private static final Logger LOGGER = LoggerFactory.getLogger(HealthCheckRegistry.class);
+    private static final int ASYNC_EXECUTOR_POOL_SIZE = 2;
 
     private final ConcurrentMap<String, HealthCheck> healthChecks;
+    private final List<HealthCheckRegistryListener> listeners;
+    private final ScheduledExecutorService asyncExecutorService;
+    private final Object lock = new Object();
 
     /**
      * Creates a new {@link HealthCheckRegistry}.
      */
     public HealthCheckRegistry() {
+        this(ASYNC_EXECUTOR_POOL_SIZE);
+    }
+
+    /**
+     * Creates a new {@link HealthCheckRegistry}.
+     *
+     * @param asyncExecutorPoolSize core pool size for async health check executions
+     */
+    public HealthCheckRegistry(int asyncExecutorPoolSize) {
+        this(createExecutorService(asyncExecutorPoolSize));
+    }
+
+    /**
+     * Creates a new {@link HealthCheckRegistry}.
+     *
+     * @param asyncExecutorService executor service for async health check executions
+     */
+    public HealthCheckRegistry(ScheduledExecutorService asyncExecutorService) {
         this.healthChecks = new ConcurrentHashMap<String, HealthCheck>();
+        this.listeners = new CopyOnWriteArrayList<HealthCheckRegistryListener>();
+        this.asyncExecutorService = asyncExecutorService;
+    }
+
+    /**
+     * Adds a {@link HealthCheckRegistryListener} to a collection of listeners that will be notified on health check
+     * registration. Listeners will be notified in the order in which they are added. The listener will be notified of all
+     * existing health checks when it first registers.
+     *
+     * @param listener listener to add
+     */
+    public void addListener(HealthCheckRegistryListener listener) {
+        listeners.add(listener);
+        for (Map.Entry<String, HealthCheck> entry : healthChecks.entrySet()) {
+            listener.onHealthCheckAdded(entry.getKey(), entry.getValue());
+        }
+    }
+
+    /**
+     * Removes a {@link HealthCheckRegistryListener} from this registry's collection of listeners.
+     *
+     * @param listener listener to remove
+     */
+    public void removeListener(HealthCheckRegistryListener listener) {
+        listeners.remove(listener);
     }
 
     /**
@@ -30,7 +99,19 @@ public class HealthCheckRegistry {
      * @param healthCheck the {@link HealthCheck} instance
      */
     public void register(String name, HealthCheck healthCheck) {
-        healthChecks.putIfAbsent(name, healthCheck);
+        HealthCheck registered = null;
+        synchronized (lock) {
+            if (!healthChecks.containsKey(name)) {
+                registered = healthCheck;
+                if (healthCheck.getClass().isAnnotationPresent(Async.class)) {
+                    registered = new AsyncHealthCheckDecorator(healthCheck, asyncExecutorService);
+                }
+                healthChecks.put(name, registered);
+            }
+        }
+        if (registered != null) {
+            onHealthCheckAdded(name, registered);
+        }
     }
 
     /**
@@ -39,7 +120,16 @@ public class HealthCheckRegistry {
      * @param name the name of the {@link HealthCheck} instance
      */
     public void unregister(String name) {
-        healthChecks.remove(name);
+        HealthCheck healthCheck = null;
+        synchronized (lock) {
+            healthCheck = healthChecks.remove(name);
+            if (healthCheck instanceof AsyncHealthCheckDecorator) {
+                ((AsyncHealthCheckDecorator) healthCheck).tearDown();
+            }
+        }
+        if (healthCheck != null) {
+            onHealthCheckRemoved(name, healthCheck);
+        }
     }
 
     /**
@@ -54,7 +144,7 @@ public class HealthCheckRegistry {
     /**
      * Runs the health check with the given name.
      *
-     * @param name    the health check's name
+     * @param name the health check's name
      * @return the result of the health check
      * @throws NoSuchElementException if there is no health check with the given name
      */
@@ -82,7 +172,8 @@ public class HealthCheckRegistry {
 
     /**
      * Runs the registered health checks in parallel and returns a map of the results.
-     * @param   executor object to launch and track health checks progress
+     *
+     * @param executor object to launch and track health checks progress
      * @return a map of the health check results
      */
     public SortedMap<String, HealthCheck.Result> runHealthChecks(ExecutorService executor) {
@@ -102,8 +193,83 @@ public class HealthCheckRegistry {
                 results.put(entry.getKey(), entry.getValue().get());
             } catch (Exception e) {
                 LOGGER.warn("Error executing health check {}", entry.getKey(), e);
+                results.put(entry.getKey(), HealthCheck.Result.unhealthy(e));
             }
         }
+
         return Collections.unmodifiableSortedMap(results);
     }
+
+
+    private void onHealthCheckAdded(String name, HealthCheck healthCheck) {
+        for (HealthCheckRegistryListener listener : listeners) {
+            listener.onHealthCheckAdded(name, healthCheck);
+        }
+    }
+
+    private void onHealthCheckRemoved(String name, HealthCheck healthCheck) {
+        for (HealthCheckRegistryListener listener : listeners) {
+            listener.onHealthCheckRemoved(name, healthCheck);
+        }
+    }
+
+    /**
+     * Shuts down the scheduled executor for async health checks
+     */
+    public void shutdown() {
+        asyncExecutorService.shutdown(); // Disable new health checks from being submitted
+        try {
+            // Give some time to the current healtch checks to finish gracefully
+            if (!asyncExecutorService.awaitTermination(1, TimeUnit.SECONDS)) {
+                asyncExecutorService.shutdownNow();
+            }
+        } catch (InterruptedException ie) {
+            asyncExecutorService.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    private static ScheduledExecutorService createExecutorService(int corePoolSize) {
+        ScheduledExecutorService asyncExecutorService = Executors.newScheduledThreadPool(corePoolSize,
+                new NamedThreadFactory("healthcheck-async-executor-"));
+        try {
+            Method method = asyncExecutorService.getClass().getMethod("setRemoveOnCancelPolicy", Boolean.TYPE);
+            method.invoke(asyncExecutorService, true);
+        } catch (NoSuchMethodException e) {
+            logSetExecutorCancellationPolicyFailure(e);
+        } catch (IllegalAccessException e) {
+            logSetExecutorCancellationPolicyFailure(e);
+        } catch (InvocationTargetException e) {
+            logSetExecutorCancellationPolicyFailure(e);
+        }
+        return asyncExecutorService;
+    }
+
+    private static void logSetExecutorCancellationPolicyFailure(Exception e) {
+        LOGGER.warn("Tried but failed to set executor cancellation policy to remove on cancel which has been introduced " +
+                "in Java 7. This could result in a memory leak if many asynchronous health checks are registered and " +
+                "removed because cancellation does not actually remove them from the executor.", e);
+    }
+
+    private static class NamedThreadFactory implements ThreadFactory {
+
+        private final ThreadGroup group;
+        private final AtomicInteger threadNumber = new AtomicInteger(1);
+        private final String namePrefix;
+
+        NamedThreadFactory(String namePrefix) {
+            SecurityManager s = System.getSecurityManager();
+            group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+            this.namePrefix = namePrefix;
+        }
+
+        @Override
+        public Thread newThread(Runnable r) {
+            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
+            t.setDaemon(true);
+            if (t.getPriority() != Thread.NORM_PRIORITY)
+                t.setPriority(Thread.NORM_PRIORITY);
+            return t;
+        }
+    }
 }
diff --git a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckRegistryListener.java b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckRegistryListener.java
new file mode 100644
index 0000000..dad5d38
--- /dev/null
+++ b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/HealthCheckRegistryListener.java
@@ -0,0 +1,26 @@
+package com.codahale.metrics.health;
+
+import java.util.EventListener;
+
+/**
+ * A listener contract for {@link HealthCheckRegistry} events.
+ */
+public interface HealthCheckRegistryListener extends EventListener {
+
+    /**
+     * Called when a new {@link HealthCheck} is added to the registry.
+     *
+     * @param name        the name of the health check
+     * @param healthCheck the health check
+     */
+    void onHealthCheckAdded(String name, HealthCheck healthCheck);
+
+    /**
+     * Called when a {@link HealthCheck} is removed from the registry.
+     *
+     * @param name        the name of the health check
+     * @param healthCheck the health check
+     */
+    void onHealthCheckRemoved(String name, HealthCheck healthCheck);
+
+}
diff --git a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/SharedHealthCheckRegistries.java b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/SharedHealthCheckRegistries.java
index 5a983c9..e65000e 100644
--- a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/SharedHealthCheckRegistries.java
+++ b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/SharedHealthCheckRegistries.java
@@ -1,10 +1,9 @@
 package com.codahale.metrics.health;
 
-import com.codahale.metrics.MetricRegistry;
-
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * A map of shared, named health registries.
@@ -13,6 +12,13 @@ public class SharedHealthCheckRegistries {
     private static final ConcurrentMap<String, HealthCheckRegistry> REGISTRIES =
             new ConcurrentHashMap<String, HealthCheckRegistry>();
 
+    private static AtomicReference<String> defaultRegistryName = new AtomicReference<String>();
+
+    /* Visible for testing */
+    static void setDefaultRegistryName(AtomicReference<String> defaultRegistryName) {
+        SharedHealthCheckRegistries.defaultRegistryName = defaultRegistryName;
+    }
+
     private SharedHealthCheckRegistries() { /* singleton */ }
 
     public static void clear() {
@@ -43,4 +49,58 @@ public class SharedHealthCheckRegistries {
         }
         return existing;
     }
+
+    /**
+     * Creates a new registry and sets it as the default one under the provided name.
+     *
+     * @param name the registry name
+     * @return the default registry
+     * @throws IllegalStateException if the name has already been set
+     */
+    public synchronized static HealthCheckRegistry setDefault(String name) {
+        final HealthCheckRegistry registry = getOrCreate(name);
+        return setDefault(name, registry);
+    }
+
+    /**
+     * Sets the provided registry as the default one under the provided name
+     *
+     * @param name                the default registry name
+     * @param healthCheckRegistry the default registry
+     * @throws IllegalStateException if the default registry has already been set
+     */
+    public static HealthCheckRegistry setDefault(String name, HealthCheckRegistry healthCheckRegistry) {
+        if (defaultRegistryName.compareAndSet(null, name)) {
+            add(name, healthCheckRegistry);
+            return healthCheckRegistry;
+        }
+        throw new IllegalStateException("Default health check registry is already set.");
+    }
+
+    /**
+     * Gets the name of the default registry, if it has been set
+     *
+     * @return the default registry
+     * @throws IllegalStateException if the default has not been set
+     */
+    public static HealthCheckRegistry getDefault() {
+        final HealthCheckRegistry healthCheckRegistry = tryGetDefault();
+        if (healthCheckRegistry != null) {
+            return healthCheckRegistry;
+        }
+        throw new IllegalStateException("Default registry name has not been set.");
+    }
+
+    /**
+     * Same as {@link #getDefault()} except returns null when the default registry has not been set.
+     *
+     * @return the default registry or null
+     */
+    public static HealthCheckRegistry tryGetDefault() {
+        final String name = defaultRegistryName.get();
+        if (name != null) {
+            return getOrCreate(name);
+        }
+        return null;
+    }
 }
diff --git a/metrics-healthchecks/src/main/java/com/codahale/metrics/health/annotation/Async.java b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/annotation/Async.java
new file mode 100644
index 0000000..91de06f
--- /dev/null
+++ b/metrics-healthchecks/src/main/java/com/codahale/metrics/health/annotation/Async.java
@@ -0,0 +1,64 @@
+package com.codahale.metrics.health.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An annotation for marking asynchronous health check execution.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+public @interface Async {
+    /**
+     * Enum representing the initial health states.
+     */
+    public enum InitialState {
+        HEALTHY, UNHEALTHY;
+    }
+
+    /**
+     * Enum representing the possible schedule types.
+     */
+    public enum ScheduleType {
+        FIXED_RATE, FIXED_DELAY;
+    }
+
+    /**
+     * Period between executions.
+     *
+     * @return period
+     */
+    long period();
+
+    /**
+     * Scheduling type of asynchronous executions.
+     *
+     * @return schedule type
+     */
+    ScheduleType scheduleType() default ScheduleType.FIXED_RATE;
+
+    /**
+     * Initial delay of first execution.
+     *
+     * @return initial delay
+     */
+    long initialDelay() default 0;
+
+    /**
+     * Time unit of initial delay and period.
+     *
+     * @return time unit
+     */
+    TimeUnit unit() default TimeUnit.SECONDS;
+
+    /**
+     * Initial health state until first asynchronous execution completes.
+     *
+     * @return initial health state
+     */
+    InitialState initialState() default InitialState.HEALTHY;
+
+}
diff --git a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/AsyncHealthCheckDecoratorTest.java b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/AsyncHealthCheckDecoratorTest.java
new file mode 100644
index 0000000..f3f2f61
--- /dev/null
+++ b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/AsyncHealthCheckDecoratorTest.java
@@ -0,0 +1,224 @@
+package com.codahale.metrics.health;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentCaptor.forClass;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.codahale.metrics.health.annotation.Async;
+
+/**
+ * Unit tests for {@link AsyncHealthCheckDecorator}.
+ */
+public class AsyncHealthCheckDecoratorTest {
+    private final HealthCheck mockHealthCheck = mock(HealthCheck.class);
+    private final ScheduledExecutorService mockExecutorService = mock(ScheduledExecutorService.class);
+    private final ScheduledFuture mockFuture = mock(ScheduledFuture.class);
+
+    @Test(expected = IllegalArgumentException.class)
+    public void nullHealthCheckTriggersInstantiationFailure() {
+        new AsyncHealthCheckDecorator(null, mockExecutorService);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void nullExecutorServiceTriggersInstantiationFailure() {
+        new AsyncHealthCheckDecorator(mockHealthCheck, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void nonAsyncHealthCheckTriggersInstantiationFailure() {
+        new AsyncHealthCheckDecorator(mockHealthCheck, mockExecutorService);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void negativePeriodTriggersInstantiationFailure() {
+        new AsyncHealthCheckDecorator(new NegativePeriodAsyncHealthCheck(), mockExecutorService);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void zeroPeriodTriggersInstantiationFailure() {
+        new AsyncHealthCheckDecorator(new ZeroPeriodAsyncHealthCheck(), mockExecutorService);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void negativeInitialValueTriggersInstantiationFailure() {
+        new AsyncHealthCheckDecorator(new NegativeInitialDelayAsyncHealthCheck(), mockExecutorService);
+    }
+
+    @Test
+    public void defaultAsyncHealthCheckTriggersSuccessfulInstantiationWithFixedRateAndHealthyState() throws Exception {
+        HealthCheck asyncHealthCheck = new DefaultAsyncHealthCheck();
+        AsyncHealthCheckDecorator asyncDecorator = new AsyncHealthCheckDecorator(asyncHealthCheck, mockExecutorService);
+
+        verify(mockExecutorService, times(1)).scheduleAtFixedRate(any(Runnable.class), eq(0L),
+                eq(1L), eq(TimeUnit.SECONDS));
+        assertThat(asyncDecorator.getHealthCheck()).isEqualTo(asyncHealthCheck);
+        assertThat(asyncDecorator.check().isHealthy()).isTrue();
+    }
+
+    @Test
+    public void fixedDelayAsyncHealthCheckTriggersSuccessfulInstantiationWithFixedDelay() throws Exception {
+        HealthCheck asyncHealthCheck = new FixedDelayAsyncHealthCheck();
+        AsyncHealthCheckDecorator asyncDecorator = new AsyncHealthCheckDecorator(asyncHealthCheck, mockExecutorService);
+
+        verify(mockExecutorService, times(1)).scheduleWithFixedDelay(any(Runnable.class), eq(0L),
+                eq(1L), eq(TimeUnit.SECONDS));
+        assertThat(asyncDecorator.getHealthCheck()).isEqualTo(asyncHealthCheck);
+    }
+
+    @Test
+    public void unhealthyAsyncHealthCheckTriggersSuccessfulInstantiationWithUnhealthyState() throws Exception {
+        HealthCheck asyncHealthCheck = new UnhealthyAsyncHealthCheck();
+        AsyncHealthCheckDecorator asyncDecorator = new AsyncHealthCheckDecorator(asyncHealthCheck, mockExecutorService);
+
+        assertThat(asyncDecorator.check().isHealthy()).isFalse();
+    }
+
+    @Test
+    public void tearDownTriggersCancellation() throws Exception {
+        when(mockExecutorService.scheduleAtFixedRate(any(Runnable.class), eq(0L), eq(1L), eq(TimeUnit.SECONDS))).
+                thenReturn(mockFuture);
+        when(mockFuture.cancel(true)).thenReturn(true);
+
+        AsyncHealthCheckDecorator asyncDecorator = new AsyncHealthCheckDecorator(new DefaultAsyncHealthCheck(), mockExecutorService);
+        asyncDecorator.tearDown();
+
+        verify(mockExecutorService, times(1)).scheduleAtFixedRate(any(Runnable.class), eq(0L),
+                eq(1L), eq(TimeUnit.SECONDS));
+        verify(mockFuture, times(1)).cancel(eq(true));
+    }
+
+    @Test
+    public void afterFirstExecutionDecoratedHealthCheckResultIsProvided() throws Exception {
+        HealthCheck.Result expectedResult = HealthCheck.Result.healthy("AsyncHealthCheckTest");
+        when(mockExecutorService.scheduleAtFixedRate(any(Runnable.class), eq(0L), eq(1L), eq(TimeUnit.SECONDS)))
+                .thenReturn(mockFuture);
+
+        AsyncHealthCheckDecorator asyncDecorator = new AsyncHealthCheckDecorator(new ConfigurableAsyncHealthCheck(expectedResult),
+                mockExecutorService);
+        HealthCheck.Result initialResult = asyncDecorator.check();
+
+        ArgumentCaptor<Runnable> runnableCaptor = forClass(Runnable.class);
+        verify(mockExecutorService, times(1)).scheduleAtFixedRate(runnableCaptor.capture(),
+                eq(0L), eq(1L), eq(TimeUnit.SECONDS));
+        Runnable capturedRunnable = runnableCaptor.getValue();
+        capturedRunnable.run();
+        HealthCheck.Result actualResult = asyncDecorator.check();
+
+        assertThat(actualResult).isEqualTo(expectedResult);
+        assertThat(actualResult).isNotEqualTo(initialResult);
+    }
+
+    @Test
+    public void exceptionInDecoratedHealthCheckWontAffectAsyncDecorator() throws Exception {
+        Exception exception = new Exception("TestException");
+        when(mockExecutorService.scheduleAtFixedRate(any(Runnable.class), eq(0L), eq(1L), eq(TimeUnit.SECONDS)))
+                .thenReturn(mockFuture);
+
+        AsyncHealthCheckDecorator asyncDecorator = new AsyncHealthCheckDecorator(new ConfigurableAsyncHealthCheck(exception),
+                mockExecutorService);
+
+        ArgumentCaptor<Runnable> runnableCaptor = forClass(Runnable.class);
+        verify(mockExecutorService, times(1)).scheduleAtFixedRate(runnableCaptor.capture(),
+                eq(0L), eq(1L), eq(TimeUnit.SECONDS));
+        Runnable capturedRunnable = runnableCaptor.getValue();
+        capturedRunnable.run();
+        HealthCheck.Result result = asyncDecorator.check();
+
+        assertThat(result.isHealthy()).isFalse();
+        assertThat(result.getError()).isEqualTo(exception);
+    }
+
+    @Async(period = -1)
+    private static class NegativePeriodAsyncHealthCheck extends HealthCheck {
+
+        @Override
+        protected Result check() throws Exception {
+            return null;
+        }
+    }
+
+    @Async(period = 0)
+    private static class ZeroPeriodAsyncHealthCheck extends HealthCheck {
+
+        @Override
+        protected Result check() throws Exception {
+            return null;
+        }
+    }
+
+    @Async(period = 1, initialDelay = -1)
+    private static class NegativeInitialDelayAsyncHealthCheck extends HealthCheck {
+
+        @Override
+        protected Result check() throws Exception {
+            return null;
+        }
+    }
+
+    @Async(period = 1)
+    private static class DefaultAsyncHealthCheck extends HealthCheck {
+
+        @Override
+        protected Result check() throws Exception {
+            return null;
+        }
+    }
+
+    @Async(period = 1, scheduleType = Async.ScheduleType.FIXED_DELAY)
+    private static class FixedDelayAsyncHealthCheck extends HealthCheck {
+
+        @Override
+        protected Result check() throws Exception {
+            return null;
+        }
+    }
+
+    @Async(period = 1, initialState = Async.InitialState.UNHEALTHY)
+    private static class UnhealthyAsyncHealthCheck extends HealthCheck {
+
+        @Override
+        protected Result check() throws Exception {
+            return null;
+        }
+    }
+
+    @Async(period = 1, initialState = Async.InitialState.UNHEALTHY)
+    private static class ConfigurableAsyncHealthCheck extends HealthCheck {
+        private final Result result;
+        private final Exception exception;
+
+        ConfigurableAsyncHealthCheck(Result result) {
+            this(result, null);
+        }
+
+        ConfigurableAsyncHealthCheck(Exception exception) {
+            this(null, exception);
+        }
+
+        private ConfigurableAsyncHealthCheck(Result result, Exception exception) {
+            this.result = result;
+            this.exception = exception;
+        }
+
+        @Override
+        protected Result check() throws Exception {
+            if (exception != null) {
+                throw exception;
+            }
+            return result;
+        }
+    }
+
+}
diff --git a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckRegistryTest.java b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckRegistryTest.java
index 50fbca0..3d3171b 100644
--- a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckRegistryTest.java
+++ b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckRegistryTest.java
@@ -1,20 +1,33 @@
 package com.codahale.metrics.health;
 
-import org.junit.Before;
-import org.junit.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
+import static org.mockito.ArgumentCaptor.forClass;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
-import static org.assertj.core.api.Assertions.*;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.codahale.metrics.health.annotation.Async;
 
 public class HealthCheckRegistryTest {
-    private final HealthCheckRegistry registry = new HealthCheckRegistry();
+    private final ScheduledExecutorService executorService = mock(ScheduledExecutorService.class);
+    private final HealthCheckRegistry registry = new HealthCheckRegistry(executorService);
+    private final HealthCheckRegistryListener listener = mock(HealthCheckRegistryListener.class);
 
     private final HealthCheck hc1 = mock(HealthCheck.class);
     private final HealthCheck hc2 = mock(HealthCheck.class);
@@ -22,25 +35,89 @@ public class HealthCheckRegistryTest {
     private final HealthCheck.Result r1 = mock(HealthCheck.Result.class);
     private final HealthCheck.Result r2 = mock(HealthCheck.Result.class);
 
+    private final HealthCheck.Result ar = mock(HealthCheck.Result.class);
+    private final HealthCheck ahc = new TestAsyncHealthCheck(ar);
+    private final ScheduledFuture af = mock(ScheduledFuture.class);
+
     @Before
     public void setUp() throws Exception {
-        when(hc1.execute()).thenReturn(r1);
+        registry.addListener(listener);
 
+        when(hc1.execute()).thenReturn(r1);
         when(hc2.execute()).thenReturn(r2);
+        when(executorService.scheduleAtFixedRate(any(AsyncHealthCheckDecorator.class),eq(0L), eq(10L), eq(TimeUnit
+                .SECONDS))).thenReturn(af);
+
+        registry.register("hc1", hc1);
+        registry.register("hc2", hc2);
+        registry.register("ahc", ahc);
+    }
+
+    @Test
+    public void asyncHealthCheckIsScheduledOnExecutor() {
+        ArgumentCaptor<AsyncHealthCheckDecorator> decoratorCaptor = forClass(AsyncHealthCheckDecorator.class);
+        verify(executorService).scheduleAtFixedRate(decoratorCaptor.capture(), eq(0L), eq(10L), eq(TimeUnit.SECONDS));
+        assertThat(decoratorCaptor.getValue().getHealthCheck()).isEqualTo(ahc);
+    }
+
+    @Test
+    public void asyncHealthCheckIsCanceledOnRemove() {
+        registry.unregister("ahc");
+
+        verify(af).cancel(true);
+    }
+
+    @Test
+    public void registeringHealthCheckTriggersNotification() {
+        verify(listener).onHealthCheckAdded("hc1", hc1);
+        verify(listener).onHealthCheckAdded("hc2", hc2);
+        verify(listener).onHealthCheckAdded(eq("ahc"), any(AsyncHealthCheckDecorator.class));
+    }
+
+    @Test
+    public void removingHealthCheckTriggersNotification() {
+        registry.unregister("hc1");
+        registry.unregister("hc2");
+        registry.unregister("ahc");
+
+        verify(listener).onHealthCheckRemoved("hc1", hc1);
+        verify(listener).onHealthCheckRemoved("hc2", hc2);
+        verify(listener).onHealthCheckRemoved(eq("ahc"), any(AsyncHealthCheckDecorator.class));
+    }
+
+    @Test
+    public void addingListenerCatchesExistingHealthChecks() {
+        HealthCheckRegistryListener listener = mock(HealthCheckRegistryListener.class);
+        HealthCheckRegistry registry = new HealthCheckRegistry();
+        registry.register("hc1", hc1);
+        registry.register("hc2", hc2);
+        registry.register("ahc", ahc);
+        registry.addListener(listener);
+
+        verify(listener).onHealthCheckAdded("hc1", hc1);
+        verify(listener).onHealthCheckAdded("hc2", hc2);
+        verify(listener).onHealthCheckAdded(eq("ahc"), any(AsyncHealthCheckDecorator.class));
+    }
 
+    @Test
+    public void removedListenerDoesNotReceiveUpdates() {
+        HealthCheckRegistryListener listener = mock(HealthCheckRegistryListener.class);
+        HealthCheckRegistry registry = new HealthCheckRegistry();
+        registry.addListener(listener);
         registry.register("hc1", hc1);
+        registry.removeListener(listener);
         registry.register("hc2", hc2);
+
+        verify(listener).onHealthCheckAdded("hc1", hc1);
     }
 
     @Test
     public void runsRegisteredHealthChecks() throws Exception {
         final Map<String, HealthCheck.Result> results = registry.runHealthChecks();
 
-        assertThat(results)
-                .contains(entry("hc1", r1));
-
-        assertThat(results)
-                .contains(entry("hc2", r2));
+        assertThat(results).contains(entry("hc1", r1));
+        assertThat(results).contains(entry("hc2", r2));
+        assertThat(results).containsKey("ahc");
     }
 
     @Test
@@ -51,11 +128,9 @@ public class HealthCheckRegistryTest {
         executor.shutdown();
         executor.awaitTermination(1, TimeUnit.SECONDS);
 
-        assertThat(results)
-                .contains(entry("hc1", r1));
-
-        assertThat(results)
-                .contains(entry("hc2", r2));
+        assertThat(results).contains(entry("hc1", r1));
+        assertThat(results).contains(entry("hc2", r2));
+        assertThat(results).containsKey("ahc");
     }
 
     @Test
@@ -64,23 +139,19 @@ public class HealthCheckRegistryTest {
 
         final Map<String, HealthCheck.Result> results = registry.runHealthChecks();
 
-        assertThat(results)
-                .doesNotContainKey("hc1");
-
-        assertThat(results)
-                .containsKey("hc2");
+        assertThat(results).doesNotContainKey("hc1");
+        assertThat(results).containsKey("hc2");
+        assertThat(results).containsKey("ahc");
     }
 
     @Test
     public void hasASetOfHealthCheckNames() throws Exception {
-        assertThat(registry.getNames())
-                .containsOnly("hc1", "hc2");
+        assertThat(registry.getNames()).containsOnly("hc1", "hc2", "ahc");
     }
 
     @Test
     public void runsHealthChecksByName() throws Exception {
-        assertThat(registry.runHealthCheck("hc1"))
-                .isEqualTo(r1);
+        assertThat(registry.runHealthCheck("hc1")).isEqualTo(r1);
     }
 
     @Test
@@ -94,4 +165,18 @@ public class HealthCheckRegistryTest {
         }
 
     }
+
+    @Async(period = 10)
+    private static class TestAsyncHealthCheck extends HealthCheck {
+        private final Result result;
+
+        TestAsyncHealthCheck(Result result) {
+            this.result = result;
+        }
+
+        @Override
+        protected Result check() throws Exception {
+            return result;
+        }
+    }
 }
diff --git a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckTest.java b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckTest.java
index acbdc87..8d3a88a 100644
--- a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckTest.java
+++ b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/HealthCheckTest.java
@@ -1,11 +1,11 @@
 package com.codahale.metrics.health;
 
-import org.junit.Test;
-
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import org.junit.Test;
+
 public class HealthCheckTest {
     private static class ExampleHealthCheck extends HealthCheck {
         private final HealthCheck underlying;
@@ -111,6 +111,70 @@ public class HealthCheckTest {
     }
 
     @Test
+    public void canHaveHealthyBuilderWithDetail() throws Exception {
+        final HealthCheck.Result result = HealthCheck.Result.builder()
+            .healthy()
+            .withDetail("detail", "value")
+            .build();
+
+        assertThat(result.isHealthy())
+            .isTrue();
+
+        assertThat(result.getMessage())
+            .isNull();
+
+        assertThat(result.getError())
+            .isNull();
+
+        assertThat(result.getDetails())
+            .containsEntry("detail", "value");
+    }
+
+    @Test
+    public void canHaveUnHealthyBuilderWithDetail() throws Exception {
+        final HealthCheck.Result result = HealthCheck.Result.builder()
+            .unhealthy()
+            .withDetail("detail", "value")
+            .build();
+
+        assertThat(result.isHealthy())
+            .isFalse();
+
+        assertThat(result.getMessage())
+            .isNull();
+
+        assertThat(result.getError())
+            .isNull();
+
+        assertThat(result.getDetails())
+            .containsEntry("detail", "value");
+    }
+
+    @Test
+    public void canHaveUnHealthyBuilderWithDetailAndError() throws Exception {
+        final RuntimeException e = mock(RuntimeException.class);
+        when(e.getMessage()).thenReturn("oh noes");
+
+        final HealthCheck.Result result = HealthCheck.Result
+            .builder()
+            .unhealthy(e)
+            .withDetail("detail", "value")
+            .build();
+
+        assertThat(result.isHealthy())
+            .isFalse();
+
+        assertThat(result.getMessage())
+            .isEqualTo("oh noes");
+
+        assertThat(result.getError())
+            .isEqualTo(e);
+
+        assertThat(result.getDetails())
+            .containsEntry("detail", "value");
+    }
+
+    @Test
     public void returnsResultsWhenExecuted() throws Exception {
         final HealthCheck.Result result = mock(HealthCheck.Result.class);
         when(underlying.execute()).thenReturn(result);
@@ -125,8 +189,27 @@ public class HealthCheckTest {
         when(e.getMessage()).thenReturn("oh noes");
 
         when(underlying.execute()).thenThrow(e);
+        HealthCheck.Result actual = healthCheck.execute();
 
-        assertThat(healthCheck.execute())
-                .isEqualTo(HealthCheck.Result.unhealthy(e));
+        assertThat(actual.isHealthy())
+                .isFalse();
+        assertThat(actual.getMessage())
+                .isEqualTo("oh noes");
+        assertThat(actual.getError())
+                .isEqualTo(e);
+        assertThat(actual.getDetails())
+                .isNull();
+    }
+
+    @Test
+    public void toStringWorksEvenForNullAttributes() throws Exception {
+        final HealthCheck.Result resultWithNullDetailValue = HealthCheck.Result.builder()
+           .unhealthy()
+           .withDetail("aNullDetail", null)
+           .build();
+        assertThat(resultWithNullDetailValue.toString())
+           .contains(
+              "Result{isHealthy=false, timestamp=", // Skip the timestamp part of the String.
+              ", aNullDetail=null}");
     }
 }
diff --git a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/SharedHealthCheckRegistriesTest.java b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/SharedHealthCheckRegistriesTest.java
new file mode 100644
index 0000000..c9c0254
--- /dev/null
+++ b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/SharedHealthCheckRegistriesTest.java
@@ -0,0 +1,97 @@
+package com.codahale.metrics.health;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SharedHealthCheckRegistriesTest {
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    @Before
+    public void setUp() {
+        SharedHealthCheckRegistries.setDefaultRegistryName(new AtomicReference<String>());
+        SharedHealthCheckRegistries.clear();
+    }
+
+    @Test
+    public void savesCreatedRegistry() {
+        final HealthCheckRegistry one = SharedHealthCheckRegistries.getOrCreate("db");
+        final HealthCheckRegistry two = SharedHealthCheckRegistries.getOrCreate("db");
+
+        assertThat(one).isSameAs(two);
+    }
+
+    @Test
+    public void returnsSetOfCreatedRegistries() {
+        SharedHealthCheckRegistries.getOrCreate("db");
+
+        assertThat(SharedHealthCheckRegistries.names()).containsOnly("db");
+    }
+
+    @Test
+    public void registryCanBeRemoved() {
+        final HealthCheckRegistry first = SharedHealthCheckRegistries.getOrCreate("db");
+        SharedHealthCheckRegistries.remove("db");
+
+        assertThat(SharedHealthCheckRegistries.names()).isEmpty();
+        assertThat(SharedHealthCheckRegistries.getOrCreate("db")).isNotEqualTo(first);
+    }
+
+    @Test
+    public void registryCanBeCleared() {
+        SharedHealthCheckRegistries.getOrCreate("db");
+        SharedHealthCheckRegistries.getOrCreate("web");
+
+        SharedHealthCheckRegistries.clear();
+
+        assertThat(SharedHealthCheckRegistries.names()).isEmpty();
+    }
+
+    @Test
+    public void defaultRegistryIsNotSetByDefault() {
+        expectedException.expect(IllegalStateException.class);
+        expectedException.expectMessage("Default registry name has not been set.");
+
+        SharedHealthCheckRegistries.getDefault();
+    }
+
+    @Test
+    public void defaultRegistryCanBeSet() {
+        HealthCheckRegistry registry = SharedHealthCheckRegistries.setDefault("default");
+
+        assertThat(SharedHealthCheckRegistries.getDefault()).isEqualTo(registry);
+    }
+
+    @Test
+    public void specificRegistryCanBeSetAsDefault() {
+        HealthCheckRegistry registry = new HealthCheckRegistry();
+        SharedHealthCheckRegistries.setDefault("default", registry);
+
+        assertThat(SharedHealthCheckRegistries.getDefault()).isEqualTo(registry);
+    }
+
+    @Test
+    public void unableToSetDefaultRegistryTwice() {
+        expectedException.expect(IllegalStateException.class);
+        expectedException.expectMessage("Default health check registry is already set.");
+
+        SharedHealthCheckRegistries.setDefault("default");
+        SharedHealthCheckRegistries.setDefault("default");
+    }
+
+    @Test
+    public void unableToSetCustomDefaultRegistryTwice() {
+        expectedException.expect(IllegalStateException.class);
+        expectedException.expectMessage("Default health check registry is already set.");
+
+        SharedHealthCheckRegistries.setDefault("default", new HealthCheckRegistry());
+        SharedHealthCheckRegistries.setDefault("default", new HealthCheckRegistry());
+    }
+}
diff --git a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/SharedMetricRegistriesTest.java b/metrics-healthchecks/src/test/java/com/codahale/metrics/health/SharedMetricRegistriesTest.java
deleted file mode 100644
index 9ee3b3d..0000000
--- a/metrics-healthchecks/src/test/java/com/codahale/metrics/health/SharedMetricRegistriesTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.codahale.metrics.health;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class SharedMetricRegistriesTest {
-    @Before
-    public void setUp() throws Exception {
-        SharedHealthCheckRegistries.clear();
-    }
-
-    @Test
-    public void memoizesRegistriesByName() throws Exception {
-        final HealthCheckRegistry one = SharedHealthCheckRegistries.getOrCreate("one");
-        final HealthCheckRegistry two = SharedHealthCheckRegistries.getOrCreate("one");
-
-        assertThat(one)
-                .isSameAs(two);
-    }
-
-    @Test
-    public void hasASetOfNames() throws Exception {
-        SharedHealthCheckRegistries.getOrCreate("one");
-
-        assertThat(SharedHealthCheckRegistries.names())
-                .containsOnly("one");
-    }
-
-    @Test
-    public void removesRegistries() throws Exception {
-        final HealthCheckRegistry one = SharedHealthCheckRegistries.getOrCreate("one");
-        SharedHealthCheckRegistries.remove("one");
-
-        assertThat(SharedHealthCheckRegistries.names())
-                .isEmpty();
-
-        final HealthCheckRegistry two = SharedHealthCheckRegistries.getOrCreate("one");
-        assertThat(two)
-                .isNotSameAs(one);
-    }
-
-    @Test
-    public void clearsRegistries() throws Exception {
-        SharedHealthCheckRegistries.getOrCreate("one");
-        SharedHealthCheckRegistries.getOrCreate("two");
-
-        SharedHealthCheckRegistries.clear();
-
-        assertThat(SharedHealthCheckRegistries.names())
-                .isEmpty();
-    }
-}
diff --git a/metrics-httpasyncclient/pom.xml b/metrics-httpasyncclient/pom.xml
index 3048bfd..1f2de12 100644
--- a/metrics-httpasyncclient/pom.xml
+++ b/metrics-httpasyncclient/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-httpasyncclient</artifactId>
@@ -25,12 +25,16 @@
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpasyncclient</artifactId>
-            <version>4.0.2</version>
+            <version>4.1.2</version>
             <exclusions>
                 <exclusion>
                     <groupId>org.apache.httpcomponents</groupId>
                     <artifactId>httpclient</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>org.apache.httpcomponents</groupId>
+                    <artifactId>httpcore</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
     </dependencies>
diff --git a/metrics-httpclient/pom.xml b/metrics-httpclient/pom.xml
index e3a516e..54c27e9 100644
--- a/metrics-httpclient/pom.xml
+++ b/metrics-httpclient/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-httpclient</artifactId>
@@ -25,7 +25,7 @@
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
-            <version>4.3.5</version>
+            <version>4.5.2</version>
         </dependency>
     </dependencies>
 </project>
diff --git a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategies.java b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategies.java
index 848a15d..4b3b928 100644
--- a/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategies.java
+++ b/metrics-httpclient/src/main/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategies.java
@@ -1,8 +1,9 @@
 package com.codahale.metrics.httpclient;
 
 import org.apache.http.HttpRequest;
-import org.apache.http.RequestLine;
 import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpRequestWrapper;
+import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.client.utils.URIBuilder;
 
 import java.net.URI;
@@ -26,11 +27,9 @@ public class HttpClientMetricNameStrategies {
             new HttpClientMetricNameStrategy() {
                 @Override
                 public String getNameFor(String name, HttpRequest request) {
-                    final RequestLine requestLine = request.getRequestLine();
-                    final URI uri = URI.create(requestLine.getUri());
                     return name(HttpClient.class,
                                 name,
-                                uri.getHost(),
+                                requestURI(request).getHost(),
                                 methodNameString(request));
                 }
             };
@@ -40,8 +39,7 @@ public class HttpClientMetricNameStrategies {
                 @Override
                 public String getNameFor(String name, HttpRequest request) {
                     try {
-                        final RequestLine requestLine = request.getRequestLine();
-                        final URIBuilder url = new URIBuilder(requestLine.getUri());
+                        final URIBuilder url = new URIBuilder(requestURI(request));
                         return name(HttpClient.class,
                                     name,
                                     url.removeQuery().build().toString(),
@@ -55,4 +53,13 @@ public class HttpClientMetricNameStrategies {
     private static String methodNameString(HttpRequest request) {
         return request.getRequestLine().getMethod().toLowerCase() + "-requests";
     }
+
+    private static URI requestURI(HttpRequest request) {
+        if (request instanceof HttpRequestWrapper)
+            return requestURI(((HttpRequestWrapper) request).getOriginal());
+
+        return (request instanceof HttpUriRequest) ?
+            ((HttpUriRequest) request).getURI() :
+                URI.create(request.getRequestLine().getUri());
+    }
 }
diff --git a/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategiesTest.java b/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategiesTest.java
index c4e74bb..97942ae 100644
--- a/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategiesTest.java
+++ b/metrics-httpclient/src/test/java/com/codahale/metrics/httpclient/HttpClientMetricNameStrategiesTest.java
@@ -1,10 +1,16 @@
 package com.codahale.metrics.httpclient;
 
+import org.apache.http.HttpRequest;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestWrapper;
+import org.apache.http.client.utils.URIUtils;
 import org.junit.Test;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+
 import static com.codahale.metrics.httpclient.HttpClientMetricNameStrategies.*;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
@@ -36,10 +42,43 @@ public class HttpClientMetricNameStrategiesTest {
     }
 
     @Test
+    public void hostAndMethodWithNameInWrappedRequest() throws URISyntaxException {
+        HttpRequest request = rewriteRequestURI(new HttpPost("http://my.host.com/whatever"));
+
+        assertThat(HOST_AND_METHOD.getNameFor("some-service", request),
+                is("org.apache.http.client.HttpClient.some-service.my.host.com.post-requests"));
+    }
+
+    @Test
+    public void hostAndMethodWithoutNameInWrappedRequest() throws URISyntaxException {
+        HttpRequest request = rewriteRequestURI(new HttpPost("http://my.host.com/whatever"));
+
+        assertThat(HOST_AND_METHOD.getNameFor(null, request),
+                is("org.apache.http.client.HttpClient.my.host.com.post-requests"));
+    }
+
+    @Test
     public void querylessUrlAndMethodWithName() {
         assertThat(QUERYLESS_URL_AND_METHOD.getNameFor(
                 "some-service",
                 new HttpPut("https://thing.com:8090/my/path?ignore=this&and=this")),
                 is("org.apache.http.client.HttpClient.some-service.https://thing.com:8090/my/path.put-requests"));
     }
+
+    @Test
+    public void querylessUrlAndMethodWithNameInWrappedRequest() throws URISyntaxException {
+        HttpRequest request = rewriteRequestURI(new HttpPut("https://thing.com:8090/my/path?ignore=this&and=this"));
+        assertThat(QUERYLESS_URL_AND_METHOD.getNameFor(
+                "some-service",
+                request),
+                is("org.apache.http.client.HttpClient.some-service.https://thing.com:8090/my/path.put-requests"));
+    }
+
+    private static HttpRequest rewriteRequestURI(HttpRequest request) throws URISyntaxException {
+        HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request);
+        URI uri = URIUtils.rewriteURI(wrapper.getURI(), null, true);
+        wrapper.setURI(uri);
+
+        return wrapper;
+    }
 }
diff --git a/metrics-graphite/pom.xml b/metrics-jcache/pom.xml
similarity index 60%
copy from metrics-graphite/pom.xml
copy to metrics-jcache/pom.xml
index f4a5900..a32ac85 100644
--- a/metrics-graphite/pom.xml
+++ b/metrics-jcache/pom.xml
@@ -5,14 +5,15 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
-    <artifactId>metrics-graphite</artifactId>
-    <name>Graphite Integration for Metrics</name>
+    <artifactId>metrics-jcache</artifactId>
+    <name>Metrics Integration for JCache</name>
     <packaging>bundle</packaging>
     <description>
-        A reporter for Metrics which announces measurements to a Graphite server.
+        Metrics Integration for JCache, JSR 107 standard for caching.
+        Uses the CacheStatisticsMXBean provided statistics.
     </description>
 
     <dependencies>
@@ -22,15 +23,15 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
-            <groupId>com.rabbitmq</groupId>
-            <artifactId>amqp-client</artifactId>
-            <version>${rabbitmq.version}</version>
-            <optional>true</optional>
+            <groupId>javax.cache</groupId>
+            <artifactId>cache-api</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.python</groupId>
-            <artifactId>jython-standalone</artifactId>
-            <version>2.5.3</version>
+            <groupId>org.ehcache</groupId>
+            <artifactId>ehcache</artifactId>
+            <version>3.1.3</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
diff --git a/metrics-jcache/src/main/java/com/codahale/metrics/jcache/JCacheGaugeSet.java b/metrics-jcache/src/main/java/com/codahale/metrics/jcache/JCacheGaugeSet.java
new file mode 100644
index 0000000..e21e844
--- /dev/null
+++ b/metrics-jcache/src/main/java/com/codahale/metrics/jcache/JCacheGaugeSet.java
@@ -0,0 +1,86 @@
+package com.codahale.metrics.jcache;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.codahale.metrics.JmxAttributeGauge;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricSet;
+
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.cache.management.CacheStatisticsMXBean;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+/**
+ * Gauge set retrieving JCache JMX attributes
+ *
+ * @author <a href="mailto:henri.tremblay at softwareag.com">Henri Tremblay</a>
+ * @author <a href="mailto:anthony.dahanne at softwareag.com">Anthony Dahanne</a>
+ */
+public class JCacheGaugeSet implements MetricSet {
+
+    private static final String M_BEAN_COORDINATES = "javax.cache:type=CacheStatistics,CacheManager=*,Cache=*";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(JCacheGaugeSet.class);
+
+    @Override
+    public Map<String, Metric> getMetrics() {
+        Set<ObjectInstance> cacheBeans = getCacheBeans();
+        List<String> availableStatsNames = retrieveStatsNames();
+
+        Map<String, Metric> gauges = new HashMap<String, Metric>(cacheBeans.size() * availableStatsNames.size());
+
+        for (ObjectInstance cacheBean : cacheBeans) {
+            ObjectName objectName = cacheBean.getObjectName();
+            String cacheName = objectName.getKeyProperty("Cache");
+
+            for (String statsName : availableStatsNames) {
+                JmxAttributeGauge jmxAttributeGauge = new JmxAttributeGauge(objectName, statsName);
+                gauges.put(name(cacheName, toSpinalCase(statsName)), jmxAttributeGauge);
+            }
+        }
+
+        return Collections.unmodifiableMap(gauges);
+    }
+
+    private Set<ObjectInstance> getCacheBeans() {
+        try {
+            return ManagementFactory.getPlatformMBeanServer().queryMBeans(ObjectName.getInstance(M_BEAN_COORDINATES), null);
+        }
+        catch(MalformedObjectNameException e) {
+            LOGGER.error("Unable to retrieve {}. Are JCache statistics enabled?", M_BEAN_COORDINATES);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private List<String> retrieveStatsNames() {
+        Method[] methods = CacheStatisticsMXBean.class.getDeclaredMethods();
+        List<String> availableStatsNames = new ArrayList<String>(methods.length);
+
+        for (Method method : methods) {
+            String methodName = method.getName();
+            if(methodName.startsWith("get")) {
+                availableStatsNames.add(methodName.substring(3));
+            }
+        }
+        return availableStatsNames;
+    }
+
+    private static String toSpinalCase(String camelCase) {
+        return camelCase.replaceAll("(.)(\\p{Upper})", "$1-$2").toLowerCase(Locale.US);
+    }
+
+}
diff --git a/metrics-jcache/src/test/java/JCacheGaugeSetTest.java b/metrics-jcache/src/test/java/JCacheGaugeSetTest.java
new file mode 100644
index 0000000..c331c58
--- /dev/null
+++ b/metrics-jcache/src/test/java/JCacheGaugeSetTest.java
@@ -0,0 +1,86 @@
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jcache.JCacheGaugeSet;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.spi.CachingProvider;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class JCacheGaugeSetTest {
+
+    private MetricRegistry registry;
+    private Cache<Object, Object> myCache;
+    private Cache<Object, Object> myOtherCache;
+    private CacheManager cacheManager;
+
+    @Before
+    public void setUp() throws Exception {
+
+        CachingProvider provider = Caching.getCachingProvider();
+        cacheManager = provider.getCacheManager(
+            getClass().getResource("ehcache.xml").toURI(),
+            getClass().getClassLoader());
+
+        myCache = cacheManager.getCache("myCache");
+        myOtherCache = cacheManager.getCache("myOtherCache");
+
+        registry = new MetricRegistry();
+        registry.register("jcache.statistics", new JCacheGaugeSet());
+    }
+
+    @Test
+    public void measuresGauges() throws Exception {
+
+        myOtherCache.get("woo");
+        assertThat(registry.getGauges().get("jcache.statistics.myOtherCache.cache-misses").getValue())
+            .isEqualTo(1L);
+
+        myCache.get("woo");
+        assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-misses").getValue())
+            .isEqualTo(1L);
+        assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-hits").getValue())
+            .isEqualTo(0L);
+        assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-gets").getValue())
+            .isEqualTo(1L);
+
+        myCache.put("woo", "whee");
+        myCache.get("woo");
+        assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-puts").getValue())
+            .isEqualTo(1L);
+        assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-hits").getValue())
+            .isEqualTo(1L);
+        assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-hit-percentage").getValue())
+            .isEqualTo(50.0f);
+        assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-miss-percentage").getValue())
+            .isEqualTo(50.0f);
+        assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-gets").getValue())
+            .isEqualTo(2L);
+
+        // cache size being 1, eviction occurs after this line
+        myCache.put("woo2", "whoza");
+        assertThat(registry.getGauges().get("jcache.statistics.myCache.cache-evictions").getValue())
+            .isEqualTo(1L);
+
+        myCache.remove("woo2");
+        assertThat((Float)registry.getGauges().get("jcache.statistics.myCache.average-get-time").getValue())
+            .isGreaterThan(0.0f);
+        assertThat((Float)registry.getGauges().get("jcache.statistics.myCache.average-put-time").getValue())
+            .isGreaterThan(0.0f);
+        assertThat((Float)registry.getGauges().get("jcache.statistics.myCache.average-remove-time").getValue())
+            .isGreaterThan(0.0f);
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        cacheManager.destroyCache("myCache");
+        cacheManager.destroyCache("myOtherCache");
+        cacheManager.close();
+    }
+}
diff --git a/metrics-jcache/src/test/resources/ehcache.xml b/metrics-jcache/src/test/resources/ehcache.xml
new file mode 100644
index 0000000..eacfd2b
--- /dev/null
+++ b/metrics-jcache/src/test/resources/ehcache.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config xmlns="http://www.ehcache.org/v3" xmlns:jsr107="http://www.ehcache.org/v3/jsr107">
+  <service>
+    <jsr107:defaults enable-management="true" enable-statistics="true"/>
+  </service>
+  <cache-template name="simple">
+    <expiry>
+      <ttl unit="seconds">3600</ttl>
+    </expiry>
+    <heap>1</heap>
+  </cache-template>
+
+  <cache alias="myCache" uses-template="simple"/>
+  <cache alias="myOtherCache" uses-template="simple"/>
+
+</config>
\ No newline at end of file
diff --git a/metrics-jcstress/README.md b/metrics-jcstress/README.md
new file mode 100644
index 0000000..0b9a360
--- /dev/null
+++ b/metrics-jcstress/README.md
@@ -0,0 +1,17 @@
+Concurrency test are based on [OpenJDK Java Concurrency Stress tests](https://wiki.openjdk.java.net/display/CodeTools/jcstress).
+
+### Command line launching
+
+Build tests jar with maven and run tests:
+````bash
+mvn clean install
+java -jar target/jcstress.jar
+````
+
+Look at results report `results/index.html`
+
+### Command line options
+
+The whole list of command line options is available by:
+
+    java -jar target/jcstress.jar
diff --git a/metrics-jcstress/findbugs-exclude.xml b/metrics-jcstress/findbugs-exclude.xml
new file mode 100644
index 0000000..097bbe3
--- /dev/null
+++ b/metrics-jcstress/findbugs-exclude.xml
@@ -0,0 +1,8 @@
+<FindBugsFilter>
+    <Match>
+        <Package name="com.codahale.metrics" />
+    </Match>
+    <Match>
+        <Package name="org.openjdk.jcstress.infra.results" />
+    </Match>
+</FindBugsFilter>
diff --git a/metrics-benchmarks/pom.xml b/metrics-jcstress/pom.xml
similarity index 54%
copy from metrics-benchmarks/pom.xml
copy to metrics-jcstress/pom.xml
index 045649d..327c20e 100644
--- a/metrics-benchmarks/pom.xml
+++ b/metrics-jcstress/pom.xml
@@ -1,18 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <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/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
-
     <parent>
-        <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <groupId>io.dropwizard.metrics</groupId>
+        <version>3.2.4</version>
     </parent>
 
-    <artifactId>metrics-benchmarks</artifactId>
-    <name>Benchmarks for Metrics</name>
-    <description>
-        A development module for performance benchmarks of Metrics classes.
-    </description>
+    <artifactId>metrics-jcstress</artifactId>
+    <version>3.2.4</version>
+    <packaging>jar</packaging>
+
+    <name>Metrics JCStress tests</name>
+
+    <!--
+       This is the demo/sample template build script for building concurrency tests with JCStress.
+       Edit as needed.
+    -->
+
+    <prerequisites>
+        <maven>3.0</maven>
+    </prerequisites>
 
     <dependencies>
         <dependency>
@@ -21,18 +29,31 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
-            <groupId>org.openjdk.jmh</groupId>
-            <artifactId>jmh-core</artifactId>
-            <version>1.0.1</version>
-        </dependency>
-        <dependency>
-            <groupId>org.openjdk.jmh</groupId>
-            <artifactId>jmh-generator-annprocess</artifactId>
-            <version>1.0.1</version>
-            <scope>provided</scope>
+            <groupId>org.openjdk.jcstress</groupId>
+            <artifactId>jcstress-core</artifactId>
+            <version>${jcstress.version}</version>
         </dependency>
     </dependencies>
 
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+        <!--
+            jcstress version to use with this project.
+          -->
+        <jcstress.version>0.1.1</jcstress.version>
+
+        <!--
+            Java source/target to use for compilation.
+          -->
+        <javac.target>1.8</javac.target>
+
+        <!--
+            Name of the test Uber-JAR to generate.
+          -->
+        <uberjar.name>jcstress</uberjar.name>
+    </properties>
+
     <build>
         <plugins>
             <plugin>
@@ -45,21 +66,35 @@
                 </configuration>
             </plugin>
             <plugin>
-                <!-- generate an uber .jar for standalone benchmarking -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.1</version>
+                <configuration>
+                    <compilerVersion>${javac.target}</compilerVersion>
+                    <source>${javac.target}</source>
+                    <target>${javac.target}</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-shade-plugin</artifactId>
                 <version>2.2</version>
                 <executions>
                     <execution>
+                        <id>main</id>
                         <phase>package</phase>
                         <goals>
                             <goal>shade</goal>
                         </goals>
                         <configuration>
-                            <finalName>benchmarks</finalName>
+                            <finalName>${uberjar.name}</finalName>
                             <transformers>
                                 <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-                                    <mainClass>org.openjdk.jmh.Main</mainClass>
+                                    <mainClass>org.openjdk.jcstress.Main</mainClass>
+                                </transformer>
+                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+                                    <resource>META-INF/TestList</resource>
                                 </transformer>
                             </transformers>
                         </configuration>
@@ -71,9 +106,10 @@
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>findbugs-maven-plugin</artifactId>
                 <configuration>
-                    <excludeFilterFile>findbugs-exclude.xml</excludeFilterFile> 
+                    <excludeFilterFile>findbugs-exclude.xml</excludeFilterFile>
                 </configuration>
             </plugin>
         </plugins>
     </build>
+
 </project>
diff --git a/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTrimReadTest.java b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTrimReadTest.java
new file mode 100644
index 0000000..3132265
--- /dev/null
+++ b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirTrimReadTest.java
@@ -0,0 +1,67 @@
+package com.codahale.metrics;
+
+import org.openjdk.jcstress.annotations.Actor;
+import org.openjdk.jcstress.annotations.Expect;
+import org.openjdk.jcstress.annotations.JCStressTest;
+import org.openjdk.jcstress.annotations.Outcome;
+import org.openjdk.jcstress.annotations.State;
+import org.openjdk.jcstress.infra.results.StringResult1;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+ at JCStressTest
+ at Outcome(
+    id = "\\[240, 241, 242, 243, 244, 245, 246, 247, 248, 249\\]",
+    expect = Expect.ACCEPTABLE,
+    desc = "Actor1 made read before Actor2 even started"
+)
+ at Outcome(
+    id = "\\[243, 244, 245, 246, 247, 248, 249\\]",
+    expect = Expect.ACCEPTABLE,
+    desc = "Actor2 made trim before Actor1 even started"
+)
+ at Outcome(
+    id = "\\[244, 245, 246, 247, 248, 249\\]",
+    expect = Expect.ACCEPTABLE,
+    desc = "Actor1 made trim, then Actor2 started trim and made startIndex change, " +
+        "before Actor1 concurrent read."
+)
+ at Outcome(
+    id = "\\[243, 244, 245, 246, 247, 248\\]",
+    expect = Expect.ACCEPTABLE,
+    desc = "Actor1 made trim, then Actor2 started trim, but not finished startIndex change, before Actor1 concurrent read."
+)
+ at State
+public class SlidingTimeWindowArrayReservoirTrimReadTest {
+    private final AtomicLong ticks = new AtomicLong(0);
+    private final SlidingTimeWindowArrayReservoir reservoir;
+
+    public SlidingTimeWindowArrayReservoirTrimReadTest() {
+        reservoir = new SlidingTimeWindowArrayReservoir(10, TimeUnit.NANOSECONDS, new Clock() {
+            @Override
+            public long getTick() {
+                return ticks.get();
+            }
+        });
+
+        for (int i = 0; i < 250; i++) {
+            ticks.set(i);
+            reservoir.update(i);
+        }
+    }
+
+    @Actor
+    public void actor1(StringResult1 r) {
+        Snapshot snapshot = reservoir.getSnapshot();
+        String stringValues = Arrays.toString(snapshot.getValues());
+        r.r1 = stringValues;
+    }
+
+    @Actor
+    public void actor2() {
+        ticks.set(253);
+        reservoir.trim();
+    }
+}
\ No newline at end of file
diff --git a/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadAllocate.java b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadAllocate.java
new file mode 100644
index 0000000..8b73a89
--- /dev/null
+++ b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadAllocate.java
@@ -0,0 +1,45 @@
+package com.codahale.metrics;
+
+import org.openjdk.jcstress.annotations.Actor;
+import org.openjdk.jcstress.annotations.Arbiter;
+import org.openjdk.jcstress.annotations.Expect;
+import org.openjdk.jcstress.annotations.JCStressTest;
+import org.openjdk.jcstress.annotations.Outcome;
+import org.openjdk.jcstress.annotations.State;
+import org.openjdk.jcstress.infra.results.StringResult1;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+ at JCStressTest
+ at Outcome(id = "\\[1023, 1029, 1034\\]", expect = Expect.ACCEPTABLE)
+ at State
+public class SlidingTimeWindowArrayReservoirWriteReadAllocate {
+
+    private final SlidingTimeWindowArrayReservoir reservoir;
+
+    public SlidingTimeWindowArrayReservoirWriteReadAllocate() {
+        reservoir = new SlidingTimeWindowArrayReservoir(500, TimeUnit.SECONDS);
+        for (int i = 0; i < 1024; i++) {
+            reservoir.update(i);
+        }
+    }
+
+    @Actor
+    public void actor1() {
+        reservoir.update(1029L);
+    }
+
+    @Actor
+    public void actor2() {
+        reservoir.update(1034L);
+    }
+
+    @Arbiter
+    public void arbiter(StringResult1 r) {
+        Snapshot snapshot = reservoir.getSnapshot();
+        long[] values = snapshot.getValues();
+        String stringValues = Arrays.toString(Arrays.copyOfRange(values, values.length - 3, values.length));
+        r.r1 = stringValues;
+    }
+}
\ No newline at end of file
diff --git a/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadTest.java b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadTest.java
new file mode 100644
index 0000000..420770d
--- /dev/null
+++ b/metrics-jcstress/src/main/java/com/codahale/metrics/SlidingTimeWindowArrayReservoirWriteReadTest.java
@@ -0,0 +1,45 @@
+package com.codahale.metrics;
+
+import org.openjdk.jcstress.annotations.Actor;
+import org.openjdk.jcstress.annotations.Expect;
+import org.openjdk.jcstress.annotations.JCStressTest;
+import org.openjdk.jcstress.annotations.Outcome;
+import org.openjdk.jcstress.annotations.State;
+import org.openjdk.jcstress.infra.results.StringResult1;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+ at JCStressTest
+ at Outcome(id = "\\[\\]", expect = Expect.ACCEPTABLE)
+ at Outcome(id = "\\[31\\]", expect = Expect.ACCEPTABLE)
+ at Outcome(id = "\\[15\\]", expect = Expect.ACCEPTABLE)
+ at Outcome(id = "\\[31, 15\\]", expect = Expect.ACCEPTABLE)
+ at Outcome(id = "\\[15, 31\\]", expect = Expect.ACCEPTABLE)
+ at State
+public class SlidingTimeWindowArrayReservoirWriteReadTest {
+
+    private final SlidingTimeWindowArrayReservoir reservoir;
+
+    public SlidingTimeWindowArrayReservoirWriteReadTest() {
+        reservoir = new SlidingTimeWindowArrayReservoir(1, TimeUnit.SECONDS);
+    }
+
+    @Actor
+    public void actor1() {
+        reservoir.update(31L);
+    }
+
+    @Actor
+    public void actor2() {
+        reservoir.update(15L);
+    }
+
+    @Actor
+    public void actor3(StringResult1 r) {
+        Snapshot snapshot = reservoir.getSnapshot();
+        String stringValues = Arrays.toString(snapshot.getValues());
+        r.r1 = stringValues;
+    }
+
+}
\ No newline at end of file
diff --git a/metrics-jdbi/pom.xml b/metrics-jdbi/pom.xml
index 2cf791e..24290d1 100644
--- a/metrics-jdbi/pom.xml
+++ b/metrics-jdbi/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-jdbi</artifactId>
diff --git a/metrics-jersey/pom.xml b/metrics-jersey/pom.xml
index 2a49394..9d8b4c2 100644
--- a/metrics-jersey/pom.xml
+++ b/metrics-jersey/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-jersey</artifactId>
diff --git a/metrics-jersey2/pom.xml b/metrics-jersey2/pom.xml
index 91ba016..912abbc 100644
--- a/metrics-jersey2/pom.xml
+++ b/metrics-jersey2/pom.xml
@@ -5,7 +5,7 @@
     <parent>
 	<groupId>io.dropwizard.metrics</groupId>
 	<artifactId>metrics-parent</artifactId>
-	<version>3.1.2</version>
+	<version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-jersey2</artifactId>
diff --git a/metrics-jersey2/src/main/java/com/codahale/metrics/jersey2/InstrumentedResourceMethodApplicationListener.java b/metrics-jersey2/src/main/java/com/codahale/metrics/jersey2/InstrumentedResourceMethodApplicationListener.java
index 0e1fa86..7454953 100644
--- a/metrics-jersey2/src/main/java/com/codahale/metrics/jersey2/InstrumentedResourceMethodApplicationListener.java
+++ b/metrics-jersey2/src/main/java/com/codahale/metrics/jersey2/InstrumentedResourceMethodApplicationListener.java
@@ -6,16 +6,21 @@ import com.codahale.metrics.Timer;
 import com.codahale.metrics.annotation.ExceptionMetered;
 import com.codahale.metrics.annotation.Metered;
 import com.codahale.metrics.annotation.Timed;
-import jersey.repackaged.com.google.common.collect.ImmutableMap;
+import org.glassfish.jersey.server.model.ModelProcessor;
 import org.glassfish.jersey.server.model.Resource;
 import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.model.ResourceModel;
 import org.glassfish.jersey.server.monitoring.ApplicationEvent;
 import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
 import org.glassfish.jersey.server.monitoring.RequestEvent;
 import org.glassfish.jersey.server.monitoring.RequestEventListener;
 
+import javax.ws.rs.core.Configuration;
 import javax.ws.rs.ext.Provider;
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import static com.codahale.metrics.MetricRegistry.name;
 
@@ -30,12 +35,12 @@ import static com.codahale.metrics.MetricRegistry.name;
  */
 
 @Provider
-public class InstrumentedResourceMethodApplicationListener implements ApplicationEventListener {
+public class InstrumentedResourceMethodApplicationListener implements ApplicationEventListener, ModelProcessor {
 
     private final MetricRegistry metrics;
-    private ImmutableMap<Method, Timer> timers = ImmutableMap.of();
-    private ImmutableMap<Method, Meter> meters = ImmutableMap.of();
-    private ImmutableMap<Method, ExceptionMeterMetric> exceptionMeters = ImmutableMap.of();
+    private ConcurrentMap<Method, Timer> timers = new ConcurrentHashMap<>();
+    private ConcurrentMap<Method, Meter> meters = new ConcurrentHashMap<>();
+    private ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters = new ConcurrentHashMap<>();
 
     /**
      * Construct an application event listener using the given metrics registry.
@@ -70,10 +75,10 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
     }
 
     private static class TimerRequestEventListener implements RequestEventListener {
-        private final ImmutableMap<Method, Timer> timers;
+        private final ConcurrentMap<Method, Timer> timers;
         private Timer.Context context = null;
 
-        public TimerRequestEventListener(final ImmutableMap<Method, Timer> timers) {
+        public TimerRequestEventListener(final ConcurrentMap<Method, Timer> timers) {
             this.timers = timers;
         }
 
@@ -94,9 +99,9 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
     }
 
     private static class MeterRequestEventListener implements RequestEventListener {
-        private final ImmutableMap<Method, Meter> meters;
+        private final ConcurrentMap<Method, Meter> meters;
 
-        public MeterRequestEventListener(final ImmutableMap<Method, Meter> meters) {
+        public MeterRequestEventListener(final ConcurrentMap<Method, Meter> meters) {
             this.meters = meters;
         }
 
@@ -113,9 +118,9 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
     }
 
     private static class ExceptionMeterRequestEventListener implements RequestEventListener {
-        private final ImmutableMap<Method, ExceptionMeterMetric> exceptionMeters;
+        private final ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters;
 
-        public ExceptionMeterRequestEventListener(final ImmutableMap<Method, ExceptionMeterMetric> exceptionMeters) {
+        public ExceptionMeterRequestEventListener(final ConcurrentMap<Method, ExceptionMeterMetric> exceptionMeters) {
             this.exceptionMeters = exceptionMeters;
         }
 
@@ -155,30 +160,48 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
     @Override
     public void onEvent(ApplicationEvent event) {
         if (event.getType() == ApplicationEvent.Type.INITIALIZATION_APP_FINISHED) {
-            final ImmutableMap.Builder<Method, Timer> timerBuilder = ImmutableMap.<Method, Timer>builder();
-            final ImmutableMap.Builder<Method, Meter> meterBuilder = ImmutableMap.<Method, Meter>builder();
-            final ImmutableMap.Builder<Method, ExceptionMeterMetric> exceptionMeterBuilder = ImmutableMap.<Method, ExceptionMeterMetric>builder();
-
-            for (final Resource resource : event.getResourceModel().getResources()) {
-                for (final ResourceMethod method : resource.getAllMethods()) {
-                    registerTimedAnnotations(timerBuilder, method);
-                    registerMeteredAnnotations(meterBuilder, method);
-                    registerExceptionMeteredAnnotations(exceptionMeterBuilder, method);
-                }
+            registerMetricsForModel(event.getResourceModel());
+        }
+    }
 
-                for (final Resource childResource : resource.getChildResources()) {
-                    for (final ResourceMethod method : childResource.getAllMethods()) {
-                        registerTimedAnnotations(timerBuilder, method);
-                        registerMeteredAnnotations(meterBuilder, method);
-                        registerExceptionMeteredAnnotations(exceptionMeterBuilder, method);
-                    }
-                }
+    @Override
+    public ResourceModel processResourceModel(ResourceModel resourceModel, Configuration configuration) {
+        return resourceModel;
+    }
+
+    @Override
+    public ResourceModel processSubResource(ResourceModel subResourceModel, Configuration configuration) {
+        registerMetricsForModel(subResourceModel);
+        return subResourceModel;
+    }
+
+    private void registerMetricsForModel(ResourceModel resourceModel) {
+        for (final Resource resource : resourceModel.getResources()) {
+
+            final Timed classLevelTimed = getClassLevelAnnotation(resource, Timed.class);
+            final Metered classLevelMetered = getClassLevelAnnotation(resource, Metered.class);
+            final ExceptionMetered classLevelExceptionMetered = getClassLevelAnnotation(resource, ExceptionMetered.class);
+
+            for (final ResourceMethod method : resource.getAllMethods()) {
+                registerTimedAnnotations(method, classLevelTimed);
+                registerMeteredAnnotations(method, classLevelMetered);
+                registerExceptionMeteredAnnotations(method, classLevelExceptionMetered);
             }
 
-            timers = timerBuilder.build();
-            meters = meterBuilder.build();
-            exceptionMeters = exceptionMeterBuilder.build();
+            for (final Resource childResource : resource.getChildResources()) {
+
+                final Timed classLevelTimedChild = getClassLevelAnnotation(childResource, Timed.class);
+                final Metered classLevelMeteredChild = getClassLevelAnnotation(childResource, Metered.class);
+                final ExceptionMetered classLevelExceptionMeteredChild = getClassLevelAnnotation(childResource, ExceptionMetered.class);
+
+                for (final ResourceMethod method : childResource.getAllMethods()) {
+                    registerTimedAnnotations(method, classLevelTimedChild);
+                    registerMeteredAnnotations(method, classLevelMeteredChild);
+                    registerExceptionMeteredAnnotations(method, classLevelExceptionMeteredChild);
+                }
+            }
         }
+
     }
 
     @Override
@@ -191,33 +214,58 @@ public class InstrumentedResourceMethodApplicationListener implements Applicatio
         return listener;
     }
 
-    private void registerTimedAnnotations(final ImmutableMap.Builder<Method, Timer> builder,
-                                          final ResourceMethod method) {
+    private <T extends Annotation> T getClassLevelAnnotation(final Resource resource, final Class<T> annotationClazz) {
+        T annotation = null;
+
+        for (final Class<?> clazz : resource.getHandlerClasses()) {
+            annotation = clazz.getAnnotation(annotationClazz);
+
+            if (annotation != null) {
+                break;
+            }
+        }
+        return annotation;
+    }
+
+    private void registerTimedAnnotations(final ResourceMethod method, final Timed classLevelTimed) {
         final Method definitionMethod = method.getInvocable().getDefinitionMethod();
+        if (classLevelTimed != null) {
+            timers.putIfAbsent(definitionMethod, timerMetric(this.metrics, method, classLevelTimed));
+            return;
+        }
+
         final Timed annotation = definitionMethod.getAnnotation(Timed.class);
 
         if (annotation != null) {
-            builder.put(definitionMethod, timerMetric(this.metrics, method, annotation));
+            timers.putIfAbsent(definitionMethod, timerMetric(this.metrics, method, annotation));
         }
     }
 
-    private void registerMeteredAnnotations(final ImmutableMap.Builder<Method, Meter> builder,
-                                            final ResourceMethod method) {
+    private void registerMeteredAnnotations(final ResourceMethod method, final Metered classLevelMetered) {
         final Method definitionMethod = method.getInvocable().getDefinitionMethod();
+
+        if (classLevelMetered != null) {
+            meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, classLevelMetered));
+            return;
+        }
         final Metered annotation = definitionMethod.getAnnotation(Metered.class);
 
         if (annotation != null) {
-            builder.put(definitionMethod, meterMetric(metrics, method, annotation));
+            meters.putIfAbsent(definitionMethod, meterMetric(metrics, method, annotation));
         }
     }
 
-    private void registerExceptionMeteredAnnotations(final ImmutableMap.Builder<Method, ExceptionMeterMetric> builder,
-                                                     final ResourceMethod method) {
+    private void registerExceptionMeteredAnnotations(final ResourceMethod method, final ExceptionMetered classLevelExceptionMetered) {
         final Method definitionMethod = method.getInvocable().getDefinitionMethod();
+
+        if (classLevelExceptionMetered != null) {
+            exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, classLevelExceptionMetered));
+            return;
+        }
         final ExceptionMetered annotation = definitionMethod.getAnnotation(ExceptionMetered.class);
 
         if (annotation != null) {
-            builder.put(definitionMethod, new ExceptionMeterMetric(metrics, method, annotation));
+            exceptionMeters.putIfAbsent(definitionMethod, new ExceptionMeterMetric(metrics, method, annotation));
         }
     }
 
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsJerseyTest.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsExceptionMeteredPerClassJerseyTest.java
similarity index 61%
copy from metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsJerseyTest.java
copy to metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsExceptionMeteredPerClassJerseyTest.java
index 87928fd..d930dfd 100644
--- a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsJerseyTest.java
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsExceptionMeteredPerClassJerseyTest.java
@@ -2,17 +2,14 @@ package com.codahale.metrics.jersey2;
 
 import com.codahale.metrics.Meter;
 import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.Timer;
-import com.codahale.metrics.jersey2.resources.InstrumentedResource;
-import org.glassfish.jersey.client.ClientResponse;
+import com.codahale.metrics.jersey2.resources.InstrumentedResourceExceptionMeteredPerClass;
+import com.codahale.metrics.jersey2.resources.InstrumentedSubResourceExceptionMeteredPerClass;
 import org.glassfish.jersey.server.ResourceConfig;
 import org.glassfish.jersey.test.JerseyTest;
 import org.junit.Test;
 
-import javax.ws.rs.NotFoundException;
 import javax.ws.rs.ProcessingException;
 import javax.ws.rs.core.Application;
-import javax.ws.rs.core.Response;
 import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -23,10 +20,9 @@ import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
 
 /**
  * Tests registering {@link InstrumentedResourceMethodApplicationListener} as a singleton
- * in a Jersey {@link org.glassfish.jersey.server.ResourceConfig}
+ * in a Jersey {@link ResourceConfig}
  */
-
-public class SingletonMetricsJerseyTest extends JerseyTest {
+public class SingletonMetricsExceptionMeteredPerClassJerseyTest extends JerseyTest {
     static {
         Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
     }
@@ -38,42 +34,47 @@ public class SingletonMetricsJerseyTest extends JerseyTest {
         this.registry = new MetricRegistry();
 
         ResourceConfig config = new ResourceConfig();
+
         config = config.register(new MetricsFeature(this.registry));
-        config = config.register(InstrumentedResource.class);
+        config = config.register(InstrumentedResourceExceptionMeteredPerClass.class);
 
         return config;
     }
 
     @Test
-    public void timedMethodsAreTimed() {
-        assertThat(target("timed")
+    public void exceptionMeteredMethodsAreExceptionMetered() {
+        final Meter meter = registry.meter(name(InstrumentedResourceExceptionMeteredPerClass.class,
+                "exceptionMetered",
+                "exceptions"));
+
+        assertThat(target("exception-metered")
                 .request()
                 .get(String.class))
-                .isEqualTo("yay");
+                .isEqualTo("fuh");
 
-        final Timer timer = registry.timer(name(InstrumentedResource.class, "timed"));
+        assertThat(meter.getCount()).isZero();
 
-        assertThat(timer.getCount()).isEqualTo(1);
-    }
+        try {
+            target("exception-metered")
+                    .queryParam("splode", true)
+                    .request()
+                    .get(String.class);
 
-    @Test
-    public void meteredMethodsAreMetered() {
-        assertThat(target("metered")
-                .request()
-                .get(String.class))
-                .isEqualTo("woo");
+            failBecauseExceptionWasNotThrown(ProcessingException.class);
+        } catch (ProcessingException e) {
+            assertThat(e.getCause()).isInstanceOf(IOException.class);
+        }
 
-        final Meter meter = registry.meter(name(InstrumentedResource.class, "metered"));
         assertThat(meter.getCount()).isEqualTo(1);
     }
 
     @Test
-    public void exceptionMeteredMethodsAreExceptionMetered() {
-        final Meter meter = registry.meter(name(InstrumentedResource.class,
+    public void subresourcesFromLocatorsRegisterMetrics() {
+        final Meter meter = registry.meter(name(InstrumentedSubResourceExceptionMeteredPerClass.class,
                 "exceptionMetered",
                 "exceptions"));
 
-        assertThat(target("exception-metered")
+        assertThat(target("subresource/exception-metered")
                 .request()
                 .get(String.class))
                 .isEqualTo("fuh");
@@ -81,7 +82,7 @@ public class SingletonMetricsJerseyTest extends JerseyTest {
         assertThat(meter.getCount()).isZero();
 
         try {
-            target("exception-metered")
+            target("subresource/exception-metered")
                     .queryParam("splode", true)
                     .request()
                     .get(String.class);
@@ -94,16 +95,4 @@ public class SingletonMetricsJerseyTest extends JerseyTest {
         assertThat(meter.getCount()).isEqualTo(1);
     }
 
-    @Test
-    public void testResourceNotFound() {
-        final Response response = target().path("not-found").request().get();
-        assertThat(response.getStatus()).isEqualTo(404);
-
-        try {
-            target().path("not-found").request().get(ClientResponse.class);
-            failBecauseExceptionWasNotThrown(NotFoundException.class);
-        } catch (NotFoundException e) {
-            assertThat(e.getMessage()).isEqualTo("HTTP 404 Not Found");
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsJerseyTest.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsJerseyTest.java
index 87928fd..4d95099 100644
--- a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsJerseyTest.java
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsJerseyTest.java
@@ -4,6 +4,7 @@ import com.codahale.metrics.Meter;
 import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.Timer;
 import com.codahale.metrics.jersey2.resources.InstrumentedResource;
+import com.codahale.metrics.jersey2.resources.InstrumentedSubResource;
 import org.glassfish.jersey.client.ClientResponse;
 import org.glassfish.jersey.server.ResourceConfig;
 import org.glassfish.jersey.test.JerseyTest;
@@ -25,7 +26,6 @@ import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
  * Tests registering {@link InstrumentedResourceMethodApplicationListener} as a singleton
  * in a Jersey {@link org.glassfish.jersey.server.ResourceConfig}
  */
-
 public class SingletonMetricsJerseyTest extends JerseyTest {
     static {
         Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
@@ -106,4 +106,16 @@ public class SingletonMetricsJerseyTest extends JerseyTest {
             assertThat(e.getMessage()).isEqualTo("HTTP 404 Not Found");
         }
     }
-}
\ No newline at end of file
+
+    @Test
+    public void subresourcesFromLocatorsRegisterMetrics() {
+        assertThat(target("subresource/timed")
+                .request()
+                .get(String.class))
+                .isEqualTo("yay");
+
+        final Timer timer = registry.timer(name(InstrumentedSubResource.class, "timed"));
+        assertThat(timer.getCount()).isEqualTo(1);
+
+    }
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsMeteredPerClassJerseyTest.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsMeteredPerClassJerseyTest.java
new file mode 100644
index 0000000..7b92e7e
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsMeteredPerClassJerseyTest.java
@@ -0,0 +1,66 @@
+package com.codahale.metrics.jersey2;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.jersey2.resources.InstrumentedResourceMeteredPerClass;
+import com.codahale.metrics.jersey2.resources.InstrumentedSubResourceMeteredPerClass;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+import javax.ws.rs.core.Application;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests registering {@link InstrumentedResourceMethodApplicationListener} as a singleton
+ * in a Jersey {@link ResourceConfig}
+ */
+public class SingletonMetricsMeteredPerClassJerseyTest extends JerseyTest {
+    static {
+        Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
+    }
+
+    private MetricRegistry registry;
+
+    @Override
+    protected Application configure() {
+        this.registry = new MetricRegistry();
+
+        ResourceConfig config = new ResourceConfig();
+
+        config = config.register(new MetricsFeature(this.registry));
+        config = config.register(InstrumentedResourceMeteredPerClass.class);
+
+        return config;
+    }
+
+    @Test
+    public void meteredPerClassMethodsAreMetered() {
+        assertThat(target("meteredPerClass")
+                .request()
+                .get(String.class))
+                .isEqualTo("yay");
+
+        final Meter meter = registry.meter(name(InstrumentedResourceMeteredPerClass.class, "meteredPerClass"));
+
+        assertThat(meter.getCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void subresourcesFromLocatorsRegisterMetrics() {
+        assertThat(target("subresource/meteredPerClass")
+                .request()
+                .get(String.class))
+                .isEqualTo("yay");
+
+        final Meter meter = registry.meter(name(InstrumentedSubResourceMeteredPerClass.class, "meteredPerClass"));
+        assertThat(meter.getCount()).isEqualTo(1);
+
+    }
+
+
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsTimedPerClassJerseyTest.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsTimedPerClassJerseyTest.java
new file mode 100644
index 0000000..95fbada
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/SingletonMetricsTimedPerClassJerseyTest.java
@@ -0,0 +1,66 @@
+package com.codahale.metrics.jersey2;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.jersey2.resources.InstrumentedResourceTimedPerClass;
+import com.codahale.metrics.jersey2.resources.InstrumentedSubResourceTimedPerClass;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+
+import javax.ws.rs.core.Application;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests registering {@link InstrumentedResourceMethodApplicationListener} as a singleton
+ * in a Jersey {@link ResourceConfig}
+ */
+public class SingletonMetricsTimedPerClassJerseyTest extends JerseyTest {
+    static {
+        Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF);
+    }
+
+    private MetricRegistry registry;
+
+    @Override
+    protected Application configure() {
+        this.registry = new MetricRegistry();
+
+        ResourceConfig config = new ResourceConfig();
+
+        config = config.register(new MetricsFeature(this.registry));
+        config = config.register(InstrumentedResourceTimedPerClass.class);
+
+        return config;
+    }
+
+    @Test
+    public void timedPerClassMethodsAreTimed() {
+        assertThat(target("timedPerClass")
+                .request()
+                .get(String.class))
+                .isEqualTo("yay");
+
+        final Timer timer = registry.timer(name(InstrumentedResourceTimedPerClass.class, "timedPerClass"));
+
+        assertThat(timer.getCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void subresourcesFromLocatorsRegisterMetrics() {
+        assertThat(target("subresource/timedPerClass")
+                .request()
+                .get(String.class))
+                .isEqualTo("yay");
+
+        final Timer timer = registry.timer(name(InstrumentedSubResourceTimedPerClass.class, "timedPerClass"));
+        assertThat(timer.getCount()).isEqualTo(1);
+
+    }
+
+
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java
index 669a716..8b7bf33 100644
--- a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java
@@ -34,4 +34,9 @@ public class InstrumentedResource {
         }
         return "fuh";
     }
+
+    @Path("/subresource")
+    public InstrumentedSubResource locateSubResource() {
+        return new InstrumentedSubResource();
+    }
 }
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceExceptionMeteredPerClass.java
similarity index 57%
copy from metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java
copy to metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceExceptionMeteredPerClass.java
index 669a716..b5ac922 100644
--- a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceExceptionMeteredPerClass.java
@@ -1,32 +1,17 @@
 package com.codahale.metrics.jersey2.resources;
 
 import com.codahale.metrics.annotation.ExceptionMetered;
-import com.codahale.metrics.annotation.Metered;
-import com.codahale.metrics.annotation.Timed;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import java.io.IOException;
 
+ at ExceptionMetered(cause = IOException.class)
 @Path("/")
 @Produces(MediaType.TEXT_PLAIN)
-public class InstrumentedResource {
-    @GET
-    @Timed
-    @Path("/timed")
-    public String timed() {
-        return "yay";
-    }
-
-    @GET
-    @Metered
-    @Path("/metered")
-    public String metered() {
-        return "woo";
-    }
+public class InstrumentedResourceExceptionMeteredPerClass {
 
     @GET
-    @ExceptionMetered(cause = IOException.class)
     @Path("/exception-metered")
     public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
         if (splode) {
@@ -34,4 +19,10 @@ public class InstrumentedResource {
         }
         return "fuh";
     }
+
+    @Path("/subresource")
+    public InstrumentedSubResourceExceptionMeteredPerClass locateSubResource() {
+        return new InstrumentedSubResourceExceptionMeteredPerClass();
+    }
+
 }
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceMeteredPerClass.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceMeteredPerClass.java
new file mode 100644
index 0000000..c572ba3
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceMeteredPerClass.java
@@ -0,0 +1,26 @@
+package com.codahale.metrics.jersey2.resources;
+
+import com.codahale.metrics.annotation.Metered;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+ at Metered
+ at Path("/")
+ at Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedResourceMeteredPerClass {
+
+    @GET
+    @Path("/meteredPerClass")
+    public String meteredPerClass() {
+        return "yay";
+    }
+
+    @Path("/subresource")
+    public InstrumentedSubResourceMeteredPerClass locateSubResource() {
+        return new InstrumentedSubResourceMeteredPerClass();
+    }
+
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceTimedPerClass.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceTimedPerClass.java
new file mode 100644
index 0000000..16ad16a
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResourceTimedPerClass.java
@@ -0,0 +1,26 @@
+package com.codahale.metrics.jersey2.resources;
+
+import com.codahale.metrics.annotation.Timed;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+ at Timed
+ at Path("/")
+ at Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedResourceTimedPerClass {
+
+    @GET
+    @Path("/timedPerClass")
+    public String timedPerClass() {
+        return "yay";
+    }
+
+    @Path("/subresource")
+    public InstrumentedSubResourceTimedPerClass locateSubResource() {
+        return new InstrumentedSubResourceTimedPerClass();
+    }
+
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResource.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResource.java
new file mode 100644
index 0000000..8ef7612
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResource.java
@@ -0,0 +1,18 @@
+package com.codahale.metrics.jersey2.resources;
+
+import com.codahale.metrics.annotation.Timed;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+
+ at Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResource {
+    @GET
+    @Timed
+    @Path("/timed")
+    public String timed() {
+        return "yay";
+    }
+
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
similarity index 55%
copy from metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java
copy to metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
index 669a716..3a120aa 100644
--- a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedResource.java
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceExceptionMeteredPerClass.java
@@ -1,32 +1,15 @@
 package com.codahale.metrics.jersey2.resources;
 
 import com.codahale.metrics.annotation.ExceptionMetered;
-import com.codahale.metrics.annotation.Metered;
-import com.codahale.metrics.annotation.Timed;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import java.io.IOException;
 
- at Path("/")
+ at ExceptionMetered(cause = IOException.class)
 @Produces(MediaType.TEXT_PLAIN)
-public class InstrumentedResource {
+public class InstrumentedSubResourceExceptionMeteredPerClass {
     @GET
-    @Timed
-    @Path("/timed")
-    public String timed() {
-        return "yay";
-    }
-
-    @GET
-    @Metered
-    @Path("/metered")
-    public String metered() {
-        return "woo";
-    }
-
-    @GET
-    @ExceptionMetered(cause = IOException.class)
     @Path("/exception-metered")
     public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
         if (splode) {
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceMeteredPerClass.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceMeteredPerClass.java
new file mode 100644
index 0000000..36aad65
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceMeteredPerClass.java
@@ -0,0 +1,18 @@
+package com.codahale.metrics.jersey2.resources;
+
+import com.codahale.metrics.annotation.Metered;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+ at Metered
+ at Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceMeteredPerClass {
+    @GET
+    @Path("/meteredPerClass")
+    public String meteredPerClass() {
+        return "yay";
+    }
+}
diff --git a/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceTimedPerClass.java b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceTimedPerClass.java
new file mode 100644
index 0000000..2db0e5c
--- /dev/null
+++ b/metrics-jersey2/src/test/java/com/codahale/metrics/jersey2/resources/InstrumentedSubResourceTimedPerClass.java
@@ -0,0 +1,18 @@
+package com.codahale.metrics.jersey2.resources;
+
+import com.codahale.metrics.annotation.Timed;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+ at Timed
+ at Produces(MediaType.TEXT_PLAIN)
+public class InstrumentedSubResourceTimedPerClass {
+    @GET
+    @Path("/timedPerClass")
+    public String timedPerClass() {
+        return "yay";
+    }
+}
diff --git a/metrics-jetty8/pom.xml b/metrics-jetty8/pom.xml
index d18365c..35de02e 100644
--- a/metrics-jetty8/pom.xml
+++ b/metrics-jetty8/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-jetty8</artifactId>
diff --git a/metrics-jetty9-legacy/pom.xml b/metrics-jetty9-legacy/pom.xml
index 970c6a7..22a63e8 100644
--- a/metrics-jetty9-legacy/pom.xml
+++ b/metrics-jetty9-legacy/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-jetty9-legacy</artifactId>
diff --git a/metrics-jetty9/pom.xml b/metrics-jetty9/pom.xml
index 896fe44..378e225 100644
--- a/metrics-jetty9/pom.xml
+++ b/metrics-jetty9/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-jetty9</artifactId>
diff --git a/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactory.java b/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactory.java
index 02d3f8c..ce218e7 100644
--- a/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactory.java
+++ b/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedConnectionFactory.java
@@ -7,14 +7,25 @@ import org.eclipse.jetty.server.ConnectionFactory;
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.util.component.ContainerLifeCycle;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
 public class InstrumentedConnectionFactory extends ContainerLifeCycle implements ConnectionFactory {
     private final ConnectionFactory connectionFactory;
     private final Timer timer;
+    private Method getProtocols;
 
     public InstrumentedConnectionFactory(ConnectionFactory connectionFactory, Timer timer) {
         this.connectionFactory = connectionFactory;
         this.timer = timer;
         addBean(connectionFactory);
+        try {
+            getProtocols = connectionFactory.getClass().getMethod("getProtocols");
+        } catch (NoSuchMethodException ignore) {
+            getProtocols = null;
+        }
     }
 
     @Override
@@ -22,6 +33,17 @@ public class InstrumentedConnectionFactory extends ContainerLifeCycle implements
         return connectionFactory.getProtocol();
     }
 
+    @SuppressWarnings("unchecked")
+    public List<String> getProtocols() {
+        try {
+            return getProtocols != null ?
+                    (List<String>) getProtocols.invoke(connectionFactory) :
+                    Collections.<String>emptyList();
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new IllegalStateException("Unable to invoke `connectionFactory#getProtocols`", e);
+        }
+    }
+
     @Override
     public Connection newConnection(Connector connector, EndPoint endPoint) {
         final Connection connection = connectionFactory.newConnection(connector, endPoint);
diff --git a/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedHandler.java b/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedHandler.java
index a94cece..83a4af3 100644
--- a/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedHandler.java
+++ b/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedHandler.java
@@ -227,6 +227,7 @@ public class InstrumentedHandler extends HandlerWrapper {
             // new request
             activeRequests.inc();
             start = request.getTimeStamp();
+            state.addListener(listener);
         } else {
             // resumed request
             start = System.currentTimeMillis();
@@ -246,9 +247,6 @@ public class InstrumentedHandler extends HandlerWrapper {
             dispatches.update(dispatched, TimeUnit.MILLISECONDS);
 
             if (state.isSuspended()) {
-                if (state.isInitial()) {
-                    state.addListener(listener);
-                }
                 activeSuspended.inc();
             } else if (state.isInitial()) {
                 updateResponses(httpRequest, httpResponse, start);
diff --git a/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPool.java b/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPool.java
index ac4114d..ca79b60 100644
--- a/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPool.java
+++ b/metrics-jetty9/src/main/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPool.java
@@ -12,6 +12,7 @@ import static com.codahale.metrics.MetricRegistry.name;
 
 public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
     private final MetricRegistry metricRegistry;
+    private String prefix;
 
     public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry) {
         this(registry, 200);
@@ -40,32 +41,53 @@ public class InstrumentedQueuedThreadPool extends QueuedThreadPool {
                                         @Name("minThreads") int minThreads,
                                         @Name("idleTimeout") int idleTimeout,
                                         @Name("queue") BlockingQueue<Runnable> queue) {
+        this(registry, maxThreads, minThreads, idleTimeout, queue, null);
+    }
+
+    public InstrumentedQueuedThreadPool(@Name("registry") MetricRegistry registry,
+                                        @Name("maxThreads") int maxThreads,
+                                        @Name("minThreads") int minThreads,
+                                        @Name("idleTimeout") int idleTimeout,
+                                        @Name("queue") BlockingQueue<Runnable> queue,
+                                        @Name("prefix") String prefix) {
         super(maxThreads, minThreads, idleTimeout, queue);
         this.metricRegistry = registry;
+        this.prefix = prefix;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
     }
 
     @Override
     protected void doStart() throws Exception {
         super.doStart();
-        metricRegistry.register(name(QueuedThreadPool.class, getName(), "utilization"), new RatioGauge() {
+
+        final String prefix = this.prefix == null ? name(QueuedThreadPool.class, getName()) : name(this.prefix, getName());
+
+        metricRegistry.register(name(prefix, "utilization"), new RatioGauge() {
             @Override
             protected Ratio getRatio() {
                 return Ratio.of(getThreads() - getIdleThreads(), getThreads());
             }
         });
-        metricRegistry.register(name(QueuedThreadPool.class, getName(), "utilization-max"), new RatioGauge() {
+        metricRegistry.register(name(prefix, "utilization-max"), new RatioGauge() {
             @Override
             protected Ratio getRatio() {
                 return Ratio.of(getThreads() - getIdleThreads(), getMaxThreads());
             }
         });
-        metricRegistry.register(name(QueuedThreadPool.class, getName(), "size"), new Gauge<Integer>() {
+        metricRegistry.register(name(prefix, "size"), new Gauge<Integer>() {
             @Override
             public Integer getValue() {
                 return getThreads();
             }
         });
-        metricRegistry.register(name(QueuedThreadPool.class, getName(), "jobs"), new Gauge<Integer>() {
+        metricRegistry.register(name(prefix, "jobs"), new Gauge<Integer>() {
             @Override
             public Integer getValue() {
                 // This assumes the QueuedThreadPool is using a BlockingArrayQueue or
diff --git a/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedHandlerTest.java b/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedHandlerTest.java
index 9b7c86b..cd34171 100644
--- a/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedHandlerTest.java
+++ b/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedHandlerTest.java
@@ -3,13 +3,25 @@ package com.codahale.metrics.jetty9;
 import com.codahale.metrics.MetricRegistry;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.LockSupport;
+
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class InstrumentedHandlerTest {
@@ -22,7 +34,7 @@ public class InstrumentedHandlerTest {
     @Before
     public void setUp() throws Exception {
         handler.setName("handler");
-        handler.setHandler(new DefaultHandler());
+        handler.setHandler(new TestHandler());
         server.addConnector(connector);
         server.setHandler(handler);
         server.start();
@@ -43,41 +55,146 @@ public class InstrumentedHandlerTest {
 
     @Test
     public void createsMetricsForTheHandler() throws Exception {
-        final ContentResponse response = client.GET("http://localhost:" + connector.getLocalPort() + "/hello");
+        final ContentResponse response = client.GET(uri("/hello"));
 
         assertThat(response.getStatus())
                 .isEqualTo(404);
 
         assertThat(registry.getNames())
                 .containsOnly(
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.1xx-responses",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.2xx-responses",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.3xx-responses",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.4xx-responses",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.5xx-responses",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.percent-4xx-1m",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.percent-4xx-5m",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.percent-4xx-15m",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.percent-5xx-1m",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.percent-5xx-5m",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.percent-5xx-15m",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.active-suspended",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.async-dispatches",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.async-timeouts",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.get-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.put-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.active-dispatches",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.trace-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.other-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.connect-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.dispatches",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.head-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.post-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.options-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.active-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.delete-requests",
-                        "org.eclipse.jetty.server.handler.DefaultHandler.handler.move-requests"
+                        MetricRegistry.name(TestHandler.class,"handler.1xx-responses"),
+                        MetricRegistry.name(TestHandler.class,"handler.2xx-responses"),
+                        MetricRegistry.name(TestHandler.class,"handler.3xx-responses"),
+                        MetricRegistry.name(TestHandler.class,"handler.4xx-responses"),
+                        MetricRegistry.name(TestHandler.class,"handler.5xx-responses"),
+                        MetricRegistry.name(TestHandler.class,"handler.percent-4xx-1m"),
+                        MetricRegistry.name(TestHandler.class,"handler.percent-4xx-5m"),
+                        MetricRegistry.name(TestHandler.class,"handler.percent-4xx-15m"),
+                        MetricRegistry.name(TestHandler.class,"handler.percent-5xx-1m"),
+                        MetricRegistry.name(TestHandler.class,"handler.percent-5xx-5m"),
+                        MetricRegistry.name(TestHandler.class,"handler.percent-5xx-15m"),
+                        MetricRegistry.name(TestHandler.class,"handler.requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.active-suspended"),
+                        MetricRegistry.name(TestHandler.class,"handler.async-dispatches"),
+                        MetricRegistry.name(TestHandler.class,"handler.async-timeouts"),
+                        MetricRegistry.name(TestHandler.class,"handler.get-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.put-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.active-dispatches"),
+                        MetricRegistry.name(TestHandler.class,"handler.trace-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.other-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.connect-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.dispatches"),
+                        MetricRegistry.name(TestHandler.class,"handler.head-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.post-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.options-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.active-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.delete-requests"),
+                        MetricRegistry.name(TestHandler.class,"handler.move-requests")
                 );
     }
+
+
+    @Test
+    public void responseTimesAreRecordedForBlockingResponses() throws Exception {
+
+        final ContentResponse response = client.GET(uri("/blocking"));
+
+        assertThat(response.getStatus())
+                .isEqualTo(200);
+
+        assertResponseTimesValid();
+    }
+
+    @Test
+    public void responseTimesAreRecordedForAsyncResponses() throws Exception {
+
+        final ContentResponse response = client.GET(uri("/async"));
+
+        assertThat(response.getStatus())
+                .isEqualTo(200);
+
+        assertResponseTimesValid();
+    }
+
+    private void assertResponseTimesValid() {
+        assertThat(registry.getMeters().get(metricName() + ".2xx-responses")
+                .getCount()).isGreaterThan(0L);
+
+
+        assertThat(registry.getTimers().get(metricName() + ".get-requests")
+                .getSnapshot().getMedian()).isGreaterThan(0.0).isLessThan(TimeUnit.SECONDS.toNanos(1));
+
+        assertThat(registry.getTimers().get(metricName() + ".requests")
+                .getSnapshot().getMedian()).isGreaterThan(0.0).isLessThan(TimeUnit.SECONDS.toNanos(1));
+    }
+
+    private String uri(String path) {
+        return "http://localhost:" + connector.getLocalPort() + path;
+    }
+
+    private String metricName() {
+        return MetricRegistry.name(TestHandler.class.getName(), "handler");
+    }
+
+    /**
+     * test handler.
+     *
+     * Supports
+     *
+     * /blocking - uses the standard servlet api
+     * /async - uses the 3.1 async api to complete the request
+     *
+     * all other requests will return 404
+     */
+    private static class TestHandler extends AbstractHandler {
+        @Override
+        public void handle(
+                String path,
+                Request request,
+                final HttpServletRequest httpServletRequest,
+                final HttpServletResponse httpServletResponse
+        ) throws IOException, ServletException {
+            request.setHandled(true);
+            if (path.equals("/blocking")) {
+                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
+                httpServletResponse.setStatus(200);
+                httpServletResponse.setContentType("text/plain");
+                httpServletResponse.getWriter().write("some content from the blocking request\n");
+            } else if (path.equals("/async")) {
+                final AsyncContext context = request.startAsync();
+                Thread t = new Thread() {
+                    public void run() {
+                        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
+                        httpServletResponse.setStatus(200);
+                        httpServletResponse.setContentType("text/plain");
+                        final ServletOutputStream servletOutputStream;
+                        try {
+                            servletOutputStream = httpServletResponse.getOutputStream();
+                            servletOutputStream.setWriteListener(
+                                    new WriteListener() {
+                                        @Override
+                                        public void onWritePossible() throws IOException {
+                                            servletOutputStream.write("some content from the async\n".getBytes());
+                                            context.complete();
+                                        }
+
+                                        @Override
+                                        public void onError(Throwable throwable) {
+                                            context.complete();
+                                        }
+                                    }
+                            );
+                        } catch (IOException e) {
+                            context.complete();
+                        }
+                    }
+                };
+                t.start();
+            } else {
+                httpServletResponse.setStatus(404);
+                httpServletResponse.setContentType("text/plain");
+                httpServletResponse.getWriter().write("Not Found\n");
+            }
+        }
+    }
 }
diff --git a/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPoolTest.java b/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPoolTest.java
new file mode 100644
index 0000000..2b4ed04
--- /dev/null
+++ b/metrics-jetty9/src/test/java/com/codahale/metrics/jetty9/InstrumentedQueuedThreadPoolTest.java
@@ -0,0 +1,49 @@
+package com.codahale.metrics.jetty9;
+
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+public class InstrumentedQueuedThreadPoolTest {
+    private static final String PREFIX = "prefix";
+
+    private final MetricRegistry metricRegistry = mock(MetricRegistry.class);
+    private final InstrumentedQueuedThreadPool iqtp = new InstrumentedQueuedThreadPool(metricRegistry);
+    private final ArgumentCaptor<String> metricNameCaptor = ArgumentCaptor.forClass(String.class);
+
+    @After
+    public void tearDown() throws Exception {
+        iqtp.stop();
+    }
+
+    @Test
+    public void customMetricsPrefix() throws Exception{
+        iqtp.setPrefix(PREFIX);
+        iqtp.doStart();
+
+        verify(metricRegistry, atLeastOnce()).register(metricNameCaptor.capture(), any(Metric.class));
+        String metricName = metricNameCaptor.getValue();
+        assertThat("Custom metric's prefix doesn't match", metricName, startsWith(PREFIX));
+
+    }
+
+    @Test
+    public void metricsPrefixBackwardCompatible() throws Exception{
+        iqtp.doStart();
+
+        verify(metricRegistry, atLeastOnce()).register(metricNameCaptor.capture(), any(Metric.class));
+        String metricName = metricNameCaptor.getValue();
+        assertThat("The default metrics prefix was changed", metricName, startsWith(QueuedThreadPool.class.getName()));
+    }
+
+}
diff --git a/metrics-json/pom.xml b/metrics-json/pom.xml
index a9e0986..a0b7af6 100644
--- a/metrics-json/pom.xml
+++ b/metrics-json/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-json</artifactId>
diff --git a/metrics-json/src/main/java/com/codahale/metrics/json/HealthCheckModule.java b/metrics-json/src/main/java/com/codahale/metrics/json/HealthCheckModule.java
index 42746fc..61f068c 100644
--- a/metrics-json/src/main/java/com/codahale/metrics/json/HealthCheckModule.java
+++ b/metrics-json/src/main/java/com/codahale/metrics/json/HealthCheckModule.java
@@ -1,5 +1,6 @@
 package com.codahale.metrics.json;
 
+import com.codahale.metrics.health.HealthCheck;
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.Version;
 import com.fasterxml.jackson.databind.JsonSerializer;
@@ -7,10 +8,11 @@ import com.fasterxml.jackson.databind.Module;
 import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.module.SimpleSerializers;
 import com.fasterxml.jackson.databind.ser.std.StdSerializer;
-import com.codahale.metrics.health.HealthCheck;
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
 
 public class HealthCheckModule extends Module {
     private static class HealthCheckResultSerializer extends StdSerializer<HealthCheck.Result> {
@@ -32,6 +34,15 @@ public class HealthCheckModule extends Module {
 
             serializeThrowable(json, result.getError(), "error");
 
+            Map<String, Object> details = result.getDetails();
+            if (details != null && !details.isEmpty()) {
+                Iterator<Map.Entry<String, Object>> it = details.entrySet().iterator();
+                while (it.hasNext()) {
+                    Map.Entry<String, Object> e = it.next();
+                    json.writeObjectField(e.getKey(), e.getValue());
+                }
+            }
+
             json.writeEndObject();
         }
 
diff --git a/metrics-json/src/main/java/com/codahale/metrics/json/MetricsModule.java b/metrics-json/src/main/java/com/codahale/metrics/json/MetricsModule.java
index 905b5f0..4dcf46c 100644
--- a/metrics-json/src/main/java/com/codahale/metrics/json/MetricsModule.java
+++ b/metrics-json/src/main/java/com/codahale/metrics/json/MetricsModule.java
@@ -15,7 +15,7 @@ import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 
 public class MetricsModule extends Module {
-    static final Version VERSION = new Version(3, 0, 0, "", "com.codahale.metrics", "metrics-json");
+    static final Version VERSION = new Version(3, 1, 3, "", "com.codahale.metrics", "metrics-json");
 
     private static class GaugeSerializer extends StdSerializer<Gauge> {
         private GaugeSerializer() {
diff --git a/metrics-json/src/test/java/com/codahale/metrics/json/HealthCheckModuleTest.java b/metrics-json/src/test/java/com/codahale/metrics/json/HealthCheckModuleTest.java
index 2280a64..cbbd557 100644
--- a/metrics-json/src/test/java/com/codahale/metrics/json/HealthCheckModuleTest.java
+++ b/metrics-json/src/test/java/com/codahale/metrics/json/HealthCheckModuleTest.java
@@ -4,6 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.codahale.metrics.health.HealthCheck;
 import org.junit.Test;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -81,4 +86,39 @@ public class HealthCheckModuleTest {
                                    "}" +
                                    "}");
     }
+
+    @Test
+    public void serializeResultWithDetail() throws Exception {
+        Map<String, Object> complex = new LinkedHashMap<String, Object>();
+        complex.put("field", "value");
+
+        HealthCheck.Result result = HealthCheck.Result.builder()
+            .healthy()
+            .withDetail("boolean", true)
+            .withDetail("integer", 1)
+            .withDetail("long", 2L)
+            .withDetail("float", 3.546F)
+            .withDetail("double", 4.567D)
+            .withDetail("BigInteger", new BigInteger("12345"))
+            .withDetail("BigDecimal", new BigDecimal("12345.56789"))
+            .withDetail("String", "string")
+            .withDetail("complex", complex)
+            .build();
+
+        assertThat(mapper.writeValueAsString(result))
+            .isEqualTo("{" +
+                "\"healthy\":true," +
+                "\"boolean\":true," +
+                "\"integer\":1," +
+                "\"long\":2," +
+                "\"float\":3.546," +
+                "\"double\":4.567," +
+                "\"BigInteger\":12345," +
+                "\"BigDecimal\":12345.56789," +
+                "\"String\":\"string\"," +
+                "\"complex\":{" +
+                    "\"field\":\"value\"" +
+                "}" +
+            "}");
+    }
 }
diff --git a/metrics-json/src/test/java/com/codahale/metrics/json/MetricsModuleTest.java b/metrics-json/src/test/java/com/codahale/metrics/json/MetricsModuleTest.java
index 94d8e96..bf162bd 100644
--- a/metrics-json/src/test/java/com/codahale/metrics/json/MetricsModuleTest.java
+++ b/metrics-json/src/test/java/com/codahale/metrics/json/MetricsModuleTest.java
@@ -201,7 +201,7 @@ public class MetricsModuleTest {
 
         assertThat(mapper.writeValueAsString(registry))
                 .isEqualTo("{" +
-                                   "\"version\":\"3.0.0\"," +
+                                   "\"version\":\"3.1.3\"," +
                                    "\"gauges\":{}," +
                                    "\"counters\":{}," +
                                    "\"histograms\":{}," +
diff --git a/metrics-jvm/pom.xml b/metrics-jvm/pom.xml
index e2a5c07..b2d412c 100644
--- a/metrics-jvm/pom.xml
+++ b/metrics-jvm/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-jvm</artifactId>
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/MemoryUsageGaugeSet.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/MemoryUsageGaugeSet.java
index 7e0d319..949e948 100644
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/MemoryUsageGaugeSet.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/MemoryUsageGaugeSet.java
@@ -178,6 +178,16 @@ public class MemoryUsageGaugeSet implements MetricSet {
                 }
             });
 
+            // Only register GC usage metrics if the memory pool supports usage statistics.
+            if (pool.getCollectionUsage() != null) {
+            	gauges.put(name(poolName, "used-after-gc"),new Gauge<Long>() {
+                    @Override
+                    public Long getValue() {
+                        return pool.getCollectionUsage().getUsed();
+                    }
+                });
+            }
+
             gauges.put(name(poolName, "init"),new Gauge<Long>() {
                 @Override
                 public Long getValue() {
diff --git a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDump.java b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDump.java
old mode 100644
new mode 100755
index 34da284..a001961
--- a/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDump.java
+++ b/metrics-jvm/src/main/java/com/codahale/metrics/jvm/ThreadDump.java
@@ -32,7 +32,7 @@ public class ThreadDump {
 
         for (int ti = threads.length - 1; ti >= 0; ti--) {
             final ThreadInfo t = threads[ti];
-            writer.printf("%s id=%d state=%s",
+            writer.printf("\"%s\" id=%d state=%s",
                           t.getThreadName(),
                           t.getThreadId(),
                           t.getThreadState());
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/MemoryUsageGaugeSetTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/MemoryUsageGaugeSetTest.java
index dbfe2c6..4929627 100644
--- a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/MemoryUsageGaugeSetTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/MemoryUsageGaugeSetTest.java
@@ -18,6 +18,7 @@ public class MemoryUsageGaugeSetTest {
     private final MemoryUsage nonHeap = mock(MemoryUsage.class);
     private final MemoryUsage pool = mock(MemoryUsage.class);
     private final MemoryUsage weirdPool = mock(MemoryUsage.class);
+    private final MemoryUsage weirdCollection = mock(MemoryUsage.class);
     private final MemoryMXBean mxBean = mock(MemoryMXBean.class);
     private final MemoryPoolMXBean memoryPool = mock(MemoryPoolMXBean.class);
     private final MemoryPoolMXBean weirdMemoryPool = mock(MemoryPoolMXBean.class);
@@ -48,13 +49,19 @@ public class MemoryUsageGaugeSetTest {
         when(weirdPool.getUsed()).thenReturn(300L);
         when(weirdPool.getMax()).thenReturn(-1L);
 
+        when(weirdCollection.getUsed()).thenReturn(290L);
+
         when(mxBean.getHeapMemoryUsage()).thenReturn(heap);
         when(mxBean.getNonHeapMemoryUsage()).thenReturn(nonHeap);
 
         when(memoryPool.getUsage()).thenReturn(pool);
+        // Mock that "Big Pool" is a non-collected pool therefore doesn't
+        // have collection usage statistics.
+        when(memoryPool.getCollectionUsage()).thenReturn(null);
         when(memoryPool.getName()).thenReturn("Big Pool");
 
         when(weirdMemoryPool.getUsage()).thenReturn(weirdPool);
+        when(weirdMemoryPool.getCollectionUsage()).thenReturn(weirdCollection);
         when(weirdMemoryPool.getName()).thenReturn("Weird Pool");
     }
 
@@ -81,9 +88,11 @@ public class MemoryUsageGaugeSetTest {
                         "pools.Big-Pool.used",
                         "pools.Big-Pool.usage",
                         "pools.Big-Pool.max",
+                        // skip in non-collected pools - "pools.Big-Pool.used-after-gc",
                         "pools.Weird-Pool.init",
                         "pools.Weird-Pool.committed",
                         "pools.Weird-Pool.used",
+                        "pools.Weird-Pool.used-after-gc",
                         "pools.Weird-Pool.usage",
                         "pools.Weird-Pool.max");
     }
@@ -249,6 +258,14 @@ public class MemoryUsageGaugeSetTest {
     }
 
     @Test
+    public void hasAGaugeForWeirdCollectionPoolUsed() throws Exception {
+        final Gauge gauge = (Gauge) gauges.getMetrics().get("pools.Weird-Pool.used-after-gc");
+
+        assertThat(gauge.getValue())
+                .isEqualTo(290L);
+    }
+
+    @Test
     public void autoDetectsMemoryUsageBeanAndMemoryPools() throws Exception {
         assertThat(new MemoryUsageGaugeSet().getMetrics().keySet())
                 .isNotEmpty();
diff --git a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDumpTest.java b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDumpTest.java
old mode 100644
new mode 100755
index 654a51d..a62b6da
--- a/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDumpTest.java
+++ b/metrics-jvm/src/test/java/com/codahale/metrics/jvm/ThreadDumpTest.java
@@ -43,7 +43,7 @@ public class ThreadDumpTest {
         threadDump.dump(output);
 
         assertThat(output.toString())
-                .isEqualTo(String.format("runnable id=100 state=RUNNABLE%n" +
+                .isEqualTo(String.format("\"runnable\" id=100 state=RUNNABLE%n" +
                                                  "    at Blah.blee(Blah.java:100)%n" +
                                                  "%n" +
                                                  "%n"));
diff --git a/metrics-log4j/pom.xml b/metrics-log4j/pom.xml
index b3936c7..8f4a82b 100644
--- a/metrics-log4j/pom.xml
+++ b/metrics-log4j/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-log4j</artifactId>
diff --git a/metrics-log4j2/pom.xml b/metrics-log4j2/pom.xml
index 56d9f22..00cbc37 100644
--- a/metrics-log4j2/pom.xml
+++ b/metrics-log4j2/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-log4j2</artifactId>
@@ -16,9 +16,22 @@
     </description>
 
     <properties>
-        <log4j2.version>2.0.2</log4j2.version>
+        <log4j2.version>2.3</log4j2.version>
     </properties>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <parallel>suites</parallel>
+                    <threadCount>1</threadCount>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
     <dependencies>
         <dependency>
             <groupId>io.dropwizard.metrics</groupId>
diff --git a/metrics-log4j2/src/main/java/com/codahale/metrics/log4j2/InstrumentedAppender.java b/metrics-log4j2/src/main/java/com/codahale/metrics/log4j2/InstrumentedAppender.java
index af92498..78f4af7 100644
--- a/metrics-log4j2/src/main/java/com/codahale/metrics/log4j2/InstrumentedAppender.java
+++ b/metrics-log4j2/src/main/java/com/codahale/metrics/log4j2/InstrumentedAppender.java
@@ -8,6 +8,9 @@ import org.apache.logging.log4j.core.Filter;
 import org.apache.logging.log4j.core.Layout;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
 
 import java.io.Serializable;
 
@@ -18,16 +21,20 @@ import static com.codahale.metrics.MetricRegistry.name;
  * number of statements being logged. The meter names are the logging level names appended to the
  * name of the appender.
  */
+ at Plugin(name = "MetricsAppender", category = "Core", elementType = "appender")
 public class InstrumentedAppender extends AbstractAppender {
-    private final MetricRegistry registry;
 
-    private Meter all;
-    private Meter trace;
-    private Meter debug;
-    private Meter info;
-    private Meter warn;
-    private Meter error;
-    private Meter fatal;
+    private static final long serialVersionUID = 1L;
+
+    private transient final MetricRegistry registry;
+
+    private transient Meter all;
+    private transient Meter trace;
+    private transient Meter debug;
+    private transient Meter info;
+    private transient Meter warn;
+    private transient Meter error;
+    private transient Meter fatal;
 
     /**
      * Create a new instrumented appender using the given registry name.
@@ -75,6 +82,23 @@ public class InstrumentedAppender extends AbstractAppender {
         this.registry = registry;
     }
 
+    /**
+     * Create a new instrumented appender using the given appender name and registry.
+     * @param appenderName The name of the appender.
+     * @param registry the metric registry
+     */
+    public InstrumentedAppender(String appenderName, MetricRegistry registry) {
+        super(appenderName, null, null, true);
+        this.registry = registry;
+    }
+
+    @PluginFactory
+    public static InstrumentedAppender createAppender(
+            @PluginAttribute("name") String name,
+            @PluginAttribute( value = "registryName", defaultString = "log4j2Metrics") String registry) {
+        return new InstrumentedAppender(name, SharedMetricRegistries.getOrCreate(registry));
+    }
+
     @Override
     public void start() {
         this.all = registry.meter(name(getName(), "all"));
diff --git a/metrics-log4j2/src/test/java/com/codahale/metrics/log4j2/InstrumentedAppenderConfigTest.java b/metrics-log4j2/src/test/java/com/codahale/metrics/log4j2/InstrumentedAppenderConfigTest.java
new file mode 100644
index 0000000..b17d9ed
--- /dev/null
+++ b/metrics-log4j2/src/test/java/com/codahale/metrics/log4j2/InstrumentedAppenderConfigTest.java
@@ -0,0 +1,65 @@
+package com.codahale.metrics.log4j2;
+
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.SharedMetricRegistries;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InstrumentedAppenderConfigTest {
+  public static final String METRIC_NAME_PREFIX = "metrics";
+  public static final String REGISTRY_NAME = "shared-metrics-registry";
+
+  private final MetricRegistry registry = SharedMetricRegistries.getOrCreate(REGISTRY_NAME);
+  private ConfigurationSource source;
+  private LoggerContext context;
+
+  @Before
+  public void setUp() throws Exception {
+    source = new ConfigurationSource(this.getClass().getClassLoader().getResourceAsStream("log4j2-testconfig.xml"));
+    context = Configurator.initialize(null, source);
+  }
+  @After
+  public void tearDown() throws Exception {
+    context.stop();
+  }
+
+  // The biggest test is that we can initialize the log4j2 config at all.
+
+  @Test
+  public void canRecordAll() throws Exception {
+    Logger logger = context.getLogger(this.getClass().getName());
+
+    long initialAllCount = registry.meter(METRIC_NAME_PREFIX + ".all").getCount();
+    logger.error("an error message");
+    assertThat(registry.meter(METRIC_NAME_PREFIX + ".all").getCount())
+            .isEqualTo(initialAllCount + 1);
+  }
+
+  @Test
+  public void canRecordError() throws Exception {
+    Logger logger = context.getLogger(this.getClass().getName());
+
+    long initialErrorCount = registry.meter(METRIC_NAME_PREFIX + ".error").getCount();
+    logger.error("an error message");
+    assertThat(registry.meter(METRIC_NAME_PREFIX + ".all").getCount())
+            .isEqualTo(initialErrorCount + 1);
+  }
+
+  @Test
+  public void noInvalidRecording() throws Exception {
+    Logger logger = context.getLogger(this.getClass().getName());
+
+    long initialInfoCount = registry.meter(METRIC_NAME_PREFIX + ".info").getCount();
+    logger.error("an error message");
+    assertThat(registry.meter(METRIC_NAME_PREFIX + ".info").getCount())
+            .isEqualTo(initialInfoCount);
+  }
+
+}
diff --git a/metrics-log4j2/src/test/resources/log4j2-testconfig.xml b/metrics-log4j2/src/test/resources/log4j2-testconfig.xml
new file mode 100644
index 0000000..676eca4
--- /dev/null
+++ b/metrics-log4j2/src/test/resources/log4j2-testconfig.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="INFO" name="log4j2-config" packages="com.codahale.metrics.log4j2">
+    <Appenders>
+        <MetricsAppender name="metrics" registryName="shared-metrics-registry"/>
+    </Appenders>
+    <Loggers>
+        <Root level="INFO">
+            <AppenderRef ref="metrics" />
+        </Root>
+    </Loggers>
+</Configuration>
diff --git a/metrics-logback/pom.xml b/metrics-logback/pom.xml
index f4db61d..d84666c 100644
--- a/metrics-logback/pom.xml
+++ b/metrics-logback/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-logback</artifactId>
@@ -16,7 +16,7 @@
     </description>
 
     <properties>
-        <logback.version>1.1.2</logback.version>
+        <logback.version>1.1.10</logback.version>
     </properties>
 
     <dependencies>
diff --git a/metrics-servlet/pom.xml b/metrics-servlet/pom.xml
index 7b1d6b3..153ca93 100644
--- a/metrics-servlet/pom.xml
+++ b/metrics-servlet/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-servlet</artifactId>
diff --git a/metrics-servlet/src/main/java/com/codahale/metrics/servlet/AbstractInstrumentedFilter.java b/metrics-servlet/src/main/java/com/codahale/metrics/servlet/AbstractInstrumentedFilter.java
index 29feb71..c06dac9 100644
--- a/metrics-servlet/src/main/java/com/codahale/metrics/servlet/AbstractInstrumentedFilter.java
+++ b/metrics-servlet/src/main/java/com/codahale/metrics/servlet/AbstractInstrumentedFilter.java
@@ -30,6 +30,8 @@ public abstract class AbstractInstrumentedFilter implements Filter {
     // initialized after call of init method
     private ConcurrentMap<Integer, Meter> metersByStatusCode;
     private Meter otherMeter;
+    private Meter timeoutsMeter;
+    private Meter errorsMeter;
     private Counter activeRequests;
     private Timer requestTimer;
 
@@ -68,6 +70,10 @@ public abstract class AbstractInstrumentedFilter implements Filter {
         }
         this.otherMeter = metricsRegistry.meter(name(metricName,
                                                      otherMetricName));
+        this.timeoutsMeter = metricsRegistry.meter(name(metricName,
+                                                        "timeouts"));
+        this.errorsMeter = metricsRegistry.meter(name(metricName,
+                                                      "errors"));
         this.activeRequests = metricsRegistry.counter(name(metricName,
                                                            "activeRequests"));
         this.requestTimer = metricsRegistry.timer(name(metricName,
@@ -100,12 +106,30 @@ public abstract class AbstractInstrumentedFilter implements Filter {
                 new StatusExposingServletResponse((HttpServletResponse) response);
         activeRequests.inc();
         final Timer.Context context = requestTimer.time();
+        boolean error = false;
         try {
             chain.doFilter(request, wrappedResponse);
+        } catch (IOException e) {
+            error = true;
+            throw e;
+        } catch (ServletException e) {
+            error = true;
+            throw e;
+        } catch (RuntimeException e) {
+            error = true;
+            throw e;
         } finally {
-            context.stop();
-            activeRequests.dec();
-            markMeterForStatusCode(wrappedResponse.getStatus());
+            if (!error && request.isAsyncStarted()) {
+                request.getAsyncContext().addListener(new AsyncResultListener(context));
+            } else {
+                context.stop();
+                activeRequests.dec();
+                if (error) {
+                    errorsMeter.mark();
+                } else {
+                    markMeterForStatusCode(wrappedResponse.getStatus());
+                }
+            }
         }
     }
 
@@ -154,4 +178,44 @@ public abstract class AbstractInstrumentedFilter implements Filter {
             return httpStatus;
         }
     }
+
+    private class AsyncResultListener implements AsyncListener {
+        private Timer.Context context;
+        private boolean done = false;
+
+        public AsyncResultListener(Timer.Context context) {
+            this.context = context;
+        }
+
+        @Override
+        public void onComplete(AsyncEvent event) throws IOException {
+            if (!done) {
+                HttpServletResponse suppliedResponse = (HttpServletResponse) event.getSuppliedResponse();
+                context.stop();
+                activeRequests.dec();
+                markMeterForStatusCode(suppliedResponse.getStatus());
+            }
+        }
+
+        @Override
+        public void onTimeout(AsyncEvent event) throws IOException {
+            context.stop();
+            activeRequests.dec();
+            timeoutsMeter.mark();
+            done = true;
+        }
+
+        @Override
+        public void onError(AsyncEvent event) throws IOException {
+            context.stop();
+            activeRequests.dec();
+            errorsMeter.mark();
+            done = true;
+        }
+
+        @Override
+        public void onStartAsync(AsyncEvent event) throws IOException {
+
+        }
+    }
 }
diff --git a/metrics-servlets/pom.xml b/metrics-servlets/pom.xml
index d802445..738c4d3 100644
--- a/metrics-servlets/pom.xml
+++ b/metrics-servlets/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.dropwizard.metrics</groupId>
         <artifactId>metrics-parent</artifactId>
-        <version>3.1.2</version>
+        <version>3.2.4</version>
     </parent>
 
     <artifactId>metrics-servlets</artifactId>
@@ -38,6 +38,11 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>com.papertrail</groupId>
+            <artifactId>profiler</artifactId>
+            <version>1.0.2</version>
+        </dependency>
+        <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
             <version>${servlet.version}</version>
diff --git a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/AdminServlet.java b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/AdminServlet.java
index 39d2a47..a3300d4 100755
--- a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/AdminServlet.java
+++ b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/AdminServlet.java
@@ -14,27 +14,31 @@ public class AdminServlet extends HttpServlet {
     public static final String DEFAULT_METRICS_URI = "/metrics";
     public static final String DEFAULT_PING_URI = "/ping";
     public static final String DEFAULT_THREADS_URI = "/threads";
+    public static final String DEFAULT_CPU_PROFILE_URI = "/pprof";
 
     public static final String METRICS_URI_PARAM_KEY = "metrics-uri";
     public static final String PING_URI_PARAM_KEY = "ping-uri";
     public static final String THREADS_URI_PARAM_KEY = "threads-uri";
     public static final String HEALTHCHECK_URI_PARAM_KEY = "healthcheck-uri";
     public static final String SERVICE_NAME_PARAM_KEY= "service-name";
+    public static final String CPU_PROFILE_URI_PARAM_KEY = "cpu-profile-uri";
 
     private static final String TEMPLATE = String.format(
             "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"%n" +
                     "        \"http://www.w3.org/TR/html4/loose.dtd\">%n" +
                     "<html>%n" +
                     "<head>%n" +
-                    "  <title>Metrics{8}</title>%n" +
+                    "  <title>Metrics{10}</title>%n" +
                     "</head>%n" +
                     "<body>%n" +
-                    "  <h1>Operational Menu{8}</h1>%n" +
+                    "  <h1>Operational Menu{10}</h1>%n" +
                     "  <ul>%n" +
                     "    <li><a href=\"{0}{1}?pretty=true\">Metrics</a></li>%n" +
                     "    <li><a href=\"{2}{3}\">Ping</a></li>%n" +
                     "    <li><a href=\"{4}{5}\">Threads</a></li>%n" +
                     "    <li><a href=\"{6}{7}?pretty=true\">Healthcheck</a></li>%n" +
+                    "    <li><a href=\"{8}{9}\">CPU Profile</a></li>%n" +
+                    "    <li><a href=\"{8}{9}?state=blocked\">CPU Contention</a></li>%n" +
                     "  </ul>%n" +
                     "</body>%n" +
                     "</html>"
@@ -46,10 +50,12 @@ public class AdminServlet extends HttpServlet {
     private transient MetricsServlet metricsServlet;
     private transient PingServlet pingServlet;
     private transient ThreadDumpServlet threadDumpServlet;
+    private transient CpuProfileServlet cpuProfileServlet;
     private transient String metricsUri;
     private transient String pingUri;
     private transient String threadsUri;
     private transient String healthcheckUri;
+    private transient String cpuprofileUri;
     private transient String serviceName;
 
     @Override
@@ -68,10 +74,14 @@ public class AdminServlet extends HttpServlet {
         this.threadDumpServlet = new ThreadDumpServlet();
         threadDumpServlet.init(config);
 
+        this.cpuProfileServlet = new CpuProfileServlet();
+        cpuProfileServlet.init(config);
+
         this.metricsUri = getParam(config.getInitParameter(METRICS_URI_PARAM_KEY), DEFAULT_METRICS_URI);
         this.pingUri = getParam(config.getInitParameter(PING_URI_PARAM_KEY), DEFAULT_PING_URI);
         this.threadsUri = getParam(config.getInitParameter(THREADS_URI_PARAM_KEY), DEFAULT_THREADS_URI);
         this.healthcheckUri = getParam(config.getInitParameter(HEALTHCHECK_URI_PARAM_KEY), DEFAULT_HEALTHCHECK_URI);
+        this.cpuprofileUri = getParam(config.getInitParameter(CPU_PROFILE_URI_PARAM_KEY), DEFAULT_CPU_PROFILE_URI);
         this.serviceName = getParam(config.getInitParameter(SERVICE_NAME_PARAM_KEY), null);
     }
 
@@ -85,7 +95,7 @@ public class AdminServlet extends HttpServlet {
         final PrintWriter writer = resp.getWriter();
         try {
             writer.println(MessageFormat.format(TEMPLATE, path, metricsUri, path, pingUri, path,
-                                                threadsUri, path, healthcheckUri,
+                                                threadsUri, path, healthcheckUri, path, cpuprofileUri,
                                                 serviceName == null ? "" : " (" + serviceName + ")"));
         } finally {
             writer.close();
@@ -105,6 +115,8 @@ public class AdminServlet extends HttpServlet {
             pingServlet.service(req, resp);
         } else if (uri.equals(threadsUri)) {
             threadDumpServlet.service(req, resp);
+        } else if (uri.equals(cpuprofileUri)) {
+            cpuProfileServlet.service(req, resp);
         } else {
             resp.sendError(HttpServletResponse.SC_NOT_FOUND);
         }
diff --git a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/CpuProfileServlet.java b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/CpuProfileServlet.java
new file mode 100644
index 0000000..a49f9a6
--- /dev/null
+++ b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/CpuProfileServlet.java
@@ -0,0 +1,81 @@
+package com.codahale.metrics.servlets;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.joda.time.Duration;
+import com.papertrail.profiler.CpuProfile;
+
+/**
+ * An HTTP servlets which outputs a <a href="https://github.com/gperftools/gperftools">pprof</a> parseable response.
+ */
+public class CpuProfileServlet extends HttpServlet {
+    private static final long serialVersionUID = -668666696530287501L;
+    private static final String CONTENT_TYPE = "pprof/raw";
+    private static final String CACHE_CONTROL = "Cache-Control";
+    private static final String NO_CACHE = "must-revalidate,no-cache,no-store";
+    private final Lock lock = new ReentrantLock();
+
+    @Override
+    protected void doGet(HttpServletRequest req,
+                         HttpServletResponse resp) throws ServletException, IOException {
+
+        int duration = 10;
+        if (req.getParameter("duration") != null) {
+            try {
+                duration = Integer.parseInt(req.getParameter("duration"));
+            } catch (NumberFormatException e) {
+                duration = 10;
+            }
+        }
+
+        int frequency = 100;
+        if (req.getParameter("frequency") != null) {
+            try {
+                frequency = Integer.parseInt(req.getParameter("frequency"));
+            } catch (NumberFormatException e) {
+                frequency = 100;
+            }
+        }
+
+        final Thread.State state;
+        if ("blocked".equalsIgnoreCase(req.getParameter("state"))) {
+            state = Thread.State.BLOCKED;
+        }
+        else {
+            state = Thread.State.RUNNABLE;
+        }
+
+        resp.setStatus(HttpServletResponse.SC_OK);
+        resp.setHeader(CACHE_CONTROL, NO_CACHE);
+        resp.setContentType(CONTENT_TYPE);
+        final OutputStream output = resp.getOutputStream();
+        try {
+            doProfile(output, duration, frequency, state);
+        } finally {
+            output.close();
+        }
+    }
+
+    protected void doProfile(OutputStream out, int duration, int frequency, Thread.State state) throws IOException {
+        if (lock.tryLock()) {
+            try {
+                CpuProfile profile = CpuProfile.record(Duration.standardSeconds(duration),
+                        frequency, state);
+                if (profile == null) {
+                    throw new RuntimeException("could not create CpuProfile");
+                }
+                profile.writeGoogleProfile(out);
+                return;
+            } finally {
+                lock.unlock();
+            }
+        }
+        throw new RuntimeException("Only one profile request may be active at a time");
+    }
+}
diff --git a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/HealthCheckServlet.java b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/HealthCheckServlet.java
index 6e8c068..c670158 100644
--- a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/HealthCheckServlet.java
+++ b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/HealthCheckServlet.java
@@ -84,6 +84,12 @@ public class HealthCheckServlet extends HttpServlet {
     }
 
     @Override
+    public void destroy() {
+        super.destroy();
+        registry.shutdown();
+    }
+
+    @Override
     protected void doGet(HttpServletRequest req,
                          HttpServletResponse resp) throws ServletException, IOException {
         final SortedMap<String, HealthCheck.Result> results = runHealthChecks();
diff --git a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/MetricsServlet.java b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/MetricsServlet.java
index 547c5ee..31232cf 100644
--- a/metrics-servlets/src/main/java/com/codahale/metrics/servlets/MetricsServlet.java
+++ b/metrics-servlets/src/main/java/com/codahale/metrics/servlets/MetricsServlet.java
@@ -167,7 +167,7 @@ public class MetricsServlet extends HttpServlet {
         }
         resp.setHeader("Cache-Control", "must-revalidate,no-cache,no-store");
         resp.setStatus(HttpServletResponse.SC_OK);
-        
+
         final OutputStream output = resp.getOutputStream();
         try {
             if (jsonpParamName != null && req.getParameter(jsonpParamName) != null) {
diff --git a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletTest.java
index 147696c..f6c58f2 100755
--- a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletTest.java
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/AdminServletTest.java
@@ -50,6 +50,8 @@ public class AdminServletTest extends AbstractServletTest {
                                 "    <li><a href=\"/context/admin/ping\">Ping</a></li>%n" +
                                 "    <li><a href=\"/context/admin/threads\">Threads</a></li>%n" +
                                 "    <li><a href=\"/context/admin/healthcheck?pretty=true\">Healthcheck</a></li>%n" +
+                                "    <li><a href=\"/context/admin/pprof\">CPU Profile</a></li>%n" +
+                                "    <li><a href=\"/context/admin/pprof?state=blocked\">CPU Contention</a></li>%n" +
                                 "  </ul>%n" +
                                 "</body>%n" +
                                 "</html>%n"
diff --git a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/CpuProfileServletTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/CpuProfileServletTest.java
new file mode 100644
index 0000000..280672c
--- /dev/null
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/CpuProfileServletTest.java
@@ -0,0 +1,43 @@
+package com.codahale.metrics.servlets;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CpuProfileServletTest extends AbstractServletTest {
+
+    @Override
+    protected void setUp(ServletTester tester) {
+        tester.addServlet(CpuProfileServlet.class, "/pprof");
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        request.setMethod("GET");
+        request.setURI("/pprof?duration=1");
+        request.setVersion("HTTP/1.0");
+
+        processRequest();
+    }
+
+    @Test
+    public void returns200OK() throws Exception {
+        assertThat(response.getStatus())
+                .isEqualTo(200);
+    }
+
+    @Test
+    public void returnsPprofRaw() throws Exception {
+        assertThat(response.get(HttpHeader.CONTENT_TYPE))
+                .isEqualTo("pprof/raw");
+    }
+
+    @Test
+    public void returnsUncacheable() throws Exception {
+        assertThat(response.get(HttpHeader.CACHE_CONTROL))
+                .isEqualTo("must-revalidate,no-cache,no-store");
+
+    }
+}
diff --git a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/MetricsServletContextListenerTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/MetricsServletContextListenerTest.java
index 5d2a33a..df27344 100644
--- a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/MetricsServletContextListenerTest.java
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/MetricsServletContextListenerTest.java
@@ -76,7 +76,7 @@ public class MetricsServletContextListenerTest extends AbstractServletTest {
                 .isEqualTo(allowedOrigin);
         assertThat(response.getContent())
                 .isEqualTo("{" +
-                        "\"version\":\"3.0.0\"," +
+                        "\"version\":\"3.1.3\"," +
                         "\"gauges\":{" +
                         "\"g1\":{\"value\":100}" +
                         "}," +
@@ -106,7 +106,7 @@ public class MetricsServletContextListenerTest extends AbstractServletTest {
                 .isEqualTo(allowedOrigin);
         assertThat(response.getContent())
                 .isEqualTo(String.format("{%n" +
-                        "  \"version\" : \"3.0.0\",%n" +
+                        "  \"version\" : \"3.1.3\",%n" +
                         "  \"gauges\" : {%n" +
                         "    \"g1\" : {%n" +
                         "      \"value\" : 100%n" +
diff --git a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/MetricsServletTest.java b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/MetricsServletTest.java
index 7dca453..c2036df 100644
--- a/metrics-servlets/src/test/java/com/codahale/metrics/servlets/MetricsServletTest.java
+++ b/metrics-servlets/src/test/java/com/codahale/metrics/servlets/MetricsServletTest.java
@@ -65,7 +65,7 @@ public class MetricsServletTest extends AbstractServletTest {
                 .isEqualTo("*");
         assertThat(response.getContent())
                 .isEqualTo("{" +
-                                   "\"version\":\"3.0.0\"," +
+                                   "\"version\":\"3.1.3\"," +
                                    "\"gauges\":{" +
                                        "\"g1\":{\"value\":100}" +
                                    "}," +
@@ -96,7 +96,7 @@ public class MetricsServletTest extends AbstractServletTest {
                 .isEqualTo("*");
         assertThat(response.getContent())
                 .isEqualTo("{" +
-                                   "\"version\":\"3.0.0\"," +
+                                   "\"version\":\"3.1.3\"," +
                                    "\"gauges\":{" +
                                        "\"g1\":{\"value\":100}" +
                                    "}," +
@@ -127,7 +127,7 @@ public class MetricsServletTest extends AbstractServletTest {
                 .isEqualTo("*");
         assertThat(response.getContent())
                 .isEqualTo(callbackParamVal + "({" +
-                                   "\"version\":\"3.0.0\"," +
+                                   "\"version\":\"3.1.3\"," +
                                    "\"gauges\":{" +
                                        "\"g1\":{\"value\":100}" +
                                    "}," +
@@ -157,7 +157,7 @@ public class MetricsServletTest extends AbstractServletTest {
                 .isEqualTo("*");
         assertThat(response.getContent())
                 .isEqualTo(String.format("{%n" +
-                                                 "  \"version\" : \"3.0.0\",%n" +
+                                                 "  \"version\" : \"3.1.3\",%n" +
                                                  "  \"gauges\" : {%n" +
                                                  "    \"g1\" : {%n" +
                                                  "      \"value\" : 100%n" +
diff --git a/pom.xml b/pom.xml
index 1e1c933..0186e1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,15 +7,16 @@
 
     <groupId>io.dropwizard.metrics</groupId>
     <artifactId>metrics-parent</artifactId>
-    <version>3.1.2</version>
+    <version>3.2.4</version>
     <packaging>pom</packaging>
     <name>Metrics Parent</name>
     <description>
         The Metrics library.
     </description>
-    <url>http://metrics.codahale.com/</url>
+    <url>http://metrics.dropwizard.io/</url>
 
     <modules>
+        <module>docs</module>
         <module>metrics-annotation</module>
         <module>metrics-benchmarks</module>
         <module>metrics-core</module>
@@ -25,6 +26,7 @@
         <module>metrics-graphite</module>
         <module>metrics-httpclient</module>
         <module>metrics-httpasyncclient</module>
+        <module>metrics-jcache</module>
         <module>metrics-jdbi</module>
         <module>metrics-jersey</module>
         <module>metrics-jersey2</module>
@@ -38,18 +40,19 @@
         <module>metrics-logback</module>
         <module>metrics-servlet</module>
         <module>metrics-servlets</module>
-    </modules>
+        <module>metrics-jcstress</module>
+  </modules>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <servlet.version>3.1.0</servlet.version>
-        <slf4j.version>1.7.7</slf4j.version>
-        <jackson.version>2.4.2</jackson.version>
+        <slf4j.version>1.7.22</slf4j.version>
+        <jackson.version>2.6.6</jackson.version>
         <jetty8.version>8.1.11.v20130520</jetty8.version>
         <jetty9.legacy.version>9.0.4.v20130625</jetty9.legacy.version>
         <jetty9.version>9.2.2.v20140723</jetty9.version>
-        <rabbitmq.version>3.3.5</rabbitmq.version>
+        <rabbitmq.version>3.6.6</rabbitmq.version>
     </properties>
 
     <developers>
@@ -83,7 +86,7 @@
         <connection>scm:git:git://github.com/dropwizard/metrics.git</connection>
         <developerConnection>scm:git:git at github.com:dropwizard/metrics.git</developerConnection>
         <url>http://github.com/dropwizard/metrics/</url>
-        <tag>v3.1.2</tag>
+        <tag>v3.2.4</tag>
     </scm>
 
     <issueManagement>
@@ -179,7 +182,12 @@
                     <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-gpg-plugin</artifactId>
-                        <version>1.4</version>
+                        <version>1.6</version>
+                        <configuration>
+                            <gpgArguments>
+                                <argument>--no-tty</argument>
+                            </gpgArguments>
+                        </configuration>
                         <executions>
                             <execution>
                                 <id>sign-artifacts</id>
@@ -189,9 +197,6 @@
                                 </goals>
                             </execution>
                         </executions>
-                        <configuration>
-                            <keyname>4D13335B</keyname>
-                        </configuration>
                     </plugin>
                 </plugins>
             </build>
@@ -281,7 +286,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-release-plugin</artifactId>
-                <version>2.4.1</version>
+                <version>2.5.3</version>
                 <configuration>
                     <autoVersionSubmodules>true</autoVersionSubmodules>
                     <mavenExecutorId>forked-path</mavenExecutorId>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/dropwizard-metrics.git



More information about the pkg-java-commits mailing list