[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 [](http://travis-ci.org/dropwizard/metrics)
+[](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